summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2012-07-29 10:01:40 -0400
committerChris McDonough <chrism@plope.com>2012-07-29 10:01:40 -0400
commitd204b348535e4f28df4d59d25aa9b493306d41e5 (patch)
tree8027ef04a07bd0c01f60d347d8fca2ec4d8acc7c
parentc7fcdf1665cfdc1173559baa0a56d9a06fcba448 (diff)
parent14f9fe44ec75c055d89374a7852e1ca2af0ff31c (diff)
downloadpyramid-d204b348535e4f28df4d59d25aa9b493306d41e5.tar.gz
pyramid-d204b348535e4f28df4d59d25aa9b493306d41e5.tar.bz2
pyramid-d204b348535e4f28df4d59d25aa9b493306d41e5.zip
Merge branch 'master' of github.com:Pylons/pyramid
-rw-r--r--.travis.yml19
-rw-r--r--CHANGES.txt13
-rw-r--r--CONTRIBUTORS.txt4
-rw-r--r--HACKING.txt10
-rw-r--r--docs/narr/commandline.rst7
-rw-r--r--docs/narr/hooks.rst30
-rw-r--r--docs/narr/logging.rst7
-rw-r--r--docs/narr/sessions.rst13
-rw-r--r--docs/narr/templates.rst16
-rw-r--r--docs/narr/urldispatch.rst6
-rw-r--r--docs/tutorials/modwsgi/index.rst11
-rw-r--r--docs/tutorials/wiki2/design.rst2
-rw-r--r--pyramid/events.py32
-rw-r--r--pyramid/mako_templating.py34
-rw-r--r--pyramid/tests/fixtures/components.mak3
-rw-r--r--pyramid/tests/fixtures/hellocompo.mak3
-rw-r--r--pyramid/tests/test_mako_templating.py34
-rw-r--r--pyramid/tests/test_urldispatch.py8
-rw-r--r--pyramid/urldispatch.py4
-rw-r--r--pyramid/view.py6
-rw-r--r--tox.ini10
21 files changed, 227 insertions, 45 deletions
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..490fd2df7
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,19 @@
+language: python
+
+python:
+ - 2.6
+ - 2.7
+ - pypy
+ - 3.2
+
+# Why does travis-ci's PyPy blow up? Pyramid's tests
+# run fine under tox.
+matrix:
+ allow_failures:
+ - python: pypy
+
+script: python setup.py test
+
+notifications:
+ email:
+ - pyramid-checkins@lists.repoze.org
diff --git a/CHANGES.txt b/CHANGES.txt
index 7c2af4451..c6afaf0c7 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -9,6 +9,14 @@ Bug Fixes
return the empty list. This was incorrect, it should have unconditionally
returned ``[Everyone]``, and now does.
+- Explicit url dispatch regexes can now contain colons.
+ https://github.com/Pylons/pyramid/issues/629
+
+- On at least one 64-bit Ubuntu system under Python 3.2, using the
+ ``view_config`` decorator caused a ``RuntimeError: dictionary changed size
+ during iteration`` exception. It no longer does. See
+ https://github.com/Pylons/pyramid/issues/635 for more information.
+
Features
--------
@@ -41,3 +49,8 @@ Features
- The static view machinery now raises (rather than returns) ``HTTPNotFound``
and ``HTTPMovedPermanently`` exceptions, so these can be caught by the
NotFound view (and other exception views).
+
+- The mako renderer now accepts a def name and returns the template def
+ result for the view being called. The uri format using an asset spec is
+ package:path/to/template#defname.mako. The old way of returning a tuple
+ from the view is supported for backward compatibility, ('defname', {}).
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 98f73d5f9..c3995aaba 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -172,3 +172,7 @@ Contributors
- Wayne Witzel III, 2012/03/27
- Marin Rukavina, 2012/05/03
+
+- Marc Abramowitz, 2012/06/13
+
+- Jeff Cook, 2012/06/16
diff --git a/HACKING.txt b/HACKING.txt
index dd735bf22..38c263ed7 100644
--- a/HACKING.txt
+++ b/HACKING.txt
@@ -127,13 +127,19 @@ using to develop Pyramid):
1. Run ``$yourvenv/bin/python setup.py dev docs``. This will cause Sphinx
and all development requirements to be installed in your virtualenv.
-2. cd to the ``docs`` directory within your Pyramid checkout and execute
+2. Update all git submodules from the top-level of your Pyramid checkout, like
+ so:
+ git submodule update --init --recursive
+ This will checkout theme subrepositories and prevent error conditions when
+ HTML docs are generated.
+
+3. cd to the ``docs`` directory within your Pyramid checkout and execute
``make clean html SPHINXBUILD=$yourvenv/bin/sphinx-build``. The
``SPHINXBUILD=...`` hair is there in order to tell it to use the
virtualenv Python, which will have both Sphinx and Pyramid (for API
documentation generation) installed.
-3. Open the ``docs/_build/html/index.html`` file to see the resulting HTML
+4. Open the ``docs/_build/html/index.html`` file to see the resulting HTML
rendering.
Change Log
diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst
index 4be436836..af53c1f78 100644
--- a/docs/narr/commandline.rst
+++ b/docs/narr/commandline.rst
@@ -654,8 +654,11 @@ use the following command:
.. code-block:: python
- import logging.config
- logging.config.fileConfig('/path/to/my/development.ini')
+ import pyramid.paster
+ pyramid.paster.setup_logging('/path/to/my/development.ini')
+
+See :ref:`logging_chapter` for more information on logging within
+:app:`Pyramid`.
.. index::
single: console script
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index a2143b3c5..332805152 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -289,6 +289,36 @@ keys added to the renderer globals dictionary by all
:class:`pyramid.events.BeforeRender` subscribers and renderer globals
factories must be unique.
+The dictionary returned from the view is accessible through the
+:attr:`rendering_val` attribute of a :class:`~pyramid.events.BeforeRender`
+event.
+
+Suppose you return ``{'mykey': 'somevalue', 'mykey2': 'somevalue2'}`` from
+your view callable, like so:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.view import view_config
+
+ @view_config(renderer='some_renderer')
+ def myview(request):
+ return {'mykey': 'somevalue', 'mykey2': 'somevalue2'}
+
+:attr:`rendering_val` can be used to access these values from the
+:class:`~pyramid.events.BeforeRender` object:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.events import subscriber
+ from pyramid.events import BeforeRender
+
+ @subscriber(BeforeRender)
+ def read_return(event):
+ # {'mykey': 'somevalue'} is returned from the view
+ print(event.rendering_val['mykey'])
+
See the API documentation for the :class:`~pyramid.events.BeforeRender` event
interface at :class:`pyramid.interfaces.IBeforeRender`.
diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst
index 044655c1f..f4c38abb6 100644
--- a/docs/narr/logging.rst
+++ b/docs/narr/logging.rst
@@ -14,7 +14,7 @@ how to send log messages to loggers that you've configured.
which help configure logging. All of the scaffolds which ship along with
:app:`Pyramid` do this. If you're not using a scaffold, or if you've used
a third-party scaffold which does not create these files, the
- configuration information in this chapter will not be applicable.
+ configuration information in this chapter may not be applicable.
.. _logging_config:
@@ -36,10 +36,11 @@ application-related and logging-related sections in the configuration file
can coexist peacefully, and the logging-related sections in the file are used
from when you run ``pserve``.
-The ``pserve`` command calls the `logging.fileConfig function
+The ``pserve`` command calls the :func:`pyramid.paster.setup_logging`
+function, a thin wrapper around the `logging.fileConfig
<http://docs.python.org/lib/logging-config-api.html>`_ using the specified
ini file if it contains a ``[loggers]`` section (all of the
-scaffold-generated ``.ini`` files do). ``logging.fileConfig`` reads the
+scaffold-generated ``.ini`` files do). ``setup_logging`` reads the
logging configuration from the ini file upon which ``pserve`` was
invoked.
diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst
index 6ff9e3dea..1aa1b6341 100644
--- a/docs/narr/sessions.rst
+++ b/docs/narr/sessions.rst
@@ -151,13 +151,12 @@ Using Alternate Session Factories
---------------------------------
At the time of this writing, exactly one alternate session factory
-implementation exists, named ``pyramid_beaker``. This is a session
-factory that uses the `Beaker <http://beaker.groovie.org/>`_ library
-as a backend. Beaker has support for file-based sessions, database
-based sessions, and encrypted cookie-based sessions. See
-`http://github.com/Pylons/pyramid_beaker
-<http://github.com/Pylons/pyramid_beaker>`_ for more information about
-``pyramid_beaker``.
+implementation exists, named ``pyramid_beaker``. This is a session factory
+that uses the `Beaker <http://beaker.groovie.org/>`_ library as a backend.
+Beaker has support for file-based sessions, database based sessions, and
+encrypted cookie-based sessions. See `the pyramid_beaker documentation
+<http://docs.pylonsproject.org/projects/pyramid_beaker/en/latest/>`_ for more
+information about ``pyramid_beaker``.
.. index::
single: session factory (custom)
diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst
index 9db0b1c4d..860010a1a 100644
--- a/docs/narr/templates.rst
+++ b/docs/narr/templates.rst
@@ -714,6 +714,22 @@ This template doesn't use any advanced features of Mako, only the
:term:`renderer globals`. See the `the Mako documentation
<http://www.makotemplates.org/>`_ to use more advanced features.
+Using def inside Mako Templates
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To use a def inside a Mako template, given a :term:`Mako` template file named
+``foo.mak`` and a def named ``bar``, you can configure the template as a
+:term:`renderer` like so:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.view import view_config
+
+ @view_config(renderer='foo#bar.mak')
+ def my_view(request):
+ return {'project':'my project'}
+
.. index::
single: automatic reloading of templates
single: template automatic reload
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst
index acbccbdfd..ecf3d026a 100644
--- a/docs/narr/urldispatch.rst
+++ b/docs/narr/urldispatch.rst
@@ -954,7 +954,7 @@ will be prepended with the first:
from pyramid.config import Configurator
def timing_include(config):
- config.add_route('show_times', /times')
+ config.add_route('show_times', '/times')
def users_include(config):
config.add_route('show_users', '/show')
@@ -966,7 +966,7 @@ will be prepended with the first:
In the above configuration, the ``show_users`` route will still have an
effective route pattern of ``/users/show``. The ``show_times`` route
-however, will have an effective pattern of ``/users/timing/show_times``.
+however, will have an effective pattern of ``/users/timing/times``.
Route prefixes have no impact on the requirement that the set of route
*names* in any given Pyramid configuration must be entirely unique. If you
@@ -981,7 +981,7 @@ that may be added in the future. For example:
from pyramid.config import Configurator
def timing_include(config):
- config.add_route('timing.show_times', /times')
+ config.add_route('timing.show_times', '/times')
def users_include(config):
config.add_route('users.show_users', '/show')
diff --git a/docs/tutorials/modwsgi/index.rst b/docs/tutorials/modwsgi/index.rst
index e070f8eda..d11167344 100644
--- a/docs/tutorials/modwsgi/index.rst
+++ b/docs/tutorials/modwsgi/index.rst
@@ -73,9 +73,10 @@ commands and files.
.. code-block:: python
- from pyramid.paster import get_app
- application = get_app(
- '/Users/chrism/modwsgi/env/myapp/production.ini', 'main')
+ from pyramid.paster import get_app, setup_logging
+ ini_path = '/Users/chrism/modwsgi/env/myapp/production.ini'
+ setup_logging(ini_path)
+ application = get_app(ini_path, 'main')
The first argument to ``get_app`` is the project configuration file
name. It's best to use the ``production.ini`` file provided by your
@@ -85,6 +86,10 @@ commands and files.
``application`` is important: mod_wsgi requires finding such an
assignment when it opens the file.
+ The call to ``setup_logging`` initializes the standard library's
+ `logging` module to allow logging within your application.
+ See :ref:`logging_config`.
+
#. Make the ``pyramid.wsgi`` script executable.
.. code-block:: text
diff --git a/docs/tutorials/wiki2/design.rst b/docs/tutorials/wiki2/design.rst
index 2e6fc0e77..deaf32ef6 100644
--- a/docs/tutorials/wiki2/design.rst
+++ b/docs/tutorials/wiki2/design.rst
@@ -20,7 +20,7 @@ Models
We'll be using a SQLite database to hold our wiki data, and we'll be using
:term:`SQLAlchemy` to access the data in this database.
-Within the database, we define a single table named `tables`, whose elements
+Within the database, we define a single table named `pages`, whose elements
will store the wiki pages. There are two columns: `name` and `data`.
URLs like ``/PageName`` will try to find an element in
diff --git a/pyramid/events.py b/pyramid/events.py
index e181ef33f..db274823c 100644
--- a/pyramid/events.py
+++ b/pyramid/events.py
@@ -200,10 +200,34 @@ class BeforeRender(dict):
setting an overriding value (which can be done using ``.get`` or
``__contains__`` of the event object).
- The event has an additional attribute named ``rendering_val``. This is
- the (non-system) value returned by a view or passed to ``render*`` as
- ``value``. This feature is new in Pyramid 1.2.
-
+ The dictionary returned from the view is accessible through the
+ :attr:`rendering_val` attribute of a :class:`~pyramid.events.BeforeRender`
+ event.
+
+ Suppose you return ``{'mykey': 'somevalue', 'mykey2': 'somevalue2'}`` from
+ your view callable, like so::
+
+ from pyramid.view import view_config
+
+ @view_config(renderer='some_renderer')
+ def myview(request):
+ return {'mykey': 'somevalue', 'mykey2': 'somevalue2'}
+
+ :attr:`rendering_val` can be used to access these values from the
+ :class:`~pyramid.events.BeforeRender` object::
+
+ from pyramid.events import subscriber
+ from pyramid.events import BeforeRender
+
+ @subscriber(BeforeRender)
+ def read_return(event):
+ # {'mykey': 'somevalue'} is returned from the view
+ print(event.rendering_val['mykey'])
+
+ In other words, :attr:`rendering_val` is the (non-system) value returned by a
+ view or passed to ``render*`` as ``value``. This feature is new in Pyramid
+ 1.2.
+
For a description of the values present in the renderer globals dictionary,
see :ref:`renderer_system_values`.
diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py
index 208e54bf5..bb4ccb2f0 100644
--- a/pyramid/mako_templating.py
+++ b/pyramid/mako_templating.py
@@ -1,4 +1,5 @@
import os
+import re
import sys
import threading
@@ -76,7 +77,13 @@ class MakoRendererFactoryHelper(object):
self.settings_prefix = settings_prefix
def __call__(self, info):
- path = info.name
+ p = re.compile(
+ r'(?P<asset>[\w_.:/]+)'
+ r'(?:\#(?P<defname>[\w_]+))?'
+ r'(\.(?P<ext>.*))'
+ )
+ asset, defname, ext = p.match(info.name).group('asset', 'defname', 'ext')
+ path = '%s.%s' % (asset, ext)
registry = info.registry
settings = info.settings
settings_prefix = self.settings_prefix
@@ -141,7 +148,7 @@ class MakoRendererFactoryHelper(object):
finally:
registry_lock.release()
- return MakoLookupTemplateRenderer(path, lookup)
+ return MakoLookupTemplateRenderer(path, defname, lookup)
renderer_factory = MakoRendererFactoryHelper('mako.')
@@ -156,8 +163,16 @@ class MakoRenderingException(Exception):
@implementer(ITemplateRenderer)
class MakoLookupTemplateRenderer(object):
- def __init__(self, path, lookup):
+ """ Render a :term:`Mako` template using the template
+ implied by the ``path`` argument.The ``path`` argument may be a
+ package-relative path, an absolute path, or a :term:`asset
+ specification`. If a defname is defined, in the form of
+ package:path/to/template#defname.mako, a function named ``defname``
+ inside the template will then be rendered.
+ """
+ def __init__(self, path, defname, lookup):
self.path = path
+ self.defname = defname
self.lookup = lookup
def implementation(self):
@@ -167,16 +182,19 @@ class MakoLookupTemplateRenderer(object):
context = system.pop('context', None)
if context is not None:
system['_context'] = context
- def_name = None
- if isinstance(value, tuple):
- def_name, value = value
+ if self.defname is None:
+ if isinstance(value, tuple):
+ self.defname, value = value
+ else:
+ if isinstance(value, tuple):
+ _, value = value
try:
system.update(value)
except (TypeError, ValueError):
raise ValueError('renderer was passed non-dictionary as value')
template = self.implementation()
- if def_name is not None:
- template = template.get_def(def_name)
+ if self.defname is not None:
+ template = template.get_def(self.defname)
try:
result = template.render_unicode(**system)
except:
diff --git a/pyramid/tests/fixtures/components.mak b/pyramid/tests/fixtures/components.mak
new file mode 100644
index 000000000..cc886805c
--- /dev/null
+++ b/pyramid/tests/fixtures/components.mak
@@ -0,0 +1,3 @@
+<%def name="comp()">
+World!
+</%def> \ No newline at end of file
diff --git a/pyramid/tests/fixtures/hellocompo.mak b/pyramid/tests/fixtures/hellocompo.mak
new file mode 100644
index 000000000..142676a11
--- /dev/null
+++ b/pyramid/tests/fixtures/hellocompo.mak
@@ -0,0 +1,3 @@
+<%namespace name="comp" file="pyramid.tests:fixtures/components.mak"/>
+Namespace
+Hello ${comp.comp()} \ No newline at end of file
diff --git a/pyramid/tests/test_mako_templating.py b/pyramid/tests/test_mako_templating.py
index fbb04273b..41fa9bdc4 100644
--- a/pyramid/tests/test_mako_templating.py
+++ b/pyramid/tests/test_mako_templating.py
@@ -315,7 +315,7 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase):
def test_instance_implements_ITemplate(self):
from zope.interface.verify import verifyObject
from pyramid.interfaces import ITemplateRenderer
- verifyObject(ITemplateRenderer, self._makeOne(None, None))
+ verifyObject(ITemplateRenderer, self._makeOne(None, None, None))
def test_class_implements_ITemplate(self):
from zope.interface.verify import verifyClass
@@ -324,7 +324,7 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase):
def test_call(self):
lookup = DummyLookup()
- instance = self._makeOne('path', lookup)
+ instance = self._makeOne('path', None, lookup)
result = instance({}, {'system':1})
self.assertTrue(isinstance(result, text_type))
self.assertEqual(result, text_('result'))
@@ -332,7 +332,7 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase):
def test_call_with_system_context(self):
# lame
lookup = DummyLookup()
- instance = self._makeOne('path', lookup)
+ instance = self._makeOne('path', None, lookup)
result = instance({}, {'context':1})
self.assertTrue(isinstance(result, text_type))
self.assertEqual(result, text_('result'))
@@ -340,21 +340,36 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase):
def test_call_with_tuple_value(self):
lookup = DummyLookup()
- instance = self._makeOne('path', lookup)
+ instance = self._makeOne('path', None, lookup)
result = instance(('fub', {}), {'context':1})
self.assertEqual(lookup.deffed, 'fub')
self.assertEqual(result, text_('result'))
self.assertEqual(lookup.values, {'_context':1})
+ def test_call_with_defname(self):
+ lookup = DummyLookup()
+ instance = self._makeOne('path', 'defname', lookup)
+ result = instance({}, {'system':1})
+ self.assertTrue(isinstance(result, text_type))
+ self.assertEqual(result, text_('result'))
+
+ def test_call_with_defname_with_tuple_value(self):
+ lookup = DummyLookup()
+ instance = self._makeOne('path', 'defname', lookup)
+ result = instance(('defname', {}), {'context':1})
+ self.assertEqual(lookup.deffed, 'defname')
+ self.assertEqual(result, text_('result'))
+ self.assertEqual(lookup.values, {'_context':1})
+
def test_call_with_nondict_value(self):
lookup = DummyLookup()
- instance = self._makeOne('path', lookup)
+ instance = self._makeOne('path', None, lookup)
self.assertRaises(ValueError, instance, None, {})
def test_call_render_raises(self):
from pyramid.mako_templating import MakoRenderingException
lookup = DummyLookup(exc=NotImplementedError)
- instance = self._makeOne('path', lookup)
+ instance = self._makeOne('path', None, lookup)
try:
instance({}, {})
except MakoRenderingException as e:
@@ -364,7 +379,7 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase):
def test_implementation(self):
lookup = DummyLookup()
- instance = self._makeOne('path', lookup)
+ instance = self._makeOne('path', None, lookup)
result = instance.implementation().render_unicode()
self.assertTrue(isinstance(result, text_type))
self.assertEqual(result, text_('result'))
@@ -402,6 +417,11 @@ class TestIntegration(unittest.TestCase):
result = render('hello_inherit_pkg.mak', {}).replace('\r','')
self.assertEqual(result, text_('Layout\nHello World!\n'))
+ def test_render_namespace(self):
+ from pyramid.renderers import render
+ result = render('hellocompo.mak', {}).replace('\r','')
+ self.assertEqual(result, text_('\nNamespace\nHello \nWorld!\n'))
+
def test_render_to_response(self):
from pyramid.renderers import render_to_response
result = render_to_response('helloworld.mak', {'a':1})
diff --git a/pyramid/tests/test_urldispatch.py b/pyramid/tests/test_urldispatch.py
index e15242f75..b2164717e 100644
--- a/pyramid/tests/test_urldispatch.py
+++ b/pyramid/tests/test_urldispatch.py
@@ -311,6 +311,14 @@ class TestCompileRoute(unittest.TestCase):
self.assertEqual(matcher('foo/baz/biz/buz/bar'), None)
self.assertEqual(generator({'baz':1, 'buz':2, 'bar': 'html'}),
'/foo/1/biz/2.html')
+
+ def test_custom_regex_with_colons(self):
+ matcher, generator = self._callFUT('foo/{baz}/biz/{buz:(?:[^/\.]+)}.{bar}')
+ self.assertEqual(matcher('/foo/baz/biz/buz.bar'),
+ {'baz':'baz', 'buz':'buz', 'bar':'bar'})
+ self.assertEqual(matcher('foo/baz/biz/buz/bar'), None)
+ self.assertEqual(generator({'baz':1, 'buz':2, 'bar': 'html'}),
+ '/foo/1/biz/2.html')
def test_mixed_newstyle_oldstyle_pattern_defaults_to_newstyle(self):
# pattern: '\\/foo\\/(?P<baz>abc)\\/biz\\/(?P<buz>[^/]+)\\/bar$'
diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py
index cccff14ba..4182ea665 100644
--- a/pyramid/urldispatch.py
+++ b/pyramid/urldispatch.py
@@ -148,7 +148,9 @@ def _compile_route(route):
name = pat.pop() # unicode
name = name[1:-1]
if ':' in name:
- name, reg = name.split(':')
+ # reg may contain colons as well,
+ # so we must strictly split name into two parts
+ name, reg = name.split(':', 1)
else:
reg = '[^/]+'
gen.append('%%(%s)s' % native_(name)) # native
diff --git a/pyramid/view.py b/pyramid/view.py
index bb531c326..1df0849c0 100644
--- a/pyramid/view.py
+++ b/pyramid/view.py
@@ -199,7 +199,7 @@ class view_config(object):
custom_predicates=default, context=default,
decorator=default, mapper=default, http_cache=default,
match_param=default):
- L = locals()
+ L = dict(locals()) # See issue #635 for dict() rationale
if (context is not default) or (for_ is not default):
L['context'] = context or for_
for k, v in L.items():
@@ -367,7 +367,7 @@ class notfound_view_config(object):
path_info=default, custom_predicates=default,
decorator=default, mapper=default, match_param=default,
append_slash=False):
- L = locals()
+ L = dict(locals()) # See issue #635 for dict() rationale
for k, v in L.items():
if k not in ('self', 'L') and v is not default:
self.__dict__[k] = v
@@ -432,7 +432,7 @@ class forbidden_view_config(object):
xhr=default, accept=default, header=default,
path_info=default, custom_predicates=default,
decorator=default, mapper=default, match_param=default):
- L = locals()
+ L = dict(locals()) # See issue #635 for dict() rationale
for k, v in L.items():
if k not in ('self', 'L') and v is not default:
self.__dict__[k] = v
diff --git a/tox.ini b/tox.ini
index 97aa6c4d0..85bd41bda 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
[tox]
envlist =
- py26,py27,py32,pypy,cover
+ py26,py27,py32,py33,pypy,cover
[testenv]
commands =
@@ -21,6 +21,14 @@ deps =
virtualenv
venusian
+[testenv:py33]
+commands =
+ python setup.py test -q
+deps =
+ WebTest
+ virtualenv
+ venusian
+
[testenv:cover]
basepython =
python2.6