Mucking about with microframeworks
Python does not lack for web frameworks, from all-encompassing frameworks like Django to "nanoframeworks" such as WebCore. A recent "spare time" project caused me to look into options in the middle of this range of choices, which is where the Python "microframeworks" live. In particular, I tried out the Bottle and Flask microframeworks—and learned a lot in the process.
I have some experience working with Python for the web, starting with the Quixote framework that we use here at LWN. I have also done some playing with Django along the way. Neither of those seemed quite right for this latest toy web application. Plus I had heard some good things about Bottle and Flask at various PyCons over the last few years, so it seemed worth an investigation.
Web applications have lots of different parts: form handling, HTML template processing, session management, database access, authentication, internationalization, and so on. Frameworks provide solutions for some or all of those parts. The nano-to-micro-to-full-blown spectrum is defined (loosely, at least) based on how much of this functionality a given framework provides or has opinions about. Most frameworks at any level will allow plugging in different parts, based on the needs of the application and its developers, but nanoframeworks provide little beyond request and response handling, while full-blown frameworks provide an entire stack by default. That stack handles most or all of what a web application requires.
The list of web frameworks on the Python wiki is rather eye-opening. It gives a good idea of the diversity of frameworks, what they provide, what other packages they connect to or use, as well as some idea of how full-blown (or "full-stack" on the wiki page) they are. It seems clear that there is something for everyone out there—and that's just for Python. Other languages undoubtedly have their own sets of frameworks (e.g. Ruby on Rails).
Drinking the WSGI
Modern Python web applications are typically invoked using the Web Server Gateway Interface (WSGI, pronounced "whiskey"). It came out of an effort to have a common web interface instead of the many choices that faced users in the early days (e.g. Common Gateway Interface (CGI) and friends, mod_python). WSGI was first specified in PEP 333 ("Python Web Server Gateway Interface v1.0") in 2003 and was updated in 2010 to version 1.0.1 in PEP 3333, which added various improvements including better Python 3 support. At this point, it seems safe to say that WSGI has caught on; both Bottle and Flask use it (as does Django and it is supported by Quixote as well).
At its most basic, a WSGI application simply provides a way for the web server to call a function with two parameters every time it gets a request from a client:
result = application(environ, start_response)environ is a dictionary containing the CGI-style environment variables (e.g. HTTP_USER_AGENT, REMOTE_ADDR, REQUEST_METHOD) along with some wsgi.* values. The start_response() parameter is a function to be called by the application to pass the HTTP status (e.g. "200 OK", "404 Not Found") and a list of tuples with the HTTP response headers (e.g. "Content-type") and values. The application() function returns an iterable yielding zero or more strings of type bytes, which is generally the HTML response to the client.
The Python standard library has the wsgiref module that provides various utilities and a reference implementation of a WSGI server. In just a few lines of code, with no dependencies other than Python itself, one can run a simple WSGI server locally.
Similarly, both Bottle and Flask have the ability to simply run a development web server locally, which uses the application code in much the same way as it will be used on a "real" server. That feature is not uncommon in the web-framework world and it is quite useful. There are various easy ways to debug the code before deploying it using those local servers. The application can then be deployed, using Apache and mod_wsgi, say, to an internet-facing server.
Bottle
One of the nice things about Bottle is that it lacks any dependencies outside of the Python standard library. It can be installed from the Python Package Index (PyPI) using pip or by way of your Linux distribution's package repositories (e.g. dnf install python3-bottle as I did on Fedora). As might be expected, a simple "hello world" example is just that, simple:
from bottle import route, run, template @route('/hello/<name>') def index(name): return template('<b>Hello {{name}}</b>!', name=name) run(host='localhost', port=8080)Running that sets up a local server that can be accessed via URLs like "http://localhost:8080/hello/world". The route() decorator will send any requests that look like "/hello/XXX" to the index() function, passing anything after the second slash as name.
Bottle uses the SimpleTemplate engine for template handling. As its name implies, it is rather straightforward to use. Double curly braces ("{{" and "}}") enclose substitutions to be made in the text. Those substitutions can be Python expressions that evaluate to something with a string representation:
{{ name or "amigo" }}That will substitute name if it has a value (i.e. not None or "") or "amigo" if not. Those substitutions will be HTML escaped in order to avoid cross-site scripting problems, unless the expression starts with a "!", which disables that transformation. Obviously, that feature should be used with great care.
Beyond that, Python code can be embedded in the templates either as a single line that starts with "%" or in a block surrounded by "<%" and "%>". The template() function can be used to render a template as above, or it can be passed a file name:
return template('hola_template', sub1='foo', sub2='bar', ...)That will look for a file called views/hola_template.tpl to render; any substitutions should be passed as keyword arguments. The view() decorator can be used instead to render the indicated template based on the dictionary returned from the function:
@route('/hola') @view('hola_template') def hola(name='amigo'): ... return { name=name, sub1='foo', ... }
There is support for using the HTTP request data via a global request object, which provides access mechanisms for various parts of the request such as the request method, form data, cookies, and so on. Likewise, a global response object is used to handle responses sent to the browser.
That covers the bulk of Bottle in a nutshell. Other functionality is available through the plugin interface. There is a list of plugins available, covering things like authentication, Redis integration, using various database systems, session handling, and so forth.
It was quite easy to get started with Bottle and to get quite a ways down the path of implementing my toy application. As I considered further features and directions for it, though, I started to encounter some of the limitations of Bottle. The form handling was fairly rudimentary, though the WTForms form rendering and validation library is mentioned as an option in the Bottle documentation. Beyond that, the largely blank page for the Bottle plugin for the SQLAlchemy database interface and object-relational mapping (ORM) library did not exactly inspire confidence. The latest bottle-sqlalchemy release was from 2015, which is also potentially worrisome.
Many of the limitations of Bottle are intentional, and perfectly reasonable, but as I cast about a bit more, I encountered Miguel Grinberg's Flask Mega-Tutorial, which caused me to look at Flask more closely. Part of my intention with this "project" was to investigate and learn; Grinberg's excellent tutorial makes using Flask even easier than Bottle (which was not particularly hard). I found no equivalent document for Bottle, which may have made all the difference.
Flask
Once I had poked around in the tutorial and the Flask documentation a bit, I decided to see how hard it would be to take the existing Bottle application and move it to Flask. The answer to that was surprising, at least to me. The alterations required were minimal, really, with some changes needed to the templates (by default Flask looks for .html files in the templates directory), call render_template() rather than using template() or @view(), and a little bit of change to the application set-up boilerplate. A Flask "hello world" might look like the following:
from flask import Flask, render_template_string app = Flask(__name__) @app.route('/hello/<name>') def hello(name): return render_template_string('Hello {{name}}', name=name)
While the Bottle "hello world" program could be run directly from the command line to start its development web server, Flask takes a different approach. If the above code were in a file called hw.py, the following command would start the development server:
$ FLASK_APP="hw" flask runNote that on Fedora, the Python 3 version of flask is run as flask-3. A .flaskenv file can be populated with the FLASK_APP environment variable (along with the helpful "FLASK_ENV=development" setting for debugging features) so that it does not need to be specified on every run. In development mode, the server notices changes to the application and reloads it, which is rather helpful.
Flask uses the Jinja2 templating language, which shares many features with Bottle's template system, though with some syntax differences. The biggest difference, at least for the fairly simple templates I have been working with, is that statements are enclosed in "{%" and "%}", rather than Bottle's angle-bracket scheme. In truth, I have yet to run into things I couldn't do with either templating system. There are extensions for both frameworks to switch to a different templating language if that is needed or desired.
One nice feature is that templates can inherit from a base template in Flask. That can also be done in Bottle using @view() but it is less convenient—or so it seemed to me. Flask also has direct support for sessions, so values can be stored and retrieved from the object. Flask serializes the session data into a cookie that gets cryptographically signed. That means the session's contents are visible to users, but cannot be modified; it also means that session data needs to fit inside the 4K limit imposed for cookies by most browsers.
The difference between the core functionality of Flask and Bottle is not huge by any means. Either makes a good basis for a simple web application. The main difference between them seems to be embodied in the momentum of the project and its community. Perhaps Bottle is simply mature and has the majority of the features its users and developers are looking for, much like Quixote:
Bottle has fairly frequent releases, but otherwise seems to be standing still. The Twitter feed, blog, mailing list, and GitHub repository have not been updated much recently, for example. Bottle also lacks the "killer tutorial" that Grinberg has put together for Flask. But part of what makes that Flask tutorial so useful is all of the plugins from the Flask community that Grinberg uses along the way.
In some sense, the tutorial takes Flask from a microframework to a full-stack framework (or a long way down that path anyway). It is an opinionated tutorial that picks and chooses various Flask plugins that help implement each chapter's feature for the "microblog" application that he describes. For example, it uses Flask-WTF to interface to WTForms, Flask-SQLAlchemy for an ORM, Flask-Login for user-session management, Flask-Mail for sending email, and so on.
While I haven't (yet, perhaps) needed some of those features, I did confirm that most or all of the packages are available for Fedora, which is convenient for me. In many ways, Grinberg's tutorial "tied the room together" in terms of seeing a simple Flask application growing into something "real". It showed how to add some functionality I wanted to Flask (form handling in particular) and to see how other possible features could also be added easily down the road.
One could perhaps argue that simply starting with a full-stack framework, rather than adding bits piecemeal to get there, might make more sense—and perhaps it does. But those larger frameworks are rather more daunting to get started with and are, obviously, opinionated as well. If I disagree with Grinberg about the need for a particular piece, I can just leave it out or choose something different; that's more difficult to do with, say, Django.
Lots of liquor references
Apparently working with web applications (and frameworks) leads developers to start thinking about whiskey bottles and flasks, or so it would seem based on some of the names. Web programming is a fiddly business in several dimensions. Web frameworks help with some of the server-side fiddly bits, but there are still plenty more available to be tackled.
HTML and CSS are sometimes irritatingly painful to work with and web frameworks can only provide so much help there. At one level, HTML/CSS is a truly amazing technology that is supported by so many different kinds of programs and devices. On another, though, it is an ugly, hard-to-use format with an inadequate set of user-interface controls and mechanisms so that it often seems much harder than it should be to accomplish what you are trying to do.</rant>
But, of course, web programming is fun and you can easily show your friends what silly thing you have been working on, no matter how far away they live. For that, Pythonistas (and perhaps others) should look at the huge diversity of web frameworks available for the language and, if the mood to create that silly thing strikes, give one of them a try. Bottle or Flask might be a great place to start.
Index entries for this article | |
---|---|
Python | Web |
Posted Jul 10, 2019 0:11 UTC (Wed)
by Kamilion (subscriber, #42576)
[Link] (2 responses)
Although, I'd like to point out some of it's best extensions aren't very well known like flask-classy/classful
Can also go the other way, and sit on top of some C code.
Sanic is a drop in async replacement for Flask (more or less) with massive performance gain.
Responder is a really nice tool as well, which can mount existing flask endpoints and handle background threads.
Borrowing the magic of node's libuv, and the uvloop FFI, python can zoom as fast as Golang can.
Unfortunately, I've been using uwsgi+nginx+uwsgi_pass for ages; and I'm reluctant to move back to proxy_pass, so I've stuck to pure Flask for now.
I could add Responder or Sanic to my existing webapps with a snap, more or less, I'd have to change out this singular python object, from Flask's implementation to Sanic's (which is API compatible)
flask_core = Flask(__name__)
api = responder.API()
But don't blame me if ye cannot get ye flask.
Posted Jul 11, 2019 13:36 UTC (Thu)
by hazmat (subscriber, #668)
[Link]
Posted Jul 11, 2019 19:05 UTC (Thu)
by rillian (subscriber, #11344)
[Link]
I'd also come across projects like FastAPI and uvicorn, but have had trouble understanding where they sit in the ecosystem. They talk about being really fast because async, but the examples are all just serving "Hello, World" over json. I couldn't find any guides about which framework to choose for different applications.
Posted Jul 10, 2019 0:58 UTC (Wed)
by pablotron (subscriber, #105494)
[Link] (22 responses)
It's also worth noting that the recommended way of installing Flask and extensions is in a virtual environment (e.g. virtualenv or pipenv), rather than via system packages (dnf, apt, etc).
From the Flask documentation:
Use a virtual environment to manage the dependencies for your project, both in development and in production.
What problem does a virtual environment solve? The more Python projects you have, the more likely it is that you need to work with different versions of Python libraries, or even Python itself. Newer versions of libraries for one project can break compatibility in another project.
Virtual environments are independent groups of Python libraries, one for each project. Packages installed for one project will not affect other projects or the operating system’s packages.
Posted Jul 10, 2019 15:17 UTC (Wed)
by NYKevin (subscriber, #129325)
[Link] (7 responses)
Posted Jul 11, 2019 14:31 UTC (Thu)
by mirabilos (subscriber, #84359)
[Link] (5 responses)
Posted Jul 11, 2019 22:01 UTC (Thu)
by smitty_one_each (subscriber, #28989)
[Link] (3 responses)
Posted Jul 11, 2019 23:21 UTC (Thu)
by mirabilos (subscriber, #84359)
[Link] (1 responses)
Posted Jul 12, 2019 0:50 UTC (Fri)
by smitty_one_each (subscriber, #28989)
[Link]
And that is sensible if one is merely installing some python for the infrastructure case, e.g. Ansible.
If doing any substantial tinkering, the distro is going to be a pain point.
Posted Jul 15, 2019 19:32 UTC (Mon)
by mathstuf (subscriber, #69389)
[Link]
Posted Jul 12, 2019 7:07 UTC (Fri)
by massimiliano (subscriber, #3048)
[Link]
For me this quickly became install system-wide in a container.
It's the "general way" of having project-specific environments (regardless of the language or framework used in each project) and keeping my development workstation clean.
Moreover, these days I'm going to deploy containers anyway, so developing inside containers effectively unifies local and "remote" (or "cloud") build environments.
Posted Jul 16, 2019 9:41 UTC (Tue)
by gdamjan (subscriber, #33634)
[Link]
you can (have more versions per single user), see the PYTHONUSERBASE environment variable[1]. it''s basically a single env var virtualenv, built-in into python with no need for the virtualenv hacks.
reference:
Posted Jul 10, 2019 19:44 UTC (Wed)
by Sesse (subscriber, #53779)
[Link] (13 responses)
Posted Jul 10, 2019 20:21 UTC (Wed)
by Cyberax (✭ supporter ✭, #52523)
[Link] (9 responses)
Posted Jul 11, 2019 9:30 UTC (Thu)
by smurf (subscriber, #17840)
[Link] (2 responses)
Posted Jul 11, 2019 9:56 UTC (Thu)
by Cyberax (✭ supporter ✭, #52523)
[Link] (1 responses)
Quick googling also doesn't fill me with confidence about the ease of doing it.
Posted Jul 20, 2019 19:28 UTC (Sat)
by garloff (subscriber, #319)
[Link]
Posted Jul 11, 2019 14:32 UTC (Thu)
by mirabilos (subscriber, #84359)
[Link] (5 responses)
Posted Jul 11, 2019 14:48 UTC (Thu)
by rahulsundaram (subscriber, #21946)
[Link] (2 responses)
How did you get to conclude that?
Posted Jul 11, 2019 15:03 UTC (Thu)
by mirabilos (subscriber, #84359)
[Link] (1 responses)
Posted Jul 11, 2019 15:19 UTC (Thu)
by rahulsundaram (subscriber, #21946)
[Link]
You are asking developers to limit themselves to match a single slow moving distribution's schedule. History has clearly shown us that it is not going to work. Distributions will simply get bypassed
Posted Jul 11, 2019 18:37 UTC (Thu)
by Cyberax (✭ supporter ✭, #52523)
[Link] (1 responses)
Posted Jul 11, 2019 19:54 UTC (Thu)
by rodgerd (guest, #58896)
[Link]
Posted Jul 11, 2019 17:12 UTC (Thu)
by patrakov (subscriber, #97174)
[Link] (2 responses)
-------- Forwarded Message --------
Hello,
you are listed as a maintainer of python-jinja2.
Today I was reading the blog of Armin Ronacher (the upstream author of
http://lucumr.pocoo.org/2016/12/29/careful-with-str-format/
I.e., Jinja 2.8.1 is a security release, for a sandbox escape via a
There is no CVE ID for this vulnerability.
Debian Stable is still at 2.8-1, i.e. does not include any patches over
Posted Jul 11, 2019 17:24 UTC (Thu)
by zdzichu (subscriber, #17118)
[Link]
Posted Jul 12, 2019 8:07 UTC (Fri)
by pabs (subscriber, #43278)
[Link]
https://security-tracker.debian.org/tracker/CVE-2016-10745
It is marked as not going to get an update by the Debian security team due to being a minor issue. The maintainer or anyone else can do an update for the issue:
https://www.debian.org/doc/manuals/developers-reference/c...
Posted Jul 10, 2019 6:58 UTC (Wed)
by smurf (subscriber, #17840)
[Link]
Posted Jul 10, 2019 12:19 UTC (Wed)
by mirabilos (subscriber, #84359)
[Link]
> Note: We strongly recommend always using a container, virtualization, or sandboxing environment of some kind when developing using Python; installing things system-wide is yucky (for a variety of reasons) nine times out of ten. We prefer light-weight virtualenv, others prefer solutions as robust as Vagrant.
This reads as “we don’t want to be packaged in a distro and thus will break things willy-nilly to ensure they don’t do that”.
Mucking about with microframeworks
https://github.com/teracyhq/flask-classful
https://github.com/huge-success/sanic
https://python-responder.org/en/latest/tour.html#mount-a-...
https://github.com/kennethreitz/responder
It sits on top of Uvicorn and Starlette ASGI.
https://www.starlette.io/
https://magic.io/blog/uvloop-blazing-fast-python-networking/
https://www.techempower.com/benchmarks/#section=data-r17&...
becomes:
flask_core = Sanic(__name__)
api.mount('/yeflask', flask_core)
https://tvtropes.org/pmwiki/pmwiki.php/Main/YouCantGetYeF...
Mucking about with microframeworks
Mucking about with microframeworks
Mucking about with microframeworks
Mucking about with microframeworks
Mucking about with microframeworks
Mucking about with microframeworks
app_a is humming along, and uses a component.
I start app_b and install the same component, and app_b upgrades a dependency that breaks app_a.
This will occur or be discovered at the least opportune time.
Mucking about with microframeworks
Mucking about with microframeworks
Mucking about with microframeworks
Mucking about with microframeworks
Mucking about with microframeworks
[1] https://www.python.org/dev/peps/pep-0370/
Mucking about with microframeworks
Mucking about with microframeworks
Mucking about with microframeworks
Mucking about with microframeworks
Mucking about with microframeworks
https://build.opensuse.org/
Before you dismiss b/c it sounds like it's SUSE focused: It builds RPMs and DEBs for a large set of Linux distros (and many architectures, though most Py packages are noarch).
Mucking about with microframeworks
Mucking about with microframeworks
Mucking about with microframeworks
Mucking about with microframeworks
Mucking about with microframeworks
Mucking about with microframeworks
Mucking about with microframeworks
Subject: Regarding python-jinja2 in Debian Stable
Date: Tue, 16 Oct 2018 04:35:31 +0500
From: Alexander E. Patrakov <patrakov@gmail.com>
To: piotr@debian.org
the said package), and found these two entries from year 2016:
https://palletsprojects.com/blog/jinja-281-released/
crafted template, with an exploit readily available and upstream
admitting that attacker-controlled templates do sometimes happen.
the vulnerable upstream 2.8 release. Could you please investigate
whether it makes sense to include the security fix?
Mucking about with microframeworks
Mucking about with microframeworks
https://www.debian.org/doc/manuals/developers-reference/c...
Mucking about with microframeworks
WebCore is not suitable for production