diff options
author | Mark McClain <mark.mcclain@shootq.com> | 2011-09-02 15:55:01 -0400 |
---|---|---|
committer | Mark McClain <mark.mcclain@shootq.com> | 2011-09-02 15:55:01 -0400 |
commit | 615f6a1c55ab41724db6d08c99ba1a0884c7abc4 (patch) | |
tree | 72ffe6f387b3bfbf004ea2d5bed71e46821b8911 | |
parent | 3385dec6b5b22494723df90523a9bd53da2a9d7a (diff) | |
parent | f2bbbd8e56c308ff0eba7ca44b48cef046db0851 (diff) | |
download | pecan-615f6a1c55ab41724db6d08c99ba1a0884c7abc4.tar.gz |
Merge branch 'master' of github.com:pecan/pecan
-rw-r--r-- | .coveragerc | 2 | ||||
-rw-r--r-- | LICENSE | 24 | ||||
-rw-r--r-- | docs/source/conf.py | 4 | ||||
-rw-r--r-- | docs/source/templates.rst | 116 | ||||
-rw-r--r-- | pecan/__init__.py | 8 | ||||
-rw-r--r-- | pecan/configuration.py | 3 | ||||
-rw-r--r-- | pecan/templating.py | 2 | ||||
-rw-r--r-- | setup.py | 4 | ||||
-rw-r--r-- | tests/test_hooks.py | 16 | ||||
-rw-r--r-- | tests/test_templating.py | 21 |
10 files changed, 185 insertions, 15 deletions
diff --git a/.coveragerc b/.coveragerc index ec9a6c5..1e3fc17 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,2 @@ [run] -omit = pecan/commands/*.py, pecan/templates/__init__.py +omit = pecan/commands/*.py, pecan/templates/__init__.py, pecan/testing.py @@ -0,0 +1,24 @@ +Copyright (c) <2011>, Jonathan LaCour +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the <organization> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/source/conf.py b/docs/source/conf.py index df96d30..36e7d20 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -48,9 +48,9 @@ copyright = u'2010, Jonathan LaCour' # built documents. # # The short X.Y version. -version = '0.0.1' +version = '0.1.0' # The full version, including alpha/beta/rc tags. -release = '0.0.1' +release = '0.1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/templates.rst b/docs/source/templates.rst index e69de29..7ba33e1 100644 --- a/docs/source/templates.rst +++ b/docs/source/templates.rst @@ -0,0 +1,116 @@ +.. _templates: + +Templating in Pecan +=================== + +Pecan supports a variety of templating engines out of the box, and also provides +the ability to easily add support for new template engines. Currently, Pecan +supports the following templating engines: + + * `Mako <http://www.makotemplates.org/>`_ + * `Genshi <http://genshi.edgewall.org/>`_ + * `Kajiki <http://kajiki.pythonisito.com/>`_ + * `Jinja2 <http://jinja.pocoo.org/>`_ + * `JSON` + +The default template system is `mako`, but can be configured by passing the +``default_renderer`` key in your app configuration:: + + app = { + 'default_renderer' : 'kajiki', + # ... + } + +The available renderer type strings are `mako`, `genshi`, `kajiki`, `jinja`, +and `json`. + + +Using Renderers +--------------- + +:ref:`pecan_decorators` defines a decorator called ``@expose``, which is used +to flag a method as a controller. The ``@expose`` decorator takes a variety of +parameters, including a ``template`` argument, which is the path to the template +file to use for that controller. A controller will use the default template +engine, unless the path is prefixed by another renderer name:: + + class MyController(object): + @expose('path/to/mako/template.html') + def index(self): + return dict(message='I am a mako template') + + @expose('kajiki:path/to/kajiki/template.html') + def my_controller(self): + return dict(message='I am a kajiki template') + +For more information on the expose decorator, refer to :ref:`pecan_decorators`, +:ref:`pecan_core`, and :ref:`routing`. + + +Template Overrides and Manual Rendering +--------------------------------------- + +The :ref:`pecan_core` module contains two useful helper functions related to +templating. The first is ``override_template``, which allows you to overrides +which template is used in your controller, and the second is ``render``, which +allows you to manually render output using the Pecan templating framework. + +To use ``override_template``, simply call it within the body of your controller + +:: + + class MyController(object): + @expose('template_one.html') + def index(self): + # ... + override_template('template_two.html') + return dict(message='I will now render with template_two.html') + +The ``render`` helper is also quite simple to use:: + + @expose() + def controller(self): + return render('my_template.html', dict(message='I am the namespace')) + + +The JSON Renderer +----------------- + +Pecan provides a `JSON` renderer by simple passing ``@expose('json')``. For +more information on using `JSON` in Pecan, please refer to :ref:`jsonify` and +:ref:`pecan_jsonify`. + + +Custom Renderers +---------------- + +To define a custom renderer, you simply create a class that follows a simple +protocol:: + + class MyRenderer(object): + def __init__(self, path, extra_vars): + ''' + Your renderer is provided with a path to templates, + as configured by your application, and any extra + template variables, also as configured + ''' + pass + + def render(self, template_path, namespace): + ''' + Lookup the template based on the path, and render + your output based upon the supplied namespace + dictionary, as returned from the controller. + ''' + return str(namespace) + + +To enable your custom renderer, you can define a ``custom_renderers`` key In +your application's configuration:: + + app = { + 'custom_renderers' : { + 'my_renderer' : MyRenderer + }, + # ... + } diff --git a/pecan/__init__.py b/pecan/__init__.py index 20ff37a..cadbcef 100644 --- a/pecan/__init__.py +++ b/pecan/__init__.py @@ -25,6 +25,10 @@ def make_app(root, static_root=None, debug=False, errorcfg={}, wrap_app=None, lo ''' ''' + if hasattr(conf, 'requestviewer'): + existing_hooks = kw.get('hooks', []) + existing_hooks.append(RequestViewerHook(conf.requestviewer)) + kw['hooks'] = existing_hooks app = Pecan(root, **kw) if wrap_app: @@ -39,8 +43,4 @@ def make_app(root, static_root=None, debug=False, errorcfg={}, wrap_app=None, lo app = Cascade([StaticURLParser(static_root), app]) if isinstance(logging, dict) or logging == True: app = TransLogger(app, **(isinstance(logging, dict) and logging or {})) - if hasattr(conf, 'requestviewer'): - existing_hooks = kw.get('hooks', []) - existing_hooks.append(RequestViewerHook(conf.requestviewer)) - kw['hooks'] = existing_hooks return app diff --git a/pecan/configuration.py b/pecan/configuration.py index 45a2007..7844ccd 100644 --- a/pecan/configuration.py +++ b/pecan/configuration.py @@ -175,9 +175,6 @@ def import_module(conf): :param conf: The string to the configuration. Automatically strips off ".py" file extensions. ''' - if conf.endswith('.py'): - conf = conf[:-3] - if '.' in conf: parts = conf.split('.') name = '.'.join(parts[:-1]) diff --git a/pecan/templating.py b/pecan/templating.py index a69248a..eedfe7a 100644 --- a/pecan/templating.py +++ b/pecan/templating.py @@ -1,7 +1,5 @@ import cgi -__all__ = ['RendererFactory'] - _builtin_renderers = {} error_formatters = [] @@ -1,6 +1,6 @@ from setuptools import setup, Command, find_packages -version = '0.1' +version = '0.1.0' # # integration with py.test for `python setup.py test` @@ -46,7 +46,7 @@ except: setup( name = 'pecan', version = version, - description = "A WSGI object-dispatching web framework, in the spirit of TurboGears, only much much smaller, with many fewer dependencies.", + description = "A WSGI object-dispatching web framework, designed to be lean and fast, with few dependancies.", long_description = None, classifiers = [], keywords = '', diff --git a/tests/test_hooks.py b/tests/test_hooks.py index e50d5ec..f40aa44 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -4,6 +4,7 @@ from pecan.core import state from pecan.hooks import PecanHook, TransactionHook, HookController, RequestViewerHook from pecan.configuration import Config from pecan.decorators import transactional, after_commit +from copy import copy from formencode import Schema, validators from webtest import TestApp @@ -1065,6 +1066,21 @@ class TestTransactionHook(object): class TestRequestViewerHook(object): + + def test_hook_from_config(self): + from pecan.configuration import _runtime_conf as conf + conf['requestviewer'] = { + 'blacklist': ['/favicon.ico'] + } + + class RootController(object): + pass + + app = make_app(RootController()) + while hasattr(app, 'application'): + app = app.application + del conf.__values__['requestviewer'] + assert app.hooks def test_basic_single_default_hook(self): diff --git a/tests/test_templating.py b/tests/test_templating.py index bfeabcd..64e6e06 100644 --- a/tests/test_templating.py +++ b/tests/test_templating.py @@ -1,6 +1,9 @@ from unittest import TestCase -from pecan.templating import RendererFactory +from pecan.templating import RendererFactory, format_line_context + +import os +import tempfile class TestTemplate(TestCase): def setUp(self): @@ -26,3 +29,19 @@ class TestTemplate(TestCase): self.assertEqual(extra_vars.make_ns({'bar':2}), {'foo':1, 'bar':2}) self.assertEqual(extra_vars.make_ns({'foo':2}), {'foo':2}) + + +class TestTemplateLineFormat(TestCase): + + def setUp(self): + self.f = tempfile.NamedTemporaryFile() + + def tearDown(self): + os.remove(self.f.name) + + def test_format_line_context(self): + for i in range(11): + self.f.write('Testing Line %d\n' % i) + self.f.flush() + + assert format_line_context(self.f.name, 0).count('Testing Line') == 10 |