User Guide

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.

Hello World

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

Handling Requests

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.

Bootstrapping

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',])
)

Serving

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.

Components

Architecture

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``.
        """

Default Implementations

The default implementations are as follows:

Workflow

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.

Overriding

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.

Request Handler API

RequestHandler provides the following useful attributes and methods:

  • self.request is an IRequest instance encapsulating the incoming HTTP request
  • self.response is an IResponse instance you can choose to manipulate and return (or update indirectly through the IResponseNormaliser)
  • self.settings is an ISettings instance that provides dictionary-like access to your settings
  • self.auth is an IAuthenticationManager instance that provides is_authenticated and current_user properties
  • self.cookies is an ISecureCookieWrapper instance that provides methods to set() and get() secure cookies
  • self.static is an an IStaticURLGenerator instance that provides a get_url() method to generate static URLs
  • self.xsrf_input is an html <input /> element you can include in forms to protect against XSRF attacks
  • return self.error() to return an HTTP error
  • return self.redirect() to redirect the request
  • return self.render() to return a rendered template

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).

Table Of Contents

Previous topic

Getting Started

Next topic

Recipes

This Page