weblayer is made up of a number of components. You can use them “out of the box”, as shown by the Hello World example, or you can pick and choose from and override them, as introduced in the Components section.
You can then take advantage of the Request Handler API when writing your web application.
helloworld.py shows how to start writing a web application using weblayer‘s default configuration:
from weblayer import Bootstrapper, RequestHandler, WSGIApplication
class Hello(RequestHandler):
def get(self, world):
return u'hello %s' % world
mapping = [(r'/(.*)', Hello)]
config = {
'cookie_secret': '...',
'static_files_path': '/var/www/static',
'template_directories': ['templates']
}
bootstrapper = Bootstrapper(settings=config, url_mapping=mapping)
application = WSGIApplication(*bootstrapper())
def main():
from wsgiref.simple_server import make_server
make_server('', 8080, application).serve_forever()
if __name__ == '__main__': # pragma: no cover
main()
Let’s walk through it. First up, we import Bootstrapper, RequestHandler and WSGIApplication:
from weblayer import Bootstrapper, RequestHandler, WSGIApplication
We then see Hello, a simple request handler (aka “view”) that subclasses the RequestHandler class we imported:
class Hello(RequestHandler):
def get(self, world):
return u'hello %s' % world
Hello defines a single method: get, which will be called when Hello receives an HTTP GET request.
Note
By default, request handlers accept GET and HEAD requests (i.e.: they are theoretically read only). You can explicitly specify which methods of each request handler should be exposed using the __all__ property.
For example, to handle HEAD, GET, POST and DOFOO requests, you might write something like:
class Hello2(RequestHandler):
""" I explicitly accept only HEAD, GET, POST and DOFOO requests.
"""
__all__ = ('head', 'get', 'post', 'dofoo')
def get(self):
form = u'<form method="post"><input name="name" /></form>'
return u'What is your name? %s' % form
def post(self):
return u'Hello %s!' % self.request.params.get('name')
def dofoo(self):
return u'I just did foo!'
Note
In the above example, HEAD requests will be handled by the def get() method. This special case is carried through from the underlying webob.Response implementation (and is documented in select_method()). In most cases, the trick is simply to remember to include 'head' in your __all__ list of exposed methods wherever you expose 'get'.
Note
In order to protect against XSRF attacks, POST Requests (that are not XMLHttpRequest requests) are validated to check for the presence and value of an _xsrf parameter. You can include this in your forms using the xsrf_input (available as xrsf_input in your templates, e.g.:
<form>
${xsrf_input}
</form>
You can disable XSRF validation by setting check_xsrf to False in your application level settings and / or by overriding the check_xsrf RequestHandler class attribute, e.g.:
class Hello3(RequestHandler):
""" I don't validate POST requests against XSRF request forgery.
"""
check_xsrf = False
Handlers are mapped to incoming requests using the incoming request path. This mapping takes the form of a list of tuples where the first item in the tuple is a regular expression and the second is a RequestHandler class.
In this case, we map Hello to all incoming requests:
mapping = [(r'/(.*)', Hello)]
The groups in the regular expression (i.e.: the parts with parenthesis around them) that match the request path are passed to the appropriate method of the request handler as arguments. So, in this case, an HTTP GET request to /foo will yield one match group, 'foo' which is passed into Hello.get as the positional argument world, resulting in the response u'hello foo'.
You can see this for yourself by running:
weblayer-demo
And then opening http://localhost:8080/foo in a web browser.
Note
The pattern of using an explicit, ordered mapping of regular expressions to request handlers is used by many frameworks, including Google App Engine’s webapp framework. Other common patterns include routes and traversal, sometimes used in tandem with declarative configuration and / or decorators.
weblayer avoids declarative configuration by default. Decorators are not explicit and can introduce problems, as explained here.
Regular expressions are preferred over routes as they are both more powerful and an essential part of any Python developer’s toolbox. It seems strange to invent another tool for the job when such a good one already exists.
Finally, traversal implies there is an object graph to traverse, which is not always the case.
You may, of course, disagree with this analysis and override the IPathRouter implementation as you see fit.
Carrying on through the example, we next hardcode three configuration settings that are required by default:
config = {
'cookie_secret': '...',
'static_files_path': '/var/www/static',
'template_directories': ['templates']
}
We then initialise a Bootstrapper with these configuration settings and the url mapping we made earlier and use it to bootstrap a WSGIApplication:
bootstrapper = Bootstrapper(settings=config, url_mapping=mapping)
application = WSGIApplication(*bootstrapper())
Note
The Bootstrapper is similar to repoze.bfg’s Configurator in that it allows for imperative configuration of components.
Note
settings implements a pattern of optional explicit declaration of settings that is inspired by tornado.options. Explicitly requiring settings allows the application to throw an error on initialisation, rather than further down the line (e.g.: when a request happens to come in).
If you choose to, you can explicitly require your own settings by calling require_setting() at module level. For example, the cookie_secret requirement is defined at the top of weblayer.cookie using:
require_setting('cookie_secret', help='a long, random sequence of bytes')
Because require_setting() works in tandem with a venusian scan to prevent duplicate import issues, to require your own settings, you must tell weblayer to scan the modules you’ve required them in.
This can be done most simply by passing in a list of dotted names of modules or packages using the packages keyword argument to Bootstrapper.__call__. For example, to require all settings declared using require_setting() in modules in the my.webapp and some.dependency packages, use:
application = WSGIApplication(
*bootstrapper(packages=['my.webapp', 'some.dependency',])
)
Finally, the remainder of the example takes care of serving the example application on http://localhost:8080:
def main():
from wsgiref.simple_server import make_server
make_server('', 8080, application).serve_forever()
if __name__ == '__main__': # pragma: no cover
main()
For more realistic setups, see the Deployment recipes.
weblayer uses the Zope Component Architecture under the hood. Individual components are said to implement one of weblayer.interfaces, listed in weblayer.interfaces.__all__:
__all__ = [
'IAuthenticationManager',
'IMethodSelector',
'IPathRouter',
'IRequest',
'IRequestHandler',
'IResponse',
'IResponseNormaliser',
'ISecureCookieWrapper',
'ISettings',
'IStaticURLGenerator',
'ITemplateRenderer',
'IWSGIApplication'
]
For example, RegExpPathRouter:
class RegExpPathRouter(object):
""" Routes paths to request handlers using regexp patterns.
"""
implements(IPathRouter)
# ``__init__`` method removed from this example for brevity
def match(self, path):
for regexp, handler_class in self._mapping:
match = regexp.match(path)
if match:
return handler_class, match.groups(), {}
return None, None, None
Is one particular implementation of IPathRouter:
class IPathRouter(Interface):
""" Maps incoming requests to request handlers using the request path.
"""
def match(path):
""" Return ``handler, args, kwargs`` from ``path``.
"""
The default implementations are as follows:
Each application requires an ISettings implementation and an IPathRouter. These are passed in to your IWSGIApplication when it is initialised, most commonly using the Bootstrapper.
When HTTP requests come in to your application, IWSGIApplication uses the IPathRouter to map the incoming requests to an IRequestHandler that is instantiated with an IRequest, IResponse and the ISettings.
The IRequestHandler then uses the IMethodSelector to select which of its methods (def get(), def post() etc.) to call to handle the request. The method is then called with *args and **kwargs derived from the incoming request path by the IPathRouter.
When writing IRequestHandler code, you can take advantage of the Request Handler API to access your IStaticURLGenerator at self.static, your IAuthenticationManager at self.auth and your ISecureCookieWrapper at self.cookies. Your ITemplateRenderer is available through self.render().
The return value of your handler method is passed to your IResponseNormaliser, which uses it to either replace or update the IResponse originally passed in to your IRequestHandler before the IResponse is called to provide a WSGI compliant response from your application.
Alternative component implementations need to declare that they implement the appropriate interface and provide the attributes and methods that the interface specifies. For example, an alternative IPathRouter implementation needs to provide a match(path) method, e.g.:
class LazyPathRouter(object):
""" Never even bothers trying.
"""
implements(IPathRouter)
def match(self, path):
return None, None, None
The simplest way to then register this component is using the Bootstrapper when bootstrapping the WSGIApplication. The override/path_router.py example shows how:
from zope.interface import implements
from weblayer import Bootstrapper, RequestHandler, WSGIApplication
from weblayer.interfaces import IPathRouter
class LazyPathRouter(object):
""" Never even bothers trying.
"""
implements(IPathRouter)
def match(self, path):
return None, None, None
class Hello(RequestHandler):
def get(self, world):
return u'hello %s' % world
mapping = [(r'/(.*)', Hello)]
config = {
'cookie_secret': '...',
'static_files_path': '/var/www/static',
'template_directories': ['templates']
}
bootstrapper = Bootstrapper(settings=config, url_mapping=mapping)
application = WSGIApplication(*bootstrapper(path_router=LazyPathRouter()))
if __name__ == '__main__':
from wsgiref.simple_server import make_server
make_server('', 8080, application).serve_forever()
If you then run this, all requests will meet with a 404 response:
$ python src/weblayer/examples/override_path_router.py
... "GET / HTTP/1.1" 404 0
... "GET /foo HTTP/1.1" 404 0
You can see two further examples at override/authentication_manager.py and override/template_renderer.py
Note
Using the Bootstrapper to register components is entirely optional. You can register components manually using (or even by monkey patching) the weblayer.component registry.
RequestHandler provides the following useful attributes and methods:
Note
When you use self.render(), it passes through any keyword arguments you provide, along with self.request as request, self.auth.current_user as current_user, self.static.get_url() as get_static_url() and self.xsrf_input as xsrf_input to the template namespace (along with any built-ins your ITemplateRenderer implementation provides).