Merge pull request #119 from ryanpetrello/next
Support for the ``pecan.ext`` namespace for extensions.
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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
6
pecan/ext/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
def install():
|
||||
from pecan.extensions import PecanExtensionImporter
|
||||
PecanExtensionImporter().install()
|
||||
|
||||
install()
|
||||
del install
|
||||
83
pecan/extensions.py
Normal file
83
pecan/extensions.py
Normal 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
|
||||
)
|
||||
Reference in New Issue
Block a user