.. _concepts:
==================
Concepts of Pylons
==================
Understanding the basic concepts of Pylons, the flow of a request and response
through the stack and how Pylons operates makes it easier to customize when
needed, in addition to clearing up misunderstandings about why things behave
the way they do.
This section acts as a basic introduction to the concept of
a :term:`WSGI` application, and :term:`WSGI Middleware` in addition to showing
how Pylons utilizes them to assemble a complete working web framework.
To follow along with the explanations below, create a project following the
:ref:`getting_started` Guide.
*****************************
The 'Why' of a Pylons Project
*****************************
A new Pylons project works a little differently than in many other web
frameworks. Rather than loading the framework, which then finds a new
projects code and runs it, Pylons creates a Python package that does the
opposite. That is, when its run, it imports objects from Pylons, assembles
the WSGI Application and stack, and returns it.
If desired, a new project could be completely cleared of the Pylons imports
and run any arbitrary WSGI application instead. This is done for a greater
degree of freedom and flexibility in building a web application that works
the way the developer needs it to.
By default, the project is configured to use standard components that most
developers will need, such as sessions, template engines, caching, high
level request and response objects, and an :term:`ORM`. By having it all
setup in the project (rather than hidden away in 'framework' code), the
developer is free to tweak and customize as needed.
In this manner, Pylons has setup a project with its *opinion* of what may
be needed by the developer, but the developer is free to use the tools
needed to accomplish the projects goals. Pylons offers an unprecedented
level of customization by exposing its functionality through the project
while still maintaining a remarkable amount of simplicity by retaining a
single standard interface between core components (:term:`WSGI`).
*****************
WSGI Applications
*****************
WSGI is a basic specification known as :pep:`333`, that describes a
method for interacting with a HTTP server. This involves a way to get access
to HTTP headers from the request, and how set HTTP headers and return content
on the way back out.
A 'Hello World' WSGI Application:
.. code-block :: python
def simple_app(environ, start_response):
start_response('200 OK', [('Content-type', 'text/html')])
return ['
Hello World']
This WSGI application does nothing but set a 200 status code for the response,
set the HTTP 'Content-type' header, and return some HTML.
The WSGI specification lays out a `set of keys that will be set in the
environ dict `_.
The WSGI interface, that is, this method of calling a function (or method of
a class) with two arguments, and handling a response as shown above, is used
throughout Pylons as a standard interface for passing control to the next
component.
Inside a new project's :file:`config/middleware.py`, the `make_app` function is
responsible for creating a WSGI application, wrapping it in WSGI middleware
(explained below) and returning it so that it may handle requests from a
HTTP server.
.. _wsgi-middleware:
***************
WSGI Middleware
***************
Within :file:`config/middleware.py` a Pylons application is wrapped in successive layers which add functionality. The process of wrapping the Pylons application in middleware results in a structure conceptually similar to the layers in an onion.
.. image:: _static/pylons_as_onion.png
:alt: Pylons middleware onion analogy
:align: center
Once the middleware has been used to wrap the Pylons application, the make_app
function returns the completed app with the following structure (outermost
layer listed first):
.. code-block:: text
Registry Manager
Status Code Redirect
Error Handler
Cache Middleware
Session Middleware
Routes Middleware
Pylons App (WSGI Application)
WSGI middleware is used extensively in Pylons to add functionality to the
base WSGI application. In Pylons, the 'base' WSGI Application is the
:class:`~pylons.wsgiapp.PylonsApp`. It's responsible for looking in the
`environ` dict that was passed in (from the Routes Middleware).
To see how this functionality is created, consider a small class that
looks at the `HTTP_REFERER` header to see if it's Google:
.. code-block :: python
class GoogleRefMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
environ['google'] = False
if 'HTTP_REFERER' in environ:
if environ['HTTP_REFERER'].startswith('http://google.com'):
environ['google'] = True
return self.app(environ, start_response)
This is considered WSGI Middleware as it still can be called and returns
like a WSGI Application, however, it's adding something to environ, and then
calls a WSGI Application that it is initialized with. That's how the layers
are built up in the `WSGI Stack` that is configured for a new Pylons project.
Some of the layers, like the Session, Routes, and Cache middleware, only add
objects to the `environ` dict, or add HTTP headers to the response (the Session middleware for example adds the session cookie header). Others, such
as the Status Code Redirect, and the Error Handler may fully intercept the
request entirely, and change how it's responded to.
*******************
Controller Dispatch
*******************
When the request passes down the middleware, the incoming URL gets parsed in
the RoutesMiddleware, and if it matches a URL (See :ref:`url-config`), the
information about the controller that should be called is put into the `environ` dict for use by :class:`~pylons.wsgiapp.PylonsApp`.
The :class:`~pylons.wsgiapp.PylonsApp` then attempts to find a controller in the :file:`controllers`
directory that matches the name of the controller, and searches for a class
inside it by a similar scheme (controller name + 'Controller', ie,
HelloController). Upon finding a controller, its then called like any other
WSGI application using the same WSGI interface that
:class:`~pylons.wsgiapp.PylonsApp` was called with.
This is why the BaseController that resides in a project's
:file:`lib/base.py` module inherits from
:class:`~pylons.controllers.core.WSGIController` and has a `__call__`
method that takes the `environ` and `start_response`. The
:class:`~pylons.controllers.core.WSGIController` locates a method in the
class that corresponds to the `action` that Routes found, calls it, and
returns the response completing the request.
******
Paster
******
Running the :command:`paster` command all by itself will
show the sets of commands it accepts:
.. code-block :: bash
$ paster
Usage: paster [paster_options] COMMAND [command_options]
Options:
--version show program's version number and exit
--plugin=PLUGINS Add a plugin to the list of commands (plugins are Egg
specs; will also require() the Egg)
-h, --help Show this help message
Commands:
create Create the file layout for a Python distribution
grep Search project for symbol
help Display help
make-config Install a package and create a fresh config file/directory
points Show information about entry points
post Run a request for the described application
request Run a request for the described application
serve Serve the described application
setup-app Setup an application, given a config file
pylons:
controller Create a Controller and accompanying functional test
restcontroller Create a REST Controller and accompanying functional test
shell Open an interactive shell with the Pylons app loaded
If :command:`paster` is run inside of a Pylons project, this should be the
output that will be printed. The last section, `pylons` will be absent if
it is not run inside a Pylons project. This is due to a dynamic plugin
system the :command:`paster` script uses, to determine what sets of
commands should be made available.
Inside a Pylons project, there is a directory ending in `.egg-info`, that has
a :file:`paster_plugins.txt` file in it. This file is looked for and read by
the :command:`paster` script, to determine what other packages should be
searched dynamically for commands. Pylons makes several commands available
for use in a Pylons project, as shown above.
***********************
Loading the Application
***********************
Running (and thus loading) an application is done using the :command:`paster`
command:
.. code-block :: bash
$ paster serve development.ini
This instructs the paster script to go into a 'serve' mode. It will attempt
to load both a server and a WSGI application that should be served, by
parsing the configuration file specified. It looks for a `[server]` block to
determine what server to use, and an `[app]` block for what WSGI application
should be used.
The basic egg block in the :file:`development.ini` for a `helloworld` project:
.. code-block :: ini
[app:main]
use = egg:helloworld
That will tell paster that it should load the helloworld :term:`egg` to locate
a WSGI application. A new Pylons application includes a line in the
:file:`setup.py` that indicates what function should be called to make the
WSGI application:
.. code-block :: python
entry_points="""
[paste.app_factory]
main = helloworld.config.middleware:make_app
[paste.app_install]
main = pylons.util:PylonsInstaller
""",
Here, the `make_app` function is specified as the `main` WSGI application that
Paste (the package that :command:`paster` comes from) should use.
The `make_app` function from the project is then called, and the server (by
default, a HTTP server) runs the WSGI application.