Files
pecan/docs/source/hooks.rst
2011-09-02 17:27:21 -04:00

10 KiB

Hooks

Pecan Hooks are a nice way to interact with the framework itself without having to write WSGI middleware.

There is nothing wrong with WSGI Middleware, and actually, it is really easy to use middleware with Pecan, but it can be hard (sometimes impossible) to have access to Pecan's internals from within middleware. Hooks make this easier.

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
  • before: called after routing, but before controller code is run
  • after: called after controller code has been run
  • on_error: called when a request generates an exception

Implementation

In the below example, we will write a simple hook that will gather some information about the request and print it to stdout.

Your hook implementation needs to import PecanHook so it can be used as a base class. From there, you'll need to override the on_route, before, after, or on_error methods:

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 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 --------------Hooks can be attached in a project-wide manner by specifying a list of hooks in your project's app.py file:

from application.root import RootController
from my_hooks import SimpleHook

app = make_app(
    RootController(),
    hooks = [SimpleHook()]
)

Hooks can also be applied selectively to controllers and their sub-controllers using the __hooks__ attribute on one or more controllers:

from pecan import expose
from my_hooks import SimpleHook

class SimpleController(object):

    __hooks__ = [SimpleHook()]

    @expose('json')
    def index(self):
        print "DO SOMETHING!"
        return dict()

Now that our SimpleHook is included, let's see what happens when we run the app and browse the application from our web browser:

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

Included Pecan Hooks

Pecan includes some hooks in its core and are very simple to start using them away. This section will describe their different uses, how to configure them and examples of common scenarios.

RequestViewerHook

This hooks is very useful for debugging purposes. It has access to every attribute the response object has plus a few others that are specific to the framework.

There are two main ways that this hook can provide information about a request:

  1. Terminal or logging output (via an file-like stream like stdout)
  2. Custom header keys in the actual response.

By default, both outputs are on if they are not configured.

For the actual object reference, please see pecan_hooks.

Automatically Enabled

This hook can be automatically added to the application itself if a certain key exists in the configuration used for the app. This key is:

requestviewer

It does not need to contain anything (could be an empty dictionary), and this is enough to force Pecan to load this hook when the WSGI application is created.

Configuration

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.

By default it will output information about these items:

  • path : Displays the url that was used to generate this response
  • status : The response from the server (e.g. '200 OK')
  • method : The method for the request (e.g. 'GET', 'POST', 'PUT or 'DELETE')
  • controller : The actual controller method in Pecan responsible for the response
  • params : A list of tuples for the params passed in at request time
  • hooks : Any hooks that are used in the app will be listed here.

No configuration will show those values in the terminal via stdout and it will also add them to the response headers (in the form of X-Pecan-item_name).

This is how the terminal output look for a /favicon.ico request :

path         - /favicon.ico
status       - 404 Not Found
method       - GET
controller   - The resource could not be found.
params       - []
hooks        - ['RequestViewerHook']

In the above case, the file was not found, and the information was properly gathered and returned via stdout.

And this is how those same values would be seen in the response headers:

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   ['RequestViewerHook']

The hook can be configured via a dictionary (or Config object from Pecan) when adding it to the application or via the requestviewer key in the actual configuration being passed to the application.

The configuration dictionary is flexible (none of the keys are required) and can hold two keys: items and blacklist.

This is how the hook would look if configured directly when using make_app (shortened for brevity):

...
hooks = [
    RequestViewerHook({'items':['path']})
]

And the same configuration could be set in the config file like:

requestviewer = {'items:['path']}

Specifying items

Items are the actual information objects that the hook will use to return information. Sometimes you will need a specific piece of information or a certain bunch of them according to the development need so the defaults will need to be changed and a list of items specified.

Remember, 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.

These is a list containing all the possible attributes the hook has access to (directly from webob):

====================== ==========================
accept

make_tempfile

accept_charset

max_forwards

accept_encoding

method

accept_language

params

application_url

path

as_string

path_info

authorization

path_info_peek

blank

path_info_pop

body

path_qs

body_file

path_url

body_file_raw

postvars

body_file_seekable

pragma

cache_control

query_string

call_application

queryvars

charset

range

content_length

referer

content_type

referrer

cookies

relative_url

copy

remote_addr

copy_body

remote_user

copy_get

remove_conditional_headers

date

request_body_tempfile_limit

decode_param_names

scheme

environ

script_name

from_file

server_name

from_string

server_port

get_response

str_GET

headers

str_POST

host

str_cookies

host_url

str_params

http_version

str_postvars

if_match

str_queryvars

if_modified_since

unicode_errors

if_none_match

upath_info

if_range

url

if_unmodified_since

urlargs

is_body_readable

urlvars

is_body_seekable

uscript_name

is_xhr make_body_seekable

user_agent

And these are the specific ones from Pecan and the hook:

  • controller
  • hooks
  • params (params is actually available from webob but it is parsed by the hook for redability)

Blacklisting

Sometimes it's annoying to get information about every single request. For this purpose, there is a matching list of url paths that you can pass into the hook so that paths that do not match are returned.

The matching is done at the start of the url path, so be careful when using this feature. For example, if you pass a configuration like this one:

{ 'blacklist': ['/f'] }

It would not show any url that starts with f, effectively behaving like a globbing regular expression (but not quite as powerful).

For any number of blocking you may need, just add as many items as wanted:

{ 'blacklist' : ['/favicon.ico', '/javascript', '/images'] }

Again, the blacklist key can be used along with the items key or not (it is not required).