written on Monday, June 14, 2010
Howdy everybody. This is the first part of a multi-part blog post about creating web frameworks based on Werkzeug and other existing code. This is obviously based on my Flask microframework. So it probably makes sense to head over to the documentation first to look at some example code. But before we get started, let's discuss first about why you should create your own frameworks.
It is quite unpopular these days to go with building your own framework; everybody quickly shouts "reinventing the wheel" and points you to one of the tons of existing web frameworks out there. But it is actually a really good idea to create a framework for an application and not go with a stock one. Why? Because you are a lot more flexible and your application might require something that does not exist yet. For an application I wrote in the past in the very early Django days the development process looked a lot like this:
Step 1: download django, Step 2: get started and feel happy, Step 3: encounter problems in the framework design and start modifiying the core, Step 4: phase more and more Django code out and end up with a completely new package that everybody hates.
Turns out: Django like every other framework out there is improving quickly, but often not in the areas you might be interested in. Then you start modifying it yourself and when Django improves sideways, you suddenly end up in the situation where it becomes nearly impossible to upgrade to a newer Django version or it's too painful. Obviously Django has greatly improved since then, but a few things continue to work differently than I want them to work. For one I personally don't like the template engine too much and also would love the ORM to ensure that objects with the same primary key are actually the same objects and queries sent less often. These are things that are very unlikely to change in Django and there are really good reasons why this will not change which are totally fine, but certainly not what I want.
Another reason to roll your own framework is that you know everything and you can fix it quickly yourself.
This is what should work at the end of the day:
from yourflask import YourFlask
app = YourFlask()
@app.route('/')
def index(request):
return 'Hello World'
if __name__ == '__main__':
app.run()
Looks a lot like a simplified Flask version, which is exactly what it should be. Not yet as capable, but easier to dive in and to understand the concepts.
In a nutshell: 1) create an application, 2) register functions on that application that listen on a specific path (or URL rule), 3) these functions return response objects or strings. We also pass the request object explicitly to the function for now because that's easier to understand and implement.
The following code implements the full framework for this blog post. As I said, it's a very simplified Flask but it is capable of producing simple web applications and to run the example from above:
from werkzeug import Request, Response, run_simple
from werkzeug.exceptions import HTTPException
from werkzeug.routing import Map, Rule
class YourFlask(object):
def __init__(self):
self.url_map = Map()
self.views = {}
def route(self, string, **options):
def decorator(f):
options['endpoint'] = f.__name__
self.views[f.__name__] = f
self.url_map.add(Rule(string, **options))
return f
return decorator
def run(self, **options):
return run_simple('localhost', 5000, self, **options)
def make_response(self, rv):
if isinstance(rv, basestring):
return Response(rv, mimetype='text/html')
return rv
def __call__(self, environ, start_response):
request = Request(environ)
adapter = self.url_map.bind_to_environ(environ)
try:
endpoint, values = adapter.match()
response = self.make_response(self.views[endpoint](request, **values))
except HTTPException, e:
response = e
return response(environ, start_response)
So how exactly does it work and what does it do? The following list is the summary of the above code:
So what we created is a WSGI application. How exactly does it work and where is the WSGI part? The majority of the pain is handled for us by Werkzeug. WSGI itself looks like this:
If you look close, we are doing that in our __call__() method. Well, it's not really visible but it happens. When we invoke the response thingy, internally Werkzeug will call the response starting function and all for us. We also use the WSGI environment when we create the request object.
The request object itself gives us access to all the stuff that is incoming from the browser: where the request went, what values were transmitted, what browser is used, the cookies etc. We will focus on that with the next blog post.
Now that all is working fine we should focus on these things next:
Stay tuned :)