Improve pecan documentation and correct intersphinx references.
Change-Id: Iac6229a2727a3c662d3fe9e83e1aa02ef648f025
This commit is contained in:
@@ -1,8 +1,5 @@
|
||||
.. _commands:
|
||||
|
||||
.. |argparse| replace:: ``argparse``
|
||||
.. _argparse: http://docs.python.org/dev/library/argparse.html
|
||||
|
||||
Command Line Pecan
|
||||
==================
|
||||
|
||||
@@ -15,7 +12,7 @@ Serving a Pecan App For Development
|
||||
-----------------------------------
|
||||
|
||||
Pecan comes bundled with a lightweight WSGI development server based on
|
||||
Python's :py:mod:`wsgiref.simpleserver` module.
|
||||
Python's :py:mod:`wsgiref.simple_server` module.
|
||||
|
||||
Serving your Pecan app is as simple as invoking the ``pecan serve`` command::
|
||||
|
||||
@@ -161,28 +158,28 @@ Let's analyze this piece-by-piece.
|
||||
Overriding the ``run`` Method
|
||||
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
|
||||
First, we're subclassing :class:`pecan.commands.BaseCommand` and extending
|
||||
the :func:`run` method to:
|
||||
First, we're subclassing :class:`~pecan.commands.base.BaseCommand` and extending
|
||||
the :func:`~pecan.commands.base.BaseCommandParent.run` method to:
|
||||
|
||||
* Load a Pecan application - ``self.load_app()``
|
||||
* Wrap it in a fake WGSI environment - ``webtest.TestApp()``
|
||||
* Issue an HTTP GET request against it - ``app.get(args.path)``
|
||||
* Load a Pecan application - :func:`~pecan.core.load_app`
|
||||
* Wrap it in a fake WGSI environment - :class:`~webtest.app.TestApp`
|
||||
* Issue an HTTP GET request against it - :meth:`~webtest.app.TestApp.get`
|
||||
|
||||
Defining Custom 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 :class:`pecan.commands.BaseCommand`
|
||||
*adding* to the arguments list provided by :class:`~pecan.commands.base.BaseCommand`
|
||||
(which already provides an argument for the ``config_file``), rather
|
||||
than overriding it entirely.
|
||||
|
||||
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,
|
||||
: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.
|
||||
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 :py:mod:`argparse` module (more specifically,
|
||||
:meth:`~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.
|
||||
|
||||
::
|
||||
|
||||
@@ -211,7 +208,7 @@ 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 :func:`setuptools.setup`.
|
||||
distribution’s ``setup.py`` file, you'll find a call to :func:`~setuptools.setup`.
|
||||
|
||||
::
|
||||
|
||||
@@ -225,7 +222,7 @@ distribution’s ``setup.py`` file, you'll find a call to :func:`setuptools.setu
|
||||
)
|
||||
|
||||
Assuming it doesn't exist already, we'll add the ``entry_points`` argument
|
||||
to the :func:`setup` call, and define a ``[pecan.command]`` definition for your custom
|
||||
to the :func:`~setuptools.setup` call, and define a ``[pecan.command]`` definition for your custom
|
||||
command::
|
||||
|
||||
|
||||
|
||||
@@ -28,7 +28,15 @@ import os
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc']
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx']
|
||||
|
||||
intersphinx_mapping = {
|
||||
'python': ('http://docs.python.org', None),
|
||||
'webob': ('http://docs.webob.org/en/latest', None),
|
||||
'webtest': ('http://webtest.readthedocs.org/en/latest/', None),
|
||||
'beaker': ('http://beaker.readthedocs.org/en/latest/', None),
|
||||
'paste': ('http://pythonpaste.org', None),
|
||||
}
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
@@ -78,7 +78,8 @@ Let's look at each value and what it means:
|
||||
the project root).
|
||||
|
||||
**debug**
|
||||
Enables ``WebError`` to display tracebacks in the browser
|
||||
Enables the ability to display tracebacks in the browser and interactively
|
||||
debug during development.
|
||||
|
||||
.. warning::
|
||||
|
||||
@@ -149,33 +150,35 @@ Dictionary Conversion
|
||||
---------------------
|
||||
|
||||
In certain situations you might want to deal with keys and values, but in strict
|
||||
dictionary form. The :class:`Config` object has a helper method for this purpose
|
||||
that will return a dictionary representation of the configuration, including nested values.
|
||||
dictionary form. The :class:`~pecan.configuration.Config` object has a helper
|
||||
method for this purpose that will return a dictionary representation of the
|
||||
configuration, including nested values.
|
||||
|
||||
Below is a representation of how you can access the :func:`as_dict` method and what
|
||||
it returns as a result (shortened for brevity):
|
||||
Below is a representation of how you can access the
|
||||
:meth:`~pecan.configuration.Config.to_dict` method and what it returns as
|
||||
a result (shortened for brevity):
|
||||
|
||||
::
|
||||
|
||||
>>> from pecan import conf
|
||||
>>> conf
|
||||
Config({'app': Config({'errors': {}, 'template_path': '', 'static_root': 'public', [...]
|
||||
>>> conf.as_dict()
|
||||
>>> conf.to_dict()
|
||||
{'app': {'errors': {}, 'template_path': '', 'static_root': 'public', [...]
|
||||
|
||||
|
||||
Prefixing Dictionary Keys
|
||||
-------------------------
|
||||
|
||||
:func:`Config.as_dict` allows you to pass an optional string argument
|
||||
if you need to prefix the keys in the returned dictionary.
|
||||
:func:`~pecan.configuration.Config.to_dict` allows you to pass an optional
|
||||
string argument if you need to prefix the keys in the returned dictionary.
|
||||
|
||||
::
|
||||
|
||||
>>> from pecan import conf
|
||||
>>> conf
|
||||
Config({'app': Config({'errors': {}, 'template_path': '', 'static_root': 'public', [...]
|
||||
>>> conf.as_dict('prefixed_')
|
||||
>>> conf.to_dict('prefixed_')
|
||||
{'prefixed_app': {'prefixed_errors': {}, 'prefixed_template_path': '', 'prefixed_static_root': 'prefixed_public', [...]
|
||||
|
||||
|
||||
|
||||
@@ -117,9 +117,9 @@ Binding Within the Application
|
||||
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
|
||||
:class:`~pecan.hooks.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.
|
||||
|
||||
::
|
||||
@@ -145,7 +145,7 @@ database binding.
|
||||
)
|
||||
|
||||
In the above example, on HTTP ``POST``, ``PUT``, and ``DELETE``
|
||||
requests, :class:`TransactionHook` takes care of the transaction
|
||||
requests, :class:`~pecan.hooks.TransactionHook` takes care of the transaction
|
||||
automatically by following these rules:
|
||||
|
||||
#. Before controller routing has been determined, :func:`model.start`
|
||||
@@ -164,7 +164,7 @@ automatically by following these rules:
|
||||
:func:`model.clear` are called.
|
||||
|
||||
On idempotent operations (like HTTP ``GET`` and ``HEAD`` requests),
|
||||
:class:`TransactionHook` handles transactions following different
|
||||
:class:`~pecan.hooks.TransactionHook` handles transactions following different
|
||||
rules.
|
||||
|
||||
#. ``model.start_read_only()`` is called. This function should bind
|
||||
@@ -175,17 +175,17 @@ rules.
|
||||
#. If the controller returns successfully, ``model.clear()`` is
|
||||
called.
|
||||
|
||||
Also note that there is a useful :func:`@after_commit` decorator provided
|
||||
in :ref:`pecan_decorators`.
|
||||
Also note that there is a useful :func:`~pecan.decorators.after_commit`
|
||||
decorator provided in :ref:`pecan_decorators`.
|
||||
|
||||
Splitting Reads and Writes
|
||||
--------------------------
|
||||
|
||||
Employing the strategy above with :class:`TransactionHook` makes it very
|
||||
simple to split database reads and writes based upon HTTP methods
|
||||
Employing the strategy above with :class:`~pecan.hooks.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.
|
||||
privileges). It's also possible to extend
|
||||
:class:`~pecan.hooks.TransactionHook` or write your own hook implementation for
|
||||
more refined control over where and when database bindings are called.
|
||||
|
||||
@@ -9,10 +9,10 @@ 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
|
||||
(:command:`pecan serve`), using a *production-ready* server similar to the ones
|
||||
described in this document is **very highly encouraged**.
|
||||
|
||||
Installing Pecan
|
||||
@@ -21,15 +21,14 @@ Installing Pecan
|
||||
A few popular options are available for installing Pecan in production
|
||||
environments:
|
||||
|
||||
* Using `setuptools/distribute
|
||||
<http://packages.python.org/distribute/setuptools.html>`_. Manage
|
||||
* Using `setuptools <https://pypi.python.org/pypi/setuptools>`_. 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>`_.
|
||||
* Using `pip <http://www.pip-installer.org/>`_.
|
||||
Use ``pip freeze`` and ``pip install`` to create and install from
|
||||
a ``requirements.txt`` file for your project.
|
||||
|
||||
@@ -73,7 +72,7 @@ examples are:
|
||||
* `CherryPy <http://cherrypy.org/>`__
|
||||
|
||||
Generally speaking, the WSGI entry point to any Pecan application can be
|
||||
generated using ``pecan.deploy``::
|
||||
generated using :func:`~pecan.deploy.deploy`::
|
||||
|
||||
from pecan.deploy import deploy
|
||||
application = deploy('/path/to/some/app/config.py')
|
||||
|
||||
@@ -109,11 +109,11 @@ Notice that the only bit of code we added to our :class:`RootController` was::
|
||||
def notfound(self):
|
||||
return dict(status=404, message="test_project does not have this page")
|
||||
|
||||
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
|
||||
template renderer.
|
||||
We simply :func:`~pecan.decorators.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 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.
|
||||
|
||||
@@ -10,13 +10,17 @@ without having to write separate middleware.
|
||||
|
||||
Hooks allow you to execute code at key points throughout the life cycle of your request:
|
||||
|
||||
* :func:`on_route`: called before Pecan attempts to route a request to a controller
|
||||
* :func:`~pecan.hooks.PecanHook.on_route`: called before Pecan attempts to
|
||||
route a request to a controller
|
||||
|
||||
* :func:`before`: called after routing, but before controller code is run
|
||||
* :func:`~pecan.hooks.PecanHook.before`: called after routing, but before
|
||||
controller code is run
|
||||
|
||||
* :func:`after`: called after controller code has been run
|
||||
* :func:`~pecan.hooks.PecanHook.after`: called after controller code has been
|
||||
run
|
||||
|
||||
* :func:`on_error`: called when a request generates an exception
|
||||
* :func:`~pecan.hooks.PecanHook.on_error`: called when a request generates an
|
||||
exception
|
||||
|
||||
Implementating a Pecan Hook
|
||||
---------------------------
|
||||
@@ -24,9 +28,12 @@ Implementating a Pecan Hook
|
||||
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.
|
||||
Your hook implementation needs to import :class:`~pecan.hooks.PecanHook` so it
|
||||
can be used as a base class. From there, you'll want to override the
|
||||
:func:`~pecan.hooks.PecanHook.on_route`, :func:`~pecan.hooks.PecanHook.before`,
|
||||
:func:`~pecan.hooks.PecanHook.after`, or
|
||||
:func:`~pecan.hooks.PecanHook.on_error` methods to
|
||||
define behavior.
|
||||
|
||||
::
|
||||
|
||||
@@ -41,14 +48,44 @@ used as a base class. From there, you'll need to override the
|
||||
print "\nmethod: \t %s" % state.request.method
|
||||
print "\nresponse: \t %s" % state.response.status
|
||||
|
||||
: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.
|
||||
:func:`~pecan.hooks.PecanHook.on_route`, :func:`~pecan.hooks.PecanHook.before`,
|
||||
and :func:`~pecan.hooks.PecanHook.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::
|
||||
|
||||
:func:`on_error` is passed a shared state object **and** the original exception. If
|
||||
an :func:`on_error` handler returns a Response object, this response will be returned
|
||||
to the end user and no furthur :func:`on_error` hooks will be executed.
|
||||
class SimpleHook(PecanHook):
|
||||
|
||||
def on_route(self, state):
|
||||
print "\nabout to map the URL to a Python method (controller)..."
|
||||
assert state.controller is None # Routing hasn't occurred yet
|
||||
assert isinstance(state.request, webob.Request)
|
||||
assert isinstance(state.response, webob.Response)
|
||||
assert isinstance(state.hooks, list) # A list of hooks to apply
|
||||
|
||||
def before(self, state):
|
||||
print "\nabout to enter the controller..."
|
||||
if state.request.path == '/':
|
||||
#
|
||||
# `state.controller` is a reference to the actual
|
||||
# `@pecan.expose()`-ed controller that will be routed to
|
||||
# and used to generate the response body
|
||||
#
|
||||
assert state.controller.__func__ is RootController.index.__func__
|
||||
assert isinstance(state.request, webob.Request)
|
||||
assert isinstance(state.response, webob.Response)
|
||||
assert isinstance(state.hooks, list)
|
||||
|
||||
|
||||
:func:`~pecan.hooks.PecanHook.on_error` is passed a shared state object **and**
|
||||
the original exception. If an :func:`~pecan.hooks.PecanHook.on_error` handler
|
||||
returns a Response object, this response will be returned to the end user and
|
||||
no furthur :func:`~pecan.hooks.PecanHook.on_error` hooks will be executed::
|
||||
|
||||
class CustomErrorHook(PecanHook):
|
||||
|
||||
def on_error(self, state, exc):
|
||||
if isinstance(exc, SomeExceptionType):
|
||||
return webob.Response('Custom Error!', status=500)
|
||||
|
||||
Attaching Hooks
|
||||
---------------
|
||||
@@ -65,7 +102,8 @@ in your project's configuration file.
|
||||
}
|
||||
|
||||
Hooks can also be applied selectively to controllers and their sub-controllers
|
||||
using the :attr:`__hooks__` attribute on one or more controllers.
|
||||
using the :attr:`__hooks__` attribute on one or more controllers and
|
||||
subclassing :class:`~pecan.hooks.HookController`.
|
||||
|
||||
::
|
||||
|
||||
@@ -82,8 +120,8 @@ using the :attr:`__hooks__` attribute on one or more controllers.
|
||||
print "DO SOMETHING!"
|
||||
return dict()
|
||||
|
||||
Now that :class:`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.
|
||||
|
||||
::
|
||||
|
||||
|
||||
@@ -6,9 +6,10 @@ Installation
|
||||
Stable Version
|
||||
--------------
|
||||
|
||||
We recommend installing Pecan with ``pip``, but you can also try with
|
||||
``easy_install``. Creating a spot in your environment where
|
||||
Pecan can be isolated from other packages is best practice.
|
||||
We recommend installing Pecan with `pip
|
||||
<http://www.pip-installer.org/>`_, but you
|
||||
can also try with :command:`easy_install`. Creating a spot in your environment
|
||||
where Pecan can be isolated from other packages is best practice.
|
||||
|
||||
To get started with an environment for Pecan, we recommend creating a new
|
||||
`virtual environment <http://www.virtualenv.org>`_ using `virtualenv
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
.. |FileHandler| replace:: ``FileHandler``
|
||||
.. _FileHandler: http://docs.python.org/dev/library/logging.handlers.html#filehandler
|
||||
|
||||
.. |RotatingFileHandler| replace:: ``RotatingFileHandler``
|
||||
.. _RotatingFileHandler: http://docs.python.org/dev/library/logging.handlers.html#rotatingfilehandler
|
||||
|
||||
.. |SysLogHandler| replace:: ``SysLogHandler``
|
||||
.. _SysLogHandler: http://docs.python.org/dev/library/logging.handlers.html#sysloghandler
|
||||
|
||||
.. |SMTPHandler| replace:: ``SMTPHandler``
|
||||
.. _SMTPHandler: http://docs.python.org/dev/library/logging.handlers.html#smtphandler
|
||||
|
||||
.. _logging:
|
||||
|
||||
Logging
|
||||
@@ -101,11 +89,13 @@ 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
|
||||
* |RotatingFileHandler|_ - similar to |FileHandler|_, but also rotates logs
|
||||
* :class:`~logging.FileHandler` - used to log messages to a file on the filesystem
|
||||
* :class:`~logging.handlers.RotatingFileHandler` - similar to
|
||||
:class:`~logging.FileHandler`, but also rotates logs
|
||||
periodically
|
||||
* |SysLogHandler|_ - used to log messages to a UNIX syslog
|
||||
* |SMTPHandler|_ - used to log messages to an email address via SMTP
|
||||
* :class:`~logging.handlers.SysLogHandler` - used to log messages to a UNIX syslog
|
||||
* :class:`~logging.handlers.SMTPHandler` - used to log messages to an email
|
||||
address via SMTP
|
||||
|
||||
Using any of them is as simple as defining a new handler in your
|
||||
application's ``logging`` block and assigning it to one of more loggers.
|
||||
@@ -114,7 +104,7 @@ 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
|
||||
the :class:`~paste.translogger.TransLogger` middleware
|
||||
for logging requests in `Apache Combined Log Format
|
||||
<http://httpd.apache.org/docs/2.2/logs.html#combined>`_. Combined with
|
||||
file-based logging, TransLogger can be used to create an ``access.log`` file
|
||||
@@ -136,9 +126,9 @@ project's ``app.py`` as follows::
|
||||
app = TransLogger(app, setup_console_handler=False)
|
||||
return app
|
||||
|
||||
By default, ``TransLogger`` creates a logger named ``wsgi``, so you'll need to
|
||||
specify a new (file-based) handler for this logger in our Pecan configuration
|
||||
file::
|
||||
By default, :class:`~paste.translogger.TransLogger` creates a logger named
|
||||
``wsgi``, so you'll need to specify a new (file-based) handler for this logger
|
||||
in our Pecan configuration file::
|
||||
|
||||
# myapp/config.py
|
||||
|
||||
|
||||
@@ -8,4 +8,12 @@ security into your applications.
|
||||
|
||||
.. automodule:: pecan.secure
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: pecan.secure.SecureControllerBase
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: pecan.secure.SecureController
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -27,7 +27,7 @@ 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
|
||||
available on :mod:`sys.path`, yet can still be edited directly from its
|
||||
source distribution::
|
||||
|
||||
$ python setup.py develop
|
||||
@@ -117,7 +117,7 @@ basic settings you need to run your Pecan application in
|
||||
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 the
|
||||
If you just run :command:`pecan serve`, passing ``config.py`` as the
|
||||
configuration file, it will bring up the development server and serve
|
||||
the app::
|
||||
|
||||
@@ -126,7 +126,7 @@ the app::
|
||||
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:
|
||||
|
||||
@@ -230,10 +230,11 @@ now, let's examine the sample project, controller by controller::
|
||||
def index(self):
|
||||
return dict()
|
||||
|
||||
The :func:`index` method is marked as *publicly 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.
|
||||
The :func:`index` method is marked as *publicly available* via the
|
||||
:func:`~pecan.decorators.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 :func:`index` method returns a Python dictionary. This dictionary
|
||||
is used as a namespace to render the specified template (``index.html``) into
|
||||
|
||||
@@ -5,10 +5,11 @@ Reloading Automatically as Files Change
|
||||
---------------------------------------
|
||||
|
||||
Pausing to restart your development server as you work can be interruptive, so
|
||||
``pecan serve`` provides a ``--reload`` flag to make life easier.
|
||||
:command:`pecan serve` provides a ``--reload`` flag to make life easier.
|
||||
|
||||
To provide this functionality, Pecan makes use of the Python ``watchdog``
|
||||
library. You'll need to install it for development use before continuing::
|
||||
To provide this functionality, Pecan makes use of the Python
|
||||
`watchdog <https://pypi.python.org/pypi/watchdog>`_ library. You'll need to
|
||||
install it for development use before continuing::
|
||||
|
||||
$ pip install watchdog
|
||||
Downloading/unpacking watchdog
|
||||
|
||||
@@ -4,9 +4,10 @@ Writing RESTful Web Services with Pecan
|
||||
=======================================
|
||||
|
||||
If you need to write controllers to interact with objects, using the
|
||||
: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.
|
||||
:class:`~pecan.rest.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.
|
||||
|
||||
::
|
||||
|
||||
@@ -27,7 +28,7 @@ verbs of ``GET``, ``POST``, ``PUT``, and ``DELETE`` to individual methods.
|
||||
URL Mapping
|
||||
-----------
|
||||
|
||||
By default, the :class:`RestController` routes as follows:
|
||||
By default, :class:`~pecan.rest.RestController` routes as follows:
|
||||
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
| Method | Description | Example Method(s) / URL(s) |
|
||||
@@ -57,25 +58,25 @@ By default, the :class:`RestController` routes as follows:
|
||||
| | | DELETE /books/1 |
|
||||
+-----------------+--------------------------------------------------------------+--------------------------------------------+
|
||||
|
||||
Pecan's :class:`RestController` uses the ``?_method=`` query string to
|
||||
work around the lack of support for the PUT and DELETE verbs when
|
||||
Pecan's :class:`~pecan.rest.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`
|
||||
In addition to handling REST, the :class:`~pecan.rest.RestController` also
|
||||
supports the :meth:`index`, :meth:`_default`, and :meth:`_lookup`
|
||||
routing overrides.
|
||||
|
||||
.. warning::
|
||||
|
||||
If you need to override :func:`_route`, make sure to call
|
||||
If you need to override :meth:`_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``
|
||||
---------------------------
|
||||
|
||||
:class:`RestController` instances can be nested so that child resources receive the
|
||||
parameters necessary to look up parent resources.
|
||||
:class:`~pecan.rest.RestController` instances can be nested so that child
|
||||
resources receive the parameters necessary to look up parent resources.
|
||||
|
||||
For example::
|
||||
|
||||
@@ -115,16 +116,17 @@ 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 :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 pass it everything up to the child resource controller name (e.g.,
|
||||
``books`` in the above example).
|
||||
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 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 :class:`RestController` by defining a special :attr:`_custom_actions`
|
||||
behaviors to a :class:`~pecan.rest.RestController` by defining a special
|
||||
:attr:`_custom_actions`
|
||||
dictionary.
|
||||
|
||||
For example::
|
||||
|
||||
@@ -74,10 +74,10 @@ Exposing Controllers
|
||||
--------------------
|
||||
|
||||
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.
|
||||
:func:`~pecan.decorators.expose`. If a method is *not* decorated with
|
||||
:func:`~pecan.decorators.expose`, Pecan will never route a request to it.
|
||||
:func:`~pecan.decorators.expose` accepts three optional parameters, some of
|
||||
which can impact routing and the content type of the response body.
|
||||
|
||||
::
|
||||
|
||||
@@ -106,8 +106,8 @@ Let's look at an example using ``template`` and ``content_type``:
|
||||
def hello(self):
|
||||
return {'msg': 'Hello!'}
|
||||
|
||||
You'll notice that we called :func:`expose` three times, with different
|
||||
arguments.
|
||||
You'll notice that we called :func:`~pecan.decoators.expose` three times, with
|
||||
different arguments.
|
||||
|
||||
::
|
||||
|
||||
@@ -143,8 +143,8 @@ 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 :func:`_lookup`, :func:`_default`, and :func:`_route` methods. Defining these
|
||||
methods on your controller objects provides additional flexibility for
|
||||
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.
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ modifying the ``status`` attribute of the response object.
|
||||
response.status = 201
|
||||
return {'foo': 'bar'}
|
||||
|
||||
Use the utility function :func:`abort` to raise HTTP errors.
|
||||
Use the utility function :func:`~pecan.core.abort` to raise HTTP errors.
|
||||
|
||||
::
|
||||
|
||||
@@ -178,8 +178,8 @@ Use the utility function :func:`abort` to raise HTTP errors.
|
||||
abort(404)
|
||||
|
||||
|
||||
:func:`abort` raises an instance of
|
||||
:class:`webob.exc.WSGIHTTPException` which is used by Pecan to render
|
||||
:func:`~pecan.core.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
|
||||
@@ -262,9 +262,9 @@ Defining Customized Routing with ``_route``
|
||||
|
||||
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.
|
||||
:class:`~pecan.rest.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
|
||||
@@ -359,8 +359,8 @@ Helper Functions
|
||||
----------------
|
||||
|
||||
Pecan also provides several useful helper functions for moving between
|
||||
different routes. The :func:`redirect` function allows you to issue internal or
|
||||
``HTTP 302`` redirects.
|
||||
different routes. The :func:`~pecan.core.redirect` function allows you to issue
|
||||
internal or ``HTTP 302`` redirects.
|
||||
|
||||
.. seealso::
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ authorization as you see fit.
|
||||
---------------------------
|
||||
|
||||
You can wrap entire controller subtrees *or* individual method calls
|
||||
with access controls using the :func:`secure` decorator.
|
||||
with access controls using the :func:`~pecan.secure.secure` decorator.
|
||||
|
||||
To decorate a method, use one argument::
|
||||
|
||||
@@ -65,10 +65,11 @@ To secure a class, invoke with two arguments::
|
||||
--------------------
|
||||
|
||||
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.
|
||||
subclassing Pecan's :class:`~pecan.secure.SecureController`. Implementations of
|
||||
:class:`~pecan.secure.SecureController` should extend the
|
||||
:meth:`~pecan.secure.SecureControllerBase.check_permissions` class method to
|
||||
return ``True`` if the user has permissions to the controller branch and
|
||||
``False`` if they do not.
|
||||
|
||||
::
|
||||
|
||||
@@ -110,31 +111,32 @@ controller branch and ``False`` if they do not.
|
||||
unclassified = unlocked(UnclassifiedController())
|
||||
|
||||
|
||||
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.
|
||||
Also note the use of the :func:`~pecan.secure.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 :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.
|
||||
The :meth:`~pecan.secure.SecureControllerBase.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.
|
||||
|
||||
|
||||
More on ``secure``
|
||||
------------------
|
||||
|
||||
The :func:`secure` method has several advanced uses that allow you to create
|
||||
robust security policies for your application.
|
||||
The :func:`~pecan.secure.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 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 model instance. Consider the following example
|
||||
of a basic virtual filesystem.
|
||||
instance method of the controller to use as the
|
||||
:meth:`~pecan.secure.SecureControllerBase.check_permissions` method. Instance
|
||||
methods are particularly useful if you wish to authorize access to attributes
|
||||
of a model instance. Consider the following example of a basic virtual
|
||||
filesystem.
|
||||
|
||||
::
|
||||
|
||||
@@ -170,7 +172,7 @@ of a basic virtual filesystem.
|
||||
return FileController(name), remainder
|
||||
|
||||
|
||||
The :func:`secure` method also accepts a function argument. When
|
||||
The :func:`~pecan.secure.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.
|
||||
@@ -189,11 +191,11 @@ you will likely get error during module import.
|
||||
return 'Logged in'
|
||||
|
||||
|
||||
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.
|
||||
You can also use the :func:`~pecan.secure.secure` method to change the behavior
|
||||
of a :class:`~pecan.secure.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.
|
||||
|
||||
::
|
||||
|
||||
@@ -230,8 +232,10 @@ Multiple Secure Controllers
|
||||
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`.
|
||||
:func:`~pecan.secure.SecureControllerBase.check_permissions` on the
|
||||
:class:`RootController` and then
|
||||
calls :func:`~pecan.secure.SecureControllerBase.check_permissions` on the
|
||||
:class:`AdminController`.
|
||||
|
||||
::
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ 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
|
||||
:class:`SessionMiddleware` in your project's ``app.py``.
|
||||
:class:`~beaker.middleware.SessionMiddleware` in your project's ``app.py``.
|
||||
|
||||
::
|
||||
|
||||
|
||||
@@ -35,11 +35,11 @@ configuration::
|
||||
Using Template Renderers
|
||||
------------------------
|
||||
|
||||
: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.
|
||||
:py:mod:`pecan.decorators` defines a decorator called
|
||||
:func:`~pecan.decorators.expose`, which is used to flag a method as a public
|
||||
controller. The :func:`~pecan.decorators.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.
|
||||
|
||||
::
|
||||
|
||||
@@ -48,8 +48,8 @@ exposed.
|
||||
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.
|
||||
:func:`~pecan.decorators.expose` will use the default template engine unless
|
||||
the path is prefixed by another renderer name.
|
||||
|
||||
::
|
||||
|
||||
@@ -67,11 +67,11 @@ is prefixed by another renderer name.
|
||||
Overriding Templates
|
||||
--------------------
|
||||
|
||||
: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.
|
||||
:func:`~pecan.core.override_template` allows you to override the template set
|
||||
for a controller method when it is exposed. When
|
||||
:func:`~pecan.core.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.
|
||||
|
||||
::
|
||||
|
||||
@@ -85,9 +85,9 @@ of the method.
|
||||
Manual Rendering
|
||||
----------------
|
||||
|
||||
:func:`render` allows you to manually render output using the Pecan
|
||||
:func:`~pecan.core.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.
|
||||
template, and :func:`~pecan.core.render` returns the rendered output as text.
|
||||
|
||||
::
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ source code is tested.
|
||||
|
||||
A healthy suite of tests combines **unit tests** with **functional tests**. In
|
||||
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
|
||||
help of the :mod:`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.
|
||||
@@ -57,7 +57,8 @@ similar to the one included with Pecan's quickstart project.
|
||||
|
||||
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 :class:`webtest.TestApp` environment.
|
||||
an instance of the application, wrapped in a :class:`~webtest.app.TestApp`
|
||||
environment.
|
||||
|
||||
From here, it's possible to extend the :class:`FunctionalTest` base class and write
|
||||
tests that issue simulated HTTP requests.
|
||||
@@ -73,16 +74,16 @@ tests that issue simulated HTTP requests.
|
||||
|
||||
.. seealso::
|
||||
|
||||
See the `WebTest <http://pythonpaste.org/webtest/>`_ documentation
|
||||
See the :mod:`webtest` documentation
|
||||
for further information about the methods available to a
|
||||
``webtest.TestApp`` instance.
|
||||
:class:`~webtest.app.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 :class:`webtest.TestResponse` object.
|
||||
"testing variables" to any :class:`~webtest.response.TestResponse` object.
|
||||
|
||||
Let's suppose that your Pecan applicaton had some controller which took a
|
||||
``name`` as an optional argument in the URL.
|
||||
|
||||
@@ -154,10 +154,13 @@ class BaseCommandParent(object):
|
||||
},)
|
||||
|
||||
def run(self, args):
|
||||
"""To be implemented by subclasses."""
|
||||
self.args = args
|
||||
|
||||
def load_app(self):
|
||||
from pecan import load_app
|
||||
return load_app(self.args.config_file)
|
||||
|
||||
BaseCommand = BaseCommandMeta('BaseCommand', (BaseCommandParent,), {})
|
||||
BaseCommand = BaseCommandMeta('BaseCommand', (BaseCommandParent,), {
|
||||
'__doc__': BaseCommandParent.__doc__
|
||||
})
|
||||
|
||||
@@ -180,7 +180,7 @@ class Pecan(object):
|
||||
:param template_path: A relative file system path (from the project root)
|
||||
where template files live. Defaults to 'templates'.
|
||||
:param hooks: A callable which returns a list of
|
||||
:class:`pecan.hooks.PecanHook`s
|
||||
:class:`pecan.hooks.PecanHook`
|
||||
:param custom_renderers: Custom renderer objects, as a dictionary keyed
|
||||
by engine name.
|
||||
:param extra_template_vars: Any variables to inject into the template
|
||||
|
||||
@@ -40,15 +40,13 @@ class HookControllerMeta(type):
|
||||
walk_controller(cls, cls, dict_.get('__hooks__', []))
|
||||
|
||||
|
||||
'''
|
||||
A base class for controllers that would like to specify hooks on
|
||||
their controller methods. Simply create a list of hook objects
|
||||
called ``__hooks__`` as a member of the controller's namespace.
|
||||
'''
|
||||
HookController = HookControllerMeta(
|
||||
'HookController',
|
||||
(object,),
|
||||
{}
|
||||
{'__doc__': ("A base class for controllers that would like to specify "
|
||||
"hooks on their controller methods. Simply create a list "
|
||||
"of hook objects called ``__hooks__`` as a class attribute "
|
||||
"of your controller.")}
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -178,13 +178,17 @@ class SecureControllerBase(object):
|
||||
|
||||
@classmethod
|
||||
def check_permissions(cls):
|
||||
"""
|
||||
Returns `True` or `False` to grant access. Implemented in subclasses
|
||||
of :class:`SecureController`.
|
||||
"""
|
||||
return False
|
||||
|
||||
|
||||
SecureController = SecureControllerMeta(
|
||||
'SecureController',
|
||||
(SecureControllerBase,),
|
||||
{}
|
||||
{'__doc__': SecureControllerMeta.__doc__}
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user