A Summer with Morepath
November 4, 2015This summer was intense, and it wasn't just the heat. I wanted to use Morepath to build a web-application for small Swiss towns. I ended up a Morepath core contributor and accidentally wrote our first in-house webframework at Seantis. Here's what I learned.
The Neat Framework You Don't Know About
Morepath is a micro web-framework which would be as well known as Flask, Pyramid or Django, had it been invented a few years earlier. These days there doesn't seem to be a big interest in new Python web-frameworks. Probably because the ones we have do their job well. They are well-tested, there are big eco-systems around them and they hold their own conferences in circus tents.
Fortunately, I'm one of those people that loves to get into new frameworks, if they offer fresh perspectives. Morepath certainly does that. Having used and compared Flask, Django, Pyramid and Plone I felt like those frameworks had a love-child together.
At this point I don't know if any framework is good for a beginner, so I won't try to make a point for this. What I want to show you is why Morepath has me all excited.
Note that this is also not an introduction to Morepath, for that you want to look at the excellent and extensive documentation.
WSGI All the Way Down
I spent enough time debugging web applications to know where the request runs through. In Python web frameworks this is the WSGI interface. Basically, every Python web-framework - not counting Zope - has a function like this one:
def simple_app(environ, start_response): status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return ['Hello world!\n']
This is where the request arrives and the response is returned. It's called WSGI or Python Web Server Gateway Interface (PEP-0333).
Here's a Morepath application:
import morepath class Blog(morepath.App): pass
See the Blog
class? It implements the WSGI interface like this (this is the actual code from Morepath):
def __call__(self, environ, start_response): request = self.request(environ) response = self.publish(request) return response(environ, start_response)
As you can see, this is where we build a request object from the information we got and publish a response.
This is hardly new.
What's different though is that the morepath.App
class is Morepath. It's front and center. If you write something with Morepath you are writing a Morepath application and you will use said application for all the important things.
This is great. Because if you know WSGI you can reason about Morepath without having to look up any documentation. Setting up Morepath for production I never had to look up anything. uWSGI, gunicorn, chausette and so on, they all take a WSGI callable. That is to say, any Morepath application instance.
Write One-Off Applications
Ever wrote that nice little application that does everything the way you and most of your customers like, except for that one customer that offers you a lot of money to do it his or her way?
With Morepath you can write an application just for that customer!
Well, kind of. Let's extend the example from above with a model, a path and a view. Think of this as pseudo-code, it won't actually work because I don't want to drag the whole ORM setup into this:
import morepath class Blog(morepath.App): @property def db_session(self): return session() class Page(object): pass @Blog.path(model=Page, name='/pages/{id}') def get_page(app, id): return app.db_session.query(Page).filter(Page.id == id).first() @Blog.json(model=Page) def view_page(self, request): return { 'id': self.id, 'title': self.title }
Here, we open pages by id. For example we would get the page with id 1
like this: /pages/1
. Because Morepath is awesome we don't have to worry about 404
's here because the (pseudo-)code above would return None if the page was not found, which signals to Morepath that a 404
is in order.
But I don't want to get into that too much. Again, read the docs.
Now let's say we have a wealthy customer who likes our application. But, for all pages with an id below 1000 he or she wants to use a different storage meachanism. Though this is not necessarily a realistic example, it is something that might be hard to implement without touching the original code.
Who knows, this application is not even developed anymore and we don't want to touch it because all hell would break loose?
This is how we solve this problem in Morepath:
from my_blog import Blog, Page, get_page class CustomBlog(Blog): pass @CustomBlog.path(model=Page, name='/pages/{id}') def custom_get_page(app, id): if id <= 1000: # do our weird customization else: return get_page(app, id)
Morepath allows for clean reusability. To us, this is a big deal. We often reuse applications for various customers. Usually we can just use them as-is. But every so often we get this one request which is too custom to warrant a new feature and too lucrative to say no to.
With Morepath we can keep the lines of code needed for such customzations to an absolute minimum. We can also separate them cleanly from the core product.
An Accidental Framework
So how did I end up writing a Framework on top of Morepath? Turns out that there is a very small eco-system around Morepath. When I started writing a prototype using Morepath I realized that to write a traditional Python web-application I would need the following:
- A way to render html templates (including i18n).
- A way sign cookies for authentication.
- A session storage (I usually go with memcached).
- A simple way to serve static files.
- A way to use a form framework
This being only some general things missing from Morepath and its ecosystem at the time. Lots of more application-specific things, like ORM model upgrades, Elasticsearch integration and shared themes were other things we needed.
Martijn Faasen, the creator of Morepath, was nice enough to write a general way to integrate template engines around that time. I still had to integrate i18n, but that was easy.
To get signed cookies I integrated itsdangerous as a Morepath extension: https://github.com/morepath/more.itsdangerous
To get simple static files support I integrated webassets, also as an extension: https://github.com/morepath/more.webassets
(It should be noted that there's also more.static
, a more modern approach to static files using bower)
The rest I implemented directly into a core Morepath application, which is what turned out to be the framework of it all. Over time I might move more things out of this framework into additional more.*
packages.
It seems pretty obvious now that this core was destined to be its own in-house framework, but I kind of stumbled into this. Not too long after working on our web-application for small towns we were asked to write a web-application to track voting results on election day. We decided to re-use our core for this and it worked!
Conclusion
We are about to go live with two towns which signed up for our web-application for municipalities. We have our clients test our new election day application and we got there ahead of time.
These applications include everything we need in web-applictions. We render html, show forms securely, handle uploads/downloads, we compile SCSS and JSX files, we have upgrade procedures, a multi-tennant architecture for scalability and so on.
All of this works with Morepath and it all fits the design of Morepath. We are perfectly happy with it and we will be active users for many years to come.
So if you haven't heard of it before, check it out now: https://morepath.readthedocs.org/
If you would look at how we did things, go here: http://onegov.readthedocs.org/en/latest/
We are convinced that Morepath is ready for the prime time and we think that others would enjoy working with it as much as we do.