More documentation updates and tweaking.
This commit is contained in:
@@ -143,16 +143,3 @@ app is running I can access ``foo`` values like::
|
||||
>>> conf.foo
|
||||
Config({'bar': True, 'baz': False})
|
||||
|
||||
|
||||
Files and Structure
|
||||
===================
|
||||
Pecan gives you a ``start.py`` file so you can manually run your application
|
||||
from the command line. By default, this file requires a parameter which is
|
||||
a configuration file without the ``.py`` extension.
|
||||
|
||||
If you have a configuration file named ``config.py`` you would need to pass it
|
||||
to ``start.py`` like::
|
||||
|
||||
python start.py config
|
||||
|
||||
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
.. _databases:
|
||||
|
||||
Working with Databases and ORM's
|
||||
Working with Databases, Transactions, and ORM's
|
||||
=============
|
||||
Out of the box, Pecan provides no opinionated support for working with databases,
|
||||
but it's easy to hook into your ORM of choice with minimal effort. This article
|
||||
details best practices for integration the popular Python ORM, SQLAlchemy, into
|
||||
details best practices for integrating the popular Python ORM, SQLAlchemy, into
|
||||
your Pecan project.
|
||||
|
||||
init_model and Preparing Your Model
|
||||
``init_model`` and Preparing Your Model
|
||||
----------------
|
||||
Pecan's default quickstart project includes an empty stub directory for implementing
|
||||
your model as you see fit::
|
||||
|
||||
.
|
||||
└── test_project
|
||||
├── app.py
|
||||
├── __init__.py
|
||||
├── controllers
|
||||
├── model
|
||||
│ ├── __init__.py
|
||||
└── templates
|
||||
.
|
||||
└── test_project
|
||||
├── app.py
|
||||
├── __init__.py
|
||||
├── controllers
|
||||
├── model
|
||||
│ ├── __init__.py
|
||||
└── templates
|
||||
|
||||
By default, this module contains a special method, ``init_model``::
|
||||
|
||||
@@ -90,10 +90,14 @@ Binding Within the Application
|
||||
----------------
|
||||
There are several approaches that can be taken to wrap your application's requests with calls
|
||||
to appropriate model function calls. One approach is WSGI middleware. We also recommend
|
||||
Pecan hooks (see ref:`hooks`). Pecan comes with ``TransactionHook``, a hook which can
|
||||
be used to wrap requests in transactions for you. To use it, you simply include it in your
|
||||
Pecan :ref:`hooks`. Pecan comes with ``TransactionHook``, a hook which can
|
||||
be used to wrap requests in transactions for you. To use it, simply include it in your
|
||||
project's ``app.py`` file and pass it a set of functions related to database binding::
|
||||
|
||||
from pecan import conf, make_app
|
||||
from pecan.hooks import TransactionHook
|
||||
from test_project import model
|
||||
|
||||
app = make_app(
|
||||
conf.app.root,
|
||||
static_root = conf.app.static_root,
|
||||
@@ -111,28 +115,30 @@ project's ``app.py`` file and pass it a set of functions related to database bin
|
||||
)
|
||||
|
||||
For the above example, on HTTP POST, PUT, and DELETE requests, ``TransactionHook`` behaves in the
|
||||
following manner::
|
||||
following manner:
|
||||
|
||||
#. Before controller routing has been determined, ``model.start()`` is called. This function should bind to the appropriate SQLAlchemy engine and start a transaction.
|
||||
|
||||
#. Before controller routing has been determined, ``model.start()`` is called. This function
|
||||
should bind to the appropriate SQLAlchemy engine and start a transaction.
|
||||
#. Controller code is run and returns.
|
||||
#. If your controller or template rendering fails and raises an exception, ``model.rollback()``
|
||||
is called and the original exception is re-raised. This allows you to rollback your database
|
||||
transaction to avoid committing work when exceptions occur in your application code.
|
||||
|
||||
#. If your controller or template rendering fails and raises an exception, ``model.rollback()`` is called and the original exception is re-raised. This allows you to rollback your database transaction to avoid committing work when exceptions occur in your application code.
|
||||
|
||||
#. If the controller returns successfully, ``model.commit()`` and ``model.clear()`` are called.
|
||||
|
||||
On idempotent operations (like HTTP GET and HEAD requests), TransactionHook behaves in the following
|
||||
manner::
|
||||
manner:
|
||||
|
||||
#. ``model.start_read_only()`` is called. This function should bind to your SQLAlchemy engine.
|
||||
|
||||
#. Controller code is run and returns.
|
||||
|
||||
#. If the controller returns successfully, ``model.clear()`` is called.
|
||||
|
||||
Splitting Reads and Writes
|
||||
----------------
|
||||
Employing the above strategy with ``TransactionHook`` makes it very simple to split database
|
||||
reads and writes based upon HTTP methods (i.e., GET/HEAD requests are read and would potentially
|
||||
Employing the strategy above with ``TransactionHook`` makes it very simple to split database
|
||||
reads and writes based upon HTTP methods (i.e., GET/HEAD requests are read-only and would potentially
|
||||
be routed to a read-only database slave, while POST/PUT/DELETE requests require writing, and
|
||||
would bind to a master database with read/write priveleges) It's also very easy extend
|
||||
would always bind to a master database with read/write privileges). It's also very easy to extend
|
||||
``TransactionHook`` or write your own hook implementation for more refined control over where and
|
||||
when database bindings are called.
|
||||
@@ -9,7 +9,7 @@ There is nothing wrong with WSGI Middleware, and actually, it is really easy to
|
||||
use middleware with Pecan, but it can be hard (sometimes impossible) to have
|
||||
access to Pecan's internals from within middleware. Hooks make this easier.
|
||||
|
||||
Hooks offer four ways of dealing with a request:
|
||||
Hooks allow you to execute code at key points throughout the life cycle of your request:
|
||||
|
||||
* ``on_route``: called before Pecan attempts to route a request to a controller
|
||||
|
||||
@@ -22,21 +22,27 @@ Hooks offer four ways of dealing with a request:
|
||||
Implementation
|
||||
--------------
|
||||
In the below example, we will write a simple hook that will gather
|
||||
some information about the request and print it out to ``stdout``.
|
||||
some information about the request and print it to ``stdout``.
|
||||
|
||||
Your hook implementation needs to import ``PecanHook`` so it can be used as a base class::
|
||||
Your hook implementation needs to import ``PecanHook`` so it can be used as a base class.
|
||||
From there, you'll need to override the ``on_route``, ``before``, ``after``, or ``on_error`` methods::
|
||||
|
||||
from pecan.hooks import PecanHook
|
||||
|
||||
class SimpleHook(PecanHook):
|
||||
|
||||
def before(self, state):
|
||||
print "\nabout to enter the controller..."
|
||||
|
||||
def after(self, state):
|
||||
print "\nmethod: \t %s" % state.request.method
|
||||
print "\nresponse: \t %s" % state.response.status
|
||||
|
||||
``on_route``, ``before``, and ``after`` are passed a shared state object which includes useful
|
||||
``on_route``, ``before``, and ``after`` are each passed a shared state object which includes useful
|
||||
information about the request, such as the request and response object, and which controller
|
||||
was chosen by Pecan's routing.
|
||||
|
||||
``on_error`` is passed a shared state object **and** the original exception.
|
||||
|
||||
Attaching Hooks
|
||||
--------------
|
||||
@@ -63,16 +69,17 @@ using the ``__hooks__`` attribute on one or more controllers::
|
||||
|
||||
@expose('json')
|
||||
def index(self):
|
||||
print "DO SOMETHING!"
|
||||
return dict()
|
||||
|
||||
Running it
|
||||
----------
|
||||
Now that our ``SimpleHook`` is included, let's see what happens when we run
|
||||
the app and browse the application::
|
||||
the app and browse the application from our web browser::
|
||||
|
||||
pecan serve config.py
|
||||
serving on 0.0.0.0:8080 view at http://127.0.0.1:8080
|
||||
|
||||
about to enter the controller...
|
||||
DO SOMETHING!
|
||||
method: GET
|
||||
response: 200 OK
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ error handling activities, like:
|
||||
* Transforming strings from form submissions into useful Python objects.
|
||||
* Simplifying the process of re-displaying form values and associated error messages inline.
|
||||
|
||||
Rather than re-inventing the wheel, Pecan uses FormEncode for schemas and form validation.
|
||||
Rather than re-inventing the wheel, Pecan uses `FormEncode <http://formencode.org/>`_ for schemas and form validation.
|
||||
|
||||
Writing and Applying Schemas
|
||||
------------------------------
|
||||
@@ -37,7 +37,7 @@ Pecan's ``expose`` decorator::
|
||||
Validating JSON Content
|
||||
------------------------------
|
||||
In addition to simple form arguments, Pecan also makes it easy to validate JSON request bodies.
|
||||
Often, especially in AJAX requests, the request content is encoded as a JSON in the request body.
|
||||
Often, especially in AJAX requests, the request content is encoded as JSON in the request body.
|
||||
Pecan's validation can handle the decoding for you and apply schema validation to the decoded
|
||||
data structure::
|
||||
|
||||
@@ -94,7 +94,6 @@ This is especially useful when used in combination with generic
|
||||
controller methods::
|
||||
|
||||
from pecan import request, expose
|
||||
|
||||
from formencode import Schema, validators as v
|
||||
|
||||
class ProfileSchema(Schema):
|
||||
@@ -121,7 +120,8 @@ controller methods::
|
||||
"""
|
||||
This method will do something with POST arguments.
|
||||
If the schema validation fails, an internal redirect will
|
||||
cause the `profile.html` template to be rendered.
|
||||
cause the `profile.html` template to be rendered via the
|
||||
``index_get`` method.
|
||||
"""
|
||||
|
||||
name = kw.get('name')
|
||||
@@ -133,27 +133,27 @@ In this example, when form validation errors occur (for example, the email provi
|
||||
Pecan will handle pre-filling the form values in ``profile.html`` for you. Additionally, inline
|
||||
errors will be appended to the template using FormEncode's ``htmlfill``.
|
||||
|
||||
Bypassing htmlfill
|
||||
Bypassing ``htmlfill``
|
||||
------------------------------
|
||||
Sometimes you want certain fields in your templates to be ignored (i.e., not pre-filled) by htmlfill.
|
||||
Sometimes you want certain fields in your templates to be ignored (i.e., not pre-filled) by ``htmlfill``.
|
||||
A perfect use case for this is password and hidden input fields. The default Pecan template namespace
|
||||
includes a built-in function, ``static``, which allows you to enforce a static value for form fields,
|
||||
preventing htmlfill from filling it in form arguments::
|
||||
preventing ``htmlfill`` from filling in submitted form variables::
|
||||
|
||||
<form method="POST">
|
||||
<dl>
|
||||
<dt>Username:</dt>
|
||||
<dd><input type="text" name="username" /></dd>
|
||||
<dt>Password:</dt>
|
||||
<dd><input type="password" name="password" value="${static('password', '')}" /></dd>
|
||||
<input type="hidden" name="ticket" value="${static('ticket', 'RANDOM_PER_REQUEST_VALUE')}" />
|
||||
</dl>
|
||||
<button>Login</button>
|
||||
</form>
|
||||
<form method="POST">
|
||||
<dl>
|
||||
<dt>Username:</dt>
|
||||
<dd><input type="text" name="username" /></dd>
|
||||
<dt>Password:</dt>
|
||||
<dd><input type="password" name="password" value="${static('password', '')}" /></dd>
|
||||
<input type="hidden" name="ticket" value="${static('ticket', 'RANDOM_PER_REQUEST_VALUE')}" />
|
||||
</dl>
|
||||
<button>Login</button>
|
||||
</form>
|
||||
|
||||
Working with variabledecode
|
||||
Working with ``variabledecode``
|
||||
------------------------------
|
||||
Pecan also lets you take advantage of FormEncode's variabledecode for transforming flat HTML form
|
||||
Pecan also lets you take advantage of FormEncode's ``variabledecode`` for transforming flat HTML form
|
||||
submissions into nested structures::
|
||||
|
||||
from pecan import expose
|
||||
|
||||
Reference in New Issue
Block a user