Quickstart¶
Morepath is a micro-framework, and this makes it small and easy to learn. This quickstart guide should help you get started. We assume you’ve already installed Morepath; if not, see the Installation section.
Hello world¶
Let’s look at a minimal “Hello world!” application in Morepath:
import morepath
class App(morepath.App):
pass
@App.path(path='')
class Root(object):
pass
@App.view(model=Root)
def hello_world(self, request):
return "Hello world!"
if __name__ == '__main__':
config = morepath.setup()
config.scan()
config.commit()
morepath.run(App())
You can save this as hello.py
and then run it with Python:
$ python hello.py
Running <morepath.App 'Hello'> with wsgiref.simple_server on http://127.0.0.1:5000
If you now go with a web browser to the URL given, you should see “Hello world!” as expected. When you want to stop the server, just press control-C.
This application is a bit bigger than you might be used to in other web micro-frameworks. That’s for a reason: Morepath is not geared to create the most succinct “Hello world!” application but to be effective for building slightly larger applications, all the way up to huge ones.
Let’s go through the hello world app step by step to gain a better understanding.
Code Walkthrough¶
We import
morepath
.We create a subclass of
morepath.App
namedApp
. This class contains our application’s configuration: what models and views are available. It can also be instantiated into a WSGI application object.We then set up a
Root
class. Morepath is model-driven and in order to create any views, we first need at least one model, in this case the emptyRoot
class.We set up the model as the root of the website (the empty string
''
indicates the root, but'/'
works too) using themorepath.App.path()
decorator.Now we can create the “Hello world” view. It’s just a function that takes
self
andrequest
as arguments (we don’t need to use either in this case), and returns the string"Hello world!"
. Theself
argument is the instance of themodel
class that is being viewed.We then need to hook up this view with the
morepath.App.view()
decorator. We say it’s associated with theRoot
model. Since we supply no explicitname
to the decorator, the function is the default view for theRoot
model on/
.The
if __name__ == '__main__'
section is a way in Python to make the code only run if thehello.py
module is started directly with Python as discussed above. In a real-world application you instead use a setuptools entry point so that a startup script for your application is created automatically.morepath.setup()
sets up Morepath’s default behavior, and returns a Morepath config object. If your app is in a Python package and you’ve set up the rightinstall_requires
insetup.py
, consider usingmorepath.autosetup()
to be done in one step. See Configuration for more detail.We then
scan()
this module (or package) for configuration decorators (such asmorepath.App.path()
andmorepath.App.view()
) and cause the registration to be registered usingmorepath.Config.commit()
.This step ensures your configuration (model routes, views, etc) is loaded exactly once in a way that’s reusable and extensible.
Note that if you want to use a Morepath extension like
more.static
, you need to either scan this as well:import more.static ... config.scan(more.static) ...
or use
morepath.autosetup()
to automate this. See Organizing your Project for more information.We then instantiate the
App
class to create aWSGI
app using the default web server. Since you create a WSGI app you can also plug it into any other WSGI server.
This example presents a compact way to organize your code in a single module, but for a real project we recommend you read Organizing your Project. This supports organizing your project with multiple modules.
Routing¶
Morepath uses a special routing technique that is different from many other routing frameworks you may be familiar with. Morepath does not route to views, but routes to models instead.
Models¶
A model is any Python object that represents the content of your application: say a document, or a user, an address, and so on. A model may be a plain in-memory Python object or be backed by a database using an ORM such as SQLAlchemy, or some NoSQL database such as the ZODB. This is entirely up to you; Morepath does not put special requirements on models.
Above we’ve exposed a Root
model to the root route /
, which is
rather boring. To make things more interesting, let’s imagine we have
an application to manage users. Here’s our User
class:
class User(object):
def __init__(self, username, fullname, email):
self.username = username
self.fullname = fullname
self.email = email
We also create a simple users database:
users = {}
def add_user(user):
users[user.username] = user
faassen = User('faassen', 'Martijn Faassen', 'faassen@startifact.com')
bob = User('bob', 'Bob Bobsled', 'bob@example.com')
add_user(faassen)
add_user(bob)
Publishing models¶
We want our application to have URLs that look like this:
/users/faassen
/users/bob
Here’s the code to expose our users database to such a URL:
@App.path(model=User, path='/users/{username}')
def get_user(username):
return users.get(username)
The get_user
function gets a user model from the users database by
using the dictionary get
method. If the user doesn’t exist, it
returns None
. We could’ve fitted a SQLAlchemy query in here
instead.
Now let’s look at the decorator. The model
argument has the class
of the model that we’re putting on the web. The path
argument has
the URL path under which it should appear.
The path can have variables in it which are between curly braces
({
and }
). These variables become arguments to the function
being decorated. Any arguments the function has that are not in the
path are interpreted as URL parameters.
What if the user doesn’t exist? We want the end-user to see a 404
error. Morepath does this automatically for you when you return
None
for a model, which is what get_user
does when the model
cannot be found.
Now we’ve published the model to the web but we can’t view it yet.
For more on this, see Paths and Linking.
Views¶
In order to actually see a web page for a user model, we need to create a view for it:
@App.view(model=User)
def user_info(self, request):
return "User's full name is: %s" % self.fullname
The view is a function decorated by morepath.App.view()
(or
related decorators such as morepath.App.json()
and
morepath.App.html()
) that gets two arguments: self
,
which is the model that this view is working for, so in this case an
instance of User
, and request
which is the current
request. request
is a morepath.request.Request
object (a
subclass of webob.request.BaseRequest
).
Now the URLs listed above such as /users/faassen
will work.
What if we want to provide an alternative view for the user, such as
an edit
view which allows us to edit it? We need to give it a
name:
@App.view(model=User, name='edit')
def edit_user(self, request):
return "An editing UI goes here"
Now we have functionality on URLs like /users/faassen/edit
and
/users/bob/edit
.
For more on this, see Views.
Linking to models¶
Morepath is great at creating links to models: it can do it for you
automatically. Previously we’ve defined an instance of User
called
bob
. What now if we want to link to the default view of bob
?
We simply do this:
request.link(bob)
which generates the path /users/bob
for us.
What if we want to see Bob’s edit view? We do this:
request.link(bob, 'edit')
And we get /users/bob/edit
(with the hostname, for instance
http://example.com
, as a prefix).
Using morepath.Request.link`()
everywhere for link generation is
easy. You only need models and remember which view names are
available, that’s it. If you ever have to change the path of your
model, you won’t need to adjust any linking code.
For more on this, see Paths and Linking.
JSON and HTML views¶
@App.view
is rather bare-bones. You usually know more about what
you want to return than that. If you want to return JSON, you can use
the shortcut @App.json
instead to declare your view:
@App.json(model=User, name='info')
def user_json_info(self, request):
return {'username': self.username,
'fullname': self.fullname,
'email': self.email}
This automatically serializes what is returned from the function JSON,
and sets the content-type header to application/json
.
If we want to return HTML, we can use @App.html
:
@App.html(model=User)
def user_info(self, request):
return "<p>User's full name is: %s</p>" % self.fullname
This automatically sets the content type to text/html
. It doesn’t
do any HTML escaping though, so the use of %
above is unsafe! We
recommend the use of a HTML template language in that case.
Request object¶
The first argument for a view function is the request object. We’ll give a quick overview of what’s possible here, but consult the WebOb API documentation for more information.
request.GET
contains any URL parameters (?key=value
). Seewebob.request.BaseRequest.GET
.request.POST
contains any HTTP form data that was submitted. Seewebob.request.BaseRequest.POST
.request.method
gets the HTTP method (GET
,POST
, etc). Seewebob.request.BaseRequest.method
.request.cookies
contains the cookies. Seewebob.request.BaseRequest.cookies
.response.set_cookie
can be used to set cookies. Seewebob.response.Response.set_cookie()
.
Redirects¶
To redirect to another URL, use morepath.redirect()
. For example:
@App.view(model=User, name='extra')
def redirecting(self, request):
return morepath.redirect(request.link(self, 'other'))
HTTP Errors¶
To trigger an HTTP error response you can raise various WebOb HTTP
exceptions (webob.exc
). For instance:
from webob.exc import HTTPNotAcceptable
@App.view(model=User, name='extra')
def erroring(self, request):
raise HTTPNotAcceptable()
But note that Morepath already raises a lot of these errors for you automatically just by having your structure your code the Morepath way.