Cleaning up cruft, simplifying default config, and improving docs.
This commit is contained in:
@@ -3,25 +3,13 @@
|
||||
Configuration
|
||||
=============
|
||||
Pecan is very easy to configure. As long as you follow certain conventions,
|
||||
using, setting and dealing with configuration should be very intuitive.
|
||||
using, setting and dealing with configuration should be very intuitive.
|
||||
|
||||
Pecan configuration files are pure Python. These files need to specify the values in a key/value way (Python
|
||||
dictionaries) or if you need simple one-way values you can also specify them as
|
||||
direct variables (more on that below).
|
||||
Pecan configuration files are pure Python.
|
||||
|
||||
Even if you want custom configuration values, you need to get them in the
|
||||
configuration file as dictionaries.
|
||||
|
||||
No Configuration
|
||||
----------------
|
||||
What happens when no configuration is passed? Or if you are missing some values?
|
||||
Pecan fills in anything that you might have left behind, like specific values or
|
||||
even if you leave them out completely. This includes
|
||||
**app** and **server** but will not, however, include any custom configurations.
|
||||
|
||||
Defaults
|
||||
--------
|
||||
Below is the complete default values the framework uses::
|
||||
Default Values
|
||||
---------------
|
||||
Below is the complete list of default values the framework uses::
|
||||
|
||||
|
||||
server = {
|
||||
@@ -31,9 +19,9 @@ Below is the complete default values the framework uses::
|
||||
|
||||
app = {
|
||||
'root' : None,
|
||||
'modules' : [],
|
||||
'static_root' : 'public',
|
||||
'template_path' : '',
|
||||
'debug' : False
|
||||
'template_path' : ''
|
||||
}
|
||||
|
||||
|
||||
@@ -42,10 +30,9 @@ Below is the complete default values the framework uses::
|
||||
|
||||
Application Configuration
|
||||
-------------------------
|
||||
This is the part of the configuration that is specific to your application.
|
||||
Things like debug mode, Root Controller and possible Hooks, should be specified
|
||||
here. This is what is used when the framework is wrapping your application into
|
||||
a valid WSGI app.
|
||||
This is the part of the configuration that is specific to your application -
|
||||
the framework uses it to wrap your application into a valid
|
||||
`WSGI app <http://www.wsgi.org/en/latest/what.html>`_.
|
||||
|
||||
A typical application configuration might look like this::
|
||||
|
||||
@@ -60,23 +47,24 @@ A typical application configuration might look like this::
|
||||
|
||||
Let's look at each value and what it means:
|
||||
|
||||
**app** is a reserved variable name for the configuration, so make sure you are
|
||||
not overriding, otherwise you will get default values.
|
||||
**app** is a reserved variable name for the configuration, so make sure you
|
||||
don't override it.
|
||||
|
||||
**root** The root controller of your application. Remember to provide
|
||||
a string representing a Python path to some callable (e.g.,
|
||||
`yourapp.controllers.root.RootController`).
|
||||
``"yourapp.controllers.root.RootController"``).
|
||||
|
||||
**static_root** Points to the directory where your static files live (relative
|
||||
to the project root).
|
||||
to the project root). By default, Pecan comes with middleware that can be
|
||||
used to serve static files (like CSS and Javascript files) during development.
|
||||
|
||||
**template_path** Points to the directory where your template files live
|
||||
(relative to the project root).
|
||||
|
||||
**reload** - When ``True``, ``pecan serve`` will listen for file changes and
|
||||
restare your app (especially useful for development).
|
||||
restart your app (especially useful for development).
|
||||
|
||||
**debug** Enables ``WebError`` to have display tracebacks in the browser
|
||||
**debug** Enables ``WebError`` to display tracebacks in the browser
|
||||
(**IMPORTANT**: Make sure this is *always* set to ``False`` in production
|
||||
environments).
|
||||
|
||||
@@ -85,7 +73,7 @@ environments).
|
||||
|
||||
Server Configuration
|
||||
--------------------
|
||||
Pecan provides some defaults. Change these to alter the host and port your
|
||||
Pecan provides some sane defaults. Change these to alter the host and port your
|
||||
WSGI app is served on::
|
||||
|
||||
server = {
|
||||
@@ -93,6 +81,17 @@ WSGI app is served on::
|
||||
'host' : '0.0.0.0'
|
||||
}
|
||||
|
||||
Additional Configuration
|
||||
------------------------
|
||||
Your application may need access to other configuration values at runtime
|
||||
(like third-party API credentials). These types of configuration can be
|
||||
defined in their own blocks in your configuration file::
|
||||
|
||||
twitter = {
|
||||
'api_key' : 'FOO',
|
||||
'api_secret' : 'SECRET'
|
||||
}
|
||||
|
||||
.. _accessibility:
|
||||
|
||||
Accessing Configuration at Runtime
|
||||
|
@@ -12,10 +12,9 @@ Contents:
|
||||
|
||||
installation.rst
|
||||
quick_start.rst
|
||||
routing.rst
|
||||
validation_n_errors.rst
|
||||
configuration.rst
|
||||
commands.rst
|
||||
routing.rst
|
||||
configuration.rst
|
||||
templates.rst
|
||||
rest.rst
|
||||
secure_controller.rst
|
||||
@@ -42,7 +41,6 @@ for building HTTP-based applications, including:
|
||||
|
||||
* Object-dispatch for easy routing
|
||||
* Full support for REST-style controllers
|
||||
* Validation and error handling
|
||||
* Extensible security framework
|
||||
* Extensible template language support
|
||||
* Extensible JSON support
|
||||
@@ -75,13 +73,14 @@ docstrings here:
|
||||
pecan_core.rst
|
||||
pecan_configuration.rst
|
||||
pecan_decorators.rst
|
||||
pecan_default_config.rst
|
||||
pecan_deploy.rst
|
||||
pecan_hooks.rst
|
||||
pecan_jsonify.rst
|
||||
pecan_rest.rst
|
||||
pecan_routing.rst
|
||||
pecan_secure.rst
|
||||
pecan_templating.rst
|
||||
pecan_testing.rst
|
||||
pecan_util.rst
|
||||
|
||||
|
||||
|
@@ -6,27 +6,26 @@ Installation
|
||||
Stable Version
|
||||
------------------------------
|
||||
|
||||
We recommend installing Pecan with ``pip`` but you can also try with
|
||||
``easy_install`` and ``virtualenv``. Creating a spot in your environment where
|
||||
We recommend installing Pecan with ``pip``, but you can also try with
|
||||
``easy_install``. Creating a spot in your environment where
|
||||
Pecan can be isolated from other packages is best practice.
|
||||
|
||||
To get started with an environment for Pecan, create a new
|
||||
`virtual environment <http://www.virtualenv.org>`_::
|
||||
To get started with an environment for Pecan, we recommend creating a new
|
||||
`virtual environment <http://www.virtualenv.org>`_ using `virtualenv
|
||||
<http://www.virtualenv.org>`_::
|
||||
|
||||
virtualenv pecan-env
|
||||
cd pecan-env
|
||||
source bin/activate
|
||||
|
||||
The above commands create a virtual environment and *activate* it. Those
|
||||
actions will encapsulate any dependency installations for the framework,
|
||||
making it easier to debug problems if needed.
|
||||
The above commands create a virtual environment and *activate* it. This
|
||||
will isolate Pecan's dependency installations from your system packages, making
|
||||
it easier to debug problems if needed.
|
||||
|
||||
Next, let's install Pecan::
|
||||
|
||||
pip install pecan
|
||||
|
||||
After a lot of output, you should have Pecan successfully installed.
|
||||
|
||||
|
||||
Development (Unstable) Version
|
||||
------------------------------
|
||||
@@ -35,8 +34,8 @@ need to install git and clone the repo from GitHub::
|
||||
|
||||
git clone https://github.com/dreamhost/pecan.git
|
||||
|
||||
If your virtual environment is still activated, call ``setup.py`` to install
|
||||
the development version::
|
||||
Assuming your virtual environment is still activated, call ``setup.py`` to
|
||||
install the development version::
|
||||
|
||||
cd pecan
|
||||
python setup.py develop
|
||||
|
@@ -19,7 +19,6 @@ we want to use to return ``JSON`` output for a ``User`` object::
|
||||
|
||||
class UsersController(object):
|
||||
@expose('json')
|
||||
@validate()
|
||||
def current_user(self):
|
||||
'''
|
||||
return an instance of myproject.model.User which represents
|
||||
|
@@ -1,32 +0,0 @@
|
||||
.. _pecan_default_config:
|
||||
|
||||
:mod:`pecan.default_config` -- Pecan Default Configuration
|
||||
==========================================================
|
||||
|
||||
The :mod:`pecan.default_config` module contains the default configuration
|
||||
for all Pecan applications.
|
||||
|
||||
.. automodule:: pecan.default_config
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
The default configuration is as follows::
|
||||
|
||||
# Server Specific Configurations
|
||||
server = {
|
||||
'port' : '8080',
|
||||
'host' : '0.0.0.0'
|
||||
}
|
||||
|
||||
# Pecan Application Configurations
|
||||
app = {
|
||||
'root' : None,
|
||||
'modules' : [],
|
||||
'static_root' : 'public',
|
||||
'template_path' : '',
|
||||
'debug' : False,
|
||||
'force_canonical' : True,
|
||||
'errors' : {
|
||||
'__force_dict__' : True
|
||||
}
|
||||
}
|
11
docs/source/pecan_deploy.rst
Normal file
11
docs/source/pecan_deploy.rst
Normal file
@@ -0,0 +1,11 @@
|
||||
.. _pecan_deploy:
|
||||
|
||||
:mod:`pecan.deploy` -- Pecan Deploy
|
||||
===========================================
|
||||
|
||||
The :mod:`pecan.deploy` module includes fixtures to help deploy Pecan
|
||||
applications.
|
||||
|
||||
.. automodule:: pecan.deploy
|
||||
:members:
|
||||
:show-inheritance:
|
11
docs/source/pecan_testing.rst
Normal file
11
docs/source/pecan_testing.rst
Normal file
@@ -0,0 +1,11 @@
|
||||
.. _pecan_testing:
|
||||
|
||||
:mod:`pecan.testing` -- Pecan Testing
|
||||
===========================================
|
||||
|
||||
The :mod:`pecan.testing` module includes fixtures to help test Pecan
|
||||
applications.
|
||||
|
||||
.. automodule:: pecan.testing
|
||||
:members:
|
||||
:show-inheritance:
|
@@ -18,9 +18,9 @@ your shell, type::
|
||||
|
||||
$ pecan create
|
||||
|
||||
The above command will prompt you for a project name. I chose *test_project*,
|
||||
but you can also provided as an argument at the end of the example command
|
||||
above, like::
|
||||
The above command will prompt you for a project name. This example uses
|
||||
*test_project*, but you can also provide an argument at the end of the
|
||||
example command above, like::
|
||||
|
||||
$ pecan create test_project
|
||||
|
||||
@@ -36,8 +36,7 @@ This is how the layout of your new project should look::
|
||||
├── public
|
||||
│ ├── css
|
||||
│ │ └── style.css
|
||||
│ └── javascript
|
||||
│ └── shared.js
|
||||
│ └── images
|
||||
├── setup.cfg
|
||||
├── setup.py
|
||||
└── test_project
|
||||
@@ -51,12 +50,12 @@ This is how the layout of your new project should look::
|
||||
├── templates
|
||||
│ ├── error.html
|
||||
│ ├── index.html
|
||||
│ ├── layout.html
|
||||
│ └── success.html
|
||||
│ └── layout.html
|
||||
└── tests
|
||||
├── __init__.py
|
||||
├── test_config.py
|
||||
└── test_root.py
|
||||
├── config.py
|
||||
├── test_functional.py
|
||||
└── test_units.py
|
||||
|
||||
The amount of files and directories may vary, but the above structure should
|
||||
give you an idea of what you should expect.
|
||||
@@ -64,7 +63,7 @@ give you an idea of what you should expect.
|
||||
A few things have been created for you, so let's review them one by one:
|
||||
|
||||
* **public**: All your static files (like CSS and Javascript) live here. If you
|
||||
have any images (this example app doesn't) they would live here too.
|
||||
have any images they would live here too.
|
||||
|
||||
|
||||
The remaining directories encompass your models, controllers and templates, and
|
||||
@@ -162,17 +161,14 @@ This is how it looks in the project template
|
||||
|
||||
class RootController(object):
|
||||
|
||||
@expose(
|
||||
generic = True,
|
||||
template = 'index.html'
|
||||
)
|
||||
@expose(generic=True, template='index.html')
|
||||
def index(self):
|
||||
return dict()
|
||||
|
||||
|
||||
@index.when(method='POST')
|
||||
def index_post(self, name, age):
|
||||
return dict(name=name)
|
||||
|
||||
def index_post(self, q):
|
||||
redirect('http://pecan.readthedocs.org/en/latest/search.html?q=%s' % q)
|
||||
|
||||
@expose('error.html')
|
||||
def error(self, status):
|
||||
try:
|
||||
@@ -194,21 +190,10 @@ this method.
|
||||
Notice that the index method returns a dictionary - this dictionary is used as
|
||||
a namespace to render the specified template (``index.html``) into HTML.
|
||||
|
||||
**def index_post**: receives 2 arguments (*name* and *age*) that are validated
|
||||
through the *SampleForm* schema.
|
||||
**def index_post**: receives one HTTP POST argument (``q``).
|
||||
|
||||
``method`` has been set to 'POST', so HTTP POSTs to the application root (in
|
||||
our example, form submissions) will be routed to this method.
|
||||
|
||||
The ``error_handler`` has been set to index. This means that when errors are
|
||||
raised, they will be sent to the index controller and rendered through its
|
||||
template.
|
||||
|
||||
**def error**: Finally, we have the error controller that allows your application to
|
||||
display custom pages for certain HTTP errors (404, etc...).
|
||||
|
||||
Application Interaction
|
||||
-----------------------
|
||||
If you still have your application running and you visit it in your browser,
|
||||
you should see a page with some information about Pecan and the form so you can
|
||||
play a bit.
|
||||
|
@@ -1,11 +1,13 @@
|
||||
.. _routing:
|
||||
|
||||
Routing
|
||||
=======
|
||||
Controllers and Routing
|
||||
=======================
|
||||
|
||||
When a user requests a certain URL in your app, how does Pecan know which
|
||||
controller to route to? Pecan uses a method known as **object-dispatch** to map an
|
||||
HTTP request to a controller. Object-dispatch begins by splitting the
|
||||
controller to route to? Pecan uses a routing strategy known as
|
||||
**object-dispatch** to map an HTTP request to a controller.
|
||||
|
||||
Object-dispatch begins by splitting the
|
||||
path into a list of components and then walking an object path, starting at
|
||||
the root controller. You can imagine your application's controllers as a tree
|
||||
of objects (branches of the object tree map directly to URL paths). Let's look
|
||||
@@ -49,6 +51,77 @@ controller. Using the ``catalog`` object, Pecan would then lookup
|
||||
``books``, followed by ``bestsellers``. What if the URL ends in a slash?
|
||||
Pecan will check for an ``index`` method on the current object.
|
||||
|
||||
To illustrate further, the following paths...
|
||||
|
||||
::
|
||||
|
||||
└── /
|
||||
├── /hours
|
||||
└── /catalog
|
||||
└── /catalog/books
|
||||
└── /catalog/books/bestsellers
|
||||
|
||||
Would route to the following controller methods...
|
||||
|
||||
::
|
||||
|
||||
└── RootController.index
|
||||
├── RootController.hours
|
||||
└── CatalogController.index
|
||||
└── BooksController.index
|
||||
└── BooksController.bestsellers
|
||||
|
||||
Exposing Controllers
|
||||
--------------------
|
||||
|
||||
At its core, ``@expose`` is how you tell Pecan which methods in a class
|
||||
are publically-visible controllers. If a method is *not* decorated with
|
||||
``@expose``, it will not be routed to. ``@expose`` accepts three optional
|
||||
parameters, some of which can impact routing.
|
||||
|
||||
::
|
||||
|
||||
from pecan import expose
|
||||
|
||||
class RootController(object):
|
||||
@expose(
|
||||
template = None,
|
||||
content_type = 'text/html',
|
||||
generic = False
|
||||
)
|
||||
def hello(self):
|
||||
return 'Hello World'
|
||||
|
||||
|
||||
Let's look at an example using ``template`` and ``content_type``:
|
||||
|
||||
::
|
||||
|
||||
from pecan import expose
|
||||
|
||||
class RootController(object):
|
||||
@expose('json')
|
||||
@expose('text_template.mako', content_type='text/plain')
|
||||
@expose('html_template.mako')
|
||||
def hello(self):
|
||||
return {'msg': 'Hello!'}
|
||||
|
||||
You'll notice that we used three ``expose`` decorators.
|
||||
|
||||
The first tells Pecan to serialize the response namespace using JSON
|
||||
serialization when the client requests ``/hello.json``.
|
||||
|
||||
The second tells Pecan to use the ``text_template.mako`` template file when the
|
||||
client requests ``/hello.txt``.
|
||||
|
||||
The third tells Pecan to use the html_template.mako template file when the
|
||||
client requests ``/hello.html``. If the client requests ``/hello``, Pecan will
|
||||
use the ``text/html`` content type by default.
|
||||
|
||||
Please see :ref:`pecan_decorators` for more information on ``@expose``.
|
||||
|
||||
|
||||
|
||||
Routing Algorithm
|
||||
-----------------
|
||||
|
||||
@@ -60,8 +133,8 @@ methods on your controller objects provides additional flexibility for
|
||||
processing all or part of a URL.
|
||||
|
||||
|
||||
``_lookup``
|
||||
-----------
|
||||
Routing to Subcontrollers with ``_lookup``
|
||||
------------------------------------------
|
||||
|
||||
The ``_lookup`` special method provides a way to process a portion of a URL,
|
||||
and then return a new controller object to route to for the remainder.
|
||||
@@ -75,8 +148,8 @@ resort, when no other controller matches the URL via standard object-dispatch.
|
||||
|
||||
::
|
||||
|
||||
from pecan import expose
|
||||
from mymodel import get_student_by_name
|
||||
from pecan import expose, abort
|
||||
from somelib import get_student_by_name
|
||||
|
||||
class StudentController(object):
|
||||
def __init__(self, student):
|
||||
@@ -98,11 +171,11 @@ resort, when no other controller matches the URL via standard object-dispatch.
|
||||
An HTTP GET request to `/8/name` would return the name of the student
|
||||
where `primary_key == 8`.
|
||||
|
||||
``_default``
|
||||
------------
|
||||
Falling Back with ``_default``
|
||||
------------------------------
|
||||
|
||||
The ``_default`` controller is called when no other controller methods
|
||||
match the URL via standard object-dispatch.
|
||||
The ``_default`` controller is called as a last resort when no other controller
|
||||
methods match the URL via standard object-dispatch.
|
||||
|
||||
::
|
||||
|
||||
@@ -110,20 +183,24 @@ match the URL via standard object-dispatch.
|
||||
|
||||
class RootController(object):
|
||||
@expose()
|
||||
def hello(self):
|
||||
def english(self):
|
||||
return 'hello'
|
||||
|
||||
@expose()
|
||||
def bonjour(self):
|
||||
def french(self):
|
||||
return 'bonjour'
|
||||
|
||||
@expose()
|
||||
def _default(self):
|
||||
return 'I cannot say hi in that language'
|
||||
return 'I cannot say hello in that language'
|
||||
|
||||
|
||||
...so in the example above, a request to ``/spanish`` would route to
|
||||
``RootController._default``.
|
||||
|
||||
|
||||
Overriding ``_route``
|
||||
---------------------
|
||||
Defining Customized Routing with ``_route``
|
||||
-------------------------------------------
|
||||
|
||||
The ``_route`` method allows a controller to completely override the routing
|
||||
mechanism of Pecan. Pecan itself uses the ``_route`` method to implement its
|
||||
@@ -135,71 +212,68 @@ will enable you to have total control.
|
||||
Controller Arguments
|
||||
--------------------
|
||||
|
||||
A controller can receive arguments in a variety of ways, including ``GET`` and
|
||||
``POST`` variables, and even chunks of the URL itself. ``GET`` and ``POST``
|
||||
arguments simply map to arguments on the controller method, while unprocessed
|
||||
chunks of the URL can be passed as positional arguments to the controller method.
|
||||
In Pecan, HTTP ``GET`` and ``POST`` variables that are `not` consumed
|
||||
during the routing process can be passed onto the controller as arguments.
|
||||
|
||||
Depending on the signature of your controller, these arguments can be mapped
|
||||
explicitly to method arguments:
|
||||
|
||||
::
|
||||
|
||||
from pecan import expose
|
||||
|
||||
class RootController(object):
|
||||
@expose(generic=True)
|
||||
def index(self):
|
||||
return 'Default case'
|
||||
@expose()
|
||||
def index(self, arg):
|
||||
return arg
|
||||
|
||||
@index.when(method='POST')
|
||||
def index_post(self):
|
||||
return 'You POSTed to me!'
|
||||
@expose()
|
||||
def kwargs(self, **kwargs):
|
||||
return str(kwargs)
|
||||
|
||||
@index.when(method='GET')
|
||||
def index_get(self):
|
||||
return 'You GET me!'
|
||||
::
|
||||
|
||||
$ curl http://localhost:8080/?arg=foo
|
||||
foo
|
||||
$ curl http://localhost:8080/kwargs?a=1&b=2&c=3
|
||||
{u'a': u'1', u'c': u'3', u'b': u'2'}
|
||||
|
||||
...or can be consumed positionally:
|
||||
|
||||
::
|
||||
|
||||
from pecan import expose
|
||||
|
||||
class RootController(object):
|
||||
@expose()
|
||||
def args(self, *args):
|
||||
return ','.join(args)
|
||||
|
||||
::
|
||||
|
||||
$ curl http://localhost:8080/one/two/three
|
||||
one,two,three
|
||||
|
||||
The same effect can be achieved with HTTP ``POST`` body variables:
|
||||
|
||||
::
|
||||
|
||||
from pecan import expose
|
||||
|
||||
class RootController(object):
|
||||
@expose()
|
||||
def index(self, arg):
|
||||
return arg
|
||||
|
||||
::
|
||||
|
||||
$ curl -X POST "http://localhost:8080/" -H "Content-Type: application/x-www-form-urlencoded" -d "arg=foo"
|
||||
foo
|
||||
|
||||
Helper Functions
|
||||
----------------
|
||||
|
||||
Pecan also provides several useful helper functions. The ``redirect``
|
||||
function allows you to issue internal or ``HTTP 302`` redirects.
|
||||
The ``redirect`` utility, along with several other useful helpers,
|
||||
are documented in :ref:`pecan_core`.
|
||||
|
||||
|
||||
``@expose``
|
||||
-----------
|
||||
|
||||
At its core, ``@expose`` is how you tell Pecan which methods in a class
|
||||
are publically-visible controllers. ``@expose`` accepts eight optional
|
||||
parameters, some of which can impact routing.
|
||||
|
||||
::
|
||||
|
||||
expose(template = None,
|
||||
content_type = 'text/html',
|
||||
generic = False)
|
||||
|
||||
|
||||
Let's look at an example using template and content_type
|
||||
|
||||
::
|
||||
|
||||
from pecan import decorators
|
||||
|
||||
class RootController(object):
|
||||
@expose('json')
|
||||
@expose('text_template.mako', content_type='text/plain')
|
||||
@expose('html_template.mako')
|
||||
def hello(self):
|
||||
return {'msg': 'Hello!'}
|
||||
|
||||
You'll notice that we used three expose decorators. The first tells
|
||||
Pecan to serialize our response namespace using JSON serialization when
|
||||
the client requests ``/hello.json``. The second tells the templating
|
||||
engine to use ``text_template.mako`` when the client request ``/hello.txt``.
|
||||
The third tells Pecan to use the html_template.mako when the client
|
||||
requests ``/hello.html``. If the client requests ``/hello``, Pecan will
|
||||
use the text/html template.
|
||||
|
||||
Please see :ref:`pecan_decorators` for more information on ``@expose``.
|
||||
Pecan also provides several useful helper functions for moving between
|
||||
different routes. The ``redirect`` function allows you to issue internal or
|
||||
``HTTP 302`` redirects. The ``redirect`` utility, along with several other
|
||||
useful helpers, are documented in :ref:`pecan_core`.
|
||||
|
@@ -76,7 +76,7 @@ The ``render`` helper is also quite simple to use::
|
||||
The JSON Renderer
|
||||
-----------------
|
||||
|
||||
Pecan provides a `JSON` renderer by simple passing ``@expose('json')``. For
|
||||
Pecan also provides a `JSON` renderer, e.g., ``@expose('json')``. For
|
||||
more information on using `JSON` in Pecan, please refer to :ref:`jsonify` and
|
||||
:ref:`pecan_jsonify`.
|
||||
|
||||
@@ -84,7 +84,7 @@ more information on using `JSON` in Pecan, please refer to :ref:`jsonify` and
|
||||
Custom Renderers
|
||||
----------------
|
||||
|
||||
To define a custom renderer, you simply create a class that follows a simple
|
||||
To define a custom renderer, you can create a class that follows a simple
|
||||
protocol::
|
||||
|
||||
class MyRenderer(object):
|
||||
@@ -105,7 +105,7 @@ protocol::
|
||||
return str(namespace)
|
||||
|
||||
|
||||
To enable your custom renderer, you can define a ``custom_renderers`` key In
|
||||
To enable your custom renderer, you can define a ``custom_renderers`` key in
|
||||
your application's configuration::
|
||||
|
||||
app = {
|
||||
|
@@ -1,6 +0,0 @@
|
||||
.. _validation_n_errors:
|
||||
|
||||
Validation and Error Handling
|
||||
=============================
|
||||
TODO: Rewrite this (potentially as an extension/cookbook) to illustrate the
|
||||
new technique (without formencode).
|
@@ -1,6 +1,3 @@
|
||||
'''
|
||||
'''
|
||||
|
||||
from paste.cascade import Cascade
|
||||
from paste.errordocument import make_errordocument
|
||||
from paste.recursive import RecursiveMiddleware
|
||||
@@ -11,7 +8,7 @@ from weberror.evalexception import EvalException
|
||||
|
||||
from core import (
|
||||
abort, override_template, Pecan, load_app, redirect, render,
|
||||
request, response, ValidationException
|
||||
request, response
|
||||
)
|
||||
from decorators import expose
|
||||
from hooks import RequestViewerHook
|
||||
@@ -29,18 +26,21 @@ __all__ = [
|
||||
|
||||
def make_app(root, static_root=None, debug=False, errorcfg={},
|
||||
wrap_app=None, logging=False, **kw):
|
||||
'''
|
||||
|
||||
'''
|
||||
# A shortcut for the RequestViewerHook middleware.
|
||||
if hasattr(conf, 'requestviewer'):
|
||||
existing_hooks = kw.get('hooks', [])
|
||||
existing_hooks.append(RequestViewerHook(conf.requestviewer))
|
||||
kw['hooks'] = existing_hooks
|
||||
|
||||
# Instantiate the WSGI app by passing **kw onward
|
||||
app = Pecan(root, **kw)
|
||||
# Optionally wrap the app in another WSGI app
|
||||
if wrap_app:
|
||||
app = wrap_app(app)
|
||||
# Included for internal redirect support
|
||||
app = RecursiveMiddleware(app)
|
||||
# Support for interactive debugging (and error reporting)
|
||||
if debug:
|
||||
app = EvalException(
|
||||
app,
|
||||
@@ -49,9 +49,13 @@ def make_app(root, static_root=None, debug=False, errorcfg={},
|
||||
)
|
||||
else:
|
||||
app = ErrorMiddleware(app, **errorcfg)
|
||||
app = make_errordocument(app, conf, **dict(conf.app.errors))
|
||||
# Configuration for serving custom error messages
|
||||
if hasattr(conf.app, 'errors'):
|
||||
app = make_errordocument(app, conf, **dict(conf.app.errors))
|
||||
# Support for serving static files (for development convenience)
|
||||
if static_root:
|
||||
app = Cascade([StaticURLParser(static_root), app])
|
||||
# Support for simple Apache-style logs
|
||||
if isinstance(logging, dict) or logging == True:
|
||||
app = TransLogger(app, **(isinstance(logging, dict) and logging or {}))
|
||||
return app
|
||||
|
@@ -17,13 +17,7 @@ DEFAULT = {
|
||||
'root': None,
|
||||
'modules': [],
|
||||
'static_root': 'public',
|
||||
'template_path': '',
|
||||
'debug': False,
|
||||
'logging': False,
|
||||
'force_canonical': True,
|
||||
'errors': {
|
||||
'__force_dict__': True
|
||||
}
|
||||
'template_path': ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +184,7 @@ def initconf():
|
||||
|
||||
def set_config(config, overwrite=False):
|
||||
'''
|
||||
Updates the global configuration a filename.
|
||||
Updates the global configuration.
|
||||
|
||||
:param config: Can be a dictionary containing configuration, or a string
|
||||
which
|
||||
|
@@ -1,7 +1,7 @@
|
||||
from configuration import _runtime_conf, set_config
|
||||
from templating import RendererFactory
|
||||
from routing import lookup_controller, NonCanonicalPath
|
||||
from util import _cfg, splitext, encode_if_needed
|
||||
from util import _cfg, encode_if_needed
|
||||
|
||||
from webob import Request, Response, exc
|
||||
from threading import local
|
||||
@@ -9,6 +9,7 @@ from itertools import chain
|
||||
from mimetypes import guess_type, add_type
|
||||
from paste.recursive import ForwardRequestException
|
||||
from urlparse import urlsplit, urlunsplit
|
||||
from os.path import splitext
|
||||
|
||||
try:
|
||||
from simplejson import loads
|
||||
@@ -116,17 +117,6 @@ def redirect(location=None, internal=False, code=None, headers={},
|
||||
raise exc.status_map[code](location=location, headers=headers)
|
||||
|
||||
|
||||
def error_for(field):
|
||||
'''
|
||||
A convenience function for fetching the validation error for a
|
||||
particular field in a form.
|
||||
|
||||
:param field: The name of the field to get the error for.
|
||||
'''
|
||||
|
||||
return request.pecan['validation_errors'].get(field, '')
|
||||
|
||||
|
||||
def render(template, namespace):
|
||||
'''
|
||||
Render the specified template using the Pecan rendering framework
|
||||
@@ -142,31 +132,6 @@ def render(template, namespace):
|
||||
return state.app.render(template, namespace)
|
||||
|
||||
|
||||
class ValidationException(ForwardRequestException):
|
||||
'''
|
||||
This exception is raised when a validation error occurs using Pecan's
|
||||
built-in validation framework.
|
||||
'''
|
||||
|
||||
def __init__(self, location=None, errors={}):
|
||||
if state.controller is not None:
|
||||
cfg = _cfg(state.controller)
|
||||
else:
|
||||
cfg = {}
|
||||
if location is None and 'error_handler' in cfg:
|
||||
location = cfg['error_handler']
|
||||
if callable(location):
|
||||
location = location()
|
||||
if 'pecan.params' not in request.environ:
|
||||
request.environ['pecan.params'] = dict(request.params)
|
||||
request.environ[
|
||||
'pecan.validation_errors'
|
||||
] = request.pecan['validation_errors']
|
||||
request.environ['REQUEST_METHOD'] = 'GET'
|
||||
request.environ['pecan.validation_redirected'] = True
|
||||
ForwardRequestException.__init__(self, location)
|
||||
|
||||
|
||||
def load_app(config):
|
||||
'''
|
||||
Used to load a ``Pecan`` application and its environment based on passed
|
||||
@@ -383,8 +348,6 @@ class Pecan(object):
|
||||
)
|
||||
if template == 'json':
|
||||
renderer = self.renderers.get('json', self.template_path)
|
||||
else:
|
||||
namespace['error_for'] = error_for
|
||||
if ':' in template:
|
||||
renderer = self.renderers.get(
|
||||
template.split(':')[0],
|
||||
|
@@ -2,4 +2,8 @@ from core import load_app
|
||||
|
||||
|
||||
def deploy(config):
|
||||
"""
|
||||
Given a config (dictionary of relative filename), returns a configured
|
||||
WSGI app.
|
||||
"""
|
||||
return load_app(config)
|
||||
|
@@ -8,8 +8,8 @@ def setup_app(config):
|
||||
return make_app(
|
||||
config.app.root,
|
||||
static_root = config.app.static_root,
|
||||
debug = config.app.debug,
|
||||
logging = config.app.logging,
|
||||
template_path = config.app.template_path,
|
||||
force_canonical = config.app.force_canonical
|
||||
debug = getattr(config.app, 'debug', None),
|
||||
logging = getattr(config.app, 'logging', None),
|
||||
force_canonical = getattr(config.app, 'force_canonical', None)
|
||||
)
|
||||
|
@@ -12,8 +12,6 @@ app = {
|
||||
'template_path' : '%(confdir)s/${package}/templates',
|
||||
'reload' : True,
|
||||
'debug' : True,
|
||||
'logging' : False,
|
||||
'force_canonical' : True,
|
||||
'errors' : {
|
||||
'404' : '/error/404',
|
||||
'__force_dict__' : True
|
||||
|
@@ -1 +0,0 @@
|
||||
${error_for('colors-1')}
|
@@ -23,7 +23,6 @@ class TestConf(TestCase):
|
||||
'test_config/config.py'
|
||||
)))
|
||||
|
||||
self.assertTrue(conf.app.debug)
|
||||
self.assertEqual(conf.app.root, None)
|
||||
self.assertEqual(conf.app.template_path, 'myproject/templates')
|
||||
self.assertEqual(conf.app.static_root, 'public')
|
||||
@@ -41,7 +40,6 @@ class TestConf(TestCase):
|
||||
'test_config/empty.py'
|
||||
)))
|
||||
|
||||
self.assertFalse(conf.app.debug)
|
||||
self.assertEqual(conf.app.root, None)
|
||||
self.assertEqual(conf.app.template_path, '')
|
||||
self.assertEqual(conf.app.static_root, 'public')
|
||||
@@ -58,7 +56,6 @@ class TestConf(TestCase):
|
||||
'test_config/forcedict.py'
|
||||
)))
|
||||
|
||||
self.assertFalse(conf.app.debug)
|
||||
self.assertEqual(conf.app.root, None)
|
||||
self.assertEqual(conf.app.template_path, '')
|
||||
self.assertEqual(conf.app.static_root, 'public')
|
||||
@@ -99,7 +96,6 @@ class TestConf(TestCase):
|
||||
os.path.dirname(__file__), 'test_config', 'config.py'
|
||||
)
|
||||
conf = configuration.conf_from_file(path)
|
||||
self.assertTrue(conf.app.debug)
|
||||
|
||||
def test_config_illegal_ids(self):
|
||||
from pecan import configuration
|
||||
@@ -197,9 +193,6 @@ class TestConf(TestCase):
|
||||
assert isinstance(as_dict, dict)
|
||||
assert as_dict['server']['host'] == '0.0.0.0'
|
||||
assert as_dict['server']['port'] == '8080'
|
||||
assert as_dict['app']['debug'] == False
|
||||
assert as_dict['app']['errors'] == {}
|
||||
assert as_dict['app']['force_canonical'] == True
|
||||
assert as_dict['app']['modules'] == []
|
||||
assert as_dict['app']['root'] == None
|
||||
assert as_dict['app']['static_root'] == 'public'
|
||||
@@ -217,9 +210,6 @@ class TestConf(TestCase):
|
||||
assert isinstance(as_dict, dict)
|
||||
assert as_dict['server']['host'] == '0.0.0.0'
|
||||
assert as_dict['server']['port'] == '8080'
|
||||
assert as_dict['app']['debug'] == False
|
||||
assert as_dict['app']['errors'] == {}
|
||||
assert as_dict['app']['force_canonical'] == True
|
||||
assert as_dict['app']['modules'] == []
|
||||
assert as_dict['app']['root'] == None
|
||||
assert as_dict['app']['static_root'] == 'public'
|
||||
@@ -238,9 +228,6 @@ class TestConf(TestCase):
|
||||
assert isinstance(as_dict, dict)
|
||||
assert as_dict['prefix_server']['prefix_host'] == '0.0.0.0'
|
||||
assert as_dict['prefix_server']['prefix_port'] == '8080'
|
||||
assert as_dict['prefix_app']['prefix_debug'] == False
|
||||
assert as_dict['prefix_app']['prefix_errors'] == {}
|
||||
assert as_dict['prefix_app']['prefix_force_canonical'] == True
|
||||
assert as_dict['prefix_app']['prefix_modules'] == []
|
||||
assert as_dict['prefix_app']['prefix_root'] == None
|
||||
assert as_dict['prefix_app']['prefix_static_root'] == 'public'
|
||||
|
@@ -1,12 +0,0 @@
|
||||
from pecan.util import compat_splitext
|
||||
|
||||
|
||||
def test_compat_splitext():
|
||||
assert ('foo', '.bar') == compat_splitext('foo.bar')
|
||||
assert ('/foo/bar', '.txt') == compat_splitext('/foo/bar.txt')
|
||||
assert ('/foo/bar', '') == compat_splitext('/foo/bar')
|
||||
assert ('.bashrc', '') == compat_splitext('.bashrc')
|
||||
assert ('..bashrc', '') == compat_splitext('..bashrc')
|
||||
assert ('/.bashrc', '') == compat_splitext('/.bashrc')
|
||||
assert ('/foo.bar/.bashrc', '') == compat_splitext('/foo.bar/.bashrc')
|
||||
assert ('/foo.js', '.js') == compat_splitext('/foo.js.js')
|
@@ -12,31 +12,6 @@ def _cfg(f):
|
||||
return f._pecan
|
||||
|
||||
|
||||
def compat_splitext(path):
|
||||
"""
|
||||
This method emulates the behavior os.path.splitext introduced in python 2.6
|
||||
"""
|
||||
basename = os.path.basename(path)
|
||||
|
||||
index = basename.rfind('.')
|
||||
if index > 0:
|
||||
root = basename[:index]
|
||||
if root.count('.') != index:
|
||||
return (
|
||||
os.path.join(os.path.dirname(path), root),
|
||||
basename[index:]
|
||||
)
|
||||
|
||||
return (path, '')
|
||||
|
||||
|
||||
# use the builtin splitext unless we're python 2.5
|
||||
if sys.version_info >= (2, 6):
|
||||
from os.path import splitext
|
||||
else: # pragma no cover
|
||||
splitext = compat_splitext # noqa
|
||||
|
||||
|
||||
if sys.version_info >= (2, 6, 5):
|
||||
def encode_if_needed(s):
|
||||
return s
|
||||
|
Reference in New Issue
Block a user