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:
@@ -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, you’ll need to tell your
|
||||
distribution’s ``setup.py`` about its existence and reinstall. Within your
|
||||
distribution’s ``setup.py`` file, you'll find a call to ``setuptools.setup()``,
|
||||
e.g., ::
|
||||
distribution’s ``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
|
||||
|
||||
@@ -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.
|
||||
|
||||
::
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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. It’s a pre-fork worker model ported from Ruby’s Unicorn project. It
|
||||
supports both eventlet and greenlet.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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 it’s 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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
=======================
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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. Here’s 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. Here’s 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
|
||||
|
||||
@@ -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 it’s 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 it’s
|
||||
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/Model–view–controller>`_ pattern. The
|
||||
remaining directories encompass your models, controllers and templates...
|
||||
.. _MVC: http://en.wikipedia.org/wiki/Model–view–controller
|
||||
|
||||
* **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`.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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``.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user