Review: 13 Python web frameworks compared

Python programmers have many excellent options for creating web apps and APIs; Django, Weppy, Bottle, and Flask lead the way

1 2 Page 2
Page 2 of 2


Zope is not for simple RESTful APIs (per Bottle or Flask) or even basic websites with interactivity (à la Django). Rather, it’s meant to be a full-blown, enterprise-grade application server stack, similar to offerings for Java. The documentation describes the framework as “most useful for component developers, integrators, and web designers.” One major third-party product, the Plone CMS, uses Zope as its substrate and serves as a major driver of Zope’s continued development.

Zope works by taking requests from the web, matching the parameters of the request against an internal object database (ZODB), and executing that object using the request’s GET or POST parameters. Whatever comes back from the object is returned to the client. Zope uses this database-object system to simplify tasks like assigning granular object permissions, providing inheritance hierarchies for objects, and handling transactions and rollbacks for database objects.

Because of Zope’s size and complexity, installation requires some work; it’s not a matter of simply unpacking the source into a project subfolder. Some of the setup process includes compiling C modules, so installing on Windows is tricky. The prepackaged Windows binaries for Zope have not been updated since 2010, and the state of the documentation around them makes it hard to determine the best practices for setup. The documentation for the actual framework, however, is excellent. The Zope2 Book is a massively detailed compendium. 

When you fire up Zope and connect to the server, you’ll be greeted with a Web UI, where you can create and edit ZODB objects. Objects take one of three basic roles—content, logic, and presentation—and can consist of documents (basically, any file with a MIME type), Python scripts, and HTML templates.

Templates can be one of two varieties: the new and more flexible Zope Page Templates (ZPT) system, or the older and more basic DTML markup system. ZPT uses properties within HTML tags to indicate where to place data, making it easier to design templates using conventional HTML tools. But the ZPT syntax takes some getting used to.

One of the advantages Zope claims for its object-oriented methodology is that every action in the system, no matter what sort of object it acts on, is encapsulated by a transaction. Thus, if you delete a file stored in Zope’s database or make a destructive change to a piece of code, you need only roll back the action that performed it. The downside is that it’s hard to use modern source-control tools like Git on such a codebase, and it means you’re putting the data at the mercy of Zope’s custom database tools. Note that you can connect an external database like MySQL to a Zope application, but that’s strictly for hosting application data, not for replacing the ZODB.

Zope’s legacy and size translate into a number of disadvantages compared to many of the smaller, nimbler frameworks discussed here. The biggest drawback is that Zope only runs under Python 2.x, so can’t take advantage of Python 3 libraries or async syntax, although work is in progress to address that. (Zope 4, still in beta, includes Python 3 support and a great deal more as well.)

Lightweight Python web frameworks


Bottle could be considered a kind of mini-Flask, as it’s even more compact and succinct than that other “microframework.” Due to its minimal footprint, Bottle is ideal for including in other projects or for quickly delivering small projects like REST APIs.

The entire codebase for Bottle fits in a single file and has absolutely no external dependencies. Even so, Bottle comes equipped with enough functionality to build common kinds of web apps without relying on outside help.

The routing system in Bottle, which maps URLs to functions, has almost exactly the same syntax as Flask. You’re not limited to a hard-wired set of paths, either; you can create them dynamically. Request and response data, cookies, query variables, form data from a POST action, HTTP headers, and file uploads can all be accessed and manipulated by way of objects in Bottle’s framework.

Each capability has been implemented with good attention to detail. With file uploads, for instance, you don’t have to rename the file if its naming convention clashes with the target file system (such as slashes in the name on Windows); Bottle can do that for you.

Bottle includes its own simple HTML templating engine. Again, though minimal, it has been assembled with an eye for the essentials. Variables included in a template are rendered with safe HTML by default; you have to indicate which variables are safe to reproduce literally. If you’d rather swap out the template engine and use another one, such as Jinja2, Bottle lets you do so without fuss. I actually like the simple-template system bundled with Bottle; its syntax is unpretentious, and it allows you to intermix code and template text without undue difficulty.

Bottle even supports multiple server back ends. It comes with its own built-in miniserver for quick testing, but can support a wide variety of WSGI-compatible HTTP servers and fall back to plain old CGI if needed.

Bottle doesn’t need as much documentation as other frameworks, but the docs are by no means skimpy. All of the crucial stuff fits on a single (albeit long) webpage. Beyond that, you’ll find full documentation for each API, examples for how to deploy on various infrastructures, an explanation of the built-in templating language, and a slew of common recipes.

As with Flask, you can expand on Bottle’s functionality manually or via plug-ins written to complement Bottle. The list of Bottle plug-ins is nowhere near the size of Flask’s, but there are useful pieces, such as integration with various database layers and basic user authentication. For async support, Bottle can use one of the existing server adapters that runs asynchronously, such as aiohttp/uvloop.

One consequence of Bottle’s minimalism is that some items simply aren’t there. Form validation, including features like CSRF protection, isn’t included. If you want to build a web application that supports a high degree of user interaction, you’ll need to add them yourself.


CherryPy has been around for more than 10 years, but hasn’t lost the minimalism and elegance that originally distinguished it.

The framework’s premise, aside from containing only the bare bits needed to serve webpages, is that it’s meant to feel, as far as possible, not like a “web framework” but like any other kind of Python application. According to the documentation, sites like Hulu and Netflix use CherryPy in production, likely because the framework provides a highly unobtrusive base to build on.

CherryPy lets you keep your Web application apart from the core logic. To map your application’s functions to URLs or routes served by CherryPy, you create a class where the namespaces of the objects map directly to the URLs you want to serve; for instance, the root of the website is provided by a function named “index.” Parameters passed to those functions are used to handle variables provided by GET or POST methods.

The bits that CherryPy includes are meant to work as low-level building blocks. Session identifiers and cookie handling are included, but HTML templating is not. Like Bottle, CherryPy offers a way to map routes to directories on-disk for static file serving.

CherryPy will often defer to an existing third-party library to support a feature rather than try to provide it natively. WebSocket applications, for instance, aren’t supported by CherryPy directly, but through the ws4py library.

The documentation for CherryPy includes a handy tutorial walk-through of the various aspects of the program. It won’t take you through a complete end-to-end application unlike some other framework tutorials, but it’s still useful. The docs come with handy notes on deployment in various scenarios, including virtual hosts, reverse proxying via Apache and Nginx, and many others.

CherryPy uses pooled threads under the hood, the better to support multithreaded server adapters. If you want to experiment with other approaches, an unofficial third-party fork of CherryPy swaps in asyncio coroutines instead of threads.


If you’re building REST-based APIs and nothing else, Falcon provides you with no more than is absolutely necessary to do so. It’s designed to be lean and fast, with almost no dependencies beyond the standard library.

A big part of why Falcon earns the “light and slender” label has little to do with the number of lines of code in the framework. It’s because Falcon imposes almost no structure of its own on applications. All a Falcon application has to do is indicate which functions map to which API endpoints. Returning JSON from a given endpoint involves little more than setting up a route and returning your data from it via the json.dumps function from Python’s standard library. Support for Python 3’s async hasn’t yet landed in Falcon, but work is under way to make that happen.

Falcon also employs sane out-of-the-box defaults, so little tinkering is needed for setup. For example, 404s are raised by default for any route that’s not explicitly declared. If you want to return errors to the client, you can raise one of a number of stock exceptions bundled with the framework (such as HTTPBadRequest) or use a generic falcon.HTTPError exception. If you need preprocessing or postprocessing for a given route, Falcon provides hooks for those as well.

Falcon’s focus on APIs means there’s little here for building web apps with conventional HTML user interfaces. Form processing functions and CSRF protection tools, for instance, are pretty much nonexistent. That said, Falcon provides elegant options to extend its functionality, so more sophisticated items can be built. Aside from the above-mentioned hooking mechanism, you’ll find an interface for creating middleware that can be used to wrap all of Falcon’s APIs.

The documentation for Falcon is slender compared to other frameworks, but only because there’s less to cover. The user guide includes a formal step-by-step walkthrough of all major features, along with a quick-start section that lets you see sample code with or without annotation.


Most discussions about web frameworks in Python begin with some mention of Flask, and for good reason. Flask is a well-established, well-understood framework that is widely used and quite stable. It’s next to impossible to go wrong using Flask for a lightweight web project or a basic REST API, though you’ll face heavy lifting if you try to build anything larger.

Flask’s central appeal is its low barrier of entry. A basic “hello world” Flask app can be set up in fewer than 10 lines of Python. A widely used HTML templating system, Jinja2, comes with the framework to make rendering text easy, but Jinja2 can be swapped out for any number of other template engines (such as Mustache), or you can roll your own.

In the name of simplicity, Flask omits a number of niceties by default. For instance, it has no data layer or ORM out of the box, and no provisions for the likes of form validation. However, it can be expanded through extensions, of which there are dozens, covering many common use cases such as caching, form handling and validation, database connectivity, and so on. This lean-by-default design allows you to start engineering a Flask application with the absolute minimum of functionality, then layer in only the pieces you need when you need them.

Flask’s documentation is genial and easy to read. The quick-start document does an excellent job of getting you up and running while also explaining the significance of the default choices made for a simple Flask application, and the API docs are replete with good examples for how to make use of everything. Also excellent is the collection of “snippets,” which are quick-and-dirty examples of how to accomplish specific tasks with Flask, such as how to return an object if it exists or a 404 error if it doesn’t.

Flask hit its milestone 1.0 release earlier in 2018, with Python 2.6 and Python 3.3 being the minimum supported versions, and with many of its behaviors finally set in stone. Flask doesn’t explicitly support Python’s async syntax, but an API-compatible variation of Flask called Quart has been spun off to satisfy that demand.


Small and light, Pyramid hews closer to the likes of Flask or even Falcon than Django. As such, it’s well-suited to tasks like exposing existing Python code as a REST API, or providing the core for a web project where the developer does most of the heavy lifting.

A good way to describe Pyramid’s minimalism would be “policy-free,” a term used in the section of the documentation that discusses how Pyramid shapes up against other web frameworks. What kind of database or what sort of templating language you use is not Pyramid’s concern.

“[Pyramid] only supplies a mechanism to map URLs to view code,” says the documentation, “along with a set of conventions for calling those views. You are free to use third-party components that fit your needs in your applications.”

Very little work is needed to build a basic Pyramid application. As with Bottle and Flask, a Pyramid application can consist of a single Python file, apart from the files for the framework itself. A simple one-route API doesn’t take more than a dozen or so lines of code. Most of that is boilerplate like from … import statements and setting up the WSGI server.

By default, Pyramid includes several items that are common in web apps, but they’re provided as components to be stitched together, not as full-blown solutions. Support for user sessions, for instance, is included, and it even comes with CSRF protection. But support for user accounts, such as logins or account management as provided by Django, isn’t part of the deal. You’ll have to roll it yourself or add it through a plug-in. The same goes for form handling and database connections.

One way Pyramid avoids being too minimal is by providing methods for templates to be made from Pyramid projects to reuse or redeliver prior work. These templates, aka Scaffolds, generate a Pyramid app with simple routing and some starter HTML/CSS templates. Scaffolds included by default with Pyramid include a sample starter project and a project that connects to databases via the commonly used Python library SQLAlchemy.

Pyramid is similarly slender with respect to its testing and debugging tools. Bundle the debugtoolbar extension in a Pyramid app and you’ll get a clickable icon on every webpage produced by the app that generates details about the app’s execution, including a detailed traceback in the event of errors. Logging and unit testing are also present—two items that would seem foolish to exclude even from a framework this lightweight.

Pyramid’s documentation is excellent. In addition to a quick tour of the basics and a tutorial-style walk-through, you’ll find a set of community-contributed tutorials for building various projects and a cookbook of common recipes. The latter includes deployment techniques for a slew of target environments, from Google App Engine to Nginx.

Pyramid supports both Python 2 and Python 3, but does not use Python 3’s async syntax. For clues on how to leverage async in Pyramid, see the aiopyramid project, which includes a scaffold for an async-powered “hello world” application. 


Tornado is another tiny framework aimed at a specific use case. Designed for building asynchronous networking applications, Tornado is well-suited for creating services that open a great many network connections at once and keep them alive—that is, anything involving WebSockets or long polling.

Like Bottle or Falcon, Tornado omits features extraneous to its central purpose. For instance, Tornado has a built-in templating system for generating output (in HTML or otherwise) and mechanisms for internationalization, form handling, cookie setting, user authentication, and CSRF protection. But it leaves out features—like form validation and an ORM—that are more suited to user-facing web apps.

Tornado excels at providing infrastructure to apps that need close control over the nitty-gritty of asynchronous networking. For instance, Tornado doesn’t merely provide a built-in asynchronous HTTP server, but also an asynchronous HTTP client. Thus, Tornado is well-suited to building apps, such as a web scraper or a bot, that query other sites in parallel and act on the returned data.

If you’re trying to create an app that uses protocols other than HTTP, Tornado has you covered. It provides access to low-level TCP connections and sockets to utilities like DNS resolvers, as well as to third-party authentication services, and it supports interoperation with other frameworks through the WSGI standard. The documentation, which is small but not sparse, includes ample examples of how to accomplish all of this.

Tornado both leverages and complements Python’s native functionality for asynchronous behaviors. If you’re using Python 3.5, Tornado supports the built-in async and await keywords, which promise to give applications a speed boost. For earlier editions of Python, you can use a yield statement. In either case, you can use futures or callbacks to handle responses to events.

Tornado 5.0 improved integration with Python’s native async features. Thus Python 3.3 is no longer supported, and Python 3.5 users must use Python 3.5.2 or later. Tornado 6.0 will require Python 3.5 and later, and will drop Python 2 support entirely.

Tornado also provides a library of synchronization primitives—semaphores, locks, and so on—to coordinate events between asynchronous coroutines. Note that, like the Python interpreter itself, Tornado normally runs single-threaded, so these primitives aren’t the same as their threading namesakes. However, if you want to run Tornado in parallel processes to leverage multiple sockets and cores, there are tools for that.

Tornado’s documentation covers each major concept in the framework and all of the major APIs in the model. Although it includes a sample application (a web-spidering tool), it’s mainly for demonstrating Tornado’s queuing module. was originally created by the late Aaron Swartz and used as the original underpinning to Reddit. Though Reddit might have moved on from, continues to be maintained by others, chiefly Anand Chitipothu. In scope and design, is similar to Bottle and Flask; you can use it as a basic skeleton, then build on it without feeling too constrained.

To invoke a basic instance, all you need to do is pass it a list of URLs and function mappings. URLs can contain regular expressions with capture parameters, allowing you to pull data from URLs with formats like /users/RayB or /article/451. Bottle has a similar mechanism, but also provides ways to ensure that parameters conform to certain standards (for example, they can only be integers). stays clean and unpretentious in large part because it does not attempt to take on tasks that are better handled by other mechanisms. There’s no native feature that lets you serve static content from a stack, for instance; the instructions wisely recommend going through the Web server instead. By contrast, Bottle has native functionality for serving static content, although it’s optional. also includes cookie and session management, and there’s even a simple output cache. has an HTML templating system; it’s highly rudimentary, but allows for if/then/else logic. More sophisticated and more useful is’s system for dynamically generating HTML forms, with class attributes for CSS styling and basic form-validation mechanisms. This is handy if you want to produce an app with programmatically generated forms, such as a basic database explorer.’s documentation is as minimal as the framework itself, but it doesn’t skimp on providing relevant examples. The “cookbook” section (in multiple languages, no less) demonstrates many common use cases (serving static content, streaming large files incrementally, and so on). There’s even a gallery of real-life web apps built with the framework, many with source code.

Note that hasn’t been kept as up-to-date with Python 3 compatibility as other frameworks. This not only means lack of support for async syntax, but also bugs that spring from deprecated functions. Further, it’s not clear if the maintainers have plans to keep current after Python 2 reaches the end of its support lifetime.


Wheezy.web is in the Flask/Bottle/Pyramid mold of web frameworks: small, light, and focused on delivering high speed and high concurrency. The feature set is small at its core, but its creators have decked it out with an assortment of must-have functions.

It’s slightly misleading to talk about Wheezy.web as a single product. Wheezy.web glues together several other libraries created by the same author, each providing different services depending on what you want your application to do. The Wheezy.http library, for instance, is used heavily by Wheezy.web for many basic behaviors, but the library isn’t necessary unless your app has to perform user authentication.

This collection-of-libraries approach means that the easiest way to develop with Wheezy is to install it from PyPI or use easy_install to collect all of the relevant packages. I had problems using easy_install in Python 3.51, but it worked fine in Python 2.7.

The core of Wheezy.web focuses mainly on mapping routes to functions and handling redirects, but it is outfitted with a few other useful features. For instance, any routes tagged with the @secure decorator will accept only HTTPS requests and will redirect to HTTPS if an HTTP connection attempt is made. Another core addition is middleware so that path routes and HTTP errors can be customized.

Wheezy’s other libraries cover a fairly rich set of use cases. Wheezy.validation can help ensure that submitted data meets certain criteria—for instance, usernames or passwords meet length or complexity requirements. Wheezy.caching allows you to cache unchanged responses to speed up processing, and Wheezy.captcha integrates with Python’s PIL/Pillow image libraries to generate captchas. For internationalization, there’s integration with the standard GNU gettext utility.

The core Wheezy.web framework does not include a templating engine. If you need to do more than return plain text or JSON, you can add the Wheezy.template engine or hook up a number of third-party engines such as Jinja2 and Mako. Wheezy.web also omits an ORM; the examples in Wheezy’s documentation use SQLite via manual SQL strings.

Building apps with Wheezy requires slightly more boilerplate than with Flask or Bottle, but not excessively so; most of it involves setting up routes and middleware, stuff that can be abstracted away without too much effort. The details are explained in Wheezy’s documentation, which includes a “create a guestbook” tutorial but is otherwise light on bonuses.

The development of Wheezy appears to have stalled, as the last commits to the project were logged in 2015. This doesn’t bode well for its maintaining compatibility with new Python features.

Weighing the Python web framework options

Picking a Python web framework is no different from choosing any other software tool: It’s all about fitness to purpose and fitness to one’s own development habits and predilections.

If you prefer minimal, for little more than creating a REST API or wrapping existing Python code in a web framework, many of the Python frameworks profiled here are well-suited to your needs. Flask and Bottle are great choices in that regard. Bottle is especially good for including in other projects because of its compactness.

Pyramid and CherryPy impose relatively little project structure, so they’re useful for quickly wrapping existing code. Falcon and Tornado are even more minimal in that respect. They have very little overhead, but also lack the heavier tooling needed for more robust web apps. serves as a quick starting point for apps that involve user interaction (such as form submissions). Wheezy.web and its libraries allow you to bite off only as much functionality as you want to chew.

For developers with more upscale needs, Django is one of the best places to start, not only because of the wealth of out-of-the-box components but the huge user community that has wrung great results out of it over the years. If you don’t need such completeness, Weppy is a good compromise, as it sports a more expanded feature set than the more minimal frameworks.

Finally, while CubicWeb and Zope2 provide entire development environments instead of frameworks alone, they’re both top-heavy and idiosyncratic. Using them comes at the cost of learning their peculiarities.

Copyright © 2018 IDG Communications, Inc.

1 2 Page 2
Page 2 of 2