'\" t
.\" Man page generated from reStructuredText.
.
.
.nr rst2man-indent-level 0
.
.de1 rstReportMargin
\\$1 \\n[an-margin]
level \\n[rst2man-indent-level]
level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
-
\\n[rst2man-indent0]
\\n[rst2man-indent1]
\\n[rst2man-indent2]
..
.de1 INDENT
.\" .rstReportMargin pre:
. RS \\$1
. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
. nr rst2man-indent-level +1
.\" .rstReportMargin post:
..
.de UNINDENT
. RE
.\" indent \\n[an-margin]
.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
.nr rst2man-indent-level -1
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "PECAN" "1" "Apr 27, 2024" "1.5.1" "Pecan"
.SH NAME
pecan \- Pecan Documentation
.sp
Welcome to Pecan, a lean Python web framework inspired by CherryPy,
TurboGears, and Pylons. Pecan was originally created by the developers
of \fI\%ShootQ\fP while working at \fI\%Pictage\fP\&.
.sp
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 \(dqfull stack\(dq 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.
.sp
Although it is lightweight, Pecan does offer an extensive feature set
for building HTTP\-based applications, including:
.INDENT 0.0
.INDENT 3.5
.INDENT 0.0
.IP \(bu 2
Object\-dispatch for easy routing
.IP \(bu 2
Full support for REST\-style controllers
.IP \(bu 2
Extensible security framework
.IP \(bu 2
Extensible template language support
.IP \(bu 2
Extensible JSON support
.IP \(bu 2
Easy Python\-based configuration
.UNINDENT
.UNINDENT
.UNINDENT
.SH INSTALLATION
.SS Stable Version
.sp
We recommend installing Pecan with \fI\%pip\fP, but you
can also try with \fBeasy_install\fP\&. Creating a spot in your environment
where Pecan can be isolated from other packages is best practice.
.sp
To get started with an environment for Pecan, we recommend creating a new
\fI\%virtual environment\fP using \fI\%virtualenv\fP:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ virtualenv pecan\-env
$ cd pecan\-env
$ source bin/activate
.EE
.UNINDENT
.UNINDENT
.sp
The above commands create a virtual environment and \fIactivate\fP it. This
will isolate Pecan\(aqs dependency installations from your system packages, making
it easier to debug problems if needed.
.sp
Next, let\(aqs install Pecan:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pip install pecan
.EE
.UNINDENT
.UNINDENT
.SS Development (Unstable) Version
.sp
If you want to run the latest development version of Pecan you will
need to install git and clone the repo from GitHub:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ git clone https://github.com/pecan/pecan.git
.EE
.UNINDENT
.UNINDENT
.sp
Assuming your virtual environment is still activated, call \fBsetup.py\fP to
install the development version.:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ cd pecan
$ python setup.py develop
.EE
.UNINDENT
.UNINDENT
.sp
\fBNOTE:\fP
.INDENT 0.0
.INDENT 3.5
The \fBmaster\fP development branch is volatile and is generally not
recommended for production use.
.UNINDENT
.UNINDENT
.sp
Alternatively, you can also install from GitHub directly with \fBpip\fP\&.:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pip install \-e git://github.com/pecan/pecan.git#egg=pecan
.EE
.UNINDENT
.UNINDENT
.SH CREATING YOUR FIRST PECAN APPLICATION
.sp
Let\(aqs create a small sample project with Pecan.
.sp
\fBNOTE:\fP
.INDENT 0.0
.INDENT 3.5
This guide does not cover the installation of Pecan. If you need
instructions for installing Pecan, refer to \fI\%Installation\fP\&.
.UNINDENT
.UNINDENT
.SS Base Application Template
.sp
Pecan includes a basic template for starting a new project. From your
shell, type:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pecan create test_project
.EE
.UNINDENT
.UNINDENT
.sp
This example uses \fItest_project\fP as your project name, but you can replace
it with any valid Python package name you like.
.sp
Go ahead and change into your newly created project directory.:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ cd test_project
.EE
.UNINDENT
.UNINDENT
.sp
You\(aqll want to deploy it in \(dqdevelopment mode\(dq, such that it’s
available on \fBsys.path\fP, yet can still be edited directly from its
source distribution:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ python setup.py develop
.EE
.UNINDENT
.UNINDENT
.sp
Your new project contain these files:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ ls
├── MANIFEST.in
├── config.py
├── public
│\ \ ├── css
│\ \ │\ \ └── style.css
│\ \ └── images
├── setup.cfg
├── setup.py
└── test_project
\ \ ├── __init__.py
\ \ ├── app.py
\ \ ├── controllers
\ \ │\ \ ├── __init__.py
\ \ │\ \ └── root.py
\ \ ├── model
\ \ │\ \ └── __init__.py
\ \ ├── templates
\ \ │\ \ ├── error.html
\ \ │\ \ ├── index.html
\ \ │\ \ └── layout.html
\ \ └── tests
\ \ ├── __init__.py
\ \ ├── config.py
\ \ ├── test_functional.py
\ \ └── test_units.py
.EE
.UNINDENT
.UNINDENT
.sp
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.
.sp
Let\(aqs review the files created by the template.
.INDENT 0.0
.TP
\fBpublic\fP
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.
.UNINDENT
.sp
Pecan application structure generally follows the \fI\%MVC\fP pattern. The
directories under \fBtest_project\fP encompass your models, controllers
and templates.
.INDENT 0.0
.TP
\fBtest_project/controllers\fP
The container directory for your controller files.
.TP
\fBtest_project/templates\fP
All your templates go in here.
.TP
\fBtest_project/model\fP
Container for your model files.
.UNINDENT
.sp
Finally, a directory to house unit and integration tests:
.INDENT 0.0
.TP
\fBtest_project/tests\fP
All of the tests for your application.
.UNINDENT
.sp
The \fBtest_project/app.py\fP file controls how the Pecan application will be
created. This file must contain a \fBsetup_app()\fP function which returns the
WSGI application object. Generally you will not need to modify the \fBapp.py\fP
file provided by the base application template unless you need to customize
your app in a way that cannot be accomplished using config. See
\fI\%Python\-Based Configuration\fP below.
.sp
To avoid unneeded dependencies and to remain as flexible as possible,
Pecan doesn\(aqt impose any database or ORM (\fI\%Object Relational
Mapper\fP). If your project will interact with a database, you can add
code to \fBmodel/__init__.py\fP to load database bindings from your
configuration file and define tables and ORM definitions.
.SS Running the Application
.sp
The base project template creates the configuration file with the
basic settings you need to run your Pecan application in
\fBconfig.py\fP\&. 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.
.sp
If you just run \fBpecan serve\fP, passing \fBconfig.py\fP as the
configuration file, it will bring up the development server and serve
the app:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pecan serve config.py
Starting server in PID 000.
serving on 0.0.0.0:8080, view at http://127.0.0.1:8080
.EE
.UNINDENT
.UNINDENT
.sp
The location for the configuration file and the argument itself are very
flexible \- you can pass an absolute or relative path to the file.
.SS Python\-Based Configuration
.sp
For ease of use, Pecan configuration files are pure Python\-\-they\(aqre even saved
as \fB\&.py\fP files.
.sp
This is how your default (generated) configuration file should look:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
# Server Specific Configurations
server = {
\(aqport\(aq: \(aq8080\(aq,
\(aqhost\(aq: \(aq0.0.0.0\(aq
}
# Pecan Application Configurations
app = {
\(aqroot\(aq: \(aq${package}.controllers.root.RootController\(aq,
\(aqmodules\(aq: [\(aq${package}\(aq],
\(aqstatic_root\(aq: \(aq%(confdir)s/public\(aq,
\(aqtemplate_path\(aq: \(aq%(confdir)s/${package}/templates\(aq,
\(aqdebug\(aq: True,
\(aqerrors\(aq: {
\(aq404\(aq: \(aq/error/404\(aq,
\(aq__force_dict__\(aq: True
}
}
logging = {
\(aqloggers\(aq: {
\(aqroot\(aq : {\(aqlevel\(aq: \(aqINFO\(aq, \(aqhandlers\(aq: [\(aqconsole\(aq]},
\(aq${package}\(aq: {\(aqlevel\(aq: \(aqDEBUG\(aq, \(aqhandlers\(aq: [\(aqconsole\(aq]}
},
\(aqhandlers\(aq: {
\(aqconsole\(aq: {
\(aqlevel\(aq: \(aqDEBUG\(aq,
\(aqclass\(aq: \(aqlogging.StreamHandler\(aq,
\(aqformatter\(aq: \(aqsimple\(aq
}
},
\(aqformatters\(aq: {
\(aqsimple\(aq: {
\(aqformat\(aq: (\(aq%(asctime)s %(levelname)\-5.5s [%(name)s]\(aq
\(aq[%(threadName)s] %(message)s\(aq)
}
}
}
# Custom Configurations must be in Python dictionary format::
#
# foo = {\(aqbar\(aq:\(aqbaz\(aq}
#
# All configurations are accessible at::
# pecan.conf
.EE
.UNINDENT
.UNINDENT
.sp
You can also add your own configuration as Python dictionaries.
.sp
There\(aqs a lot to cover here, so we\(aqll come back to configuration files in
a later chapter (\fI\%Configuring Pecan Applications\fP).
.SS The Application Root
.sp
The \fBRoot Controller\fP is the entry point for your application. You
can think of it as being analogous to your application\(aqs root URL path
(in our case, \fBhttp://localhost:8080/\fP).
.sp
This is how it looks in the project template
(\fBtest_project.controllers.root.RootController\fP):
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
from webob.exc import status_map
class RootController(object):
@expose(generic=True, template=\(aqindex.html\(aq)
def index(self):
return dict()
@index.when(method=\(aqPOST\(aq)
def index_post(self, q):
redirect(\(aqhttps://pecan.readthedocs.io/en/latest/search.html?q=%s\(aq % q)
@expose(\(aqerror.html\(aq)
def error(self, status):
try:
status = int(status)
except ValueError:
status = 0
message = getattr(status_map.get(status), \(aqexplanation\(aq, \(aq\(aq)
return dict(status=status, message=message)
.EE
.UNINDENT
.UNINDENT
.sp
You can specify additional classes and methods if you need to do so, but for
now, let\(aqs examine the sample project, controller by controller:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
@expose(generic=True, template=\(aqindex.html\(aq)
def index(self):
return dict()
.EE
.UNINDENT
.UNINDENT
.sp
The \fBindex()\fP method is marked as \fIpublicly available\fP via the
\fI\%expose()\fP decorator (which in turn uses the
\fBindex.html\fP template) at the root of the application
(\fI\%http://127.0.0.1:8080/\fP), so any HTTP \fBGET\fP that hits the root of your
application (\fB/\fP) will be routed to this method.
.sp
Notice that the \fBindex()\fP method returns a Python dictionary. This dictionary
is used as a namespace to render the specified template (\fBindex.html\fP) into
HTML, and is the primary mechanism by which data is passed from controller to
template.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
@index.when(method=\(aqPOST\(aq)
def index_post(self, q):
redirect(\(aqhttps://pecan.readthedocs.io/en/latest/search.html?q=%s\(aq % q)
.EE
.UNINDENT
.UNINDENT
.sp
The \fBindex_post()\fP method receives one HTTP \fBPOST\fP argument (\fBq\fP). Because
the argument \fBmethod\fP to \fB@index.when()\fP has been set to \fB\(aqPOST\(aq\fP, any
HTTP \fBPOST\fP to the application root (in the example project, a form
submission) will be routed to this method.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
@expose(\(aqerror.html\(aq)
def error(self, status):
try:
status = int(status)
except ValueError:
status = 0
message = getattr(status_map.get(status), \(aqexplanation\(aq, \(aq\(aq)
return dict(status=status, message=message)
.EE
.UNINDENT
.UNINDENT
.sp
Finally, we have the \fBerror()\fP method, which allows the application to display
custom pages for certain HTTP errors (\fB404\fP, etc...).
.SS Running the Tests For Your Application
.sp
Your application comes with a few example tests that you can run, replace, and
add to. To run them:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ python setup.py test \-q
running test
running egg_info
writing requirements to sam.egg\-info/requires.txt
writing sam.egg\-info/PKG\-INFO
writing top\-level names to sam.egg\-info/top_level.txt
writing dependency_links to sam.egg\-info/dependency_links.txt
reading manifest file \(aqsam.egg\-info/SOURCES.txt\(aq
reading manifest template \(aqMANIFEST.in\(aq
writing manifest file \(aqsam.egg\-info/SOURCES.txt\(aq
running build_ext
\&....
\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-
Ran 4 tests in 0.009s
OK
.EE
.UNINDENT
.UNINDENT
.sp
The tests themselves can be found in the \fBtests\fP module in your project.
.SS Deploying to a Web Server
.sp
Ready to deploy your new Pecan app? Take a look at \fI\%Deploying Pecan in Production\fP\&.
.SH CONTROLLERS AND ROUTING
.sp
Pecan uses a routing strategy known as \fBobject\-dispatch\fP 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\(aqs controllers as a tree of objects
(branches of the object tree map directly to URL paths).
.sp
Let\(aqs look at a simple bookstore application:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
class BooksController(object):
@expose()
def index(self):
return \(dqWelcome to book section.\(dq
@expose()
def bestsellers(self):
return \(dqWe have 5 books in the top 10.\(dq
class CatalogController(object):
@expose()
def index(self):
return \(dqWelcome to the catalog.\(dq
books = BooksController()
class RootController(object):
@expose()
def index(self):
return \(dqWelcome to store.example.com!\(dq
@expose()
def hours(self):
return \(dqOpen 24/7 on the web.\(dq
catalog = CatalogController()
.EE
.UNINDENT
.UNINDENT
.sp
A request for \fB/catalog/books/bestsellers\fP from the online store would
begin with Pecan breaking the request up into \fBcatalog\fP, \fBbooks\fP, and
\fBbestsellers\fP\&. Next, Pecan would lookup \fBcatalog\fP on the root
controller. Using the \fBcatalog\fP object, Pecan would then lookup
\fBbooks\fP, followed by \fBbestsellers\fP\&. What if the URL ends in a slash?
Pecan will check for an \fBindex\fP method on the last controller object.
.sp
To illustrate further, the following paths:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
└── /
\ \ ├── /hours
\ \ └── /catalog
\ \ └── /catalog/books
\ \ └── /catalog/books/bestsellers
.EE
.UNINDENT
.UNINDENT
.sp
route to the following controller methods:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
└── RootController.index
\ \ ├── RootController.hours
\ \ └── CatalogController.index
\ \ └── BooksController.index
\ \ └── BooksController.bestsellers
.EE
.UNINDENT
.UNINDENT
.SS Exposing Controllers
.sp
You tell Pecan which methods in a class are publically\-visible via
\fI\%expose()\fP\&. If a method is \fInot\fP decorated with
\fI\%expose()\fP, Pecan will never route a request to it.
.sp
\fI\%expose()\fP can be used in a variety of ways. The
simplest case involves passing no arguments. In this scenario, the controller
returns a string representing the HTML response body.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
class RootController(object):
@expose()
def hello(self):
return \(aqHello World\(aq
.EE
.UNINDENT
.UNINDENT
.sp
A more common use case is to \fI\%specify a template and a namespace\fP:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
class RootController(object):
@expose(\(aqhtml_template.mako\(aq)
def hello(self):
return {\(aqmsg\(aq: \(aqHello!\(aq}
.EE
.UNINDENT
.UNINDENT
.INDENT 0.0
.INDENT 3.5
.sp
.EX
${msg}
.EE
.UNINDENT
.UNINDENT
.sp
Pecan also has built\-in support for a special \fI\%JSON renderer\fP, which translates template namespaces into rendered JSON text:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
class RootController(object):
@expose(\(aqjson\(aq)
def hello(self):
return {\(aqmsg\(aq: \(aqHello!\(aq}
.EE
.UNINDENT
.UNINDENT
.sp
\fI\%expose()\fP calls can also be stacked, which allows you to
serialize content differently depending on how the content is requested:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
class RootController(object):
@expose(\(aqjson\(aq)
@expose(\(aqtext_template.mako\(aq, content_type=\(aqtext/plain\(aq)
@expose(\(aqhtml_template.mako\(aq)
def hello(self):
return {\(aqmsg\(aq: \(aqHello!\(aq}
.EE
.UNINDENT
.UNINDENT
.sp
You\(aqll notice that we called \fI\%expose()\fP three times, with
different arguments.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
@expose(\(aqjson\(aq)
.EE
.UNINDENT
.UNINDENT
.sp
The first tells Pecan to serialize the response namespace using JSON
serialization when the client requests \fB/hello.json\fP or if an
\fBAccept: application/json\fP header is present.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
@expose(\(aqtext_template.mako\(aq, content_type=\(aqtext/plain\(aq)
.EE
.UNINDENT
.UNINDENT
.sp
The second tells Pecan to use the \fBtext_template.mako\fP template file when the
client requests \fB/hello.txt\fP or asks for text/plain via an \fBAccept\fP header.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
@expose(\(aqhtml_template.mako\(aq)
.EE
.UNINDENT
.UNINDENT
.sp
The third tells Pecan to use the \fBhtml_template.mako\fP template file when the
client requests \fB/hello.html\fP\&. If the client requests \fB/hello\fP, Pecan will
use the \fBtext/html\fP content type by default; in the absense of an explicit
content type, Pecan assumes the client wants HTML.
.sp
\fBSEE ALSO:\fP
.INDENT 0.0
.INDENT 3.5
.INDENT 0.0
.IP \(bu 2
\fI\%pecan.decorators \-\- Pecan Decorators\fP
.UNINDENT
.UNINDENT
.UNINDENT
.SS Specifying Explicit Path Segments
.sp
Occasionally, you may want to use a path segment in your routing that doesn\(aqt
work with Pecan\(aqs declarative approach to routing because of restrictions in
Python\(aqs syntax. For example, if you wanted to route for a path that includes
dashes, such as \fB/some\-path/\fP, the following is \fInot\fP valid Python:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
class RootController(object):
@pecan.expose()
def some\-path(self):
return dict()
.EE
.UNINDENT
.UNINDENT
.sp
To work around this, pecan allows you to specify an explicit path segment in
the \fI\%expose()\fP decorator:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
class RootController(object):
@pecan.expose(route=\(aqsome\-path\(aq)
def some_path(self):
return dict()
.EE
.UNINDENT
.UNINDENT
.sp
In this example, the pecan application will reply with an \fBHTTP 200\fP for
requests made to \fB/some\-path/\fP, but requests made to \fB/some_path/\fP will
yield an \fBHTTP 404\fP\&.
.sp
\fI\%route()\fP can also be used explicitly as an alternative to
the \fBroute\fP argument in \fI\%expose()\fP:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
class RootController(object):
@pecan.expose()
def some_path(self):
return dict()
pecan.route(\(aqsome\-path\(aq, RootController.some_path)
.EE
.UNINDENT
.UNINDENT
.sp
Routing to child controllers can be handled simliarly by utilizing
\fI\%route()\fP:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
class ChildController(object):
@pecan.expose()
def child(self):
return dict()
class RootController(object):
pass
pecan.route(RootController, \(aqchild\-path\(aq, ChildController())
.EE
.UNINDENT
.UNINDENT
.sp
In this example, the pecan application will reply with an \fBHTTP 200\fP for
requests made to \fB/child\-path/child/\fP\&.
.SS Routing Based on Request Method
.sp
The \fBgeneric\fP argument to \fI\%expose()\fP provides support for overloading URLs
based on the request method. In the following example, the same URL can be
serviced by two different methods (one for handling HTTP \fBGET\fP, another for
HTTP \fBPOST\fP) using \fIgeneric controllers\fP:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
class RootController(object):
# HTTP GET /
@expose(generic=True, template=\(aqjson\(aq)
def index(self):
return dict()
# HTTP POST /
@index.when(method=\(aqPOST\(aq, template=\(aqjson\(aq)
def index_POST(self, **kw):
uuid = create_something()
return dict(uuid=uuid)
.EE
.UNINDENT
.UNINDENT
.SS Pecan\(aqs Routing Algorithm
.sp
Sometimes, the standard object\-dispatch routing isn\(aqt 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 \fB_lookup()\fP, \fB_default()\fP, and \fB_route()\fP methods. Defining
these methods on your controller objects provides additional flexibility for
processing all or part of a URL.
.SS Routing to Subcontrollers with \fB_lookup\fP
.sp
The \fB_lookup()\fP 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.
.sp
A \fB_lookup()\fP method may accept one or more arguments, segments
of the URL path to be processed (split on
\fB/\fP). \fB_lookup()\fP 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 \fB*remainder\fP list which will be passed to the returned
controller when the object\-dispatch algorithm continues.
.sp
In addition to being used for creating controllers dynamically,
\fB_lookup()\fP is called as a last resort, when no other controller
method matches the URL and there is no \fB_default()\fP method.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose, abort
from somelib import get_student_by_name
class StudentController(object):
def __init__(self, student):
self.student = student
@expose()
def name(self):
return self.student.name
class RootController(object):
@expose()
def _lookup(self, primary_key, *remainder):
student = get_student_by_primary_key(primary_key)
if student:
return StudentController(student), remainder
else:
abort(404)
.EE
.UNINDENT
.UNINDENT
.sp
An HTTP GET request to \fB/8/name\fP would return the name of the student
where \fBprimary_key == 8\fP\&.
.SS Falling Back with \fB_default\fP
.sp
The \fB_default()\fP method is called as a last resort when no other controller
methods match the URL via standard object\-dispatch.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
class RootController(object):
@expose()
def english(self):
return \(aqhello\(aq
@expose()
def french(self):
return \(aqbonjour\(aq
@expose()
def _default(self):
return \(aqI cannot say hello in that language\(aq
.EE
.UNINDENT
.UNINDENT
.sp
In the example above, a request to \fB/spanish\fP would route to
\fBRootController._default()\fP\&.
.SS Defining Customized Routing with \fB_route\fP
.sp
The \fB_route()\fP method allows a controller to completely override the routing
mechanism of Pecan. Pecan itself uses the \fB_route()\fP method to implement its
\fI\%RestController\fP\&. If you want to design an alternative
routing system on top of Pecan, defining a base controller class that defines
a \fB_route()\fP method will enable you to have total control.
.SH INTERACTING WITH THE REQUEST AND RESPONSE OBJECT
.sp
For every HTTP request, Pecan maintains a \fI\%thread\-local reference\fP to the request and response object, \fBpecan.request\fP and
\fBpecan.response\fP\&. These are instances of \fBpecan.Request\fP
and \fBpecan.Response\fP, respectively, and can be interacted with
from within Pecan controller code:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
@pecan.expose()
def login(self):
assert pecan.request.path == \(aq/login\(aq
username = pecan.request.POST.get(\(aqusername\(aq)
password = pecan.request.POST.get(\(aqpassword\(aq)
pecan.response.status = 403
pecan.response.text = \(aqBad Login!\(aq
.EE
.UNINDENT
.UNINDENT
.sp
While Pecan abstracts away much of the need to interact with these objects
directly, there may be situations where you want to access them, such as:
.INDENT 0.0
.IP \(bu 2
Inspecting components of the URI
.IP \(bu 2
Determining aspects of the request, such as the user\(aqs IP address, or the
referer header
.IP \(bu 2
Setting specific response headers
.IP \(bu 2
Manually rendering a response body
.UNINDENT
.SS Specifying a Custom Response
.sp
Set a specific HTTP response code (such as \fB203 Non\-Authoritative Information\fP) by
modifying the \fBstatus\fP attribute of the response object.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose, response
class RootController(object):
@expose(\(aqjson\(aq)
def hello(self):
response.status = 203
return {\(aqfoo\(aq: \(aqbar\(aq}
.EE
.UNINDENT
.UNINDENT
.sp
Use the utility function \fI\%abort()\fP to raise HTTP errors.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose, abort
class RootController(object):
@expose(\(aqjson\(aq)
def hello(self):
abort(404)
.EE
.UNINDENT
.UNINDENT
.sp
\fI\%abort()\fP raises an instance of
\fBWSGIHTTPException\fP which is used by Pecan to render
default response bodies for HTTP errors. This exception is stored in
the WSGI request environ at \fBpecan.original_exception\fP, where it
can be accessed later in the request cycle (by, for example, other
middleware or \fI\%Custom Error Documents\fP).
.sp
If you\(aqd like to return an explicit response, you can do so using
\fI\%Response\fP:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose, Response
class RootController(object):
@expose()
def hello(self):
return Response(\(aqHello, World!\(aq, 202)
.EE
.UNINDENT
.UNINDENT
.SS Extending Pecan\(aqs Request and Response Object
.sp
The request and response implementations provided by WebOb are powerful, but
at times, it may be useful to extend application\-specific behavior onto your
request and response (such as specialized parsing of request headers or
customized response body serialization). To do so, define custom classes that
inherit from \fBpecan.Request\fP and \fBpecan.Response\fP, respectively:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
class MyRequest(pecan.Request):
pass
class MyResponse(pecan.Response):
pass
.EE
.UNINDENT
.UNINDENT
.sp
and modify your application configuration to use them:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from myproject import MyRequest, MyResponse
app = {
\(aqroot\(aq : \(aqproject.controllers.root.RootController\(aq,
\(aqmodules\(aq : [\(aqproject\(aq],
\(aqstatic_root\(aq : \(aq%(confdir)s/public\(aq,
\(aqtemplate_path\(aq : \(aq%(confdir)s/project/templates\(aq,
\(aqrequest_cls\(aq: MyRequest,
\(aqresponse_cls\(aq: MyResponse
}
.EE
.UNINDENT
.UNINDENT
.SS Mapping Controller Arguments
.sp
In Pecan, HTTP \fBGET\fP and \fBPOST\fP variables that are not consumed
during the routing process can be passed onto the controller method as
arguments.
.sp
Depending on the signature of the method, these arguments can be mapped
explicitly to arguments:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
class RootController(object):
@expose()
def index(self, arg):
return arg
@expose()
def kwargs(self, **kwargs):
return str(kwargs)
.EE
.UNINDENT
.UNINDENT
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ curl http://localhost:8080/?arg=foo
foo
$ curl http://localhost:8080/kwargs?a=1&b=2&c=3
{u\(aqa\(aq: u\(aq1\(aq, u\(aqc\(aq: u\(aq3\(aq, u\(aqb\(aq: u\(aq2\(aq}
.EE
.UNINDENT
.UNINDENT
.sp
or can be consumed positionally:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
class RootController(object):
@expose()
def args(self, *args):
return \(aq,\(aq.join(args)
.EE
.UNINDENT
.UNINDENT
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ curl http://localhost:8080/args/one/two/three
one,two,three
.EE
.UNINDENT
.UNINDENT
.sp
The same effect can be achieved with HTTP \fBPOST\fP body variables:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
class RootController(object):
@expose()
def index(self, arg):
return arg
.EE
.UNINDENT
.UNINDENT
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ curl \-X POST \(dqhttp://localhost:8080/\(dq \-H \(dqContent\-Type: application/x\-www\-form\-urlencoded\(dq \-d \(dqarg=foo\(dq
foo
.EE
.UNINDENT
.UNINDENT
.SS Static File Serving
.sp
Because Pecan gives you direct access to the underlying
\fBRequest\fP, serving a static file download is as simple as
setting the WSGI \fBapp_iter\fP and specifying the content type:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
import os
from random import choice
from webob.static import FileIter
from pecan import expose, response
class RootController(object):
@expose(content_type=\(aqimage/gif\(aq)
def gifs(self):
filepath = choice((
\(dq/path/to/funny/gifs/catdance.gif\(dq,
\(dq/path/to/funny/gifs/babydance.gif\(dq,
\(dq/path/to/funny/gifs/putindance.gif\(dq
))
f = open(filepath, \(aqrb\(aq)
response.app_iter = FileIter(f)
response.headers[
\(aqContent\-Disposition\(aq
] = \(aqattachment; filename=\(dq%s\(dq\(aq % os.path.basename(f.name)
.EE
.UNINDENT
.UNINDENT
.sp
If you don\(aqt know the content type ahead of time (for example, if you\(aqre
retrieving files and their content types from a data store), you can specify
it via \fBresponse.headers\fP rather than in the \fI\%expose()\fP
decorator:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
import os
from mimetypes import guess_type
from webob.static import FileIter
from pecan import expose, response
class RootController(object):
@expose()
def download(self):
f = open(\(aq/path/to/some/file\(aq, \(aqrb\(aq)
response.app_iter = FileIter(f)
response.headers[\(aqContent\-Type\(aq] = guess_type(f.name)
response.headers[
\(aqContent\-Disposition\(aq
] = \(aqattachment; filename=\(dq%s\(dq\(aq % os.path.basename(f.name)
.EE
.UNINDENT
.UNINDENT
.SS Handling File Uploads
.sp
Pecan makes it easy to handle file uploads via standard multipart forms. Simply
define your form with a file input:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
.EE
.UNINDENT
.UNINDENT
.sp
You can then read the uploaded file off of the request object in your
application\(aqs controller:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose, request
class RootController(object):
@expose()
def upload(self):
assert isinstance(request.POST[\(aqfile\(aq], cgi.FieldStorage)
data = request.POST[\(aqfile\(aq].file.read()
.EE
.UNINDENT
.UNINDENT
.SS Thread\-Safe Per\-Request Storage
.sp
For convenience, Pecan provides a Python dictionary on every request which can
be accessed and modified in a thread\-safe manner throughout the life\-cycle of
an individual request:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
pecan.request.context[\(aqcurrent_user\(aq] = some_user
print pecan.request.context.items()
.EE
.UNINDENT
.UNINDENT
.sp
This is particularly useful in situations where you want to store
metadata/context about a request (e.g., in middleware, or per\-routing hooks)
and access it later (e.g., in controller code).
.sp
For more fine\-grained control of the request, the underlying WSGI environ for
a given Pecan request can be accessed and modified via
\fBpecan.request.environ\fP\&.
.SS Helper Functions
.sp
Pecan also provides several useful helper functions for moving between
different routes. The \fI\%redirect()\fP function allows you to issue
internal or \fBHTTP 302\fP redirects.
.sp
\fBSEE ALSO:\fP
.INDENT 0.0
.INDENT 3.5
The \fBredirect()\fP utility, along with several other useful
helpers, are documented in \fI\%pecan.core \-\- Pecan Core\fP\&.
.UNINDENT
.UNINDENT
.SS Determining the URL for a Controller
.sp
Given the ability for routing to be drastically changed at runtime, it is not
always possible to correctly determine a mapping between a controller method
and a URL.
.sp
For example, in the following code that makes use of \fB_lookup()\fP to alter
the routing depending on a condition:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose, abort
from somelib import get_user_region
class DefaultRegionController(object):
@expose()
def name(self):
return \(dqDefault Region\(dq
class USRegionController(object):
@expose()
def name(self):
return \(dqUS Region\(dq
class RootController(object):
@expose()
def _lookup(self, user_id, *remainder):
if get_user_region(user_id) == \(aqus\(aq:
return USRegionController(), remainder
else:
return DefaultRegionController(), remainder
.EE
.UNINDENT
.UNINDENT
.sp
This logic depends on the geolocation of a given user and returning
a completely different class given the condition. A helper to determine what
URL \fBUSRegionController.name\fP belongs to would fail to do it correctly.
.SH TEMPLATING IN PECAN
.sp
Pecan includes support for a variety of templating engines and also
makes it easy to add support for new template engines. Currently,
Pecan supports:
.TS
box center;
l|l.
T{
Template System
T} T{
Renderer Name
T}
_
T{
\fI\%Mako\fP
T} T{
mako
T}
_
T{
\fI\%Genshi\fP
T} T{
genshi
T}
_
T{
\fI\%Kajiki\fP
T} T{
kajiki
T}
_
T{
\fI\%Jinja2\fP
T} T{
jinja
T}
_
T{
JSON
T} T{
json
T}
.TE
.sp
The default template system is \fBmako\fP, but that can be changed by
passing the \fBdefault_renderer\fP key in your application\(aqs
configuration:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
app = {
\(aqdefault_renderer\(aq : \(aqkajiki\(aq,
# ...
}
.EE
.UNINDENT
.UNINDENT
.SS Using Template Renderers
.sp
\fI\%pecan.decorators\fP defines a decorator called
\fI\%expose()\fP, which is used to flag a method as a public
controller. The \fI\%expose()\fP decorator takes a \fBtemplate\fP
argument, which can be used to specify the path to the template file to use for
the controller method being exposed.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
class MyController(object):
@expose(\(aqpath/to/mako/template.html\(aq)
def index(self):
return dict(message=\(aqI am a mako template\(aq)
.EE
.UNINDENT
.UNINDENT
.sp
\fI\%expose()\fP will use the default template engine unless
the path is prefixed by another renderer name.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
@expose(\(aqkajiki:path/to/kajiki/template.html\(aq)
def my_controller(self):
return dict(message=\(aqI am a kajiki template\(aq)
.EE
.UNINDENT
.UNINDENT
.sp
\fBSEE ALSO:\fP
.INDENT 0.0
.INDENT 3.5
.INDENT 0.0
.IP \(bu 2
\fI\%pecan.decorators \-\- Pecan Decorators\fP
.IP \(bu 2
\fI\%pecan.core \-\- Pecan Core\fP
.IP \(bu 2
\fI\%Controllers and Routing\fP
.UNINDENT
.UNINDENT
.UNINDENT
.SS Overriding Templates
.sp
\fI\%override_template()\fP allows you to override the template set
for a controller method when it is exposed. When
\fI\%override_template()\fP is called within the body of the
controller method, it changes the template that will be used for that
invocation of the method.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
class MyController(object):
@expose(\(aqtemplate_one.html\(aq)
def index(self):
# ...
override_template(\(aqtemplate_two.html\(aq)
return dict(message=\(aqI will now render with template_two.html\(aq)
.EE
.UNINDENT
.UNINDENT
.SS Manual Rendering
.sp
\fI\%render()\fP allows you to manually render output using the Pecan
templating framework. Pass the template path and values to go into the
template, and \fI\%render()\fP returns the rendered output as text.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
@expose()
def controller(self):
return render(\(aqmy_template.html\(aq, dict(message=\(aqI am the namespace\(aq))
.EE
.UNINDENT
.UNINDENT
.SS The JSON Renderer
.sp
Pecan also provides a \fBJSON\fP renderer, which you can use by exposing
a controller method with \fB@expose(\(aqjson\(aq)\fP\&.
.sp
\fBSEE ALSO:\fP
.INDENT 0.0
.INDENT 3.5
.INDENT 0.0
.IP \(bu 2
\fI\%JSON Serialization\fP
.IP \(bu 2
\fI\%pecan.jsonify \-\- Pecan JSON Support\fP
.UNINDENT
.UNINDENT
.UNINDENT
.SS Defining Custom Renderers
.sp
To define a custom renderer, you can create a class that follows the
renderer protocol:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
class MyRenderer(object):
def __init__(self, path, extra_vars):
\(aq\(aq\(aq
Your renderer is provided with a path to templates,
as configured by your application, and any extra
template variables, also as configured
\(aq\(aq\(aq
pass
def render(self, template_path, namespace):
\(aq\(aq\(aq
Lookup the template based on the path, and render
your output based upon the supplied namespace
dictionary, as returned from the controller.
\(aq\(aq\(aq
return str(namespace)
.EE
.UNINDENT
.UNINDENT
.sp
To enable your custom renderer, define a \fBcustom_renderers\fP key in
your application\(aqs configuration:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
app = {
\(aqcustom_renderers\(aq : {
\(aqmy_renderer\(aq : MyRenderer
},
# ...
}
.EE
.UNINDENT
.UNINDENT
.sp
\&...and specify the renderer in the \fI\%expose()\fP method:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
class RootController(object):
@expose(\(aqmy_renderer:template.html\(aq)
def index(self):
return dict(name=\(aqBob\(aq)
.EE
.UNINDENT
.UNINDENT
.SH WRITING RESTFUL WEB SERVICES WITH GENERIC CONTROLLERS
.sp
Pecan simplifies RESTful web services by providing a way to overload URLs based
on the request method. For most API\(aqs, the use of \fIgeneric controller\fP
definitions give you everything you need to build out robust RESTful
interfaces (and is the \fIrecommended\fP approach to writing RESTful web services
in pecan):
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import abort, expose
# Note: this is *not* thread\-safe. In real life, use a persistent data store.
BOOKS = {
\(aq0\(aq: \(aqThe Last of the Mohicans\(aq,
\(aq1\(aq: \(aqCatch\-22\(aq
}
class BookController(object):
def __init__(self, id_):
self.id_ = id_
assert self.book
@property
def book(self):
if self.id_ in BOOKS:
return dict(id=self.id_, name=BOOKS[self.id_])
abort(404)
# HTTP GET //
@expose(generic=True, template=\(aqjson\(aq)
def index(self):
return self.book
# HTTP PUT //
@index.when(method=\(aqPUT\(aq, template=\(aqjson\(aq)
def index_PUT(self, **kw):
BOOKS[self.id_] = kw[\(aqname\(aq]
return self.book
# HTTP DELETE //
@index.when(method=\(aqDELETE\(aq, template=\(aqjson\(aq)
def index_DELETE(self):
del BOOKS[self.id_]
return dict()
class RootController(object):
@expose()
def _lookup(self, id_, *remainder):
return BookController(id_), remainder
# HTTP GET /
@expose(generic=True, template=\(aqjson\(aq)
def index(self):
return [dict(id=k, name=v) for k, v in BOOKS.items()]
# HTTP POST /
@index.when(method=\(aqPOST\(aq, template=\(aqjson\(aq)
def index_POST(self, **kw):
id_ = str(len(BOOKS))
BOOKS[id_] = kw[\(aqname\(aq]
return dict(id=id_, name=kw[\(aqname\(aq])
.EE
.UNINDENT
.UNINDENT
.SH WRITING RESTFUL WEB SERVICES WITH RESTCONTROLLER
.sp
For compatability with the \fI\%TurboGears2\fP library, Pecan also provides
a class\-based solution to RESTful routing, \fI\%RestController\fP:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
from pecan.rest import RestController
from mymodel import Book
class BooksController(RestController):
@expose()
def get(self, id):
book = Book.get(id)
if not book:
abort(404)
return book.title
.EE
.UNINDENT
.UNINDENT
.SS URL Mapping
.sp
By default, \fI\%RestController\fP routes as follows:
.TS
box center;
l|l|l.
T{
Method
T} T{
Description
T} T{
Example Method(s) / URL(s)
T}
_
T{
get_one
T} T{
Display one record.
T} T{
GET /books/1
T}
_
T{
get_all
T} T{
Display all records in a resource.
T} T{
GET /books/
T}
_
T{
get
T} T{
A combo of get_one and get_all.
T} T{
GET /books/
T}
_
T{
GET /books/1
T}
_
T{
new
T} T{
Display a page to create a new resource.
T} T{
GET /books/new
T}
_
T{
edit
T} T{
Display a page to edit an existing resource.
T} T{
GET /books/1/edit
T}
_
T{
post
T} T{
Create a new record.
T} T{
POST /books/
T}
_
T{
put
T} T{
Update an existing record.
T} T{
POST /books/1?_method=put
T}
_
T{
PUT /books/1
T}
_
T{
get_delete
T} T{
Display a delete confirmation page.
T} T{
GET /books/1/delete
T}
_
T{
delete
T} T{
Delete an existing record.
T} T{
POST /books/1?_method=delete
T}
_
T{
DELETE /books/1
T}
.TE
.sp
Pecan\(aqs \fI\%RestController\fP uses the \fB?_method=\fP query string
to work around the lack of support for the PUT and DELETE verbs when
submitting forms in most current browsers.
.sp
In addition to handling REST, the \fI\%RestController\fP also
supports the \fBindex()\fP, \fB_default()\fP, and \fB_lookup()\fP
routing overrides.
.sp
\fBWARNING:\fP
.INDENT 0.0
.INDENT 3.5
If you need to override \fB_route()\fP, make sure to call
\fBRestController._route()\fP at the end of your custom method so
that the REST routing described above still occurs.
.UNINDENT
.UNINDENT
.SS Nesting \fBRestController\fP
.sp
\fI\%RestController\fP instances can be nested so that child
resources receive the parameters necessary to look up parent resources.
.sp
For example:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
from pecan.rest import RestController
from mymodel import Author, Book
class BooksController(RestController):
@expose()
def get(self, author_id, id):
author = Author.get(author_id)
if not author_id:
abort(404)
book = author.get_book(id)
if not book:
abort(404)
return book.title
class AuthorsController(RestController):
books = BooksController()
@expose()
def get(self, id):
author = Author.get(id)
if not author:
abort(404)
return author.name
class RootController(object):
authors = AuthorsController()
.EE
.UNINDENT
.UNINDENT
.sp
Accessing \fB/authors/1/books/2\fP invokes \fBBooksController.get()\fP with
\fBauthor_id\fP set to \fB1\fP and \fBid\fP set to \fB2\fP\&.
.sp
To determine which arguments are associated with the parent resource, Pecan
looks at the \fBget_one()\fP then \fBget()\fP 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., \fBbooks\fP in the above example).
.SS Defining Custom Actions
.sp
In addition to the default methods defined above, you can add additional
behaviors to a \fI\%RestController\fP by defining a special
\fB_custom_actions\fP
dictionary.
.sp
For example:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
from pecan.rest import RestController
from mymodel import Book
class BooksController(RestController):
_custom_actions = {
\(aqcheckout\(aq: [\(aqPOST\(aq]
}
@expose()
def checkout(self, id):
book = Book.get(id)
if not book:
abort(404)
book.checkout()
.EE
.UNINDENT
.UNINDENT
.sp
\fB_custom_actions\fP maps method names to the list of valid HTTP
verbs for those custom actions. In this case \fBcheckout()\fP supports
\fBPOST\fP\&.
.SH CONFIGURING PECAN APPLICATIONS
.sp
Pecan is very easy to configure. As long as you follow certain conventions,
using, setting and dealing with configuration should be very intuitive.
.sp
Pecan configuration files are pure Python. Each \(dqsection\(dq of the
configuration is a dictionary assigned to a variable name in the
configuration module.
.SS Default Values
.sp
Below is the complete list of default values the framework uses:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
server = {
\(aqport\(aq : \(aq8080\(aq,
\(aqhost\(aq : \(aq0.0.0.0\(aq
}
app = {
\(aqroot\(aq : None,
\(aqmodules\(aq : [],
\(aqstatic_root\(aq : \(aqpublic\(aq,
\(aqtemplate_path\(aq : \(aq\(aq
}
.EE
.UNINDENT
.UNINDENT
.SS Application Configuration
.sp
The \fBapp\fP configuration values are used by Pecan to wrap your
application into a valid \fI\%WSGI app\fP\&. The \fBapp\fP configuration
is specific to your application, and includes values like the root
controller class location.
.sp
A typical application configuration might look like this:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
app = {
\(aqroot\(aq : \(aqproject.controllers.root.RootController\(aq,
\(aqmodules\(aq : [\(aqproject\(aq],
\(aqstatic_root\(aq : \(aq%(confdir)s/public\(aq,
\(aqtemplate_path\(aq : \(aq%(confdir)s/project/templates\(aq,
\(aqdebug\(aq : True
}
.EE
.UNINDENT
.UNINDENT
.sp
Let\(aqs look at each value and what it means:
.INDENT 0.0
.TP
\fBmodules\fP
A list of modules where pecan will search for applications.
Generally this should contain a single item, the name of your
project\(aqs python package. At least one of the listed modules must
contain an \fBapp.setup_app\fP function which is called to create the
WSGI app. In other words, this package should be where your
\fBapp.py\fP file is located, and this file should contain a
\fBsetup_app\fP function.
.TP
\fBroot\fP
The root controller of your application. Remember to provide a
string representing a Python path to some callable (e.g.,
\fB\(dqyourapp.controllers.root.RootController\(dq\fP).
.TP
\fBstatic_root\fP
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.
.TP
\fBtemplate_path\fP
Points to the directory where your template files live (relative to
the project root).
.TP
\fBdebug\fP
Enables the ability to display tracebacks in the browser and interactively
debug during development.
.UNINDENT
.sp
\fBWARNING:\fP
.INDENT 0.0
.INDENT 3.5
\fBapp\fP is a reserved variable name for that section of the
configuration, so make sure you don\(aqt override it.
.UNINDENT
.UNINDENT
.sp
\fBWARNING:\fP
.INDENT 0.0
.INDENT 3.5
Make sure \fBdebug\fP is \fIalways\fP set to \fBFalse\fP in production environments.
.UNINDENT
.UNINDENT
.sp
\fBSEE ALSO:\fP
.INDENT 0.0
.INDENT 3.5
.INDENT 0.0
.IP \(bu 2
\fI\%Base Application Template\fP
.UNINDENT
.UNINDENT
.UNINDENT
.SS Server Configuration
.sp
Pecan provides some sane defaults. Change these to alter the host and port your
WSGI app is served on.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
server = {
\(aqport\(aq : \(aq8080\(aq,
\(aqhost\(aq : \(aq0.0.0.0\(aq
}
.EE
.UNINDENT
.UNINDENT
.SS Additional Configuration
.sp
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.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
twitter = {
\(aqapi_key\(aq : \(aqFOO\(aq,
\(aqapi_secret\(aq : \(aqSECRET\(aq
}
.EE
.UNINDENT
.UNINDENT
.SS Accessing Configuration at Runtime
.sp
You can access any configuration value at runtime via \fBpecan.conf\fP\&.
This includes custom, application, and server\-specific values.
.sp
For example, if you needed to specify a global administrator, you could
do so like this within the configuration file.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
administrator = \(aqfoo_bar_user\(aq
.EE
.UNINDENT
.UNINDENT
.sp
And it would be accessible in \fBpecan.conf\fP as:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
>>> from pecan import conf
>>> conf.administrator
\(aqfoo_bar_user\(aq
.EE
.UNINDENT
.UNINDENT
.SS Dictionary Conversion
.sp
In certain situations you might want to deal with keys and values, but in strict
dictionary form. The \fI\%Config\fP object has a helper
method for this purpose that will return a dictionary representation of the
configuration, including nested values.
.sp
Below is a representation of how you can access the
\fI\%to_dict()\fP method and what it returns as
a result (shortened for brevity):
.INDENT 0.0
.INDENT 3.5
.sp
.EX
>>> from pecan import conf
>>> conf
Config({\(aqapp\(aq: Config({\(aqerrors\(aq: {}, \(aqtemplate_path\(aq: \(aq\(aq, \(aqstatic_root\(aq: \(aqpublic\(aq, [...]
>>> conf.to_dict()
{\(aqapp\(aq: {\(aqerrors\(aq: {}, \(aqtemplate_path\(aq: \(aq\(aq, \(aqstatic_root\(aq: \(aqpublic\(aq, [...]
.EE
.UNINDENT
.UNINDENT
.SS Prefixing Dictionary Keys
.sp
\fI\%to_dict()\fP allows you to pass an optional
string argument if you need to prefix the keys in the returned dictionary.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
>>> from pecan import conf
>>> conf
Config({\(aqapp\(aq: Config({\(aqerrors\(aq: {}, \(aqtemplate_path\(aq: \(aq\(aq, \(aqstatic_root\(aq: \(aqpublic\(aq, [...]
>>> conf.to_dict(\(aqprefixed_\(aq)
{\(aqprefixed_app\(aq: {\(aqprefixed_errors\(aq: {}, \(aqprefixed_template_path\(aq: \(aq\(aq, \(aqprefixed_static_root\(aq: \(aqprefixed_public\(aq, [...]
.EE
.UNINDENT
.UNINDENT
.SS Dotted Keys, Non\-Python Idenfitiers, and Native Dictionaries
.sp
Sometimes you want to specify a configuration option that includes dotted keys
or is not a valid Python idenfitier, such as \fB()\fP\&. These situations are
especially common when configuring Python logging. By passing a special key,
\fB__force_dict__\fP, individual configuration blocks can be treated as native
dictionaries.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
logging = {
\(aqroot\(aq: {\(aqlevel\(aq: \(aqINFO\(aq, \(aqhandlers\(aq: [\(aqconsole\(aq]},
\(aqloggers\(aq: {
\(aqsqlalchemy.engine\(aq: {\(aqlevel\(aq: \(aqINFO\(aq, \(aqhandlers\(aq: [\(aqconsole\(aq]},
\(aq__force_dict__\(aq: True
},
\(aqformatters\(aq: {
\(aqcustom\(aq: {
\(aq()\(aq: \(aqmy.package.customFormatter\(aq
}
}
}
from myapp import conf
assert isinstance(conf.logging.loggers, dict)
assert isinstance(conf.logging.loggers[\(aqsqlalchemy.engine\(aq], dict)
.EE
.UNINDENT
.UNINDENT
.SH SECURITY AND AUTHENTICATION
.sp
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.
.SS \fBsecure\fP Decorator Basics
.sp
You can wrap entire controller subtrees \fIor\fP individual method calls
with access controls using the \fI\%secure()\fP decorator.
.sp
To decorate a method, use one argument:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
secure(\(aq\(aq)
.EE
.UNINDENT
.UNINDENT
.sp
To secure a class, invoke with two arguments:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
secure(object_instance, \(aq\(aq)
.EE
.UNINDENT
.UNINDENT
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
from pecan.secure import secure
class HighlyClassifiedController(object):
pass
class UnclassifiedController(object):
pass
class RootController(object):
@classmethod
def check_permissions(cls):
if user_is_admin():
return True
return False
@expose()
def index(self):
#
# This controller is unlocked to everyone,
# and will not run any security checks.
#
return dict()
@secure(\(aqcheck_permissions\(aq)
@expose()
def topsecret(self):
#
# This controller is top\-secret, and should
# only be reachable by administrators.
#
return dict()
highly_classified = secure(HighlyClassifiedController(), \(aqcheck_permissions\(aq)
unclassified = UnclassifiedController()
.EE
.UNINDENT
.UNINDENT
.SS \fBSecureController\fP
.sp
Alternatively, the same functionality can also be accomplished by
subclassing Pecan\(aqs \fI\%SecureController\fP\&. Implementations of
\fI\%SecureController\fP should extend the
\fI\%check_permissions()\fP class method to
return \fBTrue\fP if the user has permissions to the controller branch and
\fBFalse\fP if they do not.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
from pecan.secure import SecureController, unlocked
class HighlyClassifiedController(object):
pass
class UnclassifiedController(object):
pass
class RootController(SecureController):
@classmethod
def check_permissions(cls):
if user_is_admin():
return True
return False
@expose()
@unlocked
def index(self):
#
# This controller is unlocked to everyone,
# and will not run any security checks.
#
return dict()
@expose()
def topsecret(self):
#
# This controller is top\-secret, and should
# only be reachable by administrators.
#
return dict()
highly_classified = HighlyClassifiedController()
unclassified = unlocked(UnclassifiedController())
.EE
.UNINDENT
.UNINDENT
.sp
Also note the use of the \fI\%unlocked()\fP decorator in the above
example, which can be used similarly to explicitly unlock a controller for
public access without any security checks.
.SS Writing Authentication/Authorization Methods
.sp
The \fI\%check_permissions()\fP 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.
.SS More on \fBsecure\fP
.sp
The \fI\%secure()\fP method has several advanced uses that allow
you to create robust security policies for your application.
.sp
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
\fI\%check_permissions()\fP 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.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
from pecan.secure import secure
from myapp.session import get_current_user
from myapp.model import FileObject
class FileController(object):
def __init__(self, name):
self.file_object = FileObject(name)
def read_access(self):
self.file_object.read_access(get_current_user())
def write_access(self):
self.file_object.write_access(get_current_user())
@secure(\(aqwrite_access\(aq)
@expose()
def upload_file(self):
pass
@secure(\(aqread_access\(aq)
@expose()
def download_file(self):
pass
class RootController(object):
@expose()
def _lookup(self, name, *remainder):
return FileController(name), remainder
.EE
.UNINDENT
.UNINDENT
.sp
The \fI\%secure()\fP 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.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
from pecan.secure import secure
from myapp.auth import user_authenitcated
class RootController(object):
@secure(user_authenticated)
@expose()
def index(self):
return \(aqLogged in\(aq
.EE
.UNINDENT
.UNINDENT
.sp
You can also use the \fI\%secure()\fP method to change the behavior
of a \fI\%SecureController\fP\&. 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.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
from pecan.secure import SecureController, secure
from myapp.auth import user_authenticated, admin_user
class ApiController(object):
pass
class RootController(SecureController):
@classmethod
def check_permissions(cls):
return user_authenticated()
@classmethod
def check_api_permissions(cls):
return admin_user()
@expose()
def index(self):
return \(aqlogged in user\(aq
api = secure(ApiController(), \(aqcheck_api_permissions\(aq)
.EE
.UNINDENT
.UNINDENT
.sp
In the example above, pecan will \fIonly\fP call \fBadmin_user()\fP when a request is
made for \fB/api/\fP\&.
.SS Multiple Secure Controllers
.sp
Secure controllers can be nested to provide increasing levels of
security on subcontrollers. In the example below, when a request is
made for \fB/admin/index/\fP, Pecan first calls
\fI\%check_permissions()\fP on the
\fBRootController\fP and then
calls \fI\%check_permissions()\fP on the
\fBAdminController\fP\&.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
from pecan.secure import SecureController
from myapp.auth import user_logged_in, is_admin
class AdminController(SecureController):
@classmethod
def check_permissions(cls):
return is_admin()
@expose()
def index(self):
return \(aqadmin dashboard\(aq
class RootController(SecureController):
@classmethod
def check_permissions(cls):
return user_logged_in
@expose()
def index(self):
return \(aquser dashboard\(aq
.EE
.UNINDENT
.UNINDENT
.SH PECAN HOOKS
.sp
Although it is easy to use WSGI middleware with Pecan, it can be hard
(sometimes impossible) to have access to Pecan\(aqs internals from within
middleware. Pecan Hooks are a way to interact with the framework,
without having to write separate middleware.
.sp
Hooks allow you to execute code at key points throughout the life cycle of your request:
.INDENT 0.0
.IP \(bu 2
\fI\%on_route()\fP: called before Pecan attempts to
route a request to a controller
.IP \(bu 2
\fI\%before()\fP: called after routing, but before
controller code is run
.IP \(bu 2
\fI\%after()\fP: called after controller code has been
run
.IP \(bu 2
\fI\%on_error()\fP: called when a request generates an
exception
.UNINDENT
.SS Implementating a Pecan Hook
.sp
In the below example, a simple hook will gather some information about
the request and print it to \fBstdout\fP\&.
.sp
Your hook implementation needs to import \fI\%PecanHook\fP so it
can be used as a base class. From there, you\(aqll want to override the
\fI\%on_route()\fP, \fI\%before()\fP,
\fI\%after()\fP, or
\fI\%on_error()\fP methods to
define behavior.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan.hooks import PecanHook
class SimpleHook(PecanHook):
def before(self, state):
print \(dq\enabout to enter the controller...\(dq
def after(self, state):
print \(dq\enmethod: \et %s\(dq % state.request.method
print \(dq\enresponse: \et %s\(dq % state.response.status
.EE
.UNINDENT
.UNINDENT
.sp
\fI\%on_route()\fP, \fI\%before()\fP,
and \fI\%after()\fP 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\(aqs routing:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
class SimpleHook(PecanHook):
def on_route(self, state):
print \(dq\enabout to map the URL to a Python method (controller)...\(dq
assert state.controller is None # Routing hasn\(aqt 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 \(dq\enabout to enter the controller...\(dq
if state.request.path == \(aq/\(aq:
#
# \(gastate.controller\(ga is a reference to the actual
# \(ga@pecan.expose()\(ga\-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.arguments, inspect.Arguments)
print state.arguments.args
print state.arguments.varargs
print state.arguments.keywords
assert isinstance(state.request, webob.Request)
assert isinstance(state.response, webob.Response)
assert isinstance(state.hooks, list)
.EE
.UNINDENT
.UNINDENT
.sp
\fI\%on_error()\fP is passed a shared state object \fBand\fP
the original exception. If an \fI\%on_error()\fP handler
returns a Response object, this response will be returned to the end user and
no furthur \fI\%on_error()\fP hooks will be executed:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
class CustomErrorHook(PecanHook):
def on_error(self, state, exc):
if isinstance(exc, SomeExceptionType):
return webob.Response(\(aqCustom Error!\(aq, status=500)
.EE
.UNINDENT
.UNINDENT
.SS Attaching Hooks
.sp
Hooks can be attached in a project\-wide manner by specifying a list of hooks
in your project\(aqs configuration file.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
app = {
\(aqroot\(aq : \(aq...\(aq
# ...
\(aqhooks\(aq: lambda: [SimpleHook()]
}
.EE
.UNINDENT
.UNINDENT
.sp
Hooks can also be applied selectively to controllers and their sub\-controllers
using the \fB__hooks__\fP attribute on one or more controllers and
subclassing \fI\%HookController\fP\&.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
from pecan.hooks import HookController
from my_hooks import SimpleHook
class SimpleController(HookController):
__hooks__ = [SimpleHook()]
@expose(\(aqjson\(aq)
def index(self):
print \(dqDO SOMETHING!\(dq
return dict()
.EE
.UNINDENT
.UNINDENT
.sp
Now that \fBSimpleHook\fP is included, let\(aqs see what happens
when we run the app and browse the application from our web browser.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
pecan serve config.py
serving on 0.0.0.0:8080 view at http://127.0.0.1:8080
about to enter the controller...
DO SOMETHING!
method: GET
response: 200 OK
.EE
.UNINDENT
.UNINDENT
.sp
Hooks can be inherited from parent class or mixins. Just make sure to
subclass from \fI\%HookController\fP\&.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
from pecan.hooks import PecanHook, HookController
class ParentHook(PecanHook):
priority = 1
def before(self, state):
print \(dq\enabout to enter the parent controller...\(dq
class CommonHook(PecanHook):
priority = 2
def before(self, state):
print \(dq\enjust a common hook...\(dq
class SubHook(PecanHook):
def before(self, state):
print \(dq\enabout to enter the subcontroller...\(dq
class SubMixin(object):
__hooks__ = [SubHook()]
# We\(aqll use the same instance for both controllers,
# to avoid double calls
common = CommonHook()
class SubController(HookController, SubMixin):
__hooks__ = [common]
@expose(\(aqjson\(aq)
def index(self):
print \(dq\enI AM THE SUB!\(dq
return dict()
class RootController(HookController):
__hooks__ = [common, ParentHook()]
@expose(\(aqjson\(aq)
def index(self):
print \(dq\enI AM THE ROOT!\(dq
return dict()
sub = SubController()
.EE
.UNINDENT
.UNINDENT
.sp
Let\(aqs see what happens when we run the app.
First loading the root controller:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
pecan serve config.py
serving on 0.0.0.0:8080 view at http://127.0.0.1:8080
GET / HTTP/1.1\(dq 200
about to enter the parent controller...
just a common hook
I AM THE ROOT!
.EE
.UNINDENT
.UNINDENT
.sp
Then loading the sub controller:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
pecan serve config.py
serving on 0.0.0.0:8080 view at http://127.0.0.1:8080
GET /sub HTTP/1.1\(dq 200
about to enter the parent controller...
just a common hook
about to enter the subcontroller...
I AM THE SUB!
.EE
.UNINDENT
.UNINDENT
.sp
\fBNOTE:\fP
.INDENT 0.0
.INDENT 3.5
Make sure to set proper priority values for nested hooks in order
to get them executed in the desired order.
.UNINDENT
.UNINDENT
.sp
\fBWARNING:\fP
.INDENT 0.0
.INDENT 3.5
Two hooks of the same type will be added/executed twice, if passed as
different instances to a parent and a child controller.
If passed as one instance variable \- will be invoked once for both controllers.
.UNINDENT
.UNINDENT
.SS Hooks That Come with Pecan
.sp
Pecan includes some hooks in its core. This section will describe
their different uses, how to configure them, and examples of common
scenarios.
.SS RequestViewerHook
.sp
This hook is useful for debugging purposes. It has access to every
attribute the \fBresponse\fP object has plus a few others that are specific to
the framework.
.sp
There are two main ways that this hook can provide information about a request:
.INDENT 0.0
.IP 1. 3
Terminal or logging output (via an file\-like stream like \fBstdout\fP)
.IP 2. 3
Custom header keys in the actual response.
.UNINDENT
.sp
By default, both outputs are enabled.
.sp
\fBSEE ALSO:\fP
.INDENT 0.0
.INDENT 3.5
.INDENT 0.0
.IP \(bu 2
\fI\%pecan.hooks \-\- Pecan Hooks\fP
.UNINDENT
.UNINDENT
.UNINDENT
.SS Configuring RequestViewerHook
.sp
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.
.sp
By default it will output information about these items:
.INDENT 0.0
.IP \(bu 2
path : Displays the url that was used to generate this response
.IP \(bu 2
status : The response from the server (e.g. \(aq200 OK\(aq)
.IP \(bu 2
method : The method for the request (e.g. \(aqGET\(aq, \(aqPOST\(aq, \(aqPUT or \(aqDELETE\(aq)
.IP \(bu 2
controller : The actual controller method in Pecan responsible for the response
.IP \(bu 2
params : A list of tuples for the params passed in at request time
.IP \(bu 2
hooks : Any hooks that are used in the app will be listed here.
.UNINDENT
.sp
The default configuration will show those values in the terminal via
\fBstdout\fP and it will also add them to the response headers (in the
form of \fBX\-Pecan\-item_name\fP).
.sp
This is how the terminal output might look for a \fI/favicon.ico\fP request:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
path \- /favicon.ico
status \- 404 Not Found
method \- GET
controller \- The resource could not be found.
params \- []
hooks \- [\(aqRequestViewerHook\(aq]
.EE
.UNINDENT
.UNINDENT
.sp
In the above case, the file was not found, and the information was printed to
\fIstdout\fP\&. Additionally, the following headers would be present in the HTTP
response:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
X\-Pecan\-path /favicon.ico
X\-Pecan\-status 404 Not Found
X\-Pecan\-method GET
X\-Pecan\-controller The resource could not be found.
X\-Pecan\-params []
X\-Pecan\-hooks [\(aqRequestViewerHook\(aq]
.EE
.UNINDENT
.UNINDENT
.sp
The configuration dictionary is flexible (none of the keys are required) and
can hold two keys: \fBitems\fP and \fBblacklist\fP\&.
.sp
This is how the hook would look if configured directly (shortened for brevity):
.INDENT 0.0
.INDENT 3.5
.sp
.EX
\&...
\(aqhooks\(aq: lambda: [
RequestViewerHook({\(aqitems\(aq:[\(aqpath\(aq]})
]
.EE
.UNINDENT
.UNINDENT
.SS Modifying Output Format
.sp
The \fBitems\fP 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.
.sp
\fBNOTE:\fP
.INDENT 0.0
.INDENT 3.5
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.
.UNINDENT
.UNINDENT
.sp
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.
.sp
These is a list containing all the possible attributes the hook has access to
(directly from \fIwebob\fP):
.TS
box center;
l|l.
T{
T} T{
T}
_
T{
accept
T} T{
make_tempfile
T}
_
T{
accept_charset
T} T{
max_forwards
T}
_
T{
accept_encoding
T} T{
method
T}
_
T{
accept_language
T} T{
params
T}
_
T{
application_url
T} T{
path
T}
_
T{
as_string
T} T{
path_info
T}
_
T{
authorization
T} T{
path_info_peek
T}
_
T{
blank
T} T{
path_info_pop
T}
_
T{
body
T} T{
path_qs
T}
_
T{
body_file
T} T{
path_url
T}
_
T{
body_file_raw
T} T{
postvars
T}
_
T{
body_file_seekable
T} T{
pragma
T}
_
T{
cache_control
T} T{
query_string
T}
_
T{
call_application
T} T{
queryvars
T}
_
T{
charset
T} T{
range
T}
_
T{
content_length
T} T{
referer
T}
_
T{
content_type
T} T{
referrer
T}
_
T{
cookies
T} T{
relative_url
T}
_
T{
copy
T} T{
remote_addr
T}
_
T{
copy_body
T} T{
remote_user
T}
_
T{
copy_get
T} T{
remove_conditional_headers
T}
_
T{
date
T} T{
request_body_tempfile_limit
T}
_
T{
decode_param_names
T} T{
scheme
T}
_
T{
environ
T} T{
script_name
T}
_
T{
from_file
T} T{
server_name
T}
_
T{
from_string
T} T{
server_port
T}
_
T{
get_response
T} T{
str_GET
T}
_
T{
headers
T} T{
str_POST
T}
_
T{
host
T} T{
str_cookies
T}
_
T{
host_url
T} T{
str_params
T}
_
T{
http_version
T} T{
str_postvars
T}
_
T{
if_match
T} T{
str_queryvars
T}
_
T{
if_modified_since
T} T{
unicode_errors
T}
_
T{
if_none_match
T} T{
upath_info
T}
_
T{
if_range
T} T{
url
T}
_
T{
if_unmodified_since
T} T{
urlargs
T}
_
T{
is_body_readable
T} T{
urlvars
T}
_
T{
is_body_seekable
T} T{
uscript_name
T}
_
T{
is_xhr
T} T{
user_agent
T}
_
T{
make_body_seekable
T} T{
T}
.TE
.sp
And these are the specific ones from Pecan and the hook:
.INDENT 0.0
.INDENT 3.5
.INDENT 0.0
.IP \(bu 2
controller
.IP \(bu 2
hooks
.IP \(bu 2
params (params is actually available from \fIwebob\fP but it is parsed
by the hook for redability)
.UNINDENT
.UNINDENT
.UNINDENT
.SS Blacklisting Certain Paths
.sp
Sometimes it\(aqs annoying to get information about \fIevery\fP single
request. To limit the output, pass the list of URL paths for which
you do not want data as the \fBblacklist\fP\&.
.sp
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:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
{ \(aqblacklist\(aq: [\(aq/f\(aq] }
.EE
.UNINDENT
.UNINDENT
.sp
It would not show \fIany\fP url that starts with \fBf\fP, effectively behaving like
a globbing regular expression (but not quite as powerful).
.sp
For any number of blocking you may need, just add as many items as wanted:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
{ \(aqblacklist\(aq : [\(aq/favicon.ico\(aq, \(aq/javascript\(aq, \(aq/images\(aq] }
.EE
.UNINDENT
.UNINDENT
.sp
Again, the \fBblacklist\fP key can be used along with the \fBitems\fP key
or not (it is not required).
.SH JSON SERIALIZATION
.sp
Pecan includes a simple, easy\-to\-use system for generating and serving
JSON. To get started, create a file in your project called
\fBjson.py\fP and import it in your project\(aqs \fBapp.py\fP\&.
.sp
Your \fBjson\fP module will contain a series of rules for generating
JSON from objects you return in your controller.
.sp
Let\(aqs say that we have a controller in our Pecan application which
we want to use to return JSON output for a \fBUser\fP object:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from myproject.lib import get_current_user
class UsersController(object):
@expose(\(aqjson\(aq)
def current_user(self):
\(aq\(aq\(aq
return an instance of myproject.model.User which represents
the current authenticated user
\(aq\(aq\(aq
return get_current_user()
.EE
.UNINDENT
.UNINDENT
.sp
In order for this controller to function, Pecan will need to know how to
convert the \fBUser\fP 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 \fBjson.py\fP:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan.jsonify import jsonify
from myproject import model
@jsonify.register(model.User)
def jsonify_user(user):
return dict(
name = user.name,
email = user.email,
birthday = user.birthday.isoformat()
)
.EE
.UNINDENT
.UNINDENT
.sp
In this example, when an instance of the \fBmodel.User\fP class is
returned from a controller which is configured to return JSON, the
\fBjsonify_user()\fP 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.
.sp
Alternatively, the rule can be specified on the object itself, by
specifying a \fB__json__()\fP method in the class:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
class User(object):
def __init__(self, name, email, birthday):
self.name = name
self.email = email
self.birthday = birthday
def __json__(self):
return dict(
name = self.name,
email = self.email,
birthday = self.birthday.isoformat()
)
.EE
.UNINDENT
.UNINDENT
.sp
The benefit of using a \fBjson.py\fP 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
model objects.
.SH CONTEXT/THREAD-LOCALS VS. EXPLICIT ARGUMENT PASSING
.sp
In any pecan application, the module\-level \fBpecan.request\fP and
\fBpecan.response\fP are proxy objects that always refer to the request and
response being handled in the current thread.
.sp
This \fIthread locality\fP ensures that you can safely access a global reference to
the current request and response in a multi\-threaded environment without
constantly having to pass object references around in your code; it\(aqs a feature
of pecan that makes writing traditional web applications easier and less
verbose.
.sp
Some people feel thread\-locals are too implicit or magical, and that explicit
reference passing is much clearer and more maintainable in the long run.
Additionally, the default implementation provided by pecan uses
\fBthreading.local()\fP to associate these context\-local proxy objects with the
\fIthread identifier\fP of the current server thread. In asynchronous server
models \- where lots of tasks run for short amounts of time on
a \fIsingle\fP shared thread \- supporting this mechanism involves monkeypatching
\fBthreading.local()\fP to behave in a greenlet\-local manner.
.SS Disabling Thread\-Local Proxies
.sp
If you\(aqre certain that you \fIdo not\fP want to utilize context/thread\-locals in
your project, you can do so by passing the argument
\fBuse_context_locals=False\fP in your application\(aqs configuration file:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
app = {
\(aqroot\(aq: \(aqproject.controllers.root.RootController\(aq,
\(aqmodules\(aq: [\(aqproject\(aq],
\(aqstatic_root\(aq: \(aq%(confdir)s/public\(aq,
\(aqtemplate_path\(aq: \(aq%(confdir)s/project/templates\(aq,
\(aqdebug\(aq: True,
\(aquse_context_locals\(aq: False
}
.EE
.UNINDENT
.UNINDENT
.sp
Additionally, you\(aqll need to update \fBall\fP of your pecan controllers to accept
positional arguments for the current request and response:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
class RootController(object):
@pecan.expose(\(aqjson\(aq)
def index(self, req, resp):
return dict(method=req.method) # path: /
@pecan.expose()
def greet(self, req, resp, name):
return name # path: /greet/joe
.EE
.UNINDENT
.UNINDENT
.sp
It is \fIimperative\fP that the request and response arguments come \fBafter\fP
\fBself\fP and before any positional form arguments.
.SH COMMAND LINE PECAN
.sp
Any Pecan application can be controlled and inspected from the command
line using the built\-in \fBpecan\fP command. The usage examples
of \fBpecan\fP in this document are intended to be invoked from
your project\(aqs root directory.
.SS Serving a Pecan App For Development
.sp
Pecan comes bundled with a lightweight WSGI development server based on
Python\(aqs \fI\%wsgiref.simple_server\fP module.
.sp
Serving your Pecan app is as simple as invoking the \fBpecan serve\fP command:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pecan serve config.py
Starting server in PID 000.
serving on 0.0.0.0:8080, view at http://127.0.0.1:8080
.EE
.UNINDENT
.UNINDENT
.sp
and then visiting it in your browser.
.sp
The server \fBhost\fP and \fBport\fP in your configuration file can be changed as
described in \fI\%Server Configuration\fP\&.
.SS Reloading Automatically as Files Change
.sp
Pausing to restart your development server as you work can be interruptive, so
\fBpecan serve\fP provides a \fB\-\-reload\fP flag to make life easier.
.sp
To provide this functionality, Pecan makes use of the Python
\fI\%watchdog\fP library. You\(aqll need to
install it for development use before continuing:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pip install watchdog
Downloading/unpacking watchdog
\&...
Successfully installed watchdog
.EE
.UNINDENT
.UNINDENT
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pecan serve \-\-reload config.py
Monitoring for changes...
Starting server in PID 000.
serving on 0.0.0.0:8080, view at http://127.0.0.1:8080
.EE
.UNINDENT
.UNINDENT
.sp
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.
.SS The Interactive Shell
.sp
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 \fBpecan shell\fP
command:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pecan shell config.py
Pecan Interactive Shell
Python 2.7.1 (r271:86832, Jul 31 2011, 19:30:53)
[GCC 4.2.1 (Based on Apple Inc. build 5658)
The following objects are available:
wsgiapp \- This project\(aqs WSGI App instance
conf \- The current configuration
app \- webtest.TestApp wrapped around wsgiapp
>>> conf
Config({
\(aqapp\(aq: Config({
\(aqroot\(aq: \(aqmyapp.controllers.root.RootController\(aq,
\(aqmodules\(aq: [\(aqmyapp\(aq],
\(aqstatic_root\(aq: \(aq/Users/somebody/myapp/public\(aq,
\(aqtemplate_path\(aq: \(aq/Users/somebody/myapp/project/templates\(aq,
\(aqerrors\(aq: {\(aq404\(aq: \(aq/error/404\(aq},
\(aqdebug\(aq: True
}),
\(aqserver\(aq: Config({
\(aqhost\(aq: \(aq0.0.0.0\(aq,
\(aqport\(aq: \(aq8080\(aq
})
})
>>> app
>>> app.get(\(aq/\(aq)
<200 OK text/html body=\(aq\en ...\en\en\(aq/936>
.EE
.UNINDENT
.UNINDENT
.sp
Press \fBCtrl\-D\fP to exit the interactive shell (or \fBCtrl\-Z\fP on Windows).
.SS Using an Alternative Shell
.sp
\fBpecan shell\fP has optional support for the \fI\%IPython\fP
and \fI\%bpython\fP alternative shells, each of
which can be specified with the \fB\-\-shell\fP flag (or its abbreviated alias,
\fB\-s\fP), e.g.,
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pecan shell \-\-shell=ipython config.py
$ pecan shell \-s bpython config.py
.EE
.UNINDENT
.UNINDENT
.SS Configuration from an environment variable
.sp
In all the examples shown, you will see that the \fBpecan\fP commands
accepted a file path to the configuration file. An alternative to this is to
specify the configuration file in an environment variable (\fBPECAN_CONFIG\fP).
.sp
This is completely optional; if a file path is passed in explicitly, Pecan will
honor that before looking for an environment variable.
.sp
For example, to serve a Pecan application, a variable could be exported and
subsequently be re\-used when no path is passed in.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ export PECAN_CONFIG=/path/to/app/config.py
$ pecan serve
Starting server in PID 000.
serving on 0.0.0.0:8080, view at http://127.0.0.1:8080
.EE
.UNINDENT
.UNINDENT
.sp
Note that the path needs to reference a valid pecan configuration file,
otherwise the command will error out with a message indicating that
the path is invalid (for example, if a directory is passed in).
.sp
If \fBPECAN_CONFIG\fP 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.
.SS Extending \fBpecan\fP with Custom Commands
.sp
While the commands packaged with Pecan are useful, the real utility of its
command line toolset lies in its extensibility. It\(aqs convenient to be able to
write a Python script that can work \(dqin a Pecan environment\(dq with access to
things like your application\(aqs parsed configuration file or a simulated
instance of your application itself (like the one provided in the \fBpecan
shell\fP command).
.SS Writing a Custom Pecan Command
.sp
As an example, let\(aqs create a command that can be used to issue a simulated
HTTP GET to your application and print the result. Its invocation from the
command line might look something like this:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pecan wget config.py /path/to/some/resource
.EE
.UNINDENT
.UNINDENT
.sp
Let\(aqs say you have a distribution with a package in it named \fBmyapp\fP, and
that within this package is a \fBwget.py\fP module:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
# myapp/myapp/wget.py
import pecan
from webtest import TestApp
class GetCommand(pecan.commands.BaseCommand):
\(aq\(aq\(aq
Issues a (simulated) HTTP GET and returns the request body.
\(aq\(aq\(aq
arguments = pecan.commands.BaseCommand.arguments + ({
\(aqname\(aq: \(aqpath\(aq,
\(aqhelp\(aq: \(aqthe URI path of the resource to request\(aq
},)
def run(self, args):
super(GetCommand, self).run(args)
app = TestApp(self.load_app())
print app.get(args.path).body
.EE
.UNINDENT
.UNINDENT
.sp
Let\(aqs analyze this piece\-by\-piece.
.SS Overriding the \fBrun\fP Method
.sp
First, we\(aqre subclassing \fI\%BaseCommand\fP and extending
the \fI\%run()\fP method to:
.INDENT 0.0
.IP \(bu 2
Load a Pecan application \- \fI\%load_app()\fP
.IP \(bu 2
Wrap it in a fake WGSI environment \- \fI\%TestApp\fP
.IP \(bu 2
Issue an HTTP GET request against it \- \fI\%get()\fP
.UNINDENT
.SS Defining Custom Arguments
.sp
The \fBarguments\fP class attribute is used to define command line arguments
specific to your custom command. You\(aqll notice in this example that we\(aqre
\fIadding\fP to the arguments list provided by \fI\%BaseCommand\fP
(which already provides an argument for the \fBconfig_file\fP), rather
than overriding it entirely.
.sp
The format of the \fBarguments\fP class attribute is a \fI\%tuple\fP of
dictionaries, with each dictionary representing an argument definition in the
same format accepted by Python\(aqs \fI\%argparse\fP module (more specifically,
\fI\%add_argument()\fP). By providing a list of
arguments in this format, the \fBpecan\fP command can include your custom
commands in the help and usage output it provides.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pecan \-h
usage: pecan [\-h] command ...
positional arguments:
command
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
.EE
.UNINDENT
.UNINDENT
.sp
Additionally, you\(aqll notice that the first line of the docstring from
\fBGetCommand\fP \-\- \fBIssues a (simulated) HTTP GET and returns the
request body\fP \-\- is automatically used to describe the \fBwget\fP
command in the output for \fB$ pecan \-h\fP\&. Following this convention
allows you to easily integrate a summary for your command into the
Pecan command line tool.
.SS Registering a Custom Command
.sp
Now that you\(aqve written your custom command, you’ll need to tell your
distribution’s \fBsetup.py\fP about its existence and reinstall. Within your
distribution’s \fBsetup.py\fP file, you\(aqll find a call to \fBsetup()\fP\&.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
# myapp/setup.py
\&...
setup(
name=\(aqmyapp\(aq,
version=\(aq0.1\(aq,
author=\(aqJoe Somebody\(aq,
...
)
.EE
.UNINDENT
.UNINDENT
.sp
Assuming it doesn\(aqt exist already, we\(aqll add the \fBentry_points\fP argument
to the \fBsetup()\fP call, and define a \fB[pecan.command]\fP definition for your custom
command:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
# myapp/setup.py
\&...
setup(
name=\(aqmyapp\(aq,
version=\(aq0.1\(aq,
author=\(aqJoe Somebody\(aq,
...
entry_points=\(dq\(dq\(dq
[pecan.command]
wget = myapp.wget:GetCommand
\(dq\(dq\(dq
)
.EE
.UNINDENT
.UNINDENT
.sp
Once you\(aqve done this, reinstall your project in development to register the
new entry point.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ python setup.py develop
.EE
.UNINDENT
.UNINDENT
.sp
Then give it a try.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pecan wget config.py /path/to/some/resource
.EE
.UNINDENT
.UNINDENT
.SH DEVELOPING PECAN APPLICATIONS LOCALLY
.SS Reloading Automatically as Files Change
.sp
Pausing to restart your development server as you work can be interruptive, so
\fBpecan serve\fP provides a \fB\-\-reload\fP flag to make life easier.
.sp
To provide this functionality, Pecan makes use of the Python
\fI\%watchdog\fP library. You\(aqll need to
install it for development use before continuing:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pip install watchdog
Downloading/unpacking watchdog
\&...
Successfully installed watchdog
.EE
.UNINDENT
.UNINDENT
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pecan serve \-\-reload config.py
Monitoring for changes...
Starting server in PID 000.
serving on 0.0.0.0:8080, view at http://127.0.0.1:8080
.EE
.UNINDENT
.UNINDENT
.sp
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.
.SS Debugging Pecan Applications
.sp
Pecan comes with simple debugging middleware for helping diagnose problems
in your applications. To enable the debugging middleware, simply set the
\fBdebug\fP flag to \fBTrue\fP in your configuration file:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
app = {
...
\(aqdebug\(aq: True,
...
}
.EE
.UNINDENT
.UNINDENT
.sp
Once enabled, the middleware will automatically catch exceptions raised by your
application and display the Python stack trace and WSGI environment in your
browser when runtime exceptions are raised.
.sp
To improve debugging, including support for an interactive browser\-based
console, Pecan makes use of the Python \fIbacklash
\fP library. You’ll need to install it
for development use before continuing:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pip install backlash
Downloading/unpacking backlash
\&...
Successfully installed backlash
.EE
.UNINDENT
.UNINDENT
.SS Serving Static Files
.sp
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:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
app = {
...
\(aqdebug\(aq: True,
\(aqstatic_root\(aq: \(aq%(confdir)/public
}
.EE
.UNINDENT
.UNINDENT
.sp
where \fBstatic_root\fP is an absolute pathname to the directory in which your
static files live. For convenience, the path may include the \fB%(confdir)\fP
variable, which Pecan will substitute with the absolute path of your
configuration file at runtime.
.sp
\fBNOTE:\fP
.INDENT 0.0
.INDENT 3.5
In production, \fBapp.debug\fP should \fInever\fP be set to \fBTrue\fP, so you\(aqll
need to serve your static files via your production web server.
.UNINDENT
.UNINDENT
.SH DEPLOYING PECAN IN PRODUCTION
.sp
There are a variety of ways to deploy a Pecan project to a production
environment. The following examples are meant to provide \fIdirection\fP,
not explicit instruction; deployment is usually heavily dependent upon
the needs and goals of individual applications, so your mileage will
probably vary.
.sp
\fBNOTE:\fP
.INDENT 0.0
.INDENT 3.5
While Pecan comes packaged with a simple server \fIfor development use\fP
(\fBpecan serve\fP), using a \fIproduction\-ready\fP server similar to the ones
described in this document is \fBvery highly encouraged\fP\&.
.UNINDENT
.UNINDENT
.SS Installing Pecan
.sp
A few popular options are available for installing Pecan in production
environments:
.INDENT 0.0
.IP \(bu 2
Using \fI\%setuptools\fP\&. Manage
Pecan as a dependency in your project\(aqs \fBsetup.py\fP file so that it\(aqs
installed alongside your project (e.g., \fBpython
/path/to/project/setup.py install\fP). The default Pecan project
described in \fI\%Creating Your First Pecan Application\fP facilitates this by including Pecan as
a dependency for your project.
.IP \(bu 2
Using \fI\%pip\fP\&.
Use \fBpip freeze\fP and \fBpip install\fP to create and install from
a \fBrequirements.txt\fP file for your project.
.IP \(bu 2
Via the manual instructions found in \fI\%Installation\fP\&.
.UNINDENT
.sp
\fBNOTE:\fP
.INDENT 0.0
.INDENT 3.5
Regardless of the route you choose, it\(aqs highly recommended that all
deployment installations be done in a Python \fI\%virtual environment\fP\&.
.UNINDENT
.UNINDENT
.SS Disabling Debug Mode
.sp
\fBWARNING:\fP
.INDENT 0.0
.INDENT 3.5
One of the most important steps to take before deploying a Pecan app
into production is to ensure that you have disabled \fBDebug Mode\fP, which
provides a development\-oriented debugging environment for tracebacks
encountered at runtime. Failure to disable this development tool in your
production environment \fIwill\fP result in serious security issues. In your
production configuration file, ensure that \fBdebug\fP is set to \fBFalse\fP\&.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
# myapp/production_config.py
app = {
...
\(aqdebug\(aq: False
}
.EE
.UNINDENT
.UNINDENT
.UNINDENT
.UNINDENT
.SS Pecan and WSGI
.sp
WSGI is a Python standard that describes a standard interface between servers
and an application. Any Pecan application is also known as a \(dqWSGI
application\(dq because it implements the WSGI interface, so any server that is
\(dqWSGI compatible\(dq may be used to serve your application. A few popular
examples are:
.INDENT 0.0
.IP \(bu 2
\fI\%mod_wsgi\fP
.IP \(bu 2
\fI\%uWSGI\fP
.IP \(bu 2
\fI\%Gunicorn\fP
.IP \(bu 2
\fI\%waitress\fP
.IP \(bu 2
\fI\%CherryPy\fP
.UNINDENT
.sp
Generally speaking, the WSGI entry point to any Pecan application can be
generated using \fI\%deploy()\fP:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan.deploy import deploy
application = deploy(\(aq/path/to/some/app/config.py\(aq)
.EE
.UNINDENT
.UNINDENT
.SS Considerations for Static Files
.sp
Pecan comes with static file serving (e.g., CSS, Javascript, images)
middleware which is \fBnot\fP recommended for use in production.
.sp
In production, Pecan doesn\(aqt serve media files itself; it leaves that job to
whichever web server you choose.
.sp
For serving static files in production, it\(aqs best to separate your concerns by
serving static files separately from your WSGI application (primarily for
performance reasons). There are several popular ways to accomplish this. Here
are two:
.INDENT 0.0
.IP 1. 3
Set up a proxy server (such as \fI\%nginx\fP, \fI\%cherokee\fP, \fI\%CherryPy\fP, or \fI\%lighttpd\fP) to serve static files and proxy application
requests through to your WSGI application:
.INDENT 3.0
.INDENT 3.5
.sp
.EX
─── , e.g., Apache, nginx, cherokee (0.0.0.0:80) ───
│
├── Instance e.g., mod_wsgi, Gunicorn, uWSGI (127.0.0.1:5000 or /tmp/some.sock)
├── Instance e.g., mod_wsgi, Gunicorn, uWSGI (127.0.0.1:5001 or /tmp/some.sock)
├── Instance e.g., mod_wsgi, Gunicorn, uWSGI (127.0.0.1:5002 or /tmp/some.sock)
└── Instance e.g., mod_wsgi, Gunicorn, uWSGI (127.0.0.1:5003 or /tmp/some.sock)
.EE
.UNINDENT
.UNINDENT
.IP 2. 3
Serve static files via a separate service, virtual host, or CDN.
.UNINDENT
.SS Common Recipes
.SS Apache + mod_wsgi
.sp
\fI\%mod_wsgi\fP is a popular Apache
module which can be used to host any WSGI\-compatible Python
application (including your Pecan application).
.sp
To get started, check out the \fI\%installation and configuration
documentation\fP for
mod_wsgi.
.sp
For the sake of example, let\(aqs say that our project, \fBsimpleapp\fP, lives at
\fB/var/www/simpleapp\fP, and that a \fI\%virtualenv\fP
has been created at \fB/var/www/venv\fP with any necessary dependencies installed
(including Pecan). Additionally, for security purposes, we\(aqve created a user,
\fBuser1\fP, and a group, \fBgroup1\fP to execute our application under.
.sp
The first step is to create a \fB\&.wsgi\fP file which mod_wsgi will use
as an entry point for your application:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
# /var/www/simpleapp/app.wsgi
from pecan.deploy import deploy
application = deploy(\(aq/var/www/simpleapp/config.py\(aq)
.EE
.UNINDENT
.UNINDENT
.sp
Next, add Apache configuration for your application. Here\(aqs a simple
example:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
ServerName example.com
WSGIDaemonProcess simpleapp user=user1 group=group1 threads=5 python\-path=/var/www/venv/lib/python2.7/site\-packages
WSGIScriptAlias / /var/www/simpleapp/app.wsgi
WSGIProcessGroup simpleapp
WSGIApplicationGroup %{GLOBAL}
Order deny,allow
Allow from all
.EE
.UNINDENT
.UNINDENT
.sp
For more instructions and examples of mounting WSGI applications using
mod_wsgi, consult the \fI\%mod_wsgi Documentation\fP\&.
.sp
Finally, restart Apache and give it a try.
.SS uWSGI
.sp
\fI\%uWSGI\fP is a fast, self\-healing and
developer/sysadmin\-friendly application container server coded in pure C. It
uses the \fI\%uwsgi\fP
protocol, but can speak other protocols as well (http, fastcgi...).
.sp
Running Pecan applications with uWSGI is a snap:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pip install uwsgi
$ pecan create simpleapp && cd simpleapp
$ python setup.py develop
$ uwsgi \-\-http\-socket :8080 \-\-venv /path/to/virtualenv \-\-pecan config.py
.EE
.UNINDENT
.UNINDENT
.sp
or using a Unix socket (that nginx, for example, could be configured to
\fI\%proxy to\fP):
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ uwsgi \-s /tmp/uwsgi.sock \-\-venv /path/to/virtualenv \-\-pecan config.py
.EE
.UNINDENT
.UNINDENT
.SS Gunicorn
.sp
\fI\%Gunicorn\fP, or \(dqGreen Unicorn\(dq, 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.
.sp
Running a Pecan application on Gunicorn is simple. Let\(aqs walk through it with
Pecan\(aqs default project:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pip install gunicorn
$ pecan create simpleapp && cd simpleapp
$ python setup.py develop
$ gunicorn_pecan config.py
.EE
.UNINDENT
.UNINDENT
.SS CherryPy
.sp
\fI\%CherryPy\fP offers a pure Python HTTP/1.1\-compliant WSGI
thread\-pooled web server. It can support Pecan applications easily and even
serve static files like a production server would do.
.sp
The examples that follow are geared towards using CherryPy as the server in
charge of handling a Pecan app along with serving static files.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pip install cherrypy
$ pecan create simpleapp && cd simpleapp
$ python setup.py develop
.EE
.UNINDENT
.UNINDENT
.sp
To run with CherryPy, the easiest approach is to create a script in the root of
the project (alongside \fBsetup.py\fP), so that we can describe how our example
application should be served. This is how the script (named \fBrun.py\fP) looks:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
import os
import cherrypy
from cheroot import wsgi
from cheroot.wsgi import PathInfoDispatcher
from pecan.deploy import deploy
simpleapp_wsgi_app = deploy(\(aq/path/to/production_config.py\(aq)
public_path = os.path.abspath(os.path.join(os.path.dirname(__file__), \(aqpublic\(aq))
# A dummy class for our Root object
# necessary for some CherryPy machinery
class Root(object):
pass
def make_static_config(static_dir_name):
\(dq\(dq\(dq
All custom static configurations are set here, since most are common, it
makes sense to generate them just once.
\(dq\(dq\(dq
static_path = os.path.join(\(aq/\(aq, static_dir_name)
path = os.path.join(public_path, static_dir_name)
configuration = {
static_path: {
\(aqtools.staticdir.on\(aq: True,
\(aqtools.staticdir.dir\(aq: path
}
}
return cherrypy.tree.mount(Root(), \(aq/\(aq, config=configuration)
# Assuming your app has media on different paths, like \(aqcss\(aq, and \(aqimages\(aq
application = PathInfoDispatcher({
\(aq/\(aq: simpleapp_wsgi_app,
\(aq/css\(aq: make_static_config(\(aqcss\(aq),
\(aq/images\(aq: make_static_config(\(aqimages\(aq)
})
server = wsgi.Server((\(aq0.0.0.0\(aq, 8080), application, server_name=\(aqsimpleapp\(aq)
try:
server.start()
except KeyboardInterrupt:
print(\(dqTerminating server...\(dq)
server.stop()
.EE
.UNINDENT
.UNINDENT
.sp
To start the server, simply call it with the Python executable:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ python run.py
.EE
.UNINDENT
.UNINDENT
.SH LOGGING
.sp
Pecan uses the Python standard library\(aqs \fI\%logging\fP module by passing
logging configuration options into the \fI\%logging.config.dictConfig\fP
function. The full documentation for the \fBdictConfig()\fP 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.
.SS Configuring Logging
.sp
Sample logging configuration is provided with the quickstart project
introduced in \fI\%Creating Your First Pecan Application\fP:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pecan create myapp
.EE
.UNINDENT
.UNINDENT
.sp
The default configuration defines one handler and two loggers.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
# myapp/config.py
app = { ... }
server = { ... }
logging = {
\(aqroot\(aq : {\(aqlevel\(aq: \(aqINFO\(aq, \(aqhandlers\(aq: [\(aqconsole\(aq]},
\(aqloggers\(aq: {
\(aqmyapp\(aq: {\(aqlevel\(aq: \(aqDEBUG\(aq, \(aqhandlers\(aq: [\(aqconsole\(aq]}
},
\(aqhandlers\(aq: {
\(aqconsole\(aq: {
\(aqlevel\(aq: \(aqDEBUG\(aq,
\(aqclass\(aq: \(aqlogging.StreamHandler\(aq,
\(aqformatter\(aq: \(aqsimple\(aq
}
},
\(aqformatters\(aq: {
\(aqsimple\(aq: {
\(aqformat\(aq: (\(aq%(asctime)s %(levelname)\-5.5s [%(name)s]\(aq
\(aq[%(threadName)s] %(message)s\(aq)
}
}
}
.EE
.UNINDENT
.UNINDENT
.INDENT 0.0
.IP \(bu 2
\fBconsole\fP logs messages to \fBstderr\fP using the \fBsimple\fP formatter.
.IP \(bu 2
\fBmyapp\fP logs messages sent at a level above or equal to \fBDEBUG\fP to
the \fBconsole\fP handler
.IP \(bu 2
\fBroot\fP logs messages at a level above or equal to the \fBINFO\fP level to
the \fBconsole\fP handler
.UNINDENT
.SS Writing Log Messages in Your Application
.sp
The logger named \fBmyapp\fP is reserved for your usage in your Pecan
application.
.sp
Once you have configured your logging, you can place logging calls in your
code. Using the logging framework is very simple.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
# myapp/myapp/controllers/root.py
from pecan import expose
import logging
logger = logging.getLogger(__name__)
class RootController(object):
@expose()
def index(self):
if bad_stuff():
logger.error(\(aqUh\-oh!\(aq)
return dict()
.EE
.UNINDENT
.UNINDENT
.SS Logging to Files and Other Locations
.sp
Python\(aqs \fI\%logging\fP library defines a variety of handlers that assist in
writing logs to file. A few interesting ones are:
.INDENT 0.0
.IP \(bu 2
\fI\%FileHandler\fP \- used to log messages to a file on the filesystem
.IP \(bu 2
\fI\%RotatingFileHandler\fP \- similar to
\fI\%FileHandler\fP, but also rotates logs
periodically
.IP \(bu 2
\fI\%SysLogHandler\fP \- used to log messages to a UNIX syslog
.IP \(bu 2
\fI\%SMTPHandler\fP \- used to log messages to an email
address via SMTP
.UNINDENT
.sp
Using any of them is as simple as defining a new handler in your
application\(aqs \fBlogging\fP block and assigning it to one of more loggers.
.SS Logging Requests with Paste Translogger
.sp
\fI\%Paste\fP (which is not included with Pecan) includes
the \fBTransLogger\fP middleware
for logging requests in \fI\%Apache Combined Log Format\fP\&. Combined with
file\-based logging, TransLogger can be used to create an \fBaccess.log\fP file
similar to \fBApache\fP\&.
.sp
To add this middleware, modify your the \fBsetup_app\fP method in your
project\(aqs \fBapp.py\fP as follows:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
# myapp/myapp/app.py
from pecan import make_app
from paste.translogger import TransLogger
def setup_app(config):
# ...
app = make_app(
config.app.root
# ...
)
app = TransLogger(app, setup_console_handler=False)
return app
.EE
.UNINDENT
.UNINDENT
.sp
By default, \fBTransLogger\fP creates a logger named
\fBwsgi\fP, so you\(aqll need to specify a new (file\-based) handler for this logger
in our Pecan configuration file:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
# myapp/config.py
app = { ... }
server = { ... }
logging = {
\(aqloggers\(aq: {
# ...
\(aqwsgi\(aq: {\(aqlevel\(aq: \(aqINFO\(aq, \(aqhandlers\(aq: [\(aqlogfile\(aq], \(aqqualname\(aq: \(aqwsgi\(aq}
},
\(aqhandlers\(aq: {
# ...
\(aqlogfile\(aq: {
\(aqclass\(aq: \(aqlogging.FileHandler\(aq,
\(aqfilename\(aq: \(aq/etc/access.log\(aq,
\(aqlevel\(aq: \(aqINFO\(aq,
\(aqformatter\(aq: \(aqmessageonly\(aq
}
},
\(aqformatters\(aq: {
# ...
\(aqmessageonly\(aq: {\(aqformat\(aq: \(aq%(message)s\(aq}
}
}
.EE
.UNINDENT
.UNINDENT
.SH TESTING PECAN APPLICATIONS
.sp
Tests can live anywhere in your Pecan project as long as the test runner can
discover them. Traditionally, they exist in a package named \fBmyapp.tests\fP\&.
.sp
The suggested mechanism for unit and integration testing of a Pecan application
is the \fI\%unittest\fP module.
.SS Test Discovery and Other Tools
.sp
Tests for a Pecan project can be invoked as simply as \fBpython setup.py test\fP,
though it\(aqs possible to run your tests with different discovery and automation
tools. In particular, Pecan projects are known to work well with
\fI\%nose\fP, \fI\%pytest\fP,
and \fI\%tox\fP\&.
.SS Writing Functional Tests with WebTest
.sp
A \fBunit test\fP typically relies on \(dqmock\(dq or \(dqfake\(dq objects to give the code
under test enough context to run. In this way, only an individual unit of
source code is tested.
.sp
A healthy suite of tests combines \fBunit tests\fP with \fBfunctional tests\fP\&. In
the context of a Pecan application, functional tests can be written with the
help of the \fI\%webtest\fP 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\(aqs quickstart project.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
# myapp/myapp/tests/__init__.py
import os
from unittest import TestCase
from pecan import set_config
from pecan.testing import load_test_app
class FunctionalTest(TestCase):
\(dq\(dq\(dq
Used for functional tests where you need to test your
literal application and its integration with the framework.
\(dq\(dq\(dq
def setUp(self):
self.app = load_test_app(os.path.join(
os.path.dirname(__file__),
\(aqconfig.py\(aq
))
def tearDown(self):
set_config({}, overwrite=True)
.EE
.UNINDENT
.UNINDENT
.sp
The testing utility included with Pecan, \fI\%pecan.testing.load_test_app()\fP, can
be passed a file path representing a Pecan configuration file, and will return
an instance of the application, wrapped in a \fI\%TestApp\fP
environment.
.sp
From here, it\(aqs possible to extend the \fBFunctionalTest\fP base class and write
tests that issue simulated HTTP requests.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
class TestIndex(FunctionalTest):
def test_index(self):
resp = self.app.get(\(aq/\(aq)
assert resp.status_int == 200
assert \(aqHello, World\(aq in resp.body
.EE
.UNINDENT
.UNINDENT
.sp
\fBSEE ALSO:\fP
.INDENT 0.0
.INDENT 3.5
See the \fI\%webtest\fP documentation
for further information about the methods available to a
\fI\%TestApp\fP instance.
.UNINDENT
.UNINDENT
.SS Special Testing Variables
.sp
Sometimes it\(aqs not enough to make assertions about the response body of certain
requests. To aid in inspection, Pecan applications provide a special set of
\(dqtesting variables\(dq to any \fI\%TestResponse\fP object.
.sp
Let\(aqs suppose that your Pecan applicaton had some controller which took a
\fBname\fP as an optional argument in the URL.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
# myapp/myapp/controllers/root.py
from pecan import expose
class RootController(object):
@expose(\(aqindex.html\(aq)
def index(self, name=\(aqJoe\(aq):
\(dq\(dq\(dqA request to / will access this controller\(dq\(dq\(dq
return dict(name=name)
.EE
.UNINDENT
.UNINDENT
.sp
and rendered that name in it\(aqs template (and thus, the response body).
.INDENT 0.0
.INDENT 3.5
.sp
.EX
# myapp/myapp/templates/index.html
Hello, ${name}!
.EE
.UNINDENT
.UNINDENT
.sp
A functional test for this controller might look something like
.INDENT 0.0
.INDENT 3.5
.sp
.EX
class TestIndex(FunctionalTest):
def test_index(self):
resp = self.app.get(\(aq/\(aq)
assert resp.status_int == 200
assert \(aqHello, Joe!\(aq in resp.body
.EE
.UNINDENT
.UNINDENT
.sp
In addition to \fBwebtest.TestResponse.body\fP, Pecan also provides
\fBwebtest.TestResponse.namespace\fP, which represents the template namespace
returned from the controller, and \fBwebtest.TestResponse.template_name\fP, which
contains the name of the template used.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
class TestIndex(FunctionalTest):
def test_index(self):
resp = self.app.get(\(aq/\(aq)
assert resp.status_int == 200
assert resp.namespace == {\(aqname\(aq: \(aqJoe\(aq}
assert resp.template_name == \(aqindex.html\(aq
.EE
.UNINDENT
.UNINDENT
.sp
In this way, it\(aqs possible to test the return value and rendered template of
individual controllers.
.SH GENERATING AND VALIDATING FORMS
.sp
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.
.sp
This article details best practices for integrating the popular forms library,
\fI\%WTForms\fP, into your Pecan project.
.SS Defining a Form Definition
.sp
Let\(aqs start by building a basic form with a required \fBfirst_name\fP
field and an optional \fBlast_name\fP field.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from wtforms import Form, TextField, validators
class MyForm(Form):
first_name = TextField(u\(aqFirst Name\(aq, validators=[validators.required()])
last_name = TextField(u\(aqLast Name\(aq, validators=[validators.optional()])
class SomeController(object):
pass
.EE
.UNINDENT
.UNINDENT
.SS Rendering a Form in a Template
.sp
Next, let\(aqs add a controller, and pass a form instance to the template.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
from wtforms import Form, TextField, validators
class MyForm(Form):
first_name = TextField(u\(aqFirst Name\(aq, validators=[validators.required()])
last_name = TextField(u\(aqLast Name\(aq, validators=[validators.optional()])
class SomeController(object):
@expose(template=\(aqindex.html\(aq)
def index(self):
return dict(form=MyForm())
.EE
.UNINDENT
.UNINDENT
.sp
Here\(aqs the \fI\%Mako\fP template file:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
.EE
.UNINDENT
.UNINDENT
.SS Validating POST Values
.sp
Using the same \fBMyForm\fP definition, let\(aqs redirect the user if the form is
validated, otherwise, render the form again.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose, request
from wtforms import Form, TextField, validators
class MyForm(Form):
first_name = TextField(u\(aqFirst Name\(aq, validators=[validators.required()])
last_name = TextField(u\(aqLast Name\(aq, validators=[validators.optional()])
class SomeController(object):
@expose(template=\(aqindex.html\(aq)
def index(self):
my_form = MyForm(request.POST)
if request.method == \(aqPOST\(aq and my_form.validate():
# save_values()
redirect(\(aq/success\(aq)
else:
return dict(form=my_form)
.EE
.UNINDENT
.UNINDENT
.SH WORKING WITH SESSIONS AND USER AUTHENTICATION
.sp
Pecan provides no opinionated support for managing user sessions,
but it\(aqs easy to hook into your session framework of choice with minimal
effort.
.sp
This article details best practices for integrating the popular session
framework, \fI\%Beaker\fP, into your Pecan project.
.SS Setting up Session Management
.sp
There are several approaches that can be taken to set up session management.
One approach is WSGI middleware. Another is Pecan \fI\%Pecan Hooks\fP\&.
.sp
Here\(aqs an example of wrapping your WSGI application with Beaker\(aqs
\fI\%SessionMiddleware\fP in your project\(aqs \fBapp.py\fP\&.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import conf, make_app
from beaker.middleware import SessionMiddleware
from test_project import model
app = make_app(
...
)
app = SessionMiddleware(app, conf.beaker)
.EE
.UNINDENT
.UNINDENT
.sp
And a corresponding dictionary in your configuration file.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
beaker = {
\(aqsession.key\(aq : \(aqsessionkey\(aq,
\(aqsession.type\(aq : \(aqcookie\(aq,
\(aqsession.validate_key\(aq : \(aq05d2175d1090e31f42fa36e63b8d2aad\(aq,
\(aq__force_dict__\(aq : True
}
.EE
.UNINDENT
.UNINDENT
.SH WORKING WITH DATABASES, TRANSACTIONS, AND ORM'S
.sp
Pecan provides no opinionated support for working with databases, but
it\(aqs easy to hook into your ORM of choice. This article details best
practices for integrating the popular Python ORM, \fI\%SQLAlchemy\fP, into
your Pecan project.
.SS \fBinit_model\fP and Preparing Your Model
.sp
Pecan\(aqs default quickstart project includes an empty stub directory
for implementing your model as you see fit.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
\&.
└── test_project
├── app.py
├── __init__.py
├── controllers
├── model
│\ \ ├── __init__.py
└── templates
.EE
.UNINDENT
.UNINDENT
.sp
By default, this module contains a special method, \fBinit_model()\fP\&.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import conf
def init_model():
\(dq\(dq\(dq
This is a stub method which is called at application startup time.
If you need to bind to a parsed database configuration, set up tables
or ORM classes, or perform any database initialization, this is the
recommended place to do it.
For more information working with databases, and some common recipes,
see https://pecan.readthedocs.io/en/latest/databases.html
\(dq\(dq\(dq
pass
.EE
.UNINDENT
.UNINDENT
.sp
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.
.sp
Additionally, your project\(aqs \fBmodel\fP 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\(aqs what a sample Pecan
configuration file with database bindings might look like.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
# Server Specific Configurations
server = {
...
}
# Pecan Application Configurations
app = {
...
}
# Bindings and options to pass to SQLAlchemy\(aqs \(ga\(gacreate_engine\(ga\(ga
sqlalchemy = {
\(aqurl\(aq : \(aqmysql://root:@localhost/dbname?charset=utf8&use_unicode=0\(aq,
\(aqecho\(aq : False,
\(aqecho_pool\(aq : False,
\(aqpool_recycle\(aq : 3600,
\(aqencoding\(aq : \(aqutf\-8\(aq
}
.EE
.UNINDENT
.UNINDENT
.sp
And a basic model implementation that can be used to configure and
bind using SQLAlchemy.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import conf
from sqlalchemy import create_engine, MetaData
from sqlalchemy.orm import scoped_session, sessionmaker
Session = scoped_session(sessionmaker())
metadata = MetaData()
def _engine_from_config(configuration):
configuration = dict(configuration)
url = configuration.pop(\(aqurl\(aq)
return create_engine(url, **configuration)
def init_model():
conf.sqlalchemy.engine = _engine_from_config(conf.sqlalchemy)
def start():
Session.bind = conf.sqlalchemy.engine
metadata.bind = Session.bind
def commit():
Session.commit()
def rollback():
Session.rollback()
def clear():
Session.remove()
.EE
.UNINDENT
.UNINDENT
.SS Binding Within the Application
.sp
There are several approaches to wrapping your application\(aqs requests
with calls to appropriate model function calls. One approach is WSGI
middleware. We also recommend Pecan \fI\%Pecan Hooks\fP\&. Pecan comes with
\fI\%TransactionHook\fP, a hook which can be used to wrap
requests in database transactions for you. To use it, simply include it in
your project\(aqs \fBapp.py\fP file and pass it a set of functions related to
database binding.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import conf, make_app
from pecan.hooks import TransactionHook
from test_project import model
app = make_app(
conf.app.root,
static_root = conf.app.static_root,
template_path = conf.app.template_path,
debug = conf.app.debug,
hooks = [
TransactionHook(
model.start,
model.start_read_only,
model.commit,
model.rollback,
model.clear
)
]
)
.EE
.UNINDENT
.UNINDENT
.sp
In the above example, on HTTP \fBPOST\fP, \fBPUT\fP, and \fBDELETE\fP
requests, \fI\%TransactionHook\fP takes care of the transaction
automatically by following these rules:
.INDENT 0.0
.IP 1. 3
Before controller routing has been determined, \fBmodel.start()\fP
is called. This function should bind to the appropriate
SQLAlchemy engine and start a transaction.
.IP 2. 3
Controller code is run and returns.
.IP 3. 3
If your controller or template rendering fails and raises an
exception, \fBmodel.rollback()\fP 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.
.IP 4. 3
If the controller returns successfully, \fBmodel.commit()\fP and
\fBmodel.clear()\fP are called.
.UNINDENT
.sp
On idempotent operations (like HTTP \fBGET\fP and \fBHEAD\fP requests),
\fI\%TransactionHook\fP handles transactions following different
rules.
.INDENT 0.0
.IP 1. 3
\fBmodel.start_read_only()\fP is called. This function should bind
to your SQLAlchemy engine.
.IP 2. 3
Controller code is run and returns.
.IP 3. 3
If the controller returns successfully, \fBmodel.clear()\fP is
called.
.UNINDENT
.sp
Also note that there is a useful \fI\%after_commit()\fP
decorator provided in \fI\%pecan.decorators \-\- Pecan Decorators\fP\&.
.SS Splitting Reads and Writes
.sp
Employing the strategy above with \fI\%TransactionHook\fP 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\(aqs also possible to extend
\fI\%TransactionHook\fP or write your own hook implementation for
more refined control over where and when database bindings are called.
.sp
Assuming a master/standby setup, where the master accepts write requests and
the standby can only get read requests, a Pecan configuration for sqlalchemy
could be:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
# Server Specific Configurations
server = {
...
}
# Pecan Application Configurations
app = {
...
}
# Master database
sqlalchemy_w = {
\(aqurl\(aq: \(aqpostgresql+psycopg2://root:@master_host/dbname\(aq,
\(aqpool_recycle\(aq: 3600,
\(aqencoding\(aq: \(aqutf\-8\(aq
}
# Read Only database
sqlalchemy_ro = {
\(aqurl\(aq: \(aqpostgresql+psycopg2://root:@standby_host/dbname\(aq,
\(aqpool_recycle\(aq: 3600,
\(aqencoding\(aq: \(aqutf\-8\(aq
}
.EE
.UNINDENT
.UNINDENT
.sp
Given the unique configuration settings for each database, the bindings would
need to change from what Pecan\(aqs default quickstart provides (see
\fI\%init_model and Preparing Your Model\fP section) to accommodate for both write and read only
requests:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import conf
from sqlalchemy import create_engine, MetaData
from sqlalchemy.orm import scoped_session, sessionmaker
Session = scoped_session(sessionmaker())
metadata = MetaData()
def init_model():
conf.sqlalchemy_w.engine = _engine_from_config(conf.sqlalchemy_w)
conf.sqlalchemy_ro.engine = _engine_from_config(conf.sqlalchemy_ro)
def _engine_from_config(configuration):
configuration = dict(configuration)
url = configuration.pop(\(aqurl\(aq)
return create_engine(url, **configuration)
def start():
Session.bind = conf.sqlalchemy_w.engine
metadata.bind = conf.sqlalchemy_w.engine
def start_read_only():
Session.bind = conf.sqlalchemy_ro.engine
metadata.bind = conf.sqlalchemy_ro.engine
def commit():
Session.commit()
def rollback():
Session.rollback()
def clear():
Session.close()
def flush():
Session.flush()
.EE
.UNINDENT
.UNINDENT
.SH CUSTOM ERROR DOCUMENTS
.sp
In this article we will configure a Pecan application to display a custom
error page whenever the server returns a \fB404 Page Not Found\fP status.
.sp
This article assumes that you have already created a test application as
described in \fI\%Creating Your First Pecan Application\fP\&.
.sp
\fBNOTE:\fP
.INDENT 0.0
.INDENT 3.5
While this example focuses on the \fBHTTP 404\fP message, the same
technique may be applied to define custom actions for any of the \fBHTTP\fP
status response codes in the 400 and 500 range. You are well advised to use
this power judiciously.
.UNINDENT
.UNINDENT
.SS Overview
.sp
Pecan makes it simple to customize error documents in two simple steps:
.INDENT 0.0
.INDENT 3.5
.INDENT 0.0
.IP \(bu 2
\fI\%Configure Routing\fP of the HTTP status messages you want to handle
in your application\(aqs \fBconfig.py\fP
.IP \(bu 2
\fI\%Write Custom Controllers\fP to handle the status messages you have configured
.UNINDENT
.UNINDENT
.UNINDENT
.SS Configure Routing
.sp
Let\(aqs configure our application \fBtest_project\fP to route \fBHTTP 404 Page
Not Found\fP messages to a custom controller.
.sp
First, let\(aqs update \fBtest_project/config.py\fP to specify a new
error\-handler.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
# Pecan Application Configurations
app = {
\(aqroot\(aq : \(aqtest_project.controllers.root.RootController\(aq,
\(aqmodules\(aq : [\(aqtest_project\(aq],
\(aqstatic_root\(aq : \(aq%(confdir)s/public\(aq,
\(aqtemplate_path\(aq : \(aq%(confdir)s/test_project/templates\(aq,
\(aqreload\(aq : True,
\(aqdebug\(aq : True,
# modify the \(aqerrors\(aq key to direct HTTP status codes to a custom
# controller
\(aqerrors\(aq : {
#404 : \(aq/error/404\(aq,
404 : \(aq/notfound\(aq,
\(aq__force_dict__\(aq : True
}
}
.EE
.UNINDENT
.UNINDENT
.sp
Instead of the default error page, Pecan will now route 404 messages
to the controller method \fBnotfound\fP\&.
.SS Write Custom Controllers
.sp
The easiest way to implement the error handler is to
add it to \fBtest_project.root.RootController\fP class
(typically in \fBtest_project/controllers/root.py\fP).
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
from webob.exc import status_map
class RootController(object):
@expose(generic=True, template=\(aqindex.html\(aq)
def index(self):
return dict()
@index.when(method=\(aqPOST\(aq)
def index_post(self, q):
redirect(\(aqhttps://pecan.readthedocs.io/en/latest/search.html?q=%s\(aq % q)
## custom handling of \(aq404 Page Not Found\(aq messages
@expose(\(aqerror.html\(aq)
def notfound(self):
return dict(status=404, message=\(dqtest_project does not have this page\(dq)
@expose(\(aqerror.html\(aq)
def error(self, status):
try:
status = int(status)
except ValueError:
status = 0
message = getattr(status_map.get(status), \(aqexplanation\(aq, \(aq\(aq)
return dict(status=status, message=message)
.EE
.UNINDENT
.UNINDENT
.sp
And that\(aqs it!
.sp
Notice that the only bit of code we added to our \fBRootController\fP was:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
## custom handling of \(aq404 Page Not Found\(aq messages
@expose(\(aqerror.html\(aq)
def notfound(self):
return dict(status=404, message=\(dqtest_project does not have this page\(dq)
.EE
.UNINDENT
.UNINDENT
.sp
We simply \fI\%expose()\fP the \fBnotfound\fP controller with the
\fBerror.html\fP template (which was conveniently generated for us and placed
under \fBtest_project/templates/\fP when we created \fBtest_project\fP). As with
any Pecan controller, we return a dictionary of variables for interpolation by
the template renderer.
.sp
Now we can modify the error template, or write a brand new one to make the 404
error status page of \fBtest_project\fP as pretty or fancy as we want.
.SH EXAMPLE APPLICATION: SIMPLE FORMS PROCESSING
.sp
This guide will walk you through building a simple Pecan web application that will do some simple forms processing.
.SS Project Setup
.sp
First, you\(aqll need to install Pecan:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pip install pecan
.EE
.UNINDENT
.UNINDENT
.sp
Use Pecan\(aqs basic template support to start a new project:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pecan create mywebsite
$ cd mywebsite
.EE
.UNINDENT
.UNINDENT
.sp
Install the new project in development mode:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ python setup.py develop
.EE
.UNINDENT
.UNINDENT
.sp
With the project ready, go into the \fBtemplates\fP folder and edit the \fBindex.html\fP file. Modify it so that it resembles this:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
<%inherit file=\(dqlayout.html\(dq />
<%def name=\(dqtitle()\(dq>
Welcome to Pecan!
%def>
% if not form_post_data is UNDEFINED:
${form_post_data[\(aqfirst_name\(aq]}, your message is: ${form_post_data[\(aqmessage\(aq]}
% endif
.EE
.UNINDENT
.UNINDENT
.sp
\fBWhat did we just do?\fP
.INDENT 0.0
.IP 1. 3
Modified the contents of the \fBform\fP tag to have two \fBinput\fP tags. The first is named \fBmessage\fP and the second is named \fBfirst_name\fP
.IP 2. 3
Added a check if \fBform_post_data\fP has not been defined so we don\(aqt show the message or wording
.IP 3. 3
Added code to display the message from the user\(aqs \fBPOST\fP action
.UNINDENT
.sp
Go into the \fBcontrollers\fP folder now and edit the \fBroot.py\fP file. There will be two functions inside of the \fBRootController\fP class which will display the \fBindex.html\fP file when your web browser hits the \fB\(aq/\(aq\fP endpoint. If the user puts some data into the textbox and hits the submit button then they will see the personalized message displayed back at them.
.sp
Modify the \fBroot.py\fP to look like this:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
class RootController(object):
@expose(generic=True, template=\(aqindex.html\(aq)
def index(self):
return dict()
@index.when(method=\(aqPOST\(aq, template=\(aqindex.html\(aq)
def index_post(self, **kwargs):
return dict(form_post_data=kwargs)
.EE
.UNINDENT
.UNINDENT
.sp
\fBWhat did we just do?\fP
.INDENT 0.0
.IP 1. 3
Modified the \fBindex\fP function to render the initial \fBindex.html\fP webpage
.IP 2. 3
Modified the \fBindex_post\fP function to return the posted data via keyword arguments
.UNINDENT
.sp
Run the application:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pecan serve config.py
.EE
.UNINDENT
.UNINDENT
.sp
Open a web browser: \fI\%http://127.0.0.1:8080/\fP
.SS Adding Validation
.sp
Enter a message into the textbox along with a name in the second textbox and press the submit button. You should see a personalized message displayed below the form once the page posts back.
.sp
One problem you might have noticed is if you don\(aqt enter a message or a first name then you simply see no value entered for that part of the message. Let\(aqs add a little validation to make sure a message and a first name was actually entered. For this, we will use \fI\%WTForms\fP but you can substitute anything else for your projects.
.sp
Add support for the \fI\%WTForms\fP library:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pip install wtforms
.EE
.UNINDENT
.UNINDENT
.sp
\fBNOTE:\fP
.INDENT 0.0
.INDENT 3.5
Keep in mind that Pecan is not opinionated when it comes to a particular library when working with form generation, validation, etc. Choose which libraries you prefer and integrate those with Pecan. This is one way of doing this, there are many more ways so feel free to handle this however you want in your own projects.
.UNINDENT
.UNINDENT
.sp
Go back to the \fBroot.py\fP files and modify it like this:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose, request
from wtforms import Form, TextField, validators
class PersonalizedMessageForm(Form):
message = TextField(u\(aqEnter a message\(aq,
validators=[validators.required()])
first_name = TextField(u\(aqEnter your first name\(aq,
validators=[validators.required()])
class RootController(object):
@expose(generic=True, template=\(aqindex.html\(aq)
def index(self):
return dict(form=PersonalizedMessageForm())
@index.when(method=\(aqPOST\(aq, template=\(aqindex.html\(aq)
def index_post(self):
form = PersonalizedMessageForm(request.POST)
if form.validate():
return dict(message=form.message.data,
first_name=form.first_name.data)
else:
return dict(form=form)
.EE
.UNINDENT
.UNINDENT
.sp
\fBWhat did we just do?\fP
.INDENT 0.0
.IP 1. 3
Added the \fBPersonalizedMessageForm\fP with two textfields and a required field validator for each
.IP 2. 3
Modified the \fBindex\fP function to create a new instance of the \fBPersonalizedMessageForm\fP class and return it
.IP 3. 3
In the \fBindex_post\fP function modify it to gather the posted data and validate it. If its valid, then set the returned data to be displayed on the webpage. If not valid, send the form which will contain the data plus the error message(s)
.UNINDENT
.sp
Modify the \fBindex.html\fP like this:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
<%inherit file=\(dqlayout.html\(dq />
## provide definitions for blocks we want to redefine
<%def name=\(dqtitle()\(dq>
Welcome to Pecan!
%def>
% if not form:
${first_name}, your message is: ${message}
% else:
% endif
.EE
.UNINDENT
.UNINDENT
.sp
\fBNOTE:\fP
.INDENT 0.0
.INDENT 3.5
Keep in mind when using the \fI\%WTForms\fP library you can customize the error messages and more. Also, you have multiple validation rules so make sure to catch all the errors which will mean you need a loop rather than the simple example above which grabs the first error item in the list. See the \fI\%documentation\fP for more information.
.UNINDENT
.UNINDENT
.sp
Run the application:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pecan serve config.py
.EE
.UNINDENT
.UNINDENT
.sp
Open a web browser: \fI\%http://127.0.0.1:8080/\fP
.sp
Try the form with valid data and with no data entered.
.SH EXAMPLE APPLICATION: SIMPLE AJAX
.sp
This guide will walk you through building a simple Pecan web application that uses AJAX to fetch JSON data from a server.
.SS Project Setup
.sp
First, you\(aqll need to install Pecan:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pip install pecan
.EE
.UNINDENT
.UNINDENT
.sp
Use Pecan\(aqs basic template support to start a new project:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pecan create myajax
$ cd myajax
.EE
.UNINDENT
.UNINDENT
.sp
Install the new project in development mode:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ python setup.py develop
.EE
.UNINDENT
.UNINDENT
.SS Adding JavaScript AJAX Support
.sp
For this project we will need to add \fI\%jQuery\fP support. To add jQuery go into the \fBtemplates\fP folder and edit the \fBlayout.html\fP file.
.sp
Adding jQuery support is easy, we actually only need one line of code:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
.EE
.UNINDENT
.UNINDENT
.sp
The JavaScript to make the AJAX call is a little more in depth but shouldn\(aqt be unfamiliar if you\(aqve ever worked with jQuery before.
.sp
The \fBlayout.html\fP file will look like this:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
${self.title()}
${self.style()}
${self.javascript()}
${self.body()}
<%def name=\(dqtitle()\(dq>
Default Title
%def>
<%def name=\(dqstyle()\(dq>
%def>
<%def name=\(dqjavascript()\(dq>
%def>
.EE
.UNINDENT
.UNINDENT
.sp
\fBWhat did we just do?\fP
.INDENT 0.0
.IP 1. 3
In the \fBhead\fP section we added jQuery support via the \fI\%Google CDN\fP
.IP 2. 3
Added JavaScript to make an AJAX call to the server via an HTTP \fBGET\fP passing in the \fBid\fP of the project to fetch more information on
.IP 3. 3
Once the \fBonSuccess\fP event is triggered by the returning data we take that and display it on the web page below the controls
.UNINDENT
.SS Adding Additional HTML
.sp
Let\(aqs edit the \fBindex.html\fP file next. We will add HTML to support the AJAX interaction between the web page and Pecan. Modify \fBindex.html\fP to look like this:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
<%inherit file=\(dqlayout.html\(dq />
<%def name=\(dqtitle()\(dq>
Welcome to Pecan!
%def>
Select a project to get details:
.EE
.UNINDENT
.UNINDENT
.sp
\fBWhat did we just do?\fP
.INDENT 0.0
.IP 1. 3
Added a dropdown control and submit button for the user to interact with. Users can pick an open source project and get more details on it
.UNINDENT
.SS Building the Model with JSON Support
.sp
The HTML and JavaScript work is now taken care of. At this point we can add a model to our project inside of the \fBmodel\fP folder. Create a file in there called \fBprojects.py\fP and add the following to it:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
class Project(object):
def __init__(self, name, licensing, repository, documentation):
self.name = name
self.licensing = licensing
self.repository = repository
self.documentation = documentation
def __json__(self):
return dict(
name=self.name,
licensing=self.licensing,
repository=self.repository,
documentation=self.documentation
)
.EE
.UNINDENT
.UNINDENT
.sp
\fBWhat did we just do?\fP
.INDENT 0.0
.IP 1. 3
Created a model called \fBProject\fP that can hold project specific data
.IP 2. 3
Added a \fB__json__\fP method so an instance of the \fBProject class\fP can be easily represented as JSON. The controller we will soon build will make use of that JSON capability
.UNINDENT
.sp
\fBNOTE:\fP
.INDENT 0.0
.INDENT 3.5
There are other ways to return JSON with Pecan, check out \fI\%JSON Serialization\fP for more information.
.UNINDENT
.UNINDENT
.SS Working with the Controllers
.sp
We don\(aqt need to do anything major to the \fBroot.py\fP file in the \fBcontrollers\fP folder except to add support for a new controller we will call \fBProjectsController\fP\&. Modify the \fBroot.py\fP like this:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose
from myajax.controllers.projects import ProjectsController
class RootController(object):
projects = ProjectsController()
@expose(generic=True, template=\(aqindex.html\(aq)
def index(self):
return dict()
.EE
.UNINDENT
.UNINDENT
.sp
\fBWhat did we just do?\fP
.INDENT 0.0
.IP 1. 3
Removed some of the initial boilerplate code since we won\(aqt be using it
.IP 2. 3
Add support for the upcoming \fBProjectsController\fP
.UNINDENT
.sp
The final piece is to add a file called \fBprojects.py\fP to the \fBcontrollers\fP folder. This new file will host the \fBProjectsController\fP which will listen for incoming AJAX \fBGET\fP calls (in our case) and return the appropriate JSON response.
.sp
Add the following code to the \fBprojects.py\fP file:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
from pecan import expose, response
from pecan.rest import RestController
from myajax.model.projects import Project
class ProjectsController(RestController):
# Note: You would probably store this information in a database
# This is just for simplicity and demonstration purposes
def __init__(self):
self.projects = [
Project(name=\(aqOpenStack\(aq,
licensing=\(aqApache 2\(aq,
repository=\(aqhttp://github.com/openstack\(aq,
documentation=\(aqhttp://docs.openstack.org\(aq),
Project(name=\(aqPecan\(aq,
licensing=\(aqBSD\(aq,
repository=\(aqhttp://github.com/pecan/pecan\(aq,
documentation=\(aqhttps://pecan.readthedocs.io\(aq),
Project(name=\(aqstevedore\(aq,
licensing=\(aqApache 2\(aq,
repository=\(aqhttp://github.com/dreamhost/pecan\(aq,
documentation=\(aqhttp://docs.openstack.org/developer/stevedore/\(aq)
]
@expose(\(aqjson\(aq, content_type=\(aqapplication/json\(aq)
def get(self, id):
response.status = 200
return self.projects[int(id)]
.EE
.UNINDENT
.UNINDENT
.sp
\fBWhat did we just do?\fP
.INDENT 0.0
.IP 1. 3
Created a local class variable called \fBprojects\fP that holds three open source projects and their details. Typically this kind of information would probably reside in a database
.IP 2. 3
Added code for the new controller that will listen on the \fBprojects\fP endpoint and serve back JSON based on the \fBid\fP passed in from the web page
.UNINDENT
.sp
Run the application:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pecan serve config.py
.EE
.UNINDENT
.UNINDENT
.sp
Open a web browser: \fI\%http://127.0.0.1:8080/\fP
.sp
There is something else we could add. What if an \fBid\fP is passed that is not found? A proper \fBHTTP 404\fP should be sent back. For this we will modify the \fBProjectsController\fP\&.
.sp
Change the \fBget\fP function to look like this:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
@expose(\(aqjson\(aq, content_type=\(aqapplication/json\(aq)
def get(self, id):
try:
response.status = 200
return self.projects[int(id)]
except (IndexError, ValueError) as ex:
abort(404)
.EE
.UNINDENT
.UNINDENT
.sp
To test this out we need to pass an invalid \fBid\fP to the \fBProjectsController\fP\&. This can be done by going into the \fBindex.html\fP and adding an additional \fBoption\fP tag with an \fBid\fP value that is outside of 0\-2.
.INDENT 0.0
.INDENT 3.5
.sp
.EX
Select a project to get details:
.EE
.UNINDENT
.UNINDENT
.sp
You can see that we added \fBWSME\fP to the list and the value is 3.
.sp
Run the application:
.INDENT 0.0
.INDENT 3.5
.sp
.EX
$ pecan serve config.py
.EE
.UNINDENT
.UNINDENT
.sp
Open a web browser: \fI\%http://127.0.0.1:8080/\fP
.sp
Select \fBWSME\fP from the list. You should see the error dialog box triggered.
.sp
Pecan\(aqs source code is well documented using Python docstrings and
comments. In addition, we have generated API documentation from the
docstrings here:
.SH PECAN.CORE -- PECAN CORE
.sp
The \fI\%pecan.core\fP module is the base module for creating and extending
Pecan. The core logic for processing HTTP requests and responses lives
here.
.INDENT 0.0
.TP
.B class pecan.core.Pecan(*args, **kw)
Bases: \fBPecanBase\fP
.sp
Pecan application object. Generally created using \fBpecan.make_app\fP,
rather than being created manually.
.sp
Creates a Pecan application instance, which is a WSGI application.
.INDENT 7.0
.TP
.B Parameters
.INDENT 7.0
.IP \(bu 2
\fBroot\fP \-\- A string representing a root controller object (e.g.,
\(dqmyapp.controller.root.RootController\(dq)
.IP \(bu 2
\fBdefault_renderer\fP \-\- The default template rendering engine to use.
Defaults to mako.
.IP \(bu 2
\fBtemplate_path\fP \-\- A relative file system path (from the project root)
where template files live. Defaults to \(aqtemplates\(aq.
.IP \(bu 2
\fBhooks\fP \-\- A callable which returns a list of
\fI\%pecan.hooks.PecanHook\fP
.IP \(bu 2
\fBcustom_renderers\fP \-\- Custom renderer objects, as a dictionary keyed
by engine name.
.IP \(bu 2
\fBextra_template_vars\fP \-\- Any variables to inject into the template
namespace automatically.
.IP \(bu 2
\fBforce_canonical\fP \-\- A boolean indicating if this project should
require canonical URLs.
.IP \(bu 2
\fBguess_content_type_from_ext\fP \-\- A boolean indicating if this project
should use the extension in the URL for guessing
the content type to return.
.IP \(bu 2
\fBuse_context_locals\fP \-\- When \fITrue\fP, \fIpecan.request\fP and
\fIpecan.response\fP will be available as
thread\-local references.
.IP \(bu 2
\fBrequest_cls\fP \-\- Can be used to specify a custom \fIpecan.request\fP object.
Defaults to \fIpecan.Request\fP\&.
.IP \(bu 2
\fBresponse_cls\fP \-\- Can be used to specify a custom \fIpecan.response\fP
object. Defaults to \fIpecan.Response\fP\&.
.UNINDENT
.UNINDENT
.INDENT 7.0
.TP
.B find_controller(_state)
The main request handler for Pecan applications.
.UNINDENT
.INDENT 7.0
.TP
.B handle_hooks(hooks, *args, **kw)
Processes hooks of the specified type.
.INDENT 7.0
.TP
.B Parameters
.INDENT 7.0
.IP \(bu 2
\fBhook_type\fP \-\- The type of hook, including \fBbefore\fP, \fBafter\fP,
\fBon_error\fP, and \fBon_route\fP\&.
.IP \(bu 2
\fB*args\fP \-\- Arguments to pass to the hooks.
.UNINDENT
.UNINDENT
.UNINDENT
.UNINDENT
.INDENT 0.0
.TP
.B class pecan.core.Request(environ, charset=None, unicode_errors=None, decode_param_names=None, **kw)
Bases: \fBRequest\fP
.UNINDENT
.INDENT 0.0
.TP
.B class pecan.core.Response(body=None, status=None, headerlist=None, app_iter=None, content_type=None, conditional_response=None, charset=