nb. the pattern I'm showing here is basically flask's blueprints
It seems that currently Klein doesn't have any builtin way to help factoring out view functions to separate modules.
For example, if you had the following app:
app = Klein()
@app.route('/users')
def list_all_users(request):
return 'all users'
then you could divide it into two modules like this:
# main.py
app = Klein()
# users.py - version 1
from main import app
@app.route('/users')
# ...
This is bad because it leads to several problems, the most immediate being circular import issues, but also this is only a separate module technically, not logically: you can't really re-use that module with another app, or test it in isolation.
An easy way to help that would be to use dependency injection:
# main.py
from users import add_users_routes
app = Klein()
add_users_routes(app)
# users.py - version 2
def list_all_users(request):
return 'all_users'
def add_users_routes(app):
app.route('/users')(list_all_users)
Now users
is a separate logical module, but it's a bit awkward, with the central add_users_routes
function. We could use Klein's .resource()
to help that:
# users.py - version 3
users = Klein()
@users.route('/')
def list_all_users(request):
return 'all users'
def add_users_routes(app):
@app.route('/users', branch=True)
def branch_to_users(request):
return users.resource()
This is already pretty nice, could maybe use some helpers so that you wouldn't need to implement that branch function, but is reusable and possible to test in isolation. The problem however is, routing to a Klein resource returns control to twisted.web, which then calls .render()
on the nested resource, which causes a werkzeug map bind, etc - it's a performance hit. A simple hello-world-grade benchmark shown that a root Klein can serve ~2200 requests per second, routing one branch deep: ~1700, two branches deep: ~1400 (experience with flask's blueprints shows that 2 levels of nesting are enough in practice)
I'm aware Klein is a web framework, and web applications aren't typically constrained by strict real-time requirements, and they're often easy to scale, but I think Klein needs some guideline on structuring applications, and while we're at it, might as well make it fast :)
Basically I think we want the syntax of users.py
version 2, with the performance of version 3. Here's how flask does it (translated to Klein):
# main.py
from users import users_blueprint
app = Klein()
users_blueprint.register(app, url_prefix='/users')
# users.py
users_blueprint = Blueprint()
@users_blueprint.route('/')
# ...
The Blueprint is simply a container that records all @route()
calls it was used for, and does them on the app when .register
is called. This makes it only a setup-time thing, with no runtime (performance) effects.
I've put together a proof-of-concept implementation for reference, see https://github.com/tehasdf/klein/tree/blueprints (here's an example.rst in the repo)