Serdar Yegulalp
Senior Writer

Review: 13 Python web frameworks compared

reviews
Aug 15, 201837 mins
Development ToolsPythonSoftware Development

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

If you are developing a web application and you have picked Python as the language to build it in, that’s a smart move. Python’s maturity of development, robust libraries, and breadth of real-world adoption have helped make it a no-brainer for web development.

Now comes the hard part: Picking one of the many Python web frameworks available. It’s not only that the number keeps growing, but it can be hard to find the one that best fits your use case. If you’re constructing a quick-and-dirty REST API, you won’t need anywhere near the plumbing and wiring required for a full user-facing application with user logins, form validations, and upload handling.

In this roundup, we’ll examine 13 of the most widely deployed Python web frameworks. We’ll note what kinds of web applications each is best suited to building and look into how they stack up against one another in these six areas:

Installation: How easy or straightforward it is to set up the framework—projects that don’t require formal installation (it can simply be dropped into an existing project as an included module), require minimal boilerplate to get started, or come with some kind of preconfigured setup get extra points.

Documentation: Nearly every decent Python project has documentation that walks through setup, illustrates basic use cases, and provides details about the APIs. Here, we give higher marks to frameworks that show how to create an entire app as part of the tutorial, include common recipes or design patterns, and otherwise go above and beyond the call of duty (such as by providing details about how to run the framework under a Python variant like PyPy or IronPython).

Management: This is a relative score, indicating how much work is required to configure and maintain the framework. Minimal frameworks score higher here by default.

Native capabilities: How many batteries are included? Higher scores go to frameworks that provide native support for internationalization, HTML templating, and a data access layer. Points also go to frameworks that make native use of Python’s recently introduced native support for asynchronous I/O operations.

Security: Frameworks that provide native security measures like cross-site request forgery (CSRF) protection and session management with encrypted cookies get higher marks.

Scalability: Most Python frameworks can make use of projects like Gevent or Gunicorn to run at scale. Here, we look at features native to the framework that promote scalability, like output and page-fragment caching.

If you’re curious about performance benchmarks, take a look at TechEmpower’s ongoing series of trials, which compare multiple web frameworks across various tasks, with code and methodologies posted to GitHub and subjected to constant reassessment. Not all of the frameworks in this discussion are analyzed there, but it’s possible to get a good sense of which frameworks perform best under what kinds of loads.

We’ll look at 13 frameworks in all. Five of these—CubicWeb, Django, Web2py, Weppy, and Zope2—take the “kitchen sink” approach, packing in most every feature you could imagine needing for a web application. The remaining eight frameworks—Bottle, CherryPy, Falcon, Flask, Pyramid, Tornado, Web.py, and Wheezy.web—offer a more minimalist take, trading bulk and completeness for simplicity and ease.

Let’s start with the heavyweights.

Heavyweight Python web frameworks

CubicWeb

CubicWeb is billed as “a semantic web application framework that favors reuse and object-oriented design.” It’s an intriguing system—as noted by Rick Grehan when he looked at it for InfoWorld back in 2011—that emphasizes the use of abstractions and reusable building blocks of code called “cubes,” but it might be too abstract or idiosyncratic for some developers.

Cubes are software components that feature a schema (data model), entities (programming logic), and views. By assembling multiple cubes, each performing its own task, you can compose software applications by reusing your own code and the code of others.

At its core, CubicWeb provides basic scaffolding used by every web app: a “repository” for data connections and storage; a “web engine” for basic HTTP request/response and CRUD actions; and a schema for modeling data. All of this is described in Python class definitions. To set up and manage instances of CubicWeb, you work with a command-line tool similar to the one used for Django.

CubicWeb does not appear to use Python 3’s native async functionality. A roundabout way to include async would be to use the cubicweb.pyramid module to use the Pyramid framework as the web server, and draw on a fork of Pyramid that uses async constructions. But anything more straightforward seems out of reach for now.

To fetch or manipulate persistent data in a CubicWeb app, you use Relation Query Language (RQL), which employs vaguely SQL-like syntax but is patterned after the W3C’s SparQL. CubicWeb’s justification for this is, again, abstraction: RQL provides a highly decoupled route to interrelate various data sources. But as it’s implemented, by manually constructing queries as strings, it’ll likely feel antiquated to developers accustomed to ORMs.

There are other obstacles to using CubicWeb. For one, setup can be a hassle. Because CubicWeb has a lot of dependencies, it’s best to use pip install to fetch them all. You may also have to perform a certain amount of manual tweaking on the local environment. This is in stark contrast to other frameworks where running pip install or dropping the framework’s code into a subfolder of another project is all that’s required.

Another potential issue is the absence of a native template engine; generating HTML is left to the developer. You could overcome this by using a third-party templating system like Jinja2 or opting for a cube that provides tools for Web UIs, such as that for the Boostrap HTML framework.

One long-standing issue with CubicWeb — the lack of Python 3 support — has been resolved. As of June 2016 and version 3.23, Python 3 support landed in CubicWeb, except for modules like Twisted that aren’t themselves fully ported.

Like Web2py, CubicWeb refers to its lengthy documentation as “the book.” It takes the time to explain CubicWeb’s unusual approach, demonstrates how to build some basic applications, includes API references, and in general goes out of its way to be specific.

Django

In the decade and change since Django first appeared, it has become one of Python’s most widely deployed frameworks for creating web applications. Django comes with most every battery you might need, so it leans more toward building big applications than small ones.

After many years of sitting at version 1.x, Django recently made a version bump to the left of the decimal point. The biggest change in Django 2.0 is that the framework now works only with Python 3.4 and up. Ideally, you should use Python 3.x anyway, so the only reason to use the 1.x branch of Django is if you’re stuck with an old version of Python.

A key part of Django’s appeal is deployment speed. Because it includes so many pieces you need for developing the average web application, you can get moving quickly. Routing, URL parsing, database connectivity (including an ORM), form validation, attack protections, and templating are all built in.

You’ll find building blocks for most common web application scenarios. User management, for instance, is found on most websites, so Django offers it as a standard element. Instead of having to create your own system for tracking user accounts, sessions, passwords, logins/logouts, admin permissions, and so on, Django has those features natively. They can be used as-is or extended to encompass new use cases with a minimal amount of work.

Django has sane and safe defaults that help shield your web application from attack. When you place a variable in a page template, such as a string with HTML or JavaScript, the contents are not rendered literally unless you explicitly designate the instance of the variable as safe. This by itself cuts down on many common cross-site scripting issues. If you want to perform form validation, you can use everything from simple CSRF protection to full-blown field-by-field validation mechanisms that return detailed error feedback.

A feature set as rich and broad as Django’s wouldn’t be much good without robust documentation to go with it. Django’s documentation site drills into every aspect of the framework from multiple angles. Working with Python 3 or other flavors of the language, doing security right, implementing common web application components (like sessions or pagination), generating sitemaps—they’re all covered. The APIs for each layer of the application—model, view, and template—are described in detail as well.

With great power, however, comes great complexity. Django applications have a reputation for being top-heavy, with many moving parts. Even a simple Django app with only a couple of routes requires a fair amount of configuration to get running. If your job is to do nothing more than set up a couple of simple REST endpoints, Django is almost certainly overkill.

Django also has its quirks. For instance, page templates cannot use callables. Example: You can pass {{user.name}} as a component in a template, but not {{user.get_name()}}. It’s one of the ways Django ensures templates don’t inadvertently do nasty things, but those constraints can be jarring if you’re not prepared for them. While there are workarounds, they tend to take a toll on performance.

Django’s core is synchronous. However, one way to add async behavior is by way of the Django Channels project. This project, an official Django add-on, adds async handling for connections and sockets to Django, while preserving Django’s programming idioms.

Web2py

In the Ruby world, Ruby on Rails is the de facto web framework. DePaul University computer science professor Massimo Di Pierro was inspired by Rails to create a web framework in Python that was similarly easy to set up and work with. The result is Web2py.

Web2py’s biggest attraction is its built-in development environment. When you set up an instance of Web2py, you’re provided with a web interface, essentially an online Python application editor, where you can configure the app’s components. This typically means creating models, views, and controllers, each described via Python modules or HTML templates. A few example apps come with Web2py out of the box. You can take those apart to see how they work or leverage them as starter templates to create your own apps.

Developers typically deploy Web2py by simply downloading its source code and using that. But for less technical users on Windows or MacOS, Web2py’s creators offer versions that are essentially standalone servers. Download, unpack, and run one of these versions, and you’ll have a local web server with a preconfigured copy of Web2py built in. This is a nice way to get a leg up on creating a Web2py app, which can then be deployed elsewhere as needed.

Web2py’s web interface was built with Bootstrap 2.16.1, so it’s easy on the eyes and easy to navigate. The in-browser editor is no substitute for a full-blown IDE, but it’s outfitted with helpful aids like line numbering and Python syntax highlighting (including auto-indentation). Also included is a quick web interface to the Python shell, so you can interact with Web2py from the command line if needed—a nice concession to experts.

The data abstraction system used in Web2py works a little differently from Django’s ORM and other ORMs inspired by it (such as Peewee). Those systems use Python classes to define models, where in Web2py you use constructor functions like define_table to instantiate models. Most of those differences are likely to be jarring only to people who already have experience with one and are starting to use the other; they’re about equally complex to newcomers. You’re not likely to have any trouble with hitching Web2py to a data provider, as it talks to nearly every major database in existence.

A truly useful database-related function is the ability to generate a diagram of the models, the better to visualize how your models relate to each other. You will need to install the pygraphviz library to enable that feature, though.

Web2py supplies many other professional-grade components: internationalization functions, multiple caching methodologies, access control and authorization, and even front-end effects (for example, a date picker in forms) via integrated support for jQuery and AJAX. Hooks for external and internal middleware are also included, although you aren’t allowed to use middleware to replace core Web2py functions.

One significant limitation of Web2py is that it is compatible with Python 2.x only. For one, this means Web2py can’t make use of Python 3’s async syntax. For two, if you rely on external libraries that are exclusive to Python 3, then you’re out of luck. However, work is under way to make Web2py Python 3 compliant, and it’s very near completion as of this writing.

It’s no wonder that Web2py’s documentation is referred to as “the book.” First, it covers a staggering amount of material on Web2py, Python, and the deployment environments used for both. Second, it’s written in a highly accessible, narrative style. Third, it talks in-depth about common application-building scenarios. There’s an entire chapter, for instance, on using jQuery (bundled with Web2Py) to build AJAX applications.

Weppy

Weppy feels like a halfway mark between the minimal simplicity of Flask and the completeness of Django. While developing a Weppy app has the straightforwardness of Flash, Weppy comes with many features found in Django, like data layers and authentication. Thus, Weppy is suited to apps that range from extremely simple to modestly sophisticated.

At first glance, Weppy code looks a great deal like Flask or Bottle code. Few instructions are needed to get a basic, single-route website up and running. Routes can be described through function decorators (the easy way) or programmatically, and the syntax for doing so hews closely to Flask/Bottle. Templating works about the same, aside from minor variations in syntax.

Weppy contrasts with those other frameworks by including some features they incorporate only as plug-ins or add-ons. For instance, neither Flask nor Bottle has a built-in ORM or a data management system. Weppy includes an ORM, albeit one based on the pyDAL project rather than the far more popular SQLAlchemy. Weppy even supports schema migrations, which Django supports as part of its ORM (also, Django’s migration system is a great deal more automated). While Weppy has an extension mechanism, the list of officially approved add-ons is tiny, far smaller than the catalog of extensions for Flask.

Lighter-weight frameworks like Weppy are often used to build RESTful APIs, and Weppy comes outfitted with convenience functions for that purpose. Put a @service decorator on a route, and the data you return is automatically formatted in your choice of JSON or XML.

Weppy includes other features that seem more in line with a larger framework, but they’re implemented without bulk. Examples: Data validation mechanisms, form handling, response caching, and user validation. In all of these cases, Weppy takes a “just enough” approach. The features provided aren’t as complete as you might find in a Django-sized framework, but a developer doesn’t need to invest a lot of work in making them useful, and they can always be extended after the fact.

Another feature found in Weppy typically associated with a more heavyweight framework is internationalization support. Strings in templates can be translated according to locale files provided with the application, which are simple Python dictionaries. The choice of language can also be set by parsing the browser request (that is, the Accept-Language HTTP header) or by binding a translation to a specific route.

Weppy’s documentation has the same flavor as the framework itself. It’s clean, readable, and written to be consumed by humans. Aside from the usual “hello world” app example, it includes a nice walkthrough tutorial that lets you create a microblogging system as a starter project.

Long-term plans for Weppy include supporting async and sockets as low-level, first-class entities. Weppy’s developers plan to introduce those features in version 2.0, and then to require Python 3.7 or better for all future versions of Weppy.

InfoWorld Scorecard
Native capability (20%)
Management (20%)
Installation (20%)
Documentation (20%)
Security (10%)
Scalability (10%)
Overall Score (100%)
Bottle 0.12 8 10 10 8 7 7 8.6
CherryPy 17.0.0 7 9 9 9 8 8 8.4
CubicWeb 3.26.4 10 8 7 10 9 7 8.6
Django 2.1 10 8 8 10 10 10 9.2
Falcon 1.4.1 7 10 8 8 7 7 8.0
Flask 1.0.2 8 9 8 9 8 8 8.4
Pyramid 1.9.2 8 8 8 10 9 7 8.4
Tornado 4.3 8 9 9 8 8 7 8.3
Web.py 0.39 8 8 10 8 9 8 8.5
Web2py 2.16.1 10 9 7 10 9 8 8.9
Weppy 1.2.11 10 8 9 9 10 9 9.1
Wheezy.web 0.1.485 9 9 8 8 8 8 8.4
Zope2 2.13.24 10 8 7 9 9 9 8.6

Zope2

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

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

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.

Falcon

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.

Flask

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.

Pyramid

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

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.

Web.py

Web.py was originally created by the late Aaron Swartz and used as the original underpinning to Reddit. Though Reddit might have moved on from Web.py, Web.py continues to be maintained by others, chiefly Anand Chitipothu. In scope and design, Web.py 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 Web.py 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).

Web.py 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 Web.py 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. Web.py also includes cookie and session management, and there’s even a simple output cache.

Web.py has an HTML templating system; it’s highly rudimentary, but allows for if/then/else logic. More sophisticated and more useful is Web.py’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.

Web.py’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 Web.py 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 Web.py current after Python 2 reaches the end of its support lifetime.

Wheezy.web

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 Wheezy.security 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. Web.py 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.

Serdar Yegulalp
Senior Writer

Serdar Yegulalp is a senior writer at InfoWorld, covering software development and operations tools, machine learning, containerization, and reviews of products in those categories. Before joining InfoWorld, Serdar wrote for the original Windows Magazine, InformationWeek, the briefly resurrected Byte, and a slew of other publications. When he's not covering IT, he's writing SF and fantasy published under his own personal imprint, Infinimata Press.

More from this author