Any sufficiently advanced application is split into discrete and independently verifiable components. In WebCore most of these components are WSGI middleware layers; they are given the opportunity to process the request coming from the user before your application is called, and process the result returned by your application before it reaches the user.
Internal to your own application we recommend Model, View, Controller (MVC) separation.
Table of Contents
MVC is a software architecture that separates the components of the application: the model represents the business logic or data; the view represents the user interface; and the controller manages user input or, in some cases, the application flow. This form of separation has become the de-facto standard not just for web applications, but for desktop software as well.
There are several core reasons why MVC is beneficial compared to more liberal “tag soup” solutions where code is mixed with presentation:
One very important point about MVC, especially if you come from a background of other MVC frameworks, is that what you believe MVC to be is probably wrong. Here are a few excellent articles you can read to get a more complete understanding of this separation:
Controllers are the glue that take a user request, perform some action based on the input, and combines modelled data with a view for presentation back to the user. Object dispatch controllers (the default) must be subclasses of web.core.Controller. In the simplest case, a “hello world” example, the controller takes no input and always returns the same view:
import web
class RootController(web.core.Controller):
def index(self):
return "project.templates.hello", dict()
Obviously real-world controllers would be slightly more complicated, returning data (from a model) to the template, accepting input, validating form data (using the model), and generally doing anything you can do in Python, because it is Python. Controllers are, however, the least re-usable component of any web application, hardest to test thoroughly, and most prone to breaking if any of the other components change.
Keep your controllers light-weight, specific, and avoid duplicating the functionality of the other layers. Your controller should not be a mutated model.
WebCore takes one of several different approaches to finding which controller to use. The default, and probably the quickest to get started with, is object dispatch. When a request comes in for a certain URL, the URL is split into components at each / character. The object dispatch dialect then attempts to find the most specific controller available using the following logic:
WebCore adheres to the de-facto standard to mark attributes (including methods) as private by prefixing the name of the attribute with an underscore. While Python itself does not enforce single underscore prefixes (and barely enforces double underscore prefixes) WebCore will prevent access to all attributes that feature the prefix. You can still use underscore-prefixed path elements, however, as the value will be passed to a valid controller method, the default method, or lookup method, if defined.
GET and POST variables are automatically decoded and passed to your controller as keyword arguments, with POST taking precedence. Elements that occur multiple times in the query string or POST body will be transformed into a list of values automatically. (No need for PHP’s list argument style of using a [] suffix.)
If there are remaining path elements, they are passed as positional arguments.
There are four methods you can define in your controller class to effect the dispatch process:
Called before the request method. You can use this method to filter the arguments passed to the request method or otherwise execute code beforehand, e.g. to perform controller-wide authorization.
class Foo(Controller):
def __before__(self, *args, **kw):
# Perform your actions here...
# Finally, allow superclasses to modify the arguments as well...
return super(Foo, self).__before__(*args, **kw)
If present this method will be called if no valid attribute can be found for the current path element, and only if no __default__ method is available. This method directly alters the dispatch mechanism allowing you to redirect the path of attribute descent.
The most common use of this method is to allow for RESTful dispatch of model objects. The __lookup__ method is passed the remaining path elements as positional arguments and GET/POST data as keyword arguments. This method should return a 2-tuple of a new controller to continue descent through and a tuple or list of remaining path elements.
For more information, see the Alternate Dispatch Mechanisms chapter.
The model is responsible for storing all data, period. This can mean data stored in a relational database, browser session, or cache. The model is also responsible for all rules, restraints, and access and behaviour requirements for this data, such as input validation, formatting, and business logic.
“Now wait just a moment!” you may be telling the screen, or paper, if you have a dead tree copy of this document, “Shouldn’t logic be put in the controller?” Yes and no. The model doesn’t just represent mere data, it represents the entire system for which that data is useful.
For more information see the Databases section and Sessions & Caching in this chapter.
For more information see the Templating & Serialization section.
The default mechanism for sessions and caching in WebCore is by way of the Beaker middleware layer. To use sessions and caching in your own application, install Beaker:
(core)$ pip install Beaker
Configure your web application to use it:
web.sessions = True
web.caching = True
And update your setup.py install_requires section to include Beaker.
Beaker has a large number of configuration options; in WebCore your prefix these options with web.sessions. and web.caching. respectively.
Complete and well-written documentation on configuration and usage is available from the Beaker website.
WebCore makes using sessions easy; you can get and store variables in the thread-local web.core.session variable as if it were a dictionary. If you have not enabled auto-saving (with the web.sessions.auto configuration directive) you will need to manually call the .save() method of the session to persist your changes across requests.
For more information, see Beaker’s “Using Sessions” online documentation.
The Beaker cache is a flexible and powerful way of storing ephemeral (temporary) information where generating that information initially is expensive. Web browsers already do caching of pages and resources on pages, Beaker provides similar functionality for fine-grained sections of your own code.
The Wiki provides a simple example of using Beaker to cache rendered content, since Textile rendering is inherently slower than returning static HTML.
@property
def rendered(self):
@web.core.cache.cache('wiki', expires=3600)
def cache(name, date):
return textile.textile(self.content)
return cache(self.name, self.modified)
The @cache decorator is a direct reference to the decorator from the Beaker package. The two arguments referenced here are the name of the cache namespace and time (in seconds) for the data to live before being invalidated/expunged from the cache.
For more information, see Beaker’s “Caching” online documentation.
If you specify debug = True in your configuration then exceptions raised from within your application will be shown in-browser will a complete interactive debugger. This is a significant security risk in a production system and as such should be disabled for production use. When disabled, exceptions will be logged to the Python logger and can be e-mailed to you. All configuration directives should be prefixed with debug..
The following configuration directives are valid when debugging is enabled:
The following configuration directives are valid when not debugging (in production):
In development mode (debug = True) WebCore will automatically search for a folder called public within your application’s package. In production, you will need to explicitly enable static file serving if you require it. To do so, set web.static = True. If WebCore can not automatically detect the path to your static files, you will need to explicitly define the path by setting the web.static.path configuration directive.
Compression allows clients using your web application to receive content faster. To enable compression, set web.compress = True in your configuration. To override the amount of compression, set web.compress.level to a number between zero (no compression) and nine (maximum compression).
One caveat to enabling compression is that if you ever return a response with zero-length body, compression will fail in an explosive way. I.e. if you implement HEAD & eTag caching of web browser responses, _do not_ enable compression.