Cleaning up cruft, simplifying default config, and improving docs.

This commit is contained in:
Ryan Petrello
2012-03-13 14:16:08 -07:00
parent 02a80731c4
commit bded674abd
21 changed files with 251 additions and 300 deletions

View File

@@ -5,23 +5,11 @@ Configuration
Pecan is very easy to configure. As long as you follow certain conventions, 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 Pecan configuration files are pure Python.
dictionaries) or if you need simple one-way values you can also specify them as
direct variables (more on that below).
Even if you want custom configuration values, you need to get them in the Default Values
configuration file as dictionaries. ---------------
Below is the complete list of default values the framework uses::
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::
server = { server = {
@@ -31,9 +19,9 @@ Below is the complete default values the framework uses::
app = { app = {
'root' : None, 'root' : None,
'modules' : [],
'static_root' : 'public', 'static_root' : 'public',
'template_path' : '', 'template_path' : ''
'debug' : False
} }
@@ -42,10 +30,9 @@ Below is the complete default values the framework uses::
Application Configuration Application Configuration
------------------------- -------------------------
This is the part of the configuration that is specific to your application. 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 the framework uses it to wrap your application into a valid
here. This is what is used when the framework is wrapping your application into `WSGI app <http://www.wsgi.org/en/latest/what.html>`_.
a valid WSGI app.
A typical application configuration might look like this:: 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: Let's look at each value and what it means:
**app** is a reserved variable name for the configuration, so make sure you are **app** is a reserved variable name for the configuration, so make sure you
not overriding, otherwise you will get default values. don't override it.
**root** The root controller of your application. Remember to provide **root** The root controller of your application. Remember to provide
a string representing a Python path to some callable (e.g., 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 **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 **template_path** Points to the directory where your template files live
(relative to the project root). (relative to the project root).
**reload** - When ``True``, ``pecan serve`` will listen for file changes and **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 (**IMPORTANT**: Make sure this is *always* set to ``False`` in production
environments). environments).
@@ -85,7 +73,7 @@ environments).
Server Configuration 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:: WSGI app is served on::
server = { server = {
@@ -93,6 +81,17 @@ WSGI app is served on::
'host' : '0.0.0.0' '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: .. _accessibility:
Accessing Configuration at Runtime Accessing Configuration at Runtime

View File

@@ -12,10 +12,9 @@ Contents:
installation.rst installation.rst
quick_start.rst quick_start.rst
routing.rst
validation_n_errors.rst
configuration.rst
commands.rst commands.rst
routing.rst
configuration.rst
templates.rst templates.rst
rest.rst rest.rst
secure_controller.rst secure_controller.rst
@@ -42,7 +41,6 @@ for building HTTP-based applications, including:
* Object-dispatch for easy routing * Object-dispatch for easy routing
* Full support for REST-style controllers * Full support for REST-style controllers
* Validation and error handling
* Extensible security framework * Extensible security framework
* Extensible template language support * Extensible template language support
* Extensible JSON support * Extensible JSON support
@@ -75,13 +73,14 @@ docstrings here:
pecan_core.rst pecan_core.rst
pecan_configuration.rst pecan_configuration.rst
pecan_decorators.rst pecan_decorators.rst
pecan_default_config.rst pecan_deploy.rst
pecan_hooks.rst pecan_hooks.rst
pecan_jsonify.rst pecan_jsonify.rst
pecan_rest.rst pecan_rest.rst
pecan_routing.rst pecan_routing.rst
pecan_secure.rst pecan_secure.rst
pecan_templating.rst pecan_templating.rst
pecan_testing.rst
pecan_util.rst pecan_util.rst

View File

@@ -6,27 +6,26 @@ Installation
Stable Version Stable Version
------------------------------ ------------------------------
We recommend installing Pecan with ``pip`` but you can also try with We recommend installing Pecan with ``pip``, but you can also try with
``easy_install`` and ``virtualenv``. Creating a spot in your environment where ``easy_install``. Creating a spot in your environment where
Pecan can be isolated from other packages is best practice. Pecan can be isolated from other packages is best practice.
To get started with an environment for Pecan, create a new To get started with an environment for Pecan, we recommend creating a new
`virtual environment <http://www.virtualenv.org>`_:: `virtual environment <http://www.virtualenv.org>`_ using `virtualenv
<http://www.virtualenv.org>`_::
virtualenv pecan-env virtualenv pecan-env
cd pecan-env cd pecan-env
source bin/activate source bin/activate
The above commands create a virtual environment and *activate* it. Those The above commands create a virtual environment and *activate* it. This
actions will encapsulate any dependency installations for the framework, will isolate Pecan's dependency installations from your system packages, making
making it easier to debug problems if needed. it easier to debug problems if needed.
Next, let's install Pecan:: Next, let's install Pecan::
pip install pecan pip install pecan
After a lot of output, you should have Pecan successfully installed.
Development (Unstable) Version 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 git clone https://github.com/dreamhost/pecan.git
If your virtual environment is still activated, call ``setup.py`` to install Assuming your virtual environment is still activated, call ``setup.py`` to
the development version:: install the development version::
cd pecan cd pecan
python setup.py develop python setup.py develop

View File

@@ -19,7 +19,6 @@ we want to use to return ``JSON`` output for a ``User`` object::
class UsersController(object): class UsersController(object):
@expose('json') @expose('json')
@validate()
def current_user(self): def current_user(self):
''' '''
return an instance of myproject.model.User which represents return an instance of myproject.model.User which represents

View File

@@ -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
}
}

View 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:

View 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:

View File

@@ -18,9 +18,9 @@ your shell, type::
$ pecan create $ pecan create
The above command will prompt you for a project name. I chose *test_project*, The above command will prompt you for a project name. This example uses
but you can also provided as an argument at the end of the example command *test_project*, but you can also provide an argument at the end of the
above, like:: example command above, like::
$ pecan create test_project $ pecan create test_project
@@ -36,8 +36,7 @@ This is how the layout of your new project should look::
├── public ├── public
│   ├── css │   ├── css
│   │   └── style.css │   │   └── style.css
│   └── javascript │   └── images
│   └── shared.js
├── setup.cfg ├── setup.cfg
├── setup.py ├── setup.py
└── test_project └── test_project
@@ -51,12 +50,12 @@ This is how the layout of your new project should look::
   ├── templates    ├── templates
   │   ├── error.html    │   ├── error.html
   │   ├── index.html    │   ├── index.html
   │   ── layout.html    │   ── layout.html
   │   └── success.html
   └── tests    └── tests
   ├── __init__.py    ├── __init__.py
   ├── test_config.py    ├── config.py
   ── test_root.py    ── test_functional.py
   └── test_units.py
The amount of files and directories may vary, but the above structure should The amount of files and directories may vary, but the above structure should
give you an idea of what you should expect. 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: 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 * **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 The remaining directories encompass your models, controllers and templates, and
@@ -162,16 +161,13 @@ This is how it looks in the project template
class RootController(object): class RootController(object):
@expose( @expose(generic=True, template='index.html')
generic = True,
template = 'index.html'
)
def index(self): def index(self):
return dict() return dict()
@index.when(method='POST') @index.when(method='POST')
def index_post(self, name, age): def index_post(self, q):
return dict(name=name) redirect('http://pecan.readthedocs.org/en/latest/search.html?q=%s' % q)
@expose('error.html') @expose('error.html')
def error(self, status): def error(self, status):
@@ -194,21 +190,10 @@ this method.
Notice that the index method returns a dictionary - this dictionary is used as Notice that the index method returns a dictionary - this dictionary is used as
a namespace to render the specified template (``index.html``) into HTML. a namespace to render the specified template (``index.html``) into HTML.
**def index_post**: receives 2 arguments (*name* and *age*) that are validated **def index_post**: receives one HTTP POST argument (``q``).
through the *SampleForm* schema.
``method`` has been set to 'POST', so HTTP POSTs to the application root (in ``method`` has been set to 'POST', so HTTP POSTs to the application root (in
our example, form submissions) will be routed to this method. 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 **def error**: Finally, we have the error controller that allows your application to
display custom pages for certain HTTP errors (404, etc...). 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.

View File

@@ -1,11 +1,13 @@
.. _routing: .. _routing:
Routing Controllers and Routing
======= =======================
When a user requests a certain URL in your app, how does Pecan know which 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 controller to route to? Pecan uses a routing strategy known as
HTTP request to a controller. Object-dispatch begins by splitting the **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 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 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 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? ``books``, followed by ``bestsellers``. What if the URL ends in a slash?
Pecan will check for an ``index`` method on the current object. 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 Routing Algorithm
----------------- -----------------
@@ -60,8 +133,8 @@ methods on your controller objects provides additional flexibility for
processing all or part of a URL. 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, 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. 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 pecan import expose, abort
from mymodel import get_student_by_name from somelib import get_student_by_name
class StudentController(object): class StudentController(object):
def __init__(self, student): 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 An HTTP GET request to `/8/name` would return the name of the student
where `primary_key == 8`. where `primary_key == 8`.
``_default`` Falling Back with ``_default``
------------ ------------------------------
The ``_default`` controller is called when no other controller methods The ``_default`` controller is called as a last resort when no other controller
match the URL via standard object-dispatch. methods match the URL via standard object-dispatch.
:: ::
@@ -110,20 +183,24 @@ match the URL via standard object-dispatch.
class RootController(object): class RootController(object):
@expose() @expose()
def hello(self): def english(self):
return 'hello' return 'hello'
@expose() @expose()
def bonjour(self): def french(self):
return 'bonjour' return 'bonjour'
@expose() @expose()
def _default(self): def _default(self):
return 'I cannot say hi in that language' return 'I cannot say hello in that language'
Overriding ``_route`` ...so in the example above, a request to ``/spanish`` would route to
--------------------- ``RootController._default``.
Defining Customized Routing with ``_route``
-------------------------------------------
The ``_route`` method allows a controller to completely override the routing The ``_route`` method allows a controller to completely override the routing
mechanism of Pecan. Pecan itself uses the ``_route`` method to implement its 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 Controller Arguments
-------------------- --------------------
A controller can receive arguments in a variety of ways, including ``GET`` and In Pecan, HTTP ``GET`` and ``POST`` variables that are `not` consumed
``POST`` variables, and even chunks of the URL itself. ``GET`` and ``POST`` during the routing process can be passed onto the controller as arguments.
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. Depending on the signature of your controller, these arguments can be mapped
explicitly to method arguments:
:: ::
from pecan import expose from pecan import expose
class RootController(object): class RootController(object):
@expose(generic=True) @expose()
def index(self): def index(self, arg):
return 'Default case' return arg
@index.when(method='POST') @expose()
def index_post(self): def kwargs(self, **kwargs):
return 'You POSTed to me!' 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 Helper Functions
---------------- ----------------
Pecan also provides several useful helper functions. The ``redirect`` Pecan also provides several useful helper functions for moving between
function allows you to issue internal or ``HTTP 302`` redirects. different routes. The ``redirect`` function allows you to issue internal or
The ``redirect`` utility, along with several other useful helpers, ``HTTP 302`` redirects. The ``redirect`` utility, along with several other
are documented in :ref:`pecan_core`. 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``.

View File

@@ -76,7 +76,7 @@ The ``render`` helper is also quite simple to use::
The JSON Renderer 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 more information on using `JSON` in Pecan, please refer to :ref:`jsonify` and
:ref:`pecan_jsonify`. :ref:`pecan_jsonify`.
@@ -84,7 +84,7 @@ more information on using `JSON` in Pecan, please refer to :ref:`jsonify` and
Custom Renderers 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:: protocol::
class MyRenderer(object): class MyRenderer(object):
@@ -105,7 +105,7 @@ protocol::
return str(namespace) 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:: your application's configuration::
app = { app = {

View File

@@ -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).

View File

@@ -1,6 +1,3 @@
'''
'''
from paste.cascade import Cascade from paste.cascade import Cascade
from paste.errordocument import make_errordocument from paste.errordocument import make_errordocument
from paste.recursive import RecursiveMiddleware from paste.recursive import RecursiveMiddleware
@@ -11,7 +8,7 @@ from weberror.evalexception import EvalException
from core import ( from core import (
abort, override_template, Pecan, load_app, redirect, render, abort, override_template, Pecan, load_app, redirect, render,
request, response, ValidationException request, response
) )
from decorators import expose from decorators import expose
from hooks import RequestViewerHook from hooks import RequestViewerHook
@@ -29,18 +26,21 @@ __all__ = [
def make_app(root, static_root=None, debug=False, errorcfg={}, def make_app(root, static_root=None, debug=False, errorcfg={},
wrap_app=None, logging=False, **kw): wrap_app=None, logging=False, **kw):
'''
''' # A shortcut for the RequestViewerHook middleware.
if hasattr(conf, 'requestviewer'): if hasattr(conf, 'requestviewer'):
existing_hooks = kw.get('hooks', []) existing_hooks = kw.get('hooks', [])
existing_hooks.append(RequestViewerHook(conf.requestviewer)) existing_hooks.append(RequestViewerHook(conf.requestviewer))
kw['hooks'] = existing_hooks kw['hooks'] = existing_hooks
# Instantiate the WSGI app by passing **kw onward
app = Pecan(root, **kw) app = Pecan(root, **kw)
# Optionally wrap the app in another WSGI app
if wrap_app: if wrap_app:
app = wrap_app(app) app = wrap_app(app)
# Included for internal redirect support
app = RecursiveMiddleware(app) app = RecursiveMiddleware(app)
# Support for interactive debugging (and error reporting)
if debug: if debug:
app = EvalException( app = EvalException(
app, app,
@@ -49,9 +49,13 @@ def make_app(root, static_root=None, debug=False, errorcfg={},
) )
else: else:
app = ErrorMiddleware(app, **errorcfg) app = ErrorMiddleware(app, **errorcfg)
# Configuration for serving custom error messages
if hasattr(conf.app, 'errors'):
app = make_errordocument(app, conf, **dict(conf.app.errors)) app = make_errordocument(app, conf, **dict(conf.app.errors))
# Support for serving static files (for development convenience)
if static_root: if static_root:
app = Cascade([StaticURLParser(static_root), app]) app = Cascade([StaticURLParser(static_root), app])
# Support for simple Apache-style logs
if isinstance(logging, dict) or logging == True: if isinstance(logging, dict) or logging == True:
app = TransLogger(app, **(isinstance(logging, dict) and logging or {})) app = TransLogger(app, **(isinstance(logging, dict) and logging or {}))
return app return app

View File

@@ -17,13 +17,7 @@ DEFAULT = {
'root': None, 'root': None,
'modules': [], 'modules': [],
'static_root': 'public', 'static_root': 'public',
'template_path': '', 'template_path': ''
'debug': False,
'logging': False,
'force_canonical': True,
'errors': {
'__force_dict__': True
}
} }
} }
@@ -190,7 +184,7 @@ def initconf():
def set_config(config, overwrite=False): 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 :param config: Can be a dictionary containing configuration, or a string
which which

View File

@@ -1,7 +1,7 @@
from configuration import _runtime_conf, set_config from configuration import _runtime_conf, set_config
from templating import RendererFactory from templating import RendererFactory
from routing import lookup_controller, NonCanonicalPath 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 webob import Request, Response, exc
from threading import local from threading import local
@@ -9,6 +9,7 @@ from itertools import chain
from mimetypes import guess_type, add_type from mimetypes import guess_type, add_type
from paste.recursive import ForwardRequestException from paste.recursive import ForwardRequestException
from urlparse import urlsplit, urlunsplit from urlparse import urlsplit, urlunsplit
from os.path import splitext
try: try:
from simplejson import loads 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) 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): def render(template, namespace):
''' '''
Render the specified template using the Pecan rendering framework Render the specified template using the Pecan rendering framework
@@ -142,31 +132,6 @@ def render(template, namespace):
return state.app.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): def load_app(config):
''' '''
Used to load a ``Pecan`` application and its environment based on passed Used to load a ``Pecan`` application and its environment based on passed
@@ -383,8 +348,6 @@ class Pecan(object):
) )
if template == 'json': if template == 'json':
renderer = self.renderers.get('json', self.template_path) renderer = self.renderers.get('json', self.template_path)
else:
namespace['error_for'] = error_for
if ':' in template: if ':' in template:
renderer = self.renderers.get( renderer = self.renderers.get(
template.split(':')[0], template.split(':')[0],

View File

@@ -2,4 +2,8 @@ from core import load_app
def deploy(config): def deploy(config):
"""
Given a config (dictionary of relative filename), returns a configured
WSGI app.
"""
return load_app(config) return load_app(config)

View File

@@ -8,8 +8,8 @@ def setup_app(config):
return make_app( return make_app(
config.app.root, config.app.root,
static_root = config.app.static_root, static_root = config.app.static_root,
debug = config.app.debug,
logging = config.app.logging,
template_path = config.app.template_path, 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)
) )

View File

@@ -12,8 +12,6 @@ app = {
'template_path' : '%(confdir)s/${package}/templates', 'template_path' : '%(confdir)s/${package}/templates',
'reload' : True, 'reload' : True,
'debug' : True, 'debug' : True,
'logging' : False,
'force_canonical' : True,
'errors' : { 'errors' : {
'404' : '/error/404', '404' : '/error/404',
'__force_dict__' : True '__force_dict__' : True

View File

@@ -1 +0,0 @@
${error_for('colors-1')}

View File

@@ -23,7 +23,6 @@ class TestConf(TestCase):
'test_config/config.py' 'test_config/config.py'
))) )))
self.assertTrue(conf.app.debug)
self.assertEqual(conf.app.root, None) self.assertEqual(conf.app.root, None)
self.assertEqual(conf.app.template_path, 'myproject/templates') self.assertEqual(conf.app.template_path, 'myproject/templates')
self.assertEqual(conf.app.static_root, 'public') self.assertEqual(conf.app.static_root, 'public')
@@ -41,7 +40,6 @@ class TestConf(TestCase):
'test_config/empty.py' 'test_config/empty.py'
))) )))
self.assertFalse(conf.app.debug)
self.assertEqual(conf.app.root, None) self.assertEqual(conf.app.root, None)
self.assertEqual(conf.app.template_path, '') self.assertEqual(conf.app.template_path, '')
self.assertEqual(conf.app.static_root, 'public') self.assertEqual(conf.app.static_root, 'public')
@@ -58,7 +56,6 @@ class TestConf(TestCase):
'test_config/forcedict.py' 'test_config/forcedict.py'
))) )))
self.assertFalse(conf.app.debug)
self.assertEqual(conf.app.root, None) self.assertEqual(conf.app.root, None)
self.assertEqual(conf.app.template_path, '') self.assertEqual(conf.app.template_path, '')
self.assertEqual(conf.app.static_root, 'public') self.assertEqual(conf.app.static_root, 'public')
@@ -99,7 +96,6 @@ class TestConf(TestCase):
os.path.dirname(__file__), 'test_config', 'config.py' os.path.dirname(__file__), 'test_config', 'config.py'
) )
conf = configuration.conf_from_file(path) conf = configuration.conf_from_file(path)
self.assertTrue(conf.app.debug)
def test_config_illegal_ids(self): def test_config_illegal_ids(self):
from pecan import configuration from pecan import configuration
@@ -197,9 +193,6 @@ class TestConf(TestCase):
assert isinstance(as_dict, dict) assert isinstance(as_dict, dict)
assert as_dict['server']['host'] == '0.0.0.0' assert as_dict['server']['host'] == '0.0.0.0'
assert as_dict['server']['port'] == '8080' 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']['modules'] == []
assert as_dict['app']['root'] == None assert as_dict['app']['root'] == None
assert as_dict['app']['static_root'] == 'public' assert as_dict['app']['static_root'] == 'public'
@@ -217,9 +210,6 @@ class TestConf(TestCase):
assert isinstance(as_dict, dict) assert isinstance(as_dict, dict)
assert as_dict['server']['host'] == '0.0.0.0' assert as_dict['server']['host'] == '0.0.0.0'
assert as_dict['server']['port'] == '8080' 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']['modules'] == []
assert as_dict['app']['root'] == None assert as_dict['app']['root'] == None
assert as_dict['app']['static_root'] == 'public' assert as_dict['app']['static_root'] == 'public'
@@ -238,9 +228,6 @@ class TestConf(TestCase):
assert isinstance(as_dict, dict) assert isinstance(as_dict, dict)
assert as_dict['prefix_server']['prefix_host'] == '0.0.0.0' assert as_dict['prefix_server']['prefix_host'] == '0.0.0.0'
assert as_dict['prefix_server']['prefix_port'] == '8080' 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_modules'] == []
assert as_dict['prefix_app']['prefix_root'] == None assert as_dict['prefix_app']['prefix_root'] == None
assert as_dict['prefix_app']['prefix_static_root'] == 'public' assert as_dict['prefix_app']['prefix_static_root'] == 'public'

View File

@@ -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')

View File

@@ -12,31 +12,6 @@ def _cfg(f):
return f._pecan 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): if sys.version_info >= (2, 6, 5):
def encode_if_needed(s): def encode_if_needed(s):
return s return s