Merge branch 'master' of github.com:pecan/pecan

This commit is contained in:
Jeremy M. Jones
2011-09-02 16:15:57 -04:00
9 changed files with 185 additions and 12 deletions

View File

@@ -1,2 +1,2 @@
[run]
omit = pecan/commands/*.py, pecan/templates/__init__.py
omit = pecan/commands/*.py, pecan/templates/__init__.py, pecan/testing.py

24
LICENSE Normal file
View File

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

View File

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

View File

@@ -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
},
# ...
}

View File

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

View File

@@ -1,7 +1,5 @@
import cgi
__all__ = ['RendererFactory']
_builtin_renderers = {}
error_formatters = []

View File

@@ -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 = '',

View File

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

View File

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