summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Petrello <lists@ryanpetrello.com>2013-05-07 11:24:57 -0400
committerRyan Petrello <lists@ryanpetrello.com>2013-05-07 11:24:57 -0400
commita7b22da4391facc843f61e585f83734226ddc441 (patch)
tree95780a34d1d149c7323270a8f03ae547459d76c6
parenta53d9cdf255267321f23f5c2372f7d1500234739 (diff)
parente985be86ae89eccfe3c344196112b31a265ab741 (diff)
downloadpecan-a7b22da4391facc843f61e585f83734226ddc441.tar.gz
Merge remote-tracking branch 'origin/next'0.2.4
-rw-r--r--docs/source/changes.rst5
-rw-r--r--docs/source/commands.rst84
-rw-r--r--docs/source/conf.py4
-rw-r--r--docs/source/configuration.rst112
-rw-r--r--docs/source/databases.rst102
-rw-r--r--docs/source/deployment.rst91
-rw-r--r--docs/source/development.rst16
-rw-r--r--docs/source/errors.rst25
-rw-r--r--docs/source/forms.rst19
-rw-r--r--docs/source/hooks.rst111
-rw-r--r--docs/source/index.rst14
-rw-r--r--docs/source/installation.rst22
-rw-r--r--docs/source/jsonify.rst28
-rw-r--r--docs/source/logging.rst42
-rw-r--r--docs/source/quick_start.rst106
-rw-r--r--docs/source/reload.rst4
-rw-r--r--docs/source/rest.rst51
-rw-r--r--docs/source/routing.rst124
-rw-r--r--docs/source/secure_controller.rst81
-rw-r--r--docs/source/sessions.rst12
-rw-r--r--docs/source/templates.rst99
-rw-r--r--docs/source/testing.rst51
-rw-r--r--pecan/rest.py37
-rw-r--r--pecan/routing.py37
-rw-r--r--pecan/tests/test_rest.py219
-rw-r--r--setup.py2
26 files changed, 1012 insertions, 486 deletions
diff --git a/docs/source/changes.rst b/docs/source/changes.rst
index 26453d4..b2d6540 100644
--- a/docs/source/changes.rst
+++ b/docs/source/changes.rst
@@ -1,3 +1,8 @@
+0.2.4
+=====
+* Add support for ``_lookup`` methods as a fallback in RestController.
+* A variety of improvements to project documentation.
+
0.2.3
=====
* Add a variety of optimizations to ``pecan.core`` that improve request
diff --git a/docs/source/commands.rst b/docs/source/commands.rst
index 9105f41..0e75583 100644
--- a/docs/source/commands.rst
+++ b/docs/source/commands.rst
@@ -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
diff --git a/docs/source/conf.py b/docs/source/conf.py
index ff740e9..c331fe3 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -49,9 +49,9 @@ copyright = u'2010, Jonathan LaCour'
# built documents.
#
# The short X.Y version.
-version = '0.2.3'
+version = '0.2.4'
# The full version, including alpha/beta/rc tags.
-release = '0.2.3'
+release = '0.2.4'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst
index 84549a7..f578ab4 100644
--- a/docs/source/configuration.rst
+++ b/docs/source/configuration.rst
@@ -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.
+
+**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.
-**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.
+**template_path**
+ Points to the directory where your template files live (relative to
+ the project root).
-**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"``).
+**debug**
+ Enables ``WebError`` to display tracebacks in the browser
-**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.
+.. warning::
-**template_path** Points to the directory where your template files live
-(relative to the project root).
+ ``app`` is a reserved variable name for that section of the
+ configuration, so make sure you don't override it.
-**debug** Enables ``WebError`` to display tracebacks in the browser
-(**IMPORTANT**: Make sure this is *always* set to ``False`` in production
-environments).
+.. 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.
+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 ``as_dict`` method and what
-should return as a result (shortened for brevity):
+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.
::
diff --git a/docs/source/databases.rst b/docs/source/databases.rst
index 0ab5f30..8e92c4c 100644
--- a/docs/source/databases.rst
+++ b/docs/source/databases.rst
@@ -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 :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.
-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::
+::
# 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.
diff --git a/docs/source/deployment.rst b/docs/source/deployment.rst
index fd1fc25..b95ab39 100644
--- a/docs/source/deployment.rst
+++ b/docs/source/deployment.rst
@@ -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.
diff --git a/docs/source/development.rst b/docs/source/development.rst
index 8adf770..f4e3217 100644
--- a/docs/source/development.rst
+++ b/docs/source/development.rst
@@ -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.
diff --git a/docs/source/errors.rst b/docs/source/errors.rst
index c7c9092..2b78b02 100644
--- a/docs/source/errors.rst
+++ b/docs/source/errors.rst
@@ -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.
-
diff --git a/docs/source/forms.rst b/docs/source/forms.rst
index 91c3f06..7f7e2da 100644
--- a/docs/source/forms.rst
+++ b/docs/source/forms.rst
@@ -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
diff --git a/docs/source/hooks.rst b/docs/source/hooks.rst
index bc00c75..9a9d099 100644
--- a/docs/source/hooks.rst
+++ b/docs/source/hooks.rst
@@ -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).
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 8f10827..614baee 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -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
=======================
diff --git a/docs/source/installation.rst b/docs/source/installation.rst
index 0a670b5..dd66a1f 100644
--- a/docs/source/installation.rst
+++ b/docs/source/installation.rst
@@ -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
diff --git a/docs/source/jsonify.rst b/docs/source/jsonify.rst
index 73a1144..ac06c30 100644
--- a/docs/source/jsonify.rst
+++ b/docs/source/jsonify.rst
@@ -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.
diff --git a/docs/source/logging.rst b/docs/source/logging.rst
index f637e03..2b6985a 100644
--- a/docs/source/logging.rst
+++ b/docs/source/logging.rst
@@ -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
diff --git a/docs/source/quick_start.rst b/docs/source/quick_start.rst
index f0a52ed..2907885 100644
--- a/docs/source/quick_start.rst
+++ b/docs/source/quick_start.rst
@@ -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 server
+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`.
diff --git a/docs/source/reload.rst b/docs/source/reload.rst
index b53d11a..923043e 100644
--- a/docs/source/reload.rst
+++ b/docs/source/reload.rst
@@ -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.
diff --git a/docs/source/rest.rst b/docs/source/rest.rst
index d316a63..c0f31ee 100644
--- a/docs/source/rest.rst
+++ b/docs/source/rest.rst
@@ -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.
+
+In addition to handling REST, the :class:`RestController` also
+supports the :func:`index`, :func:`_default`, and :func:`_lookup`
+routing overrides.
+
+.. warning::
-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.
+ 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``.
diff --git a/docs/source/routing.rst b/docs/source/routing.rst
index 6ed3524..9c02a63 100644
--- a/docs/source/routing.rst
+++ b/docs/source/routing.rst
@@ -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
+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`.
diff --git a/docs/source/secure_controller.rst b/docs/source/secure_controller.rst
index 65709b5..090b512 100644
--- a/docs/source/secure_controller.rst
+++ b/docs/source/secure_controller.rst
@@ -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)::
+
+``SecureController``
+-------------------
+
+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
diff --git a/docs/source/sessions.rst b/docs/source/sessions.rst
index 0faa83d..cefba7d 100644
--- a/docs/source/sessions.rst
+++ b/docs/source/sessions.rst
@@ -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',
diff --git a/docs/source/templates.rst b/docs/source/templates.rst
index 271d815..991bf6f 100644
--- a/docs/source/templates.rst
+++ b/docs/source/templates.rst
@@ -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:
-
- * `Mako <http://www.makotemplates.org/>`_
- * `Genshi <http://genshi.edgewall.org/>`_
- * `Kajiki <http://kajiki.pythonisito.com/>`_
- * `Jinja2 <http://jinja.pocoo.org/>`_
- * `JSON`
-
-The default template system is `mako`, but can be configured by passing the
-``default_renderer`` key in your application's configuration::
+Pecan includes support for a variety of templating engines and also
+makes it easy to add support for new template engines. Currently,
+Pecan supports:
+
+=============== =============
+Template System Renderer Name
+=============== =============
+ Mako_ mako
+ Genshi_ genshi
+ Kajiki_ kajiki
+ Jinja2_ jinja
+ JSON json
+=============== =============
+
+.. _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
----------------------------------------
-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.
+Overriding Templates
+--------------------
-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 = {
diff --git a/docs/source/testing.rst b/docs/source/testing.rst
index 048b685..481166e 100644
--- a/docs/source/testing.rst
+++ b/docs/source/testing.rst
@@ -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 :mod:`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 :class:`FunctionalTest` base class and write
+tests that issue simulated HTTP requests.
-From here, it's possible to extend the ``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
+contains the name of the template used.
+
+::
class TestIndex(FunctionalTest):
diff --git a/pecan/rest.py b/pecan/rest.py
index 08b5efb..542ccd5 100644
--- a/pecan/rest.py
+++ b/pecan/rest.py
@@ -1,8 +1,10 @@
from inspect import getargspec, ismethod
+from webob import exc
+
from core import abort, request
from decorators import expose
-from routing import lookup_controller
+from routing import lookup_controller, handle_lookup_traversal
from util import iscontroller
@@ -53,11 +55,42 @@ class RestController(object):
# handle the request
handler = getattr(self, '_handle_%s' % method, self._handle_custom)
- result = handler(method, args)
+
+ try:
+ result = handler(method, args)
+
+ #
+ # If the signature of the handler does not match the number
+ # of remaining positional arguments, attempt to handle
+ # a _lookup method (if it exists)
+ #
+ argspec = getargspec(result[0])
+ num_args = len(argspec[0][1:])
+ if num_args < len(args):
+ _lookup_result = self._handle_lookup(args)
+ if _lookup_result:
+ return _lookup_result
+ except exc.HTTPNotFound:
+ #
+ # If the matching handler results in a 404, attempt to handle
+ # a _lookup method (if it exists)
+ #
+ _lookup_result = self._handle_lookup(args)
+ if _lookup_result:
+ return _lookup_result
+ raise
# return the result
return result
+ def _handle_lookup(self, args):
+ # check for lookup controllers
+ lookup = getattr(self, '_lookup', None)
+ if args and iscontroller(lookup):
+ result = handle_lookup_traversal(lookup, args)
+ if result:
+ return lookup_controller(*result)
+
def _find_controller(self, *args):
'''
Returns the appropriate controller for routing a custom action.
diff --git a/pecan/routing.py b/pecan/routing.py
index d38c22e..a7a6d4f 100644
--- a/pecan/routing.py
+++ b/pecan/routing.py
@@ -1,3 +1,5 @@
+import warnings
+
from webob import exc
from secure import handle_security, cross_boundary
@@ -42,25 +44,30 @@ def lookup_controller(obj, url_path):
else:
# Notfound handler is an internal redirect, so continue
# traversal
- try:
- result = obj(*remainder)
- if result:
- prev_obj = obj
- obj, remainder = result
- # crossing controller boundary
- cross_boundary(prev_obj, obj)
- break
- except TypeError, te:
- import warnings
- msg = 'Got exception calling lookup(): %s (%s)'
- warnings.warn(
- msg % (te, te.args),
- RuntimeWarning
- )
+ result = handle_lookup_traversal(obj, remainder)
+ if result:
+ return lookup_controller(*result)
else:
raise exc.HTTPNotFound
+def handle_lookup_traversal(obj, args):
+ try:
+ result = obj(*args)
+ if result:
+ prev_obj = obj
+ obj, remainder = result
+ # crossing controller boundary
+ cross_boundary(prev_obj, obj)
+ return result
+ except TypeError as te:
+ msg = 'Got exception calling lookup(): %s (%s)'
+ warnings.warn(
+ msg % (te, te.args),
+ RuntimeWarning
+ )
+
+
def find_object(obj, remainder, notfound_handlers):
'''
'Walks' the url path in search of an action for which a controller is
diff --git a/pecan/tests/test_rest.py b/pecan/tests/test_rest.py
index 6cd7bba..d5e7f5e 100644
--- a/pecan/tests/test_rest.py
+++ b/pecan/tests/test_rest.py
@@ -943,3 +943,222 @@ class TestRestController(PecanTestCase):
assert r.status_int == 200
assert r.namespace['foo'] == 'bar'
assert r.namespace['spam'] == 'eggs'
+
+ def test_nested_rest_with_lookup(self):
+
+ class SubController(RestController):
+
+ @expose()
+ def get_all(self):
+ return "SUB"
+
+ class FinalController(RestController):
+
+ def __init__(self, id_):
+ self.id_ = id_
+
+ @expose()
+ def get_all(self):
+ return "FINAL-%s" % self.id_
+
+ @expose()
+ def post(self):
+ return "POST-%s" % self.id_
+
+ class LookupController(RestController):
+
+ sub = SubController()
+
+ def __init__(self, id_):
+ self.id_ = id_
+
+ @expose()
+ def _lookup(self, id_, *remainder):
+ return FinalController(id_), remainder
+
+ @expose()
+ def get_all(self):
+ raise AssertionError("Never Reached")
+
+ @expose()
+ def post(self):
+ return "POST-LOOKUP-%s" % self.id_
+
+ @expose()
+ def put(self, id_):
+ return "PUT-LOOKUP-%s-%s" % (self.id_, id_)
+
+ @expose()
+ def delete(self, id_):
+ return "DELETE-LOOKUP-%s-%s" % (self.id_, id_)
+
+ class FooController(RestController):
+
+ @expose()
+ def _lookup(self, id_, *remainder):
+ return LookupController(id_), remainder
+
+ @expose()
+ def get_one(self, id_):
+ return "GET ONE"
+
+ @expose()
+ def get_all(self):
+ return "INDEX"
+
+ @expose()
+ def post(self):
+ return "POST"
+
+ @expose()
+ def put(self, id_):
+ return "PUT-%s" % id_
+
+ @expose()
+ def delete(self, id_):
+ return "DELETE-%s" % id_
+
+ class RootController(RestController):
+ foo = FooController()
+
+ app = TestApp(make_app(RootController()))
+
+ r = app.get('/foo')
+ assert r.status_int == 200
+ assert r.body == 'INDEX'
+
+ r = app.post('/foo')
+ assert r.status_int == 200
+ assert r.body == 'POST'
+
+ r = app.get('/foo/1')
+ assert r.status_int == 200
+ assert r.body == 'GET ONE'
+
+ r = app.post('/foo/1')
+ assert r.status_int == 200
+ assert r.body == 'POST-LOOKUP-1'
+
+ r = app.put('/foo/1')
+ assert r.status_int == 200
+ assert r.body == 'PUT-1'
+
+ r = app.delete('/foo/1')
+ assert r.status_int == 200
+ assert r.body == 'DELETE-1'
+
+ r = app.put('/foo/1/2')
+ assert r.status_int == 200
+ assert r.body == 'PUT-LOOKUP-1-2'
+
+ r = app.delete('/foo/1/2')
+ assert r.status_int == 200
+ assert r.body == 'DELETE-LOOKUP-1-2'
+
+ r = app.get('/foo/1/2')
+ assert r.status_int == 200
+ assert r.body == 'FINAL-2'
+
+ r = app.post('/foo/1/2')
+ assert r.status_int == 200
+ assert r.body == 'POST-2'
+
+ def test_dynamic_rest_lookup(self):
+ class BarController(RestController):
+ @expose()
+ def get_all(self):
+ return "BAR"
+
+ @expose()
+ def put(self):
+ return "PUT_BAR"
+
+ @expose()
+ def delete(self):
+ return "DELETE_BAR"
+
+ class BarsController(RestController):
+ @expose()
+ def _lookup(self, id_, *remainder):
+ return BarController(), remainder
+
+ @expose()
+ def get_all(self):
+ return "BARS"
+
+ @expose()
+ def post(self):
+ return "POST_BARS"
+
+ class FooController(RestController):
+ bars = BarsController()
+
+ @expose()
+ def get_all(self):
+ return "FOO"
+
+ @expose()
+ def put(self):
+ return "PUT_FOO"
+
+ @expose()
+ def delete(self):
+ return "DELETE_FOO"
+
+ class FoosController(RestController):
+ @expose()
+ def _lookup(self, id_, *remainder):
+ return FooController(), remainder
+
+ @expose()
+ def get_all(self):
+ return "FOOS"
+
+ @expose()
+ def post(self):
+ return "POST_FOOS"
+
+ class RootController(RestController):
+ foos = FoosController()
+
+ app = TestApp(make_app(RootController()))
+
+ r = app.get('/foos')
+ assert r.status_int == 200
+ assert r.body == 'FOOS'
+
+ r = app.post('/foos')
+ assert r.status_int == 200
+ assert r.body == 'POST_FOOS'
+
+ r = app.get('/foos/foo')
+ assert r.status_int == 200
+ assert r.body == 'FOO'
+
+ r = app.put('/foos/foo')
+ assert r.status_int == 200
+ assert r.body == 'PUT_FOO'
+
+ r = app.delete('/foos/foo')
+ assert r.status_int == 200
+ assert r.body == 'DELETE_FOO'
+
+ r = app.get('/foos/foo/bars')
+ assert r.status_int == 200
+ assert r.body == 'BARS'
+
+ r = app.post('/foos/foo/bars')
+ assert r.status_int == 200
+ assert r.body == 'POST_BARS'
+
+ r = app.get('/foos/foo/bars/bar')
+ assert r.status_int == 200
+ assert r.body == 'BAR'
+
+ r = app.put('/foos/foo/bars/bar')
+ assert r.status_int == 200
+ assert r.body == 'PUT_BAR'
+
+ r = app.delete('/foos/foo/bars/bar')
+ assert r.status_int == 200
+ assert r.body == 'DELETE_BAR'
diff --git a/setup.py b/setup.py
index 758774c..9d1f5fc 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ import sys
from setuptools import setup, find_packages
from setuptools.command.test import test as TestCommand
-version = '0.2.3'
+version = '0.2.4'
#
# determine requirements