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