diff options
author | Ryan Petrello <lists@ryanpetrello.com> | 2013-07-22 11:37:03 -0400 |
---|---|---|
committer | Ryan Petrello <lists@ryanpetrello.com> | 2013-07-22 11:37:03 -0400 |
commit | 8e1ea099d5e64a4df8a11a3e73289368e098beeb (patch) | |
tree | fdaa53f7da6b658afd928d9fcd7f48f4ab8e7a3d | |
parent | 5f6f75a76f603397c0e2374bfefeb4f58526ecff (diff) | |
parent | b634bfb4736e4387ff12f8c3f8ca43d483aa4aa4 (diff) | |
download | pecan-8e1ea099d5e64a4df8a11a3e73289368e098beeb.tar.gz |
Merge branch 'next'0.3.1
-rw-r--r-- | README.rst | 4 | ||||
-rw-r--r-- | docs/source/changes.rst | 5 | ||||
-rw-r--r-- | docs/source/conf.py | 7 | ||||
-rw-r--r-- | docs/source/deployment.rst | 80 | ||||
-rw-r--r-- | docs/source/hooks.rst | 6 | ||||
-rw-r--r-- | pecan/core.py | 17 | ||||
-rw-r--r-- | pecan/tests/test_hooks.py | 30 | ||||
-rw-r--r-- | setup.py | 6 |
8 files changed, 142 insertions, 13 deletions
@@ -7,6 +7,10 @@ dependencies. .. _travis: http://travis-ci.org/dreamhost/pecan .. |travis| image:: https://secure.travis-ci.org/dreamhost/pecan.png +.. image:: https://pypip.in/v/pecan/badge.png + :target: https://crate.io/packages/pecan/ + :alt: Latest PyPI version + |travis|_ Installing diff --git a/docs/source/changes.rst b/docs/source/changes.rst index 8ca46aa..c0a21ba 100644 --- a/docs/source/changes.rst +++ b/docs/source/changes.rst @@ -1,3 +1,8 @@ +0.3.1 +===== +* ``on_error`` hooks can now return a Pecan Response objects. +* Minor documentation and release tooling updates. + 0.3.0 ===== * Pecan now supports Python 2.6, 2.7, 3.2, and 3.3. diff --git a/docs/source/conf.py b/docs/source/conf.py index e2bdefb..4b89f31 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -11,6 +11,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. +import pkg_resources + import sys import os @@ -49,9 +51,10 @@ copyright = u'2010, Jonathan LaCour' # built documents. # # The short X.Y version. -version = '0.3.0' +dist = pkg_resources.get_distribution('pecan') +version = release = dist.version # The full version, including alpha/beta/rc tags. -release = '0.3.0' +#release = '0.3.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/deployment.rst b/docs/source/deployment.rst index b95ab39..9cc1fec 100644 --- a/docs/source/deployment.rst +++ b/docs/source/deployment.rst @@ -11,7 +11,7 @@ probably vary. .. :: - While Pecan comes packaged with a simple server *for development use* + While Pecan comes packaged with a simple server *for development use* (``pecan serve``), using a *production-ready* server similar to the ones described in this document is **very highly encouraged**. @@ -82,7 +82,7 @@ Considerations for Static Files ------------------------------- Pecan comes with static file serving (e.g., CSS, Javascript, images) -middleware which is **not** recommended for use in production. +middleware which is **not** recommended for use in production. In production, Pecan doesn't serve media files itself; it leaves that job to whichever web server you choose. @@ -93,7 +93,7 @@ performance reasons). There are several popular ways to accomplish this. Here are two: 1. Set up a proxy server (such as `nginx <http://nginx.org/en>`__, `cherokee - <http://www.cherokee-project.com>`__, or `lighttpd + <http://www.cherokee-project.com>`__, :ref:`cherrypy`, or `lighttpd <http://www.lighttpd.net/>`__) to serve static files and proxy application requests through to your WSGI application: @@ -204,3 +204,77 @@ Pecan's default project:: $ pecan create simpleapp && cd simpleapp $ python setup.py develop $ gunicorn_pecan config.py + + +.. _cherrypy: + +CherryPy +++++++++ + +`CherryPy <http://cherrypy.org/>`__ offers a pure Python HTTP/1.1-compliant WSGI +thread-pooled web server. It can support Pecan applications easily and even +serve static files like a production server would do. + +The examples that follow are geared towards using CherryPy as the server in +charge of handling a Pecan app along with serving static files. + +:: + + $ pip install cherrypy + $ pecan create simpleapp && cd simpleapp + $ python setup.py develop + +To run with CherryPy, the easiest approach is to create a script in the root of +the project (alongside ``setup.py``), so that we can describe how our example +application should be served. This is how the script (named ``run.py``) looks:: + + import os + import cherrypy + from cherrypy import wsgiserver + + from pecan import deploy + + simpleapp_wsgi_app = deploy('/path/to/production_config.py') + + public_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'public')) + + # A dummy class for our Root object + # necessary for some CherryPy machinery + class Root(object): + pass + + def make_static_config(static_dir_name): + """ + All custom static configurations are set here, since most are common, it + makes sense to generate them just once. + """ + static_path = os.path.join('/', static_dir_name) + path = os.path.join(public_path, static_dir_name) + configuration = { + static_path: { + 'tools.staticdir.on': True, + 'tools.staticdir.dir': path + } + } + return cherrypy.tree.mount(Root(), '/', config=configuration) + + # Assuming your app has media on diferent paths, like 'css', and 'images' + application = wsgiserver.WSGIPathInfoDispatcher({ + '/': simpleapp_wsgi_app, + '/css': make_static_config('css'), + '/images': make_static_config('images') + } + ) + + server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', 8080), application, + server_name='simpleapp') + + try: + server.start() + except KeyboardInterrupt: + print "Terminating server..." + server.stop() + +To start the server, simply call it with the Python executable:: + + $ python run.py diff --git a/docs/source/hooks.rst b/docs/source/hooks.rst index 9a9d099..e1c54c4 100644 --- a/docs/source/hooks.rst +++ b/docs/source/hooks.rst @@ -46,8 +46,10 @@ object which includes useful information, such as the request and response objects, and which controller was selected by Pecan's routing. -:func:`on_error` is passed a shared state object **and** the original exception. - +:func:`on_error` is passed a shared state object **and** the original exception. If +an :func:`on_error` handler returns a Response object, this response will be returned +to the end user and no furthur :func:`on_error` hooks will be executed. + Attaching Hooks --------------- diff --git a/pecan/core.py b/pecan/core.py index c09763d..29930b6 100644 --- a/pecan/core.py +++ b/pecan/core.py @@ -299,7 +299,11 @@ class Pecan(object): hooks = reversed(state.hooks) for hook in hooks: - getattr(hook, hook_type)(*args) + result = getattr(hook, hook_type)(*args) + # on_error hooks can choose to return a Response, which will + # be used instead of the standard error pages. + if hook_type == 'on_error' and isinstance(result, Response): + return result def get_args(self, req, all_params, remainder, argspec, im_self): ''' @@ -564,11 +568,16 @@ class Pecan(object): environ['pecan.original_exception'] = e # if this is not an internal redirect, run error hooks + on_error_result = None if not isinstance(e, ForwardRequestException): - self.handle_hooks('on_error', state, e) + on_error_result = self.handle_hooks('on_error', state, e) - if not isinstance(e, exc.HTTPException): - raise + # if the on_error handler returned a Response, use it. + if isinstance(on_error_result, Response): + state.response = on_error_result + else: + if not isinstance(e, exc.HTTPException): + raise finally: # handle "after" hooks self.handle_hooks('after', state) diff --git a/pecan/tests/test_hooks.py b/pecan/tests/test_hooks.py index 66947ca..246af76 100644 --- a/pecan/tests/test_hooks.py +++ b/pecan/tests/test_hooks.py @@ -1,7 +1,10 @@ from webtest import TestApp from six import b as b_ +from six import u as u_ from six.moves import cStringIO as StringIO +from webob import Response + from pecan import make_app, expose, redirect, abort from pecan.hooks import ( PecanHook, TransactionHook, HookController, RequestViewerHook @@ -133,6 +136,33 @@ class TestHooks(PecanTestCase): assert run_hook[0] == 'on_route' assert run_hook[1] == 'error' + def test_on_error_response_hook(self): + run_hook = [] + + class RootController(object): + @expose() + def causeerror(self): + return [][1] + + class ErrorHook(PecanHook): + def on_error(self, state, e): + run_hook.append('error') + + r = Response() + r.text = u_('on_error') + + return r + + app = TestApp(make_app(RootController(), hooks=[ + ErrorHook() + ])) + + response = app.get('/causeerror') + + assert len(run_hook) == 1 + assert run_hook[0] == 'error' + assert response.text == 'on_error' + def test_prioritized_hooks(self): run_hook = [] @@ -2,7 +2,7 @@ import sys from setuptools import setup, find_packages -version = '0.3.0' +version = '0.3.1' # # determine requirements @@ -43,7 +43,6 @@ except: tests_require = requirements + [ 'virtualenv', - 'Jinja2', 'gunicorn', 'mock' ] @@ -58,6 +57,9 @@ else: # Genshi added Python3 support in 0.7 tests_require += ['Genshi>=0.7'] +if sys.version_info < (3, 0) or sys.version_info >= (3, 3): + tests_require += ['Jinja2'] + # # call setup # |