Clean up and update docs

Clean up some formatting issues in doc sources
and smooth out some wording.

Change-Id: I2bb2c9a32d67b71deeb7fcc13fd6a2949b4e195b
Signed-off-by: Doug Hellmann <doug.hellmann@dreamhost.com>
This commit is contained in:
Doug Hellmann
2013-05-03 09:34:36 -04:00
parent 98f3c566ee
commit ee74648851
20 changed files with 728 additions and 466 deletions

View File

@@ -5,15 +5,17 @@
Command Line Pecan
==================
Any Pecan application can be controlled and inspected from the command line
using the built-in ``pecan`` command. The usage examples of the ``pecan``
command in this document are intended to be invoked from your project's root
directory.
Any Pecan application can be controlled and inspected from the command
line using the built-in :command:`pecan` command. The usage examples
of :command:`pecan` in this document are intended to be invoked from
your project's root directory.
Serving a Pecan App For Development
-----------------------------------
Pecan comes bundled with a lightweight WSGI development server based on
Python's ``wsgiref.simpleserver`` module.
Python's :py:mod:`wsgiref.simpleserver` module.
Serving your Pecan app is as simple as invoking the ``pecan serve`` command::
@@ -21,7 +23,7 @@ Serving your Pecan app is as simple as invoking the ``pecan serve`` command::
Starting server in PID 000.
serving on 0.0.0.0:8080, view at http://127.0.0.1:8080
...and then visiting it in your browser.
and then visiting it in your browser.
The server ``host`` and ``port`` in your configuration file can be changed as
described in :ref:`server_configuration`.
@@ -31,6 +33,7 @@ described in :ref:`server_configuration`.
The Interactive Shell
---------------------
Pecan applications also come with an interactive Python shell which can be used
to execute expressions in an environment very similar to the one your
application runs in. To invoke an interactive shell, use the ``pecan shell``
@@ -70,10 +73,12 @@ Press ``Ctrl-D`` to exit the interactive shell (or ``Ctrl-Z`` on Windows).
Using an Alternative Shell
++++++++++++++++++++++++++
``pecan shell`` has optional support for the `IPython <http://ipython.org/>`_
and `bpython <http://bpython-interpreter.org/>`_ alternative shells, each of
which can be specified with the ``--shell`` flag (or its abbreviated alias,
``-s``), e.g.,
::
$ pecan shell --shell=ipython config.py
@@ -84,15 +89,18 @@ which can be specified with the ``--shell`` flag (or its abbreviated alias,
Configuration from an environment variable
------------------------------------------
In all the examples shown, you will see that the `pecan` commands were
accepting a file path to the configuration file. An alternative to this is to
specify the configuration file in an environment variable (``PECAN_CONFIG``).
In all the examples shown, you will see that the :command:`pecan` commands
accepted a file path to the configuration file. An alternative to this is to
specify the configuration file in an environment variable (:envvar:`PECAN_CONFIG`).
This is completely optional; if a file path is passed in explicitly, Pecan will
honor that before looking for an environment variable.
For example, to ``serve`` a Pecan application, a variable could be exported and
subsequently be re-used when no path is passed in::
For example, to serve a Pecan application, a variable could be exported and
subsequently be re-used when no path is passed in.
::
$ export PECAN_CONFIG=/path/to/app/config.py
$ pecan serve
@@ -100,15 +108,16 @@ subsequently be re-used when no path is passed in::
serving on 0.0.0.0:8080, view at http://127.0.0.1:8080
Note that the path needs to reference a valid pecan configuration file,
otherwise the command will error out with a meaningful message indicating that
otherwise the command will error out with a message indicating that
the path is invalid (for example, if a directory is passed in).
If ``PECAN_CONFIG`` is not set and no configuration is passed in, the command
If :envvar:`PECAN_CONFIG` is not set and no configuration is passed in, the command
will error out because it will not be able to locate a configuration file.
Extending ``pecan`` with Custom Commands
----------------------------------------
While the commands packaged with Pecan are useful, the real utility of its
command line toolset lies in its extensibility. It's convenient to be able to
write a Python script that can work "in a Pecan environment" with access to
@@ -152,8 +161,8 @@ Let's analyze this piece-by-piece.
Overriding the ``run`` Method
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
First, we're subclassing ``pecan.commands.BaseCommand`` and extending
the ``run`` method to:
First, we're subclassing :class:`pecan.commands.BaseCommand` and extending
the :func:`run` method to:
* Load a Pecan application - ``self.load_app()``
* Wrap it in a fake WGSI environment - ``webtest.TestApp()``
@@ -162,18 +171,20 @@ the ``run`` method to:
Defining Custom Arguments
,,,,,,,,,,,,,,,,,,,,,,,,,
The ``arguments`` class attribute is used to define command line arguments
The :attr:`arguments` class attribute is used to define command line arguments
specific to your custom command. You'll notice in this example that we're
*adding* to the arguments list provided by ``pecan.commands.BaseCommand``
*adding* to the arguments list provided by :class:`pecan.commands.BaseCommand`
(which already provides an argument for the ``config_file``), rather
than overriding it entirely.
The format of the ``arguments`` class attribute is a *tuple* of dictionaries,
The format of the :attr:`arguments` class attribute is a :class:`tuple` of dictionaries,
with each dictionary representing an argument definition in the
same format accepted by Python's |argparse|_ module (more specifically,
``argparse.ArgumentParser.add_argument``). By providing a list of arguments in
this format, the ``pecan`` command can include your custom commands in the help
and usage output it provides::
:func:`argparse.ArgumentParser.add_argument`). By providing a list of arguments in
this format, the :command:`pecan` command can include your custom commands in the help
and usage output it provides.
::
$ pecan -h
usage: pecan [-h] command ...
@@ -183,25 +194,26 @@ and usage output it provides::
wget Issues a (simulated) HTTP GET and returns the request body
serve Open an interactive shell with the Pecan app loaded
...
::
$ pecan wget -h
usage: pecan wget [-h] config_file path
$ pecan wget config.py /path/to/some/resource
Additionally, you'll notice that the first line of ``GetCommand``'s docstring
- ``Issues a (simulated) HTTP GET and returns the request body`` - is
automatically used to describe the ``wget`` command in the output for ``$ pecan
-h``. Following this convention allows you to easily integrate a summary for
your command into the Pecan command line tool.
Additionally, you'll notice that the first line of the docstring from
:class:`GetCommand` -- ``Issues a (simulated) HTTP GET and returns the
request body`` -- is automatically used to describe the :command:`wget`
command in the output for ``$ pecan -h``. Following this convention
allows you to easily integrate a summary for your command into the
Pecan command line tool.
Registering a Custom Command
++++++++++++++++++++++++++++
Now that you've written your custom command, youll need to tell your
distributions ``setup.py`` about its existence and reinstall. Within your
distributions ``setup.py`` file, you'll find a call to ``setuptools.setup()``,
e.g., ::
distributions ``setup.py`` file, you'll find a call to :func:`setuptools.setup`.
::
# myapp/setup.py
...
@@ -213,7 +225,7 @@ e.g., ::
)
Assuming it doesn't exist already, we'll add the ``entry_points`` argument
to the ``setup()`` call, and define a ``[pecan.command]`` definition for your custom
to the :func:`setup` call, and define a ``[pecan.command]`` definition for your custom
command::
@@ -231,10 +243,14 @@ command::
)
Once you've done this, reinstall your project in development to register the
new entry point::
new entry point.
::
$ python setup.py develop
...and give it a try::
Then give it a try.
::
$ pecan wget config.py /path/to/some/resource

View File

@@ -2,13 +2,17 @@
Configuring Pecan Applications
==============================
Pecan is very easy to configure. As long as you follow certain conventions,
using, setting and dealing with configuration should be very intuitive.
Pecan configuration files are pure Python.
Pecan configuration files are pure Python. Each "section" of the
configuration is a dictionary assigned to a variable name in the
configuration module.
Default Values
---------------
Below is the complete list of default values the framework uses::
@@ -30,9 +34,12 @@ Below is the complete list of default values the framework uses::
Application Configuration
-------------------------
This is the part of the configuration that is specific to your application -
the framework uses it to wrap your application into a valid
`WSGI app <http://www.wsgi.org/en/latest/what.html>`_.
The ``app`` configuration values are used by Pecan to wrap your
application into a valid `WSGI app
<http://www.wsgi.org/en/latest/what.html>`_. The ``app`` configuration
is specific to your application, and includes values like the root
controller class location.
A typical application configuration might look like this::
@@ -46,40 +53,56 @@ A typical application configuration might look like this::
Let's look at each value and what it means:
**app** is a reserved variable name for the configuration, so make sure you
don't override it.
**modules**
A list of modules where pecan will search for applications.
Generally this should contain a single item, the name of your
project's python package. At least one of the listed modules must
contain an ``app.setup_app`` function which is called to create the
WSGI app. In other words, this package should be where your
``app.py`` file is located, and this file should contain a
``setup_app`` function.
**modules** is a list of modules where pecan will search for applications.
Generally this should contain a single item, the name of your project's
python package.
At least one of the listed modules must contain an ``app.setup_app`` function
which is called to create the WSGI app. In other words, this package should
be where your ``app.py`` file is located, and this file should contain a
``setup_app`` function.
See :ref:`app_template` for more about the ``app.py`` file.
**root**
The root controller of your application. Remember to provide a
string representing a Python path to some callable (e.g.,
``"yourapp.controllers.root.RootController"``).
**root** The root controller of your application. Remember to provide
a string representing a Python path to some callable (e.g.,
``"yourapp.controllers.root.RootController"``).
**static_root**
The directory where your static files can be found (relative to
the project root). Pecan comes with middleware that can
be used to serve static files (like CSS and Javascript files) during
development.
**static_root** Points to the directory where your static files live (relative
to the project root). By default, Pecan comes with middleware that can be
used to serve static files (like CSS and Javascript files) during development.
**template_path**
Points to the directory where your template files live (relative to
the project root).
**template_path** Points to the directory where your template files live
(relative to the project root).
**debug**
Enables ``WebError`` to display tracebacks in the browser
**debug** Enables ``WebError`` to display tracebacks in the browser
(**IMPORTANT**: Make sure this is *always* set to ``False`` in production
environments).
.. warning::
``app`` is a reserved variable name for that section of the
configuration, so make sure you don't override it.
.. warning::
Make sure **debug** is *always* set to ``False`` in production environments.
.. seealso::
* :ref:`app_template`
.. _server_configuration:
Server Configuration
--------------------
Pecan provides some sane defaults. Change these to alter the host and port your
WSGI app is served on::
WSGI app is served on.
::
server = {
'port' : '8080',
@@ -88,9 +111,12 @@ WSGI app is served on::
Additional Configuration
------------------------
Your application may need access to other configuration values at runtime
(like third-party API credentials). These types of configuration can be
defined in their own blocks in your configuration file::
Your application may need access to other configuration values at
runtime (like third-party API credentials). Put these settings in
their own blocks in your configuration file.
::
twitter = {
'api_key' : 'FOO',
@@ -99,17 +125,20 @@ defined in their own blocks in your configuration file::
.. _accessibility:
Accessing Configuration at Runtime
Accessing Configuration at Runtime
----------------------------------
You can access any configuration value at runtime via ``pecan.conf``.
This includes custom, application and server-specific values.
You can access any configuration value at runtime via :py:mod:`pecan.conf`.
This includes custom, application, and server-specific values.
For example, if you needed to specify a global administrator, you could
do so like this within the configuration file::
do so like this within the configuration file.
::
administrator = 'foo_bar_user'
And it would be accessible in `pecan.conf` as::
And it would be accessible in :py:mod:`pecan.conf` as::
>>> from pecan import conf
>>> conf.administrator
@@ -118,12 +147,13 @@ And it would be accessible in `pecan.conf` as::
Dictionary Conversion
---------------------
In certain situations you might want to deal with keys and values, but in strict
dictionary form. The ``Config`` object has a helper method for this purpose
that will return a dictionary representation of itself, including nested values.
Below is a representation of how you can access the ``as_dict`` method and what
should return as a result (shortened for brevity):
In certain situations you might want to deal with keys and values, but in strict
dictionary form. The :class:`Config` object has a helper method for this purpose
that will return a dictionary representation of the configuration, including nested values.
Below is a representation of how you can access the :func:`as_dict` method and what
it returns as a result (shortened for brevity):
::
@@ -136,9 +166,9 @@ should return as a result (shortened for brevity):
Prefixing Dictionary Keys
-------------------------
``Config.as_dict`` allows you to pass an optional argument if you need to
prefix the keys in the returned dictionary. This is a single argument in string
form and it works like this (shortened for brevity):
:func:`Config.as_dict` allows you to pass an optional string argument
if you need to prefix the keys in the returned dictionary.
::

View File

@@ -2,15 +2,21 @@
Working with Databases, Transactions, and ORM's
===============================================
Out of the box, Pecan provides no opinionated support for working with databases,
but it's easy to hook into your ORM of choice with minimal effort. This article
details best practices for integrating the popular Python ORM, SQLAlchemy, into
Pecan provides no opinionated support for working with databases, but
it's easy to hook into your ORM of choice. This article details best
practices for integrating the popular Python ORM, SQLAlchemy_, into
your Pecan project.
.. _SQLAlchemy: http://sqlalchemy.org
``init_model`` and Preparing Your Model
---------------------------------------
Pecan's default quickstart project includes an empty stub directory for implementing
your model as you see fit::
Pecan's default quickstart project includes an empty stub directory
for implementing your model as you see fit.
::
.
└── test_project
@@ -21,7 +27,9 @@ your model as you see fit::
│   ├── __init__.py
└── templates
By default, this module contains a special method, ``init_model``::
By default, this module contains a special method, :func:`init_model`.
::
from pecan import conf
@@ -38,13 +46,18 @@ By default, this module contains a special method, ``init_model``::
"""
pass
The purpose of this method is to determine bindings from your configuration file and create
necessary engines, pools, etc... according to your ORM or database toolkit of choice.
The purpose of this method is to determine bindings from your
configuration file and create necessary engines, pools,
etc. according to your ORM or database toolkit of choice.
Additionally, your project's ``model`` module can be used to define functions for common binding
operations, such as starting transactions, committing or rolling back work, and clearing a Session.
This is also the location in your project where object and relation definitions should be defined.
Here's what a sample Pecan configuration file with database bindings might look like::
Additionally, your project's :py:mod:`model` module can be used to define
functions for common binding operations, such as starting
transactions, committing or rolling back work, and clearing a session.
This is also the location in your project where object and relation
definitions should be defined. Here's what a sample Pecan
configuration file with database bindings might look like.
::
# Server Specific Configurations
server = {
@@ -65,7 +78,10 @@ Here's what a sample Pecan configuration file with database bindings might look
'encoding' : 'utf-8'
}
...and a basic model implementation that can be used to configure and bind using SQLAlchemy::
And a basic model implementation that can be used to configure and
bind using SQLAlchemy.
::
from pecan import conf
from sqlalchemy import create_engine, MetaData
@@ -98,11 +114,15 @@ Here's what a sample Pecan configuration file with database bindings might look
Binding Within the Application
------------------------------
There are several approaches that can be taken to wrap your application's requests with calls
to appropriate model function calls. One approach is WSGI middleware. We also recommend
Pecan :ref:`hooks`. Pecan comes with ``TransactionHook``, a hook which can
be used to wrap requests in database transactions for you. To use it, simply include it in your
project's ``app.py`` file and pass it a set of functions related to database binding::
There are several approaches to wrapping your application's requests
with calls to appropriate model function calls. One approach is WSGI
middleware. We also recommend Pecan :ref:`hooks`. Pecan comes with
:class:`TransactionHook`, a hook which can be used to wrap requests in
database transactions for you. To use it, simply include it in your
project's ``app.py`` file and pass it a set of functions related to
database binding.
::
from pecan import conf, make_app
from pecan.hooks import TransactionHook
@@ -124,34 +144,48 @@ project's ``app.py`` file and pass it a set of functions related to database bin
]
)
For the above example, on HTTP POST, PUT, and DELETE requests, ``TransactionHook`` behaves in the
following manner:
In the above example, on HTTP ``POST``, ``PUT``, and ``DELETE``
requests, :class:`TransactionHook` takes care of the transaction
automatically by following these rules:
#. Before controller routing has been determined, ``model.start()`` is called. This function should bind to the appropriate SQLAlchemy engine and start a transaction.
#. Before controller routing has been determined, :func:`model.start`
is called. This function should bind to the appropriate
SQLAlchemy engine and start a transaction.
#. Controller code is run and returns.
#. If your controller or template rendering fails and raises an exception, ``model.rollback()`` is called and the original exception is re-raised. This allows you to rollback your database transaction to avoid committing work when exceptions occur in your application code.
#. If your controller or template rendering fails and raises an
exception, :func:`model.rollback` is called and the original
exception is re-raised. This allows you to rollback your database
transaction to avoid committing work when exceptions occur in your
application code.
#. If the controller returns successfully, ``model.commit()`` and ``model.clear()`` are called.
#. If the controller returns successfully, :func:`model.commit` and
:func:`model.clear` are called.
On idempotent operations (like HTTP GET and HEAD requests), TransactionHook behaves in the following
manner:
On idempotent operations (like HTTP ``GET`` and ``HEAD`` requests),
:class:`TransactionHook` handles transactions following different
rules.
#. ``model.start_read_only()`` is called. This function should bind to your SQLAlchemy engine.
#. ``model.start_read_only()`` is called. This function should bind
to your SQLAlchemy engine.
#. Controller code is run and returns.
#. If the controller returns successfully, ``model.clear()`` is called.
#. If the controller returns successfully, ``model.clear()`` is
called.
Also note that there is a useful ``@after_commit`` decorator provided in :ref:`pecan_decorators`.
Also note that there is a useful :func:`@after_commit` decorator provided
in :ref:`pecan_decorators`.
Splitting Reads and Writes
--------------------------
Employing the strategy above with ``TransactionHook`` makes it very simple to split database
reads and writes based upon HTTP methods (i.e., GET/HEAD requests are read-only and would potentially
be routed to a read-only database slave, while POST/PUT/DELETE requests require writing, and
would always bind to a master database with read/write privileges). It's also very easy to extend
``TransactionHook`` or write your own hook implementation for more refined control over where and
when database bindings are called.
Employing the strategy above with :class:`TransactionHook` makes it very
simple to split database reads and writes based upon HTTP methods
(i.e., GET/HEAD requests are read-only and would potentially be routed
to a read-only database slave, while POST/PUT/DELETE requests require
writing, and would always bind to a master database with read/write
privileges). It's also possible to extend :class:`TransactionHook` or
write your own hook implementation for more refined control over where
and when database bindings are called.

View File

@@ -3,36 +3,37 @@
Deploying Pecan in Production
=============================
Deploying a Pecan project to a production environment can be accomplished in
a variety of ways. A few popular options for deployment are documented here.
It is important, however, to note that these examples are meant to provide
*direction*, not explicit instruction; deployment is usually heavily dependent
upon the needs and goals of individual applications, so your mileage will
There are a variety of ways to deploy a Pecan project to a production
environment. The following examples are meant to provide *direction*,
not explicit instruction; deployment is usually heavily dependent upon
the needs and goals of individual applications, so your mileage will
probably vary.
.. note::
.. ::
While Pecan comes packaged with a simple server *for development use*
(``pecan serve``), using a *production-ready* server similar to the ones
described in this document is **very highly encouraged**.
Installing Pecan
----------------
A few popular options are avaliable for installing Pecan in production
environments:
* Using `setuptools/distribute
<http://packages.python.org/distribute/setuptools.html>`_. Manage
Pecan as a dependency in your project's ``setup.py`` file so that it's
installed alongside your project (e.g., ``python
/path/to/project/setup.py install``). The default Pecan project
described in :ref:`quick_start` facilitates this by including Pecan as
a dependency for your project.
* Using `setuptools/distribute
<http://packages.python.org/distribute/setuptools.html>`_. Manage
Pecan as a dependency in your project's ``setup.py`` file so that it's
installed alongside your project (e.g., ``python
/path/to/project/setup.py install``). The default Pecan project
described in :ref:`quick_start` facilitates this by including Pecan as
a dependency for your project.
* Using `pip <http://www.pip-installer.org/en/latest/requirements.html>`_.
Use ``pip freeze`` and ``pip install`` to create and install from
a ``requirements.txt`` file for your project.
* Using `pip <http://www.pip-installer.org/en/latest/requirements.html>`_.
Use ``pip freeze`` and ``pip install`` to create and install from
a ``requirements.txt`` file for your project.
* Via the manual instructions found in :ref:`Installation`.
* Via the manual instructions found in :ref:`Installation`.
.. note::
Regardless of the route you choose, it's highly recommended that all
@@ -41,10 +42,14 @@ environments:
Disabling Debug Mode
--------------------
One of the most important steps to take before deploying a Pecan app into
production is to disable **Debug Mode**, which is responsible for serving
static files locally and providing a development-oriented debugging environment
for tracebacks. In your production configuration file, ensure that::
One of the most important steps to take before deploying a Pecan app
into production is to disable **Debug Mode**, which is responsible for
serving static files locally and providing a development-oriented
debugging environment for tracebacks. In your production
configuration file, ensure that ``debug`` is set to ``False``.
::
# myapp/production_config.py
app = {
@@ -54,6 +59,7 @@ for tracebacks. In your production configuration file, ensure that::
Pecan and WSGI
--------------
WSGI is a Python standard that describes a standard interface between servers
and an application. Any Pecan application is also known as a "WSGI
application" because it implements the WSGI interface, so any server that is
@@ -74,6 +80,7 @@ generated using ``pecan.deploy``::
Considerations for Static Files
-------------------------------
Pecan comes with static file serving (e.g., CSS, Javascript, images)
middleware which is **not** recommended for use in production.
@@ -90,14 +97,14 @@ are two:
<http://www.lighttpd.net/>`__) to serve static files and proxy application
requests through to your WSGI application:
::
::
<HTTP Client> ─── <Production/Proxy Server>, e.g., Apache, nginx, cherokee (0.0.0.0:80) ─── <Static Files>
├── <WSGI Server> Instance e.g., mod_wsgi, Gunicorn, uWSGI (127.0.0.1:5000 or /tmp/some.sock)
├── <WSGI Server> Instance e.g., mod_wsgi, Gunicorn, uWSGI (127.0.0.1:5001 or /tmp/some.sock)
├── <WSGI Server> Instance e.g., mod_wsgi, Gunicorn, uWSGI (127.0.0.1:5002 or /tmp/some.sock)
└── <WSGI Server> Instance e.g., mod_wsgi, Gunicorn, uWSGI (127.0.0.1:5003 or /tmp/some.sock)
<HTTP Client> ─── <Production/Proxy Server>, e.g., Apache, nginx, cherokee (0.0.0.0:80) ─── <Static Files>
├── <WSGI Server> Instance e.g., mod_wsgi, Gunicorn, uWSGI (127.0.0.1:5000 or /tmp/some.sock)
├── <WSGI Server> Instance e.g., mod_wsgi, Gunicorn, uWSGI (127.0.0.1:5001 or /tmp/some.sock)
├── <WSGI Server> Instance e.g., mod_wsgi, Gunicorn, uWSGI (127.0.0.1:5002 or /tmp/some.sock)
└── <WSGI Server> Instance e.g., mod_wsgi, Gunicorn, uWSGI (127.0.0.1:5003 or /tmp/some.sock)
2. Serve static files via a separate service, virtual host, or CDN.
@@ -107,10 +114,15 @@ Common Recipes
Apache + mod_wsgi
+++++++++++++++++
`mod_wsgi <http://code.google.com/p/modwsgi/>`_ is a popular Apache module
which can be used to host any WSGI-compatible Python application (including your Pecan application).
To get started, check out the `installation and configuration documentation <http://code.google.com/p/modwsgi/wiki/InstallationInstructions>`_ for mod_wsgi.
`mod_wsgi <http://code.google.com/p/modwsgi/>`_ is a popular Apache
module which can be used to host any WSGI-compatible Python
application (including your Pecan application).
To get started, check out the `installation and configuration
documentation
<http://code.google.com/p/modwsgi/wiki/InstallationInstructions>`_ for
mod_wsgi.
For the sake of example, let's say that our project, ``simpleapp``, lives at
``/var/www/simpleapp``, and that a `virtualenv <http://www.virtualenv.org>`_
@@ -118,13 +130,15 @@ has been created at ``/var/www/venv`` with any necessary dependencies installed
(including Pecan). Additionally, for security purposes, we've created a user,
``user1``, and a group, ``group1`` to execute our application under.
The first step is to create a ``.wsgi`` file which mod_wsgi will use as an entry point for your application::
The first step is to create a ``.wsgi`` file which mod_wsgi will use
as an entry point for your application::
# /var/www/simpleapp/app.wsgi
from pecan.deploy import deploy
application = deploy('/var/www/simpleapp/config.py')
Next, add Apache configuration for your application. Here's a simple example::
Next, add Apache configuration for your application. Here's a simple
example::
<VirtualHost *>
ServerName example.com
@@ -140,12 +154,16 @@ Next, add Apache configuration for your application. Here's a simple example::
</Directory>
</VirtualHost>
For more instructions and examples of mounting WSGI applications using mod_wsgi, consult the `mod_wsgi Documentation <http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide#Mounting_The_WSGI_Application>`_.
For more instructions and examples of mounting WSGI applications using
mod_wsgi, consult the `mod_wsgi Documentation`_.
.. _mod_wsgi Documentation: http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide#Mounting_The_WSGI_Application
Finally, restart Apache and give it a try.
uWSGI
+++++
`uWSGI <http://projects.unbit.it/uwsgi/>`_ is a fast, self-healing and
developer/sysadmin-friendly application container server coded in pure C. It
uses the `uwsgi <http://projects.unbit.it/uwsgi/wiki/uwsgiProtocol>`__
@@ -163,17 +181,18 @@ Next, let's create a new file in the project root::
from pecan.deploy import deploy
application = deploy('config.py')
...and then run it with::
and then run it with::
$ uwsgi --http-socket 127.0.0.1:8000 -H /path/to/virtualenv -w wsgi
...or using a Unix socket (that nginx, for example, could be configured to
or using a Unix socket (that nginx, for example, could be configured to
`proxy to <http://projects.unbit.it/uwsgi/wiki/RunOnNginx>`_)::
$ uwsgi -s /tmp/uwsgi.sock -H ../path/to/virtualenv -w wsgi
Gunicorn
++++++++
`Gunicorn <http://gunicorn.org/>`__, or "Green Unicorn", is a WSGI HTTP Server for
UNIX. Its a pre-fork worker model ported from Rubys Unicorn project. It
supports both eventlet and greenlet.

View File

@@ -8,6 +8,7 @@ Developing Pecan Applications Locally
Debugging Pecan Applications
----------------------------
Pecan comes with simple debugging middleware for helping diagnose problems
in your applications. To enable the debugging middleware, simply set the
``debug`` flag to ``True`` in your configuration file::
@@ -33,14 +34,20 @@ console into the Python debugger, ``pdb``:
.. figure:: debug-middleware-2.png
:alt: Pecan debug middleware request debugger.
Refer to the `pdb documentation <http://docs.python.org/library/pdb.html>`_
for more information on using the Python debugger.
.. seealso::
Refer to the `pdb documentation
<http://docs.python.org/library/pdb.html>`_ for more information on
using the Python debugger.
Serving Static Files
--------------------
Pecan comes with simple file serving middleware for serving CSS, Javascript,
images, and other static files. You can configure it by ensuring that the
following options are specified in your configuration file::
following options are specified in your configuration file:
::
app = {
...
@@ -48,11 +55,12 @@ following options are specified in your configuration file::
'static_root': '%(confdir)/public
}
...where ``static_root`` is an absolute pathname to the directory in which your
where ``static_root`` is an absolute pathname to the directory in which your
static files live. For convenience, the path may include the ``%(confdir)``
variable, which Pecan will substitute with the absolute path of your
configuration file at runtime.
.. note::
In production, ``app.debug`` should *never* be set to ``True``, so you'll
need to serve your static files via your production web server.

View File

@@ -32,7 +32,10 @@ Configure Routing
Let's configure our application ``test_project`` to route ``HTTP 404 Page
Not Found`` messages to a custom controller.
First, let's tweak ``test_project/config.py``::
First, let's update ``test_project/config.py`` to specify a new
error-handler.
::
# Pecan Application Configurations
app = {
@@ -52,18 +55,19 @@ First, let's tweak ``test_project/config.py``::
}
}
Instead of the default error page, Pecan will now route 404 messages to our
very own controller named ``notfound``.
Next, let's implement the ``notfound`` controller.
Instead of the default error page, Pecan will now route 404 messages
to the controller method ``notfound``.
.. _controllers:
Write Custom Controllers
------------------------
The easiest way to implement our custom ``notfound`` error controller is to
add it to ``test_project.root.RootController`` class
(typically in ``test_project/controllers/root.py``)::
The easiest way to implement the error handler is to
add it to :class:`test_project.root.RootController` class
(typically in ``test_project/controllers/root.py``).
::
from pecan import expose
from webob.exc import status_map
@@ -98,14 +102,14 @@ add it to ``test_project.root.RootController`` class
And that's it!
Notice that the only bit of code we added to our RootController was::
Notice that the only bit of code we added to our :class:`RootController` was::
## custom handling of '404 Page Not Found' messages
@expose('error.html')
def notfound(self):
return dict(status=404, message="test_project does not have this page")
We simply ``@expose`` the ``notfound`` controller with the ``error.html``
We simply :func:`@expose` the ``notfound`` controller with the ``error.html``
template (which was conveniently generated for us and placed under
``test_project/templates/`` when we created ``test_project``). As with any
Pecan controller, we return a dictionary of variables for interpolation by the
@@ -113,4 +117,3 @@ template renderer.
Now we can modify the error template, or write a brand new one to make the 404
error status page of ``test_project`` as pretty or fancy as we want.

View File

@@ -2,7 +2,8 @@
Generating and Validating Forms
===============================
Out of the box, Pecan provides no opinionated support for working with
Pecan provides no opinionated support for working with
form generation and validation libraries, but its easy to import your
library of choice with minimal effort.
@@ -12,7 +13,10 @@ This article details best practices for integrating the popular forms library,
Defining a Form Definition
--------------------------
Let's start by building a basic form with a required ``first_name`` field and an optional ``last_name`` field::
Let's start by building a basic form with a required ``first_name``
field and an optional ``last_name`` field.
::
from wtforms import Form, TextField, validators
@@ -26,7 +30,9 @@ Let's start by building a basic form with a required ``first_name`` field and an
Rendering a Form in a Template
------------------------------
Next, let's add a controller, and pass a form instance to the template::
Next, let's add a controller, and pass a form instance to the template.
::
from pecan import expose
from wtforms import Form, TextField, validators
@@ -41,8 +47,9 @@ Next, let's add a controller, and pass a form instance to the template::
def index(self):
return dict(form=MyForm())
Here's the template file (which uses the `Mako <http://www.makeotemplates.org/>`_
templating language):
Here's the Mako_ template file:
.. _Mako: http://www.makeotemplates.org/
.. code-block:: html
@@ -61,7 +68,7 @@ templating language):
Validating POST Values
----------------------
Using the same ``MyForm`` definition, let's redirect the user if the form is
Using the same :class:`MyForm` definition, let's redirect the user if the form is
validated, otherwise, render the form again.
.. code-block:: python

View File

@@ -2,30 +2,33 @@
Pecan Hooks
===========
Pecan Hooks are a nice way to interact with the framework itself without having to
write WSGI middleware.
There is nothing wrong with WSGI Middleware, and actually, it is really easy to
use middleware with Pecan, but it can be hard (sometimes impossible) to have
access to Pecan's internals from within middleware. Hooks make this easier.
Although it is easy to use WSGI middleware with Pecan, it can be hard
(sometimes impossible) to have access to Pecan's internals from within
middleware. Pecan Hooks are a way to interact with the framework,
without having to write separate middleware.
Hooks allow you to execute code at key points throughout the life cycle of your request:
* ``on_route``: called before Pecan attempts to route a request to a controller
* :func:`on_route`: called before Pecan attempts to route a request to a controller
* ``before``: called after routing, but before controller code is run
* :func:`before`: called after routing, but before controller code is run
* ``after``: called after controller code has been run
* :func:`after`: called after controller code has been run
* ``on_error``: called when a request generates an exception
* :func:`on_error`: called when a request generates an exception
Implementating a Pecan Hook
---------------------------
In the below example, we will write a simple hook that will gather
some information about the request and print it to ``stdout``.
Your hook implementation needs to import ``PecanHook`` so it can be used as a base class.
From there, you'll need to override the ``on_route``, ``before``, ``after``, or ``on_error`` methods::
In the below example, a simple hook will gather some information about
the request and print it to ``stdout``.
Your hook implementation needs to import :class:`PecanHook` so it can be
used as a base class. From there, you'll need to override the
:func:`on_route`, :func:`before`, :func:`after`, or :func:`on_error` methods.
::
from pecan.hooks import PecanHook
@@ -38,16 +41,20 @@ From there, you'll need to override the ``on_route``, ``before``, ``after``, or
print "\nmethod: \t %s" % state.request.method
print "\nresponse: \t %s" % state.response.status
``on_route``, ``before``, and ``after`` are each passed a shared state object which includes useful
information about the request, such as the request and response object, and which controller
was chosen by Pecan's routing.
:func:`on_route`, :func:`before`, and :func:`after` are each passed a shared state
object which includes useful information, such as
the request and response objects, and which controller was selected by
Pecan's routing.
``on_error`` is passed a shared state object **and** the original exception.
:func:`on_error` is passed a shared state object **and** the original exception.
Attaching Hooks
---------------
Hooks can be attached in a project-wide manner by specifying a list of hooks
in your project's ``app.py`` file::
in your project's ``app.py`` file.
::
from application.root import RootController
from my_hooks import SimpleHook
@@ -58,7 +65,9 @@ in your project's ``app.py`` file::
)
Hooks can also be applied selectively to controllers and their sub-controllers
using the ``__hooks__`` attribute on one or more controllers::
using the :attr:`__hooks__` attribute on one or more controllers.
::
from pecan import expose
from pecan.hooks import HookController
@@ -73,8 +82,10 @@ using the ``__hooks__`` attribute on one or more controllers::
print "DO SOMETHING!"
return dict()
Now that our ``SimpleHook`` is included, let's see what happens when we run
the app and browse the application from our web browser::
Now that :class:`SimpleHook` is included, let's see what happens when we run
the app and browse the application from our web browser.
::
pecan serve config.py
serving on 0.0.0.0:8080 view at http://127.0.0.1:8080
@@ -87,29 +98,34 @@ the app and browse the application from our web browser::
Hooks That Come with Pecan
--------------------------
Pecan includes some hooks in its core and are very simple to start using them
away. This section will describe their different uses, how to configure them
and examples of common scenarios.
Pecan includes some hooks in its core. This section will describe
their different uses, how to configure them, and examples of common
scenarios.
.. _requestviewerhook:
RequestViewerHook
'''''''''''''''''
This hook is useful for debugging purposes. It has access to every
attribute the ``response`` object has plus a few others that are specific to
the framework.
There are two main ways that this hook can provide information about a request:
#. Terminal or logging output (via an file-like stream like `stdout`)
#. Terminal or logging output (via an file-like stream like ``stdout``)
#. Custom header keys in the actual response.
By default, both outputs are enabled.
For the actual object reference, please see :ref:`pecan_hooks`.
.. seealso::
* :ref:`pecan_hooks`
Enabling RequestViewerHook
..........................
This hook can be automatically added to the application itself if a certain
key, ``requestviewer``, exists in the configuration used for the app, e.g.::
@@ -123,6 +139,7 @@ created.
Configuring RequestViewerHook
.............................
There are a few ways to get this hook properly configured and running. However,
it is useful to know that no actual configuration is needed to have it up and
running.
@@ -136,11 +153,11 @@ By default it will output information about these items:
* params : A list of tuples for the params passed in at request time
* hooks : Any hooks that are used in the app will be listed here.
No configuration will show those values in the terminal via `stdout` and it
will also add them to the response headers (in the form of
`X-Pecan-item_name`).
The default configuration will show those values in the terminal via
``stdout`` and it will also add them to the response headers (in the
form of ``X-Pecan-item_name``).
This is how the terminal output might look for a `/favicon.ico` request ::
This is how the terminal output might look for a `/favicon.ico` request::
path - /favicon.ico
status - 404 Not Found
@@ -161,13 +178,13 @@ response::
X-Pecan-hooks ['RequestViewerHook']
The hook can be configured via a dictionary (or Config object from Pecan) when
adding it to the application or via the `requestviewer` key in the actual
adding it to the application or via the ``requestviewer`` key in the actual
configuration being passed to the application.
The configuration dictionary is flexible (none of the keys are required) and
can hold two keys: `items` and `blacklist`.
can hold two keys: ``items`` and ``blacklist``.
This is how the hook would look if configured directly when using `make_app`
This is how the hook would look if configured directly when using ``make_app``
(shortened for brevity)::
...
@@ -181,17 +198,19 @@ And the same configuration could be set in the config file like::
Modifying Output Format
.......................
Items are the actual information objects that the hook will use to return
information. Sometimes you will need a specific piece of information or
a certain bunch of them according to the development need so the defaults will
The ``items`` list specify the information that the hook will return.
Sometimes you will need a specific piece of information or a certain
bunch of them according to the development need so the defaults will
need to be changed and a list of items specified.
.. :note:
.. note::
When specifying a list of items, this list overrides completely the
defaults, so if a single item is listed, only that item will be returned by
the hook.
Remember, the hook has access to every single attribute the request object has
The hook has access to every single attribute the request object has
and not only to the default ones that are displayed, so you can fine tune the
information displayed.
@@ -240,7 +259,6 @@ is_body_readable urlvars
is_body_seekable uscript_name
is_xhr user_agent
make_body_seekable
====================== ==========================
And these are the specific ones from Pecan and the hook:
@@ -252,21 +270,22 @@ And these are the specific ones from Pecan and the hook:
Blacklisting Certain Paths
..........................
Sometimes it's annoying to get information about *every* single request. For this
purpose, there is a matching list of url paths that you can pass into the hook
so that paths that do not match are returned.
The matching is done at the start of the url path, so be careful when using
Sometimes it's annoying to get information about *every* single
request. To limit the ouptput, pass the list of URL paths for which
you do not want data as the ``blacklist``.
The matching is done at the start of the URL path, so be careful when using
this feature. For example, if you pass a configuration like this one::
{ 'blacklist': ['/f'] }
It would not show *any* url that starts with `f`, effectively behaving like
It would not show *any* url that starts with ``f``, effectively behaving like
a globbing regular expression (but not quite as powerful).
For any number of blocking you may need, just add as many items as wanted::
{ 'blacklist' : ['/favicon.ico', '/javascript', '/images'] }
Again, the `blacklist` key can be used along with the `items` key or not (it is
not required).
Again, the ``blacklist`` key can be used along with the ``items`` key
or not (it is not required).

View File

@@ -6,10 +6,12 @@ of `ShootQ <http://shootq.com>`_ while working at `Pictage
<http://pictage.com>`_.
Pecan was created to fill a void in the Python web-framework world a
very lightweight framework that provides object-dispatch style routing.
Pecan does not aim to be a "full stack" framework, and therefore
includes no out of the box support for things like sessions or
databases. Pecan instead focuses on HTTP itself.
very lightweight framework that provides object-dispatch style
routing. Pecan does not aim to be a "full stack" framework, and
therefore includes no out of the box support for things like sessions
or databases (although tutorials are included for integrating these
yourself in just a few lines of code). Pecan instead focuses on HTTP
itself.
Although it is lightweight, Pecan does offer an extensive feature set
for building HTTP-based applications, including:
@@ -21,10 +23,6 @@ for building HTTP-based applications, including:
* Extensible JSON support
* Easy Python-based configuration
While Pecan doesn't provide support for sessions or databases out of the
box, tutorials are included for integrating these yourself in just a few
lines of code.
Narrative Documentation
=======================

View File

@@ -4,7 +4,7 @@ Installation
============
Stable Version
------------------------------
--------------
We recommend installing Pecan with ``pip``, but you can also try with
``easy_install``. Creating a spot in your environment where
@@ -14,9 +14,9 @@ To get started with an environment for Pecan, we recommend creating a new
`virtual environment <http://www.virtualenv.org>`_ using `virtualenv
<http://www.virtualenv.org>`_::
virtualenv pecan-env
cd pecan-env
source bin/activate
$ virtualenv pecan-env
$ cd pecan-env
$ source bin/activate
The above commands create a virtual environment and *activate* it. This
will isolate Pecan's dependency installations from your system packages, making
@@ -24,7 +24,7 @@ it easier to debug problems if needed.
Next, let's install Pecan::
pip install pecan
$ pip install pecan
Development (Unstable) Version
@@ -32,18 +32,18 @@ Development (Unstable) Version
If you want to run the latest development version of Pecan you will
need to install git and clone the repo from GitHub::
git clone https://github.com/dreamhost/pecan.git -b next
$ git clone https://github.com/dreamhost/pecan.git -b next
Assuming your virtual environment is still activated, call ``setup.py`` to
install the development version::
install the development version.::
cd pecan
python setup.py develop
$ cd pecan
$ python setup.py develop
.. note::
The ``next`` development branch is **very** volatile and is never
recommended for production use.
...alternatively, you can also install from GitHub directly with ``pip``::
Alternatively, you can also install from GitHub directly with ``pip``.::
pip install -e git://github.com/dreamhost/pecan.git@next#egg=pecan
$ pip install -e git://github.com/dreamhost/pecan.git@next#egg=pecan

View File

@@ -3,17 +3,18 @@
JSON Serialization
==================
Pecan includes a simple, easy-to-use system for generating and serving
``JSON``. To get started, create a file in your project called
JSON. To get started, create a file in your project called
``json.py`` and import it in your project's ``app.py``.
Your ``json`` module will contain a series of rules for generating
``JSON`` from objects you return in your controller, utilizing
JSON from objects you return in your controller, utilizing
"generic" function support from the
`simplegeneric <http://pypi.python.org/pypi/simplegeneric>`_ library.
Let's say that we have a controller in our Pecan application which
we want to use to return ``JSON`` output for a ``User`` object::
we want to use to return JSON output for a :class:`User` object::
from myproject.lib import get_current_user
@@ -27,8 +28,8 @@ we want to use to return ``JSON`` output for a ``User`` object::
return get_current_user()
In order for this controller to function, Pecan will need to know how to
convert the ``User`` object into a ``JSON``-friendly data structure. One
way to tell Pecan how to convert an object into ``JSON`` is to define a
convert the :class:`User` object into data types compatible with JSON. One
way to tell Pecan how to convert an object into JSON is to define a
rule in your ``json.py``::
from pecan.jsonify import jsonify
@@ -42,14 +43,15 @@ rule in your ``json.py``::
birthday = user.birthday.isoformat()
)
In this example, when an instance of the ``model.User`` class is
returned from a controller which is configured to return ``JSON``, the
``jsonify_user`` rule will be called to generate that ``JSON``. Note
that the rule does not generate a ``JSON`` string, but rather generates
a Python dictionary which contains only ``JSON`` friendly data types.
In this example, when an instance of the :class:`model.User` class is
returned from a controller which is configured to return JSON, the
:func:`jsonify_user` rule will be called to convert the object to
JSON-compatible data. Note that the rule does not generate a JSON
string, but rather generates a Python dictionary which contains only
JSON friendly data types.
Alternatively, the rule can be specified on the object itself, by
specifying a ``__json__`` method on the object::
specifying a :func:`__json__` method in the class::
class User(object):
def __init__(self, name, email, birthday):
@@ -64,7 +66,7 @@ specifying a ``__json__`` method on the object::
birthday = self.birthday.isoformat()
)
The benefit of using a ``json.py`` module is having all of your ``JSON``
The benefit of using a ``json.py`` module is having all of your JSON
rules defined in a central location, but some projects prefer the
simplicity of keeping the ``JSON`` rules attached directly to their
simplicity of keeping the JSON rules attached directly to their
model objects.

View File

@@ -14,22 +14,27 @@
Logging
=======
Pecan hooks into the Python standard library's ``logging`` module by passing
logging configuration options into the
`logging.config.dictConfig
<http://docs.python.org/library/logging.config.html#configuration-dictionary-schema>`_
function. The full documentation for the `dictConfig
<http://docs.python.org/library/logging.config.html#configuration-dictionary-schema>`_
format is the best source of information for logging configuration, but to get
Pecan uses the Python standard library's :py:mod:`logging` module by passing
logging configuration options into the `logging.config.dictConfig`_
function. The full documentation for the :func:`dictConfig` format is
the best source of information for logging configuration, but to get
you started, this chapter will provide you with a few simple examples.
.. _logging.config.dictConfig: http://docs.python.org/library/logging.config.html#configuration-dictionary-schema
Configuring Logging
-------------------
Sample logging configuration is provided with the quickstart project
introduced in :ref:`quick_start`::
introduced in :ref:`quick_start`:
::
$ pecan create myapp
The default configuration defines one handler and two loggers.
::
# myapp/config.py
@@ -57,12 +62,8 @@ introduced in :ref:`quick_start`::
}
}
This configuration defines one handler:
* ``console`` logs messages to ``stderr`` using the ``simple`` formatter.
...and two loggers.
* ``myapp`` logs messages sent at a level above or equal to ``DEBUG`` to
the ``console`` handler
@@ -70,13 +71,16 @@ This configuration defines one handler:
the ``console`` handler
Writing Logs in Your Application
--------------------------------
Writing Log Messages in Your Application
----------------------------------------
The logger named ``myapp`` is reserved for your usage in your Pecan
application.
Once you have configured your logging, you can place logging calls in your
code. Using the logging framework is very simple. Heres an example::
code. Using the logging framework is very simple.
::
# myapp/myapp/controllers/root.py
from pecan import expose
@@ -91,9 +95,10 @@ code. Using the logging framework is very simple. Heres an example::
logger.error('Uh-oh!')
return dict()
Writing Logs to Files and Other Locations
-----------------------------------------
Python's ``logging`` library defines a variety of handlers that assist in
Logging to Files and Other Locations
------------------------------------
Python's :py:mod:`logging` library defines a variety of handlers that assist in
writing logs to file. A few interesting ones are:
* |FileHandler|_ - used to log messages to a file on the filesystem
@@ -107,6 +112,7 @@ application's ``logging`` block and assigning it to one of more loggers.
Logging Requests with Paste Translogger
---------------------------------------
`Paste <http://pythonpaste.org/>`_ (which is not included with Pecan) includes
the `TransLogger <http://pythonpaste.org/modules/translogger.html>`_ middleware
for logging requests in `Apache Combined Log Format

View File

@@ -7,29 +7,32 @@ Let's create a small sample project with Pecan.
.. note::
This guide does not cover the installation of Pecan. If you need
instructions for installing Pecan, go to :ref:`installation`.
instructions for installing Pecan, refer to :ref:`installation`.
.. _app_template:
Base Application Template
-------------------------
A basic template for getting started is included with Pecan. From
your shell, type::
Pecan includes a basic template for starting a new project. From your
shell, type::
$ pecan create test_project
This example uses *test_project* as your project name, but you can replace
it with any valid Python package name you like.
Go ahead and change into your newly created project directory. You'll want to
deploy it in "development mode", such that its available on ``sys.path``, yet
can still be edited directly from its source distribution::
Go ahead and change into your newly created project directory.::
$ cd test_project
You'll want to deploy it in "development mode", such that its
available on ``sys.path``, yet can still be edited directly from its
source distribution::
$ python setup.py develop
This is how the layout of your new project should look::
Your new project contain these files::
$ ls
@@ -59,69 +62,77 @@ This is how the layout of your new project should look::
   ├── test_functional.py
   └── test_units.py
The amount of files and directories may vary, but the above structure should
give you an idea of what you should expect.
The number of files and directories may vary based on the version of
Pecan, but the above structure should give you an idea of what to
expect.
A few things have been created for you, so let's review them one by one:
Let's review the files created by the template.
* **public**: All your static files (like CSS, Javascript, and images) live
here. Pecan comes with a simple file server that serves these static files
**public**
All your static files (like CSS, Javascript, and images) live here.
Pecan comes with a simple file server that serves these static files
as you develop.
Pecan application structure generally follows the MVC_ pattern. The
directories under ``test_project`` encompass your models, controllers
and templates.
Pecan application structure generally follows the
`MVC <http://en.wikipedia.org/wiki/Modelviewcontroller>`_ pattern. The
remaining directories encompass your models, controllers and templates...
.. _MVC: http://en.wikipedia.org/wiki/Modelviewcontroller
* **test_project/controllers**: The container directory for your controller files.
* **test_project/templates**: All your templates go in here.
* **test_project/model**: Container for your model files.
**test_project/controllers**
The container directory for your controller files.
**test_project/templates**
All your templates go in here.
**test_project/model**
Container for your model files.
...and finally, a directory to house unit and integration tests:
Finally, a directory to house unit and integration tests:
* **test_project/tests**: All of the tests for your application.
**test_project/tests**
All of the tests for your application.
The **test_project/app.py** file controls how the Pecan application will be
created. This file must contain a ``setup_app`` function which returns the
The ``test_project/app.py`` file controls how the Pecan application will be
created. This file must contain a :func:`setup_app` function which returns the
WSGI application object. Generally you will not need to modify the ``app.py``
file provided by the base application template unless you need to customize
your app in a way that cannot be accomplished using config. See
:ref:`python_based_config` below.
To avoid unneeded dependencies and to remain as flexible as possible, Pecan
doesn't impose any database or ORM
(`Object Relational Mapper
<http://en.wikipedia.org/wiki/Object-relational_mapping>`_) out of the box.
You may notice that **model/__init__.py** is mostly empty. If your project
will interact with a database, this if where you should add code to parse
bindings from your configuration file and define tables and ORM definitions.
To avoid unneeded dependencies and to remain as flexible as possible,
Pecan doesn't impose any database or ORM (`Object Relational
Mapper`_). If your project will interact with a database, you can add
code to ``model/__init__.py`` to load database bindings from your
configuration file and define tables and ORM definitions.
.. _Object Relational Mapper: http://en.wikipedia.org/wiki/Object-relational_mapping
.. _running_application:
Running the Application
-----------------------
Before starting up your Pecan app, you'll need a configuration file. The
base project template should have created one for you already, ``config.py``.
This file already contains the basic necessary information to run your Pecan
app, like the host and port to serve it on, where your controllers and templates
are stored on disk, and which directory to serve static files from.
The base project template creates the configuration file with the
basic settings you need to run your Pecan application in
``config.py``. This file includes the host and port to run the serves
on, the location where your controllers and templates are stored on
disk, and the name of the directory containing any static files.
If you just run ``pecan serve``, passing ``config.py`` as an argument for
configuration, it will bring up the development server and serve the app::
If you just run ``pecan serve``, passing ``config.py`` as the
configuration file, it will bring up the development server and serve
the app::
$ pecan serve config.py
Starting server in PID 000.
serving on 0.0.0.0:8080, view at http://127.0.0.1:8080
The location for the configuration file and the argument itself are very
flexible - you can pass an absolute or relative path to the file.
flexible--you can pass an absolute or relative path to the file.
.. _python_based_config:
Python-Based Configuration
--------------------------
For ease of use, Pecan configuration files are pure Python - they're even saved
For ease of use, Pecan configuration files are pure Python--they're even saved
as ``.py`` files.
This is how your default (generated) configuration file should look::
@@ -180,9 +191,10 @@ a later chapter (:ref:`Configuration`).
The Application Root
--------------------
The **Root Controller** is the root of your application. You can think of it
as being analogous to your application's root path (in our case,
``http://localhost:8080/``).
The **Root Controller** is the entry point for your application. You
can think of it as being analogous to your application's root URL path
(in our case, ``http://localhost:8080/``).
This is how it looks in the project template
(``test_project.controllers.root.RootController``)::
@@ -218,12 +230,12 @@ now, let's examine the sample project, controller by controller::
def index(self):
return dict()
The ``index`` method is marked as **publically available** via the ``@expose``
The :func:`index` method is marked as *publically available* via the :func:`@expose`
decorator (which in turn uses the ``index.html`` template) at the root of the
application (http://127.0.0.1:8080/), so any HTTP ``GET`` that hits the root of
your application (``/``) will be routed to this method.
Notice that the ``index`` method returns a Python dictionary - this dictionary
Notice that the :func:`index` method returns a Python dictionary. This dictionary
is used as a namespace to render the specified template (``index.html``) into
HTML, and is the primary mechanism by which data is passed from controller to
template.
@@ -234,8 +246,8 @@ template.
def index_post(self, q):
redirect('http://pecan.readthedocs.org/en/latest/search.html?q=%s' % q)
The ``index_post`` method receives one HTTP ``POST`` argument (``q``). Because
the argument ``method`` to ``@index.when`` has been set to ``'POST'``, any
The :func:`index_post` method receives one HTTP ``POST`` argument (``q``). Because
the argument ``method`` to :func:`@index.when` has been set to ``'POST'``, any
HTTP ``POST`` to the application root (in the example project, a form
submission) will be routed to this method.
@@ -250,11 +262,12 @@ submission) will be routed to this method.
message = getattr(status_map.get(status), 'explanation', '')
return dict(status=status, message=message)
Finally, we have the ``error`` method, which allows the application to display
Finally, we have the :func:`error` method, which allows the application to display
custom pages for certain HTTP errors (``404``, etc...).
Running the Tests For Your Application
--------------------------------------
Your application comes with a few example tests that you can run, replace, and
add to. To run them::
@@ -279,4 +292,5 @@ The tests themselves can be found in the ``tests`` module in your project.
Deploying to a Web Server
-------------------------
Ready to deploy your new Pecan app? Take a look at :ref:`deployment`.

View File

@@ -22,4 +22,6 @@ library. You'll need to install it for development use before continuing::
Starting server in PID 000.
serving on 0.0.0.0:8080, view at http://127.0.0.1:8080
As you work, Pecan will listen for any file or directory modification events in your project and silently restart your server process in the background.
As you work, Pecan will listen for any file or directory modification
events in your project and silently restart your server process in the
background.

View File

@@ -4,9 +4,11 @@ Writing RESTful Web Services with Pecan
=======================================
If you need to write controllers to interact with objects, using the
``RestController`` may help speed things up. It follows the Representational
:class:`RestController` may help speed things up. It follows the Representational
State Transfer Protocol, also known as REST, by routing the standard HTTP
verbs of ``GET``, ``POST``, ``PUT``, and ``DELETE`` to individual methods::
verbs of ``GET``, ``POST``, ``PUT``, and ``DELETE`` to individual methods.
::
from pecan import expose
from pecan.rest import RestController
@@ -25,7 +27,7 @@ verbs of ``GET``, ``POST``, ``PUT``, and ``DELETE`` to individual methods::
URL Mapping
-----------
By default, the ``RestController`` routes as follows:
By default, the :class:`RestController` routes as follows:
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| Method | Description | Example Method(s) / URL(s) |
@@ -55,19 +57,27 @@ By default, the ``RestController`` routes as follows:
| | | DELETE /books/1 |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
Pecan's ``RestController`` uses the ``?_method=`` query string to work around
the lack of PUT/DELETE form submission support in most current browsers.
Pecan's :class:`RestController` uses the ``?_method=`` query string to
work around the lack of support for the PUT and DELETE verbs when
submitting forms in most current browsers.
The ``RestController`` still supports the ``index``, ``_default``, and
``_lookup`` routing overrides. If you need to override ``_route``, however,
make sure to call ``RestController._route`` at the end of your custom
``_route`` method so that the REST routing described above still occurs.
In addition to handling REST, the :class:`RestController` also
supports the :func:`index`, :func:`_default`, and :func:`_lookup`
routing overrides.
.. warning::
If you need to override :func:`_route`, make sure to call
:func:`RestController._route` at the end of your custom method so
that the REST routing described above still occurs.
Nesting ``RestController``
---------------------------
``RestController`` instances can be nested so that child resources get the
parameters necessary to look up parent resources. For example::
:class:`RestController` instances can be nested so that child resources receive the
parameters necessary to look up parent resources.
For example::
from pecan import expose
from pecan.rest import RestController
@@ -101,21 +111,23 @@ parameters necessary to look up parent resources. For example::
authors = AuthorsController()
Accessing ``/authors/1/books/2`` would call ``BooksController.get`` with an
``author_id`` of 1 and ``id`` of 2.
Accessing ``/authors/1/books/2`` invokes :func:`BooksController.get` with
``author_id`` set to ``1`` and ``id`` set to ``2``.
To determine which arguments are associated with the parent resource, Pecan
looks at the ``get_one`` or ``get`` method signatures, in that order, in the
looks at the :func:`get_one` then :func:`get` method signatures, in that order, in the
parent controller. If the parent resource takes a variable number of arguments,
Pecan will hand it everything up to the child resource controller name (e.g.,
Pecan will pass it everything up to the child resource controller name (e.g.,
``books`` in the above example).
Defining Custom Actions
-----------------------
In addition to the default methods defined above, you can add additional
behaviors to a ``RestController`` by defining a special ``_custom_actions``
dictionary. For example::
behaviors to a :class:`RestController` by defining a special :attr:`_custom_actions`
dictionary.
For example::
from pecan import expose
from pecan.rest import RestController
@@ -135,5 +147,6 @@ dictionary. For example::
abort(404)
book.checkout()
Additional method names are the keys in the dictionary. The values are lists
of valid HTTP verbs for those custom actions, including PUT and DELETE.
:attr:`_custom_actions` maps method names to the list of valid HTTP
verbs for those custom actions. In this case :func:`checkout` supports
``POST``.

View File

@@ -3,15 +3,14 @@
Controllers and Routing
=======================
When a user requests a certain URL in your app, how does Pecan know which
controller to route to? Pecan uses a routing strategy known as
**object-dispatch** to map an HTTP request to a controller.
Pecan uses a routing strategy known as **object-dispatch** to map an
HTTP request to a controller, and then the method to call.
Object-dispatch begins by splitting the path into a list of components
and then walking an object path, starting at the root controller. You
can imagine your application's controllers as a tree of objects
(branches of the object tree map directly to URL paths).
Object-dispatch begins by splitting the
path into a list of components and then walking an object path, starting at
the root controller. You can imagine your application's controllers as a tree
of objects (branches of the object tree map directly to URL paths). Let's look
at a simple bookstore application:
Let's look at a simple bookstore application:
::
@@ -49,9 +48,9 @@ begin with Pecan breaking the request up into ``catalog``, ``books``, and
``bestsellers``. Next, Pecan would lookup ``catalog`` on the root
controller. Using the ``catalog`` object, Pecan would then lookup
``books``, followed by ``bestsellers``. What if the URL ends in a slash?
Pecan will check for an ``index`` method on the current object.
Pecan will check for an ``index`` method on the last controller object.
To illustrate further, the following paths...
To illustrate further, the following paths:
::
@@ -61,7 +60,7 @@ To illustrate further, the following paths...
   └── /catalog/books
   └── /catalog/books/bestsellers
Would route to the following controller methods...
route to the following controller methods:
::
@@ -74,11 +73,11 @@ Would route to the following controller methods...
Exposing Controllers
--------------------
At its core, ``@expose`` is how you tell Pecan which methods in a class
are publically-visible controllers. If a method is *not* decorated with
``@expose``, it will never be routed to. ``@expose`` accepts three optional
parameters, some of which can impact routing and the content type of the
response body.
You tell Pecan which methods in a class are publically-visible via
:func:`@expose`. If a method is *not* decorated with :func:`@expose`,
Pecan will never route a request to it. :func:`@expose` accepts three
optional parameters, some of which can impact routing and the content
type of the response body.
::
@@ -107,19 +106,34 @@ Let's look at an example using ``template`` and ``content_type``:
def hello(self):
return {'msg': 'Hello!'}
You'll notice that we used three ``expose`` decorators.
You'll notice that we called :func:`expose` three times, with different
arguments.
::
@expose('json')
The first tells Pecan to serialize the response namespace using JSON
serialization when the client requests ``/hello.json``.
::
@expose('text_template.mako', content_type='text/plain')
The second tells Pecan to use the ``text_template.mako`` template file when the
client requests ``/hello.txt``.
The third tells Pecan to use the html_template.mako template file when the
::
@expose('html_template.mako')
The third tells Pecan to use the ``html_template.mako`` template file when the
client requests ``/hello.html``. If the client requests ``/hello``, Pecan will
use the ``text/html`` content type by default.
Please see :ref:`pecan_decorators` for more information on ``@expose``.
.. seealso::
* :ref:`pecan_decorators`
@@ -129,15 +143,16 @@ Pecan's Routing Algorithm
Sometimes, the standard object-dispatch routing isn't adequate to properly
route a URL to a controller. Pecan provides several ways to short-circuit
the object-dispatch system to process URLs with more control, including the
special ``_lookup``, ``_default``, and ``_route`` methods. Defining these
special :func:`_lookup`, :func:`_default`, and :func:`_route` methods. Defining these
methods on your controller objects provides additional flexibility for
processing all or part of a URL.
Setting a Return Status Code
--------------------------------
----------------------------
Setting a specific HTTP response code (such as ``201 Created``) is simple:
Set a specific HTTP response code (such as ``201 Created``) by
modifying the ``status`` attribute of the response object.
::
@@ -150,7 +165,7 @@ Setting a specific HTTP response code (such as ``201 Created``) is simple:
response.status = 201
return {'foo': 'bar'}
Pecan also comes with ``abort``, a utility function for raising HTTP errors:
Use the utility function :func:`abort` to raise HTTP errors.
::
@@ -163,25 +178,31 @@ Pecan also comes with ``abort``, a utility function for raising HTTP errors:
abort(404)
Under the hood, ``abort`` raises an instance of
``webob.exc.WSGIHTTPException`` which is used by pecan to render default
response bodies for HTTP errors. This exception is stored in the WSGI request
environ at ``pecan.original_exception``, where it can be accessed later in the
request cycle (by, for example, other middleware or :ref:`errors`).
:func:`abort` raises an instance of
:class:`webob.exc.WSGIHTTPException` which is used by Pecan to render
:default response bodies for HTTP errors. This exception is stored in
:the WSGI request environ at ``pecan.original_exception``, where it
:can be accessed later in the request cycle (by, for example, other
:middleware or :ref:`errors`).
Routing to Subcontrollers with ``_lookup``
------------------------------------------
The ``_lookup`` special method provides a way to process a portion of a URL,
The :func:`_lookup` special method provides a way to process a portion of a URL,
and then return a new controller object to route to for the remainder.
A ``_lookup`` method will accept one or more arguments, representing chunks
of the URL to be processed, split on ``/``, and then provide a ``*remainder`` list
which will be processed by the returned controller via object-dispatch.
A :func:`_lookup` method may accept one or more arguments, segments
chunks of the URL path to be processed (split on
``/``). :func:`_lookup` should also take variable positional arguments
representing the rest of the path, and it should include any portion
of the path it does not process in its return value. The example below
uses a ``*remainder`` list which will be passed to the returned
controller when the object-dispatch algorithm continues.
Additionally, the ``_lookup`` method on a controller is called as a last
resort, when no other controller matches the URL via standard object-dispatch.
In addition to being used for creating controllers dynamically,
:func:`_lookup` is called as a last resort, when no other controller
method matches the URL and there is no :func:`_default` method.
::
@@ -211,7 +232,7 @@ where ``primary_key == 8``.
Falling Back with ``_default``
------------------------------
The ``_default`` controller is called as a last resort when no other controller
The :func:`_default` method is called as a last resort when no other controller
methods match the URL via standard object-dispatch.
::
@@ -232,28 +253,29 @@ methods match the URL via standard object-dispatch.
return 'I cannot say hello in that language'
...so in the example above, a request to ``/spanish`` would route to
``RootController._default``.
In the example above, a request to ``/spanish`` would route to
:func:`RootController._default`.
Defining Customized Routing with ``_route``
-------------------------------------------
The ``_route`` method allows a controller to completely override the routing
mechanism of Pecan. Pecan itself uses the ``_route`` method to implement its
``RestController``. If you want to design an alternative routing system on
top of Pecan, defining a base controller class that defines a ``_route`` method
The :func:`_route` method allows a controller to completely override the routing
mechanism of Pecan. Pecan itself uses the :func:`_route` method to implement its
:class:`RestController`. If you want to design an alternative routing system on
top of Pecan, defining a base controller class that defines a :func:`_route` method
will enable you to have total control.
Mapping Controller Arguments
----------------------------
In Pecan, HTTP ``GET`` and ``POST`` variables that are `not` consumed
during the routing process can be passed onto the controller as arguments.
In Pecan, HTTP ``GET`` and ``POST`` variables that are not consumed
during the routing process can be passed onto the controller method as
arguments.
Depending on the signature of your controller, these arguments can be mapped
explicitly to method arguments:
Depending on the signature of the method, these arguments can be mapped
explicitly to arguments:
::
@@ -275,7 +297,7 @@ explicitly to method arguments:
$ curl http://localhost:8080/kwargs?a=1&b=2&c=3
{u'a': u'1', u'c': u'3', u'b': u'2'}
...or can be consumed positionally:
or can be consumed positionally:
::
@@ -311,6 +333,10 @@ Helper Functions
----------------
Pecan also provides several useful helper functions for moving between
different routes. The ``redirect`` function allows you to issue internal or
``HTTP 302`` redirects. The ``redirect`` utility, along with several other
useful helpers, are documented in :ref:`pecan_core`.
different routes. The :func:`redirect` function allows you to issue internal or
``HTTP 302`` redirects.
.. seealso::
The :func:`redirect` utility, along with several other useful
helpers, are documented in :ref:`pecan_core`.

View File

@@ -2,23 +2,24 @@
Security and Authentication
===========================
Pecan provides no out-of-the-box support for authentication, but it does give
you the necessary tools to handle authentication and authorization as you see
fit.
In Pecan, you can wrap entire controller subtrees *or* individual method calls
with function calls to determine access and secure portions of your
application.
Pecan provides no out-of-the-box support for authentication, but it
does give you the necessary tools to handle authentication and
authorization as you see fit.
Pecan's ``secure`` decorator secures a method or class depending on invocation.
``secure`` Decorator Basics
---------------------------
You can wrap entire controller subtrees *or* individual method calls
with access controls using the :func:`secure` decorator.
To decorate a method, use one argument::
secure('<check_permissions_method>')
secure('<check_permissions_method_name>')
To secure a class, invoke with two arguments::
secure(object_instance, '<check_permissions_method>')
secure(object_instance, '<check_permissions_method_name>')
::
@@ -59,11 +60,17 @@ To secure a class, invoke with two arguments::
highly_classified = secure(HighlyClassifiedController(), 'check_permissions')
unclassified = UnclassifiedController()
Alternatively, the same functionality can also be accomplished by subclassing
Pecan's ``SecureController`` class. Implementations of ``SecureController``
should extend the ``check_permissions`` classmethod to return a ``True``
or ``False`` value (depending on whether or not the user has permissions to
the controller branch)::
``SecureControler``
-------------------
Alternatively, the same functionality can also be accomplished by
subclassing Pecan's :class:`SecureController`. Implementations of
:class:`SecureController` should extend the :func:`check_permissions`
class method to return ``True`` if the user has permissions to the
controller branch and ``False`` if they do not.
::
from pecan import expose
from pecan.secure import SecureController, unlocked
@@ -103,14 +110,15 @@ the controller branch)::
unclassified = unlocked(UnclassifiedController())
Also note the use of the ``@unlocked`` decorator in the above example, which
Also note the use of the :func:`@unlocked` decorator in the above example, which
can be used similarly to explicitly unlock a controller for public access
without any security checks.
Writing Authentication/Authorization Methods
--------------------------------------------
The ``check_permissions`` method should be used to determine user
The :func:`check_permissions` method should be used to determine user
authentication and authorization. The code you implement here could range
from simple session assertions (the existing user is authenticated as an
administrator) to connecting to an LDAP service.
@@ -118,14 +126,17 @@ administrator) to connecting to an LDAP service.
More on ``secure``
------------------
The ``secure`` method has several advanced uses that allow you to create
The :func:`secure` method has several advanced uses that allow you to create
robust security policies for your application.
First, you can pass via a string the name of either a classmethod or an
instance method of the controller to use as the ``check_permission`` method.
First, you can pass via a string the name of either a class method or an
instance method of the controller to use as the :func:`check_permission` method.
Instance methods are particularly useful if you wish to authorize access to
attributes of a particular model instance. Consider the following example
of a basic virtual filesystem::
attributes of a model instance. Consider the following example
of a basic virtual filesystem.
::
from pecan import expose
from pecan.secure import secure
@@ -159,10 +170,12 @@ of a basic virtual filesystem::
return FileController(name), remainder
The ``secure`` method also accepts a function instead of a string. When
The :func:`secure` method also accepts a function argument. When
passing a function, make sure that the function is imported from another
file or defined in the same file before the class definition -- otherwise
you will likely get error during module import. ::
file or defined in the same file before the class definition, otherwise
you will likely get error during module import.
::
from pecan import expose
from pecan.secure import secure
@@ -176,8 +189,8 @@ you will likely get error during module import. ::
return 'Logged in'
You can also use the ``secure`` method to change the behavior of a
``SecureController``. Decorating a method or wrapping a subcontroller tells
You can also use the :func:`secure` method to change the behavior of a
:class:`SecureController`. Decorating a method or wrapping a subcontroller tells
Pecan to use another security function other than the default controller
method. This is useful for situations where you want a different level or
type of security.
@@ -207,18 +220,20 @@ type of security.
api = secure(ApiController(), 'check_api_permissions')
In the example above, pecan will *only* call ``admin_user`` when a request is
In the example above, pecan will *only* call :func:`admin_user` when a request is
made for ``/api/``.
Multiple Secure Controllers
---------------------------
Pecan allows you to have nested secure controllers. In the example below, when
a request is made for ``/admin/index/``, Pecan first calls
``check_permissions`` on the RootController and then calls
``check_permissions`` on the AdminController. The ability to nest
``SecureController`` instances allows you to protect controllers with an
increasing level of protection. ::
Secure controllers can be nested to provide increasing levels of
security on subcontrollers. In the example below, when a request is
made for ``/admin/index/``, Pecan first calls
:func:`check_permissions` on the :class:`RootController` and then
calls :func:`check_permissions` on the :class:`AdminController`.
::
from pecan import expose
from pecan.secure import SecureController

View File

@@ -2,7 +2,8 @@
Working with Sessions and User Authentication
=============================================
Out of the box, Pecan provides no opinionated support for managing user sessions,
Pecan provides no opinionated support for managing user sessions,
but it's easy to hook into your session framework of choice with minimal
effort.
@@ -11,11 +12,14 @@ framework, `Beaker <http://beaker.groovie.org>`_, into your Pecan project.
Setting up Session Management
-----------------------------
There are several approaches that can be taken to set up session management.
One approach is WSGI middleware. Another is Pecan :ref:`hooks`.
Here's an example of wrapping your WSGI application with Beaker's
``SessionMiddleware`` in your project's `app.py`::
:class:`SessionMiddleware` in your project's ``app.py``.
::
from pecan import conf, make_app
from beaker.middleware import SessionMiddleware
@@ -26,7 +30,9 @@ Here's an example of wrapping your WSGI application with Beaker's
)
app = SessionMiddleware(app, conf.beaker)
...and a corresponding dictionary in your configuration file::
And a corresponding dictionary in your configuration file.
::
beaker = {
'session.key' : 'sessionkey',

View File

@@ -3,59 +3,75 @@
Templating in Pecan
===================
Pecan supports a variety of templating engines out of the box, and also provides
the ability to easily add support for new template engines. Currently, Pecan
supports the following templating engines:
Pecan includes support for a variety of templating engines and also
makes it easy to add support for new template engines. Currently,
Pecan supports:
* `Mako <http://www.makotemplates.org/>`_
* `Genshi <http://genshi.edgewall.org/>`_
* `Kajiki <http://kajiki.pythonisito.com/>`_
* `Jinja2 <http://jinja.pocoo.org/>`_
* `JSON`
=============== =============
Template System Renderer Name
=============== =============
Mako_ mako
Genshi_ genshi
Kajiki_ kajiki
Jinja2_ jinja
JSON json
=============== =============
The default template system is `mako`, but can be configured by passing the
``default_renderer`` key in your application's configuration::
.. _Mako: http://www.makotemplates.org/
.. _Genshi: http://genshi.edgewall.org/
.. _Kajiki: http://kajiki.pythonisito.com/
.. _Jinja2: http://jinja.pocoo.org/
The default template system is ``mako``, but that can be changed by
passing the ``default_renderer`` key in your application's
configuration::
app = {
'default_renderer' : 'kajiki',
# ...
}
The available renderer type strings are ``mako``, ``genshi``, ``kajiki``,
``jinja``, and ``json``.
Using Template Renderers
------------------------
:ref:`pecan_decorators` defines a decorator called ``@expose``, which is used
to flag a method as a public controller. The ``@expose`` decorator takes
a variety of parameters, including a ``template`` argument, which is the path
to the template file to use for that controller. ``@expose`` will use the
default template engine unless the path is prefixed by another renderer name::
:py:mod:`pecan.decorators` defines a decorator called :func:`@expose`, which
is used to flag a method as a public controller. The :func:`@expose`
decorator takes a ``template`` argument, which can be used to specify
the path to the template file to use for the controller method being
exposed.
::
class MyController(object):
@expose('path/to/mako/template.html')
def index(self):
return dict(message='I am a mako template')
:func:`@expose` will use the default template engine unless the path
is prefixed by another renderer name.
::
@expose('kajiki:path/to/kajiki/template.html')
def my_controller(self):
return dict(message='I am a kajiki template')
For more information on the expose decorator, refer to :ref:`pecan_decorators`,
:ref:`pecan_core`, and :ref:`routing`.
.. seealso::
* :ref:`pecan_decorators`
* :ref:`pecan_core`
* :ref:`routing`
Template Overrides and Manual Rendering
---------------------------------------
Overriding Templates
--------------------
The :ref:`pecan_core` module contains two useful helper functions related to
templating. The first is ``override_template``, which allows you to overrides
which template is used in your controller, and the second is ``render``, which
allows you to manually render output using the Pecan templating framework.
To use ``override_template``, simply call it within the body of your controller
:func:`override_template` allows you to override the template set for
a controller method when it is exposed. When
:func:`override_template` is called within the body of the controller
method, it changes the template that will be used for that invocation
of the method.
::
@@ -66,7 +82,14 @@ To use ``override_template``, simply call it within the body of your controller
override_template('template_two.html')
return dict(message='I will now render with template_two.html')
The ``render`` helper is also quite simple to use::
Manual Rendering
----------------
:func:`render` allows you to manually render output using the Pecan
templating framework. Pass the template path and values to go into the
template, and :func:`render` returns the rendered output as text.
::
@expose()
def controller(self):
@@ -76,16 +99,20 @@ The ``render`` helper is also quite simple to use::
The JSON Renderer
-----------------
Pecan also provides a `JSON` renderer, e.g., ``@expose('json')``. For
more information on using `JSON` in Pecan, please refer to :ref:`jsonify` and
:ref:`pecan_jsonify`.
Pecan also provides a ``JSON`` renderer, which you can use by exposing
a controller method with ``@expose('json')``.
.. seealso::
* :ref:`jsonify`
* :ref:`pecan_jsonify`
Defining Custom Renderers
-------------------------
To define a custom renderer, you can create a class that follows a simple
protocol::
To define a custom renderer, you can create a class that follows the
renderer protocol::
class MyRenderer(object):
def __init__(self, path, extra_vars):
@@ -105,7 +132,7 @@ protocol::
return str(namespace)
To enable your custom renderer, you can define a ``custom_renderers`` key in
To enable your custom renderer, define a ``custom_renderers`` key in
your application's configuration::
app = {

View File

@@ -3,13 +3,14 @@
Testing Pecan Applications
==========================
Tests can live anywhere in your Pecan project as long as the test runner can
discover them, though traditionally, they exist in a module at ``myapp.tests``.
discover them. Traditionally, they exist in a package named ``myapp.tests``.
The suggested mechanism for unit and integration testing of a Pecan application
is the Python ``unittest`` module.
is the ``unittest`` module.
Test Discovery and Other Tools
------------------------------
Tests for a Pecan project can be invoked as simply as ``python setup.py test``,
though it's possible to run your tests with different discovery and automation
tools. In particular, Pecan projects are known to work well with
@@ -28,7 +29,9 @@ the context of a Pecan application, functional tests can be written with the
help of the ``WebTest`` library. In this way, it is possible to write tests
that verify the behavior of an HTTP request life cycle from the controller
routing down to the HTTP response. The following is an example that is
similar to the one included with Pecan's quickstart project::
similar to the one included with Pecan's quickstart project.
::
# myapp/myapp/tests/__init__.py
@@ -52,12 +55,14 @@ similar to the one included with Pecan's quickstart project::
def tearDown(self):
set_config({}, overwrite=True)
The testing utility included with Pecan, ``pecan.testing.load_test_app``, can
The testing utility included with Pecan, :func:`pecan.testing.load_test_app`, can
be passed a file path representing a Pecan configuration file, and will return
an instance of the application, wrapped in a ``webtest.TestApp`` environment.
an instance of the application, wrapped in a :class:`webtest.TestApp` environment.
From here, it's possible to extend the ``FunctionalTest`` base class and write
tests that issue simulated HTTP requests::
From here, it's possible to extend the :class:`FunctionalTest` base class and write
tests that issue simulated HTTP requests.
::
class TestIndex(FunctionalTest):
@@ -66,17 +71,23 @@ tests that issue simulated HTTP requests::
assert resp.status_int == 200
assert 'Hello, World' in resp.body
See the `WebTest <http://pythonpaste.org/webtest/>`_ documentation for further
information about the methods available to a ``webtest.TestApp`` instance.
.. seealso::
See the `WebTest <http://pythonpaste.org/webtest/>`_ documentation
for further information about the methods available to a
``webtest.TestApp`` instance.
Special Testing Variables
-------------------------
Sometimes it's not enough to make assertions about the response body of certain
requests. To aid in inspection, Pecan applications provide a special set of
"testing variables" to any ``webtest.TestResponse`` object.
"testing variables" to any :class:`webtest.TestResponse` object.
Let's suppose that your Pecan applicaton had some controller which took a
``name`` as an optional argument in the URL::
``name`` as an optional argument in the URL.
::
# myapp/myapp/controllers/root.py
from pecan import expose
@@ -88,12 +99,16 @@ Let's suppose that your Pecan applicaton had some controller which took a
"""A request to / will access this controller"""
return dict(name=name)
...and rendered that name in it's template (and thus, the response body)::
and rendered that name in it's template (and thus, the response body).
::
# myapp/myapp/templates/index.html
Hello, ${name}!
A functional test for this controller might look something like this::
A functional test for this controller might look something like
::
class TestIndex(FunctionalTest):
@@ -102,10 +117,12 @@ A functional test for this controller might look something like this::
assert resp.status_int == 200
assert 'Hello, Joe!' in resp.body
In addition to ``webtest.TestResponse.body``, Pecan also provides
``webtest.TestResponse.namespace``, which represents the template namespace
returned from the controller, and ``webtest.TestResponse.template_name``, which
yields the name of the template used::
In addition to :attr:`webtest.TestResponse.body`, Pecan also provides
:attr:`webtest.TestResponse.namespace`, which represents the template namespace
returned from the controller, and :attr:`webtest.TestResponse.template_name`, which
yields the name of the template used.
::
class TestIndex(FunctionalTest):