Merge pull request #119 from ryanpetrello/next

Support for the ``pecan.ext`` namespace for extensions.
This commit is contained in:
Ryan Petrello
2012-03-29 11:14:36 -07:00
4 changed files with 108 additions and 19 deletions

View File

@@ -3,13 +3,13 @@
Custom Error Documents
======================
In this article we will configure a Pecan application to display a custom
Error page whenever the server returns ``404 Page not Found`` status.
error page whenever the server returns a ``404 Page Not Found`` status.
This article assumes that you have already created a test application as
described in :ref:`quick_start`
described in :ref:`quick_start`.
.. note::
While this example focuses on the ``HTTP 404 status`` message, the same
While this example focuses on the ``HTTP 404`` message, the same
technique may be applied to define custom actions for any of the ``HTTP``
status response codes in the 400 and 500 range. You are well advised to use
this power judiciously.
@@ -22,17 +22,17 @@ Overview
Pecan makes it simple to customize error documents in two simple steps:
* :ref:`configure` of the HTTP status messages you want to handle
in your application's config.py
in your application's ``config.py``
* :ref:`controllers` to handle the status messages you have configured
.. _configure:
Configure Routing
-----------------
Let's configure our application *test_project* to route the ``HTTP 404``
(page not found) messages to our custom built controller.
Let's configure our application ``test_project`` to route ``HTTP 404 Page
Not Found`` messages to a custom controller.
First, we tweak test_project/config.py::
First, let's tweak ``test_project/config.py``::
# Pecan Application Configurations
app = {
@@ -43,8 +43,8 @@ First, we tweak test_project/config.py::
'reload' : True,
'debug' : True,
## modify the 'errors' element to direct HTTP status codes to your
## own controller.
# modify the 'errors' key to direct HTTP status codes to a custom
# controller
'errors' : {
#404 : '/error/404',
404 : '/notfound',
@@ -53,17 +53,17 @@ First, we tweak test_project/config.py::
}
Instead of the default error page, Pecan will now route 404 messages to our
very own controller named *notfound*.
very own controller named ``notfound``.
Let us now implement the *notfound* Controller
Next, let's implement the ``notfound`` controller.
.. _controllers:
Write Custom Controllers
------------------------
The easiest way to implement our custom *notfound* error controller is to
The easiest way to implement our custom ``notfound`` error controller is to
add it to ``test_project.root.RootController`` class
(typically in test_project/controllers/root.py)::
(typically in ``test_project/controllers/root.py``)::
from pecan import expose
from webob.exc import status_map
@@ -98,7 +98,7 @@ add it to ``test_project.root.RootController`` class
And that's it!
Notice that the only bit of code we added to our RootController is::
Notice that the only bit of code we added to our RootController was::
## custom handling of '404 Page Not Found' messages
@expose('error.html')
@@ -107,10 +107,10 @@ Notice that the only bit of code we added to our RootController is::
We simply ``@expose`` the ``notfound`` controller with the ``error.html``
template (which was conveniently generated for us and placed under
test_project/templates/ when we created ``test_project``). As with any common
controller *@expose*'d through a template, we return a dictionary of variables
for interpolation by the template renderer.
``test_project/templates/`` when we created ``test_project``). As with any
Pecan controller, we return a dictionary of variables for interpolation by the
template renderer.
Now we can modify the error template, or write a brand new one to make the 404
error status page or ``test_project`` as pretty or fancy as we want.
error status page of ``test_project`` as pretty or fancy as we want.

View File

@@ -504,7 +504,7 @@ class Pecan(object):
try:
# add context and environment to the request
state.request.context = {}
state.request.pecan = dict(content_type=None, validation_errors={})
state.request.pecan = dict(content_type=None)
self.handle_request()
except Exception, e:

6
pecan/ext/__init__.py Normal file
View File

@@ -0,0 +1,6 @@
def install():
from pecan.extensions import PecanExtensionImporter
PecanExtensionImporter().install()
install()
del install

83
pecan/extensions.py Normal file
View File

@@ -0,0 +1,83 @@
import sys
import pkg_resources
import inspect
import logging
log = logging.getLogger(__name__)
class PecanExtensionMissing(ImportError):
pass
class PecanExtensionImporter(object):
"""
Short circuits imports for extensions.
This is used in combination with ``pecan.ext`` so that when a user does
``from pecan.ext import foo``, it will attempt to map ``foo`` to a
registered setuptools entry point in some other (Pecan extension) project.
Conversely, an extension developer may define an entry point in his
``setup.py``, e.g.,
setup(
...
entry_points='''
[pecan.extension]
celery = pecancelery.lib.core
'''
)
This is mostly for convenience and consistency. In this way, Pecan can
maintain an ecosystem of extensions that share a common namespace,
``pecan.ext``, while still maintaining backwards compatability for simple
package names (e.g., ``pecancelery``).
"""
extension_module = 'pecan.ext'
prefix = extension_module + '.'
def install(self):
if self not in sys.meta_path:
sys.meta_path.append(self)
def __eq__(self, b):
return self.__class__.__module__ == b.__class__.__module__ and \
self.__class__.__name__ == b.__class__.__name__
def __ne__(self, b):
return not self.__eq__(b)
def find_module(self, fullname, path=None):
if fullname.startswith(self.prefix):
return self
def load_module(self, fullname):
if fullname in sys.modules:
return self
extname = fullname.split(self.prefix)[1]
module = self.find_module_for_extension(extname)
realname = module.__name__
try:
__import__(realname)
except ImportError:
raise sys.exc_info()
module = sys.modules[fullname] = sys.modules[realname]
if '.' not in extname:
setattr(sys.modules[self.extension_module], extname, module)
return module
def find_module_for_extension(self, name):
for ep in pkg_resources.iter_entry_points('pecan.extension'):
if ep.name != name:
continue
log.debug('%s loading extension %s', self.__class__.__name__, ep)
module = ep.load()
if not inspect.ismodule(module):
log.debug('%s is not a module, skipping...' % module)
continue
return module
raise PecanExtensionMissing(
'The `pecan.ext.%s` extension is not installed.' % name
)