summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCasey Duncan <casey.duncan@gmail.com>2010-11-19 23:25:44 -0700
committerCasey Duncan <casey.duncan@gmail.com>2010-11-19 23:25:44 -0700
commitb6f6e5506fa51a79eb5b0bb3c95b01878526fbff (patch)
tree277dbc6019c7f5b0f2cb1b35074186e247f526a8
parent673d55a3188d88c5fcf66062a894818a66722334 (diff)
parentdd53294c3342f58e0fb62b20ad61e59c1f88ac8b (diff)
downloadpyramid-b6f6e5506fa51a79eb5b0bb3c95b01878526fbff.tar.gz
pyramid-b6f6e5506fa51a79eb5b0bb3c95b01878526fbff.tar.bz2
pyramid-b6f6e5506fa51a79eb5b0bb3c95b01878526fbff.zip
merge changes from master
-rw-r--r--.gitignore9
-rw-r--r--CHANGES.txt36
-rw-r--r--TODO.txt17
-rw-r--r--docs/narr/MyProject/myproject/__init__.py3
-rw-r--r--docs/narr/configuration.rst18
-rw-r--r--docs/narr/firstapp.rst49
-rw-r--r--docs/narr/project.rst8
-rw-r--r--docs/narr/unittesting.rst2
-rw-r--r--docs/narr/urldispatch.rst158
-rw-r--r--docs/tutorials/wiki/basiclayout.rst9
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/__init__.py2
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py2
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/__init__.py2
-rw-r--r--docs/tutorials/wiki/src/viewdecorators/tutorial/__init__.py2
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/__init__.py2
-rw-r--r--docs/tutorials/wiki2/basiclayout.rst12
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/__init__.py2
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py2
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/__init__.py2
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/__init__.py2
-rw-r--r--pyramid/authentication.py6
-rw-r--r--pyramid/configuration.py39
-rw-r--r--pyramid/mako_templating.py2
-rwxr-xr-xpyramid/paster_templates/alchemy/+package+/__init__.py_tmpl2
-rw-r--r--pyramid/paster_templates/pylons_basic/+package+/__init__.py_tmpl2
-rw-r--r--pyramid/paster_templates/pylons_minimal/+package+/__init__.py_tmpl2
-rw-r--r--pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl2
-rw-r--r--pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl2
-rw-r--r--pyramid/paster_templates/starter/+package+/__init__.py_tmpl2
-rw-r--r--pyramid/paster_templates/starter_zcml/+package+/__init__.py_tmpl2
-rw-r--r--pyramid/paster_templates/zodb/+package+/__init__.py_tmpl2
-rw-r--r--pyramid/testing.py7
-rw-r--r--pyramid/tests/test_authentication.py6
-rw-r--r--pyramid/tests/test_chameleon_text.py30
-rw-r--r--pyramid/tests/test_chameleon_zpt.py30
-rw-r--r--pyramid/tests/test_configuration.py19
-rw-r--r--pyramid/tests/test_renderers.py2
-rw-r--r--pyramid/tests/test_traversal.py26
-rw-r--r--pyramid/tests/test_urldispatch.py8
-rw-r--r--pyramid/traversal.py13
-rw-r--r--pyramid/urldispatch.py20
-rw-r--r--setup.py2
42 files changed, 327 insertions, 238 deletions
diff --git a/.gitignore b/.gitignore
index 5d46de01f..706f6493d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,9 +5,10 @@
*.pt.py
*.txt.py
.coverage
-env26
-env24
-env27
-jyenv
+env26/
+env24/
+env27/
+jyenv/
+pypyenv/
build/
dist/
diff --git a/CHANGES.txt b/CHANGES.txt
index 604f28cf4..692e3cc35 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -4,6 +4,15 @@ Next release
Features
--------
+- URL Dispatch now allows for replacement markers to be located anywhere
+ in the pattern, instead of immediately following a ``/``.
+
+- URL Dispatch now uses the form ``{marker}`` to denote a replace marker in
+ the route pattern instead of ``:marker``. The old syntax is still backwards
+ compatible and accepted. The new format allows a regular expression for that
+ marker location to be used instead of the default ``[^/]+``, for example
+ ``{marker:\d+}`` is now valid to require the marker to be digits.
+
- Add a ``pyramid.url.route_path`` API, allowing folks to generate relative
URLs. Calling ``route_path`` is the same as calling
``pyramid.url.route_url`` with the argument ``_app_url`` equal to the empty
@@ -12,6 +21,21 @@ Features
- Add a ``pyramid.request.Request.route_path`` API. This is a convenience
method of the request which calls ``pyramid.url.route_url``.
+- Make test suite pass on Jython (requires PasteScript trunk, presumably to
+ be 1.7.4).
+
+- Make test suite pass on PyPy (Chameleon doesn't work).
+
+- Surrounding application configuration with ``config.begin()`` and
+ ``config.end()`` is no longer necessary. All paster templates have been
+ changed to no longer call these functions.
+
+Documentation
+-------------
+
+- SQLAlchemy+URLDispatch and ZODB+Traversal tutorials have been updated to
+ not call ``config.begin()`` or ``config.end()``.
+
Bug Fixes
---------
@@ -25,6 +49,18 @@ Bug Fixes
- The ``pyramid_alchemy`` paster template had a typo, preventing an import
from working.
+- Fix apparent failures when calling ``pyramid.traversal.find_model(root,
+ path)`` or ``pyramid.traversal.traverse(path)`` when ``path`` is
+ (erroneously) a Unicode object. The user is meant to pass these APIs a
+ string object, never a Unicode object. In practice, however, users indeed
+ pass Unicode. Because the string that is passed must be ASCII encodeable,
+ now, if they pass a Unicode object, its data is eagerly converted to an
+ ASCII string rather than being passed along to downstream code as a
+ convenience to the user and to prevent puzzling second-order failures from
+ cropping up (all failures will occur within ``pyramid.traversal.traverse``
+ rather than later down the line as the result of calling
+ ``traversal_path``).
+
Backwards Incompatibilities
---------------------------
diff --git a/TODO.txt b/TODO.txt
index f12dcee73..ab2b521ae 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -4,11 +4,6 @@ Pyramid TODOs
Must-Have (before 1.0)
----------------------
-- Test on GAE, Jython, PyPy, IronPython.
-
-- Add docs for httpexceptions module for each webob.exc class that inherits
- from WSGIHTTPException.
-
- Add a ``handler`` ZCML directive. This implies some slightly dicey
refactoring of the configurator to allow it to generate ZCML
"discriminators" for views and routes.
@@ -21,9 +16,6 @@ Must-Have (before 1.0)
- Use ``@register_view`` instead of ``@view_config`` and change view docs to
use "view registration" instead of "view configuration".
-- Remove calls to config.begin()/config.end() from startup config code in
- tutorials and paster templates (no longer required).
-
- SQLAlchemy idiomatics:
<RaFromBRC> mcdonc: those paster templates all look pretty good... the
@@ -57,9 +49,18 @@ Must-Have (before 1.0)
- Better ``config.add_handler`` documentation.
+- Fix DottedNameResolver to not convert ImportError to ConfigurationError if
+ the import that failed was unrelated to the import requested via a dotted
+ name.
+
Should-Have
-----------
+- Try to make test suite pass on IronPython.
+
+- Add docs for httpexceptions module for each webob.exc class that inherits
+ from WSGIHTTPException.
+
- Create a ``docs`` directory for each paster template.
- Remove "BFG" from Pyramid-specific environ variables.
diff --git a/docs/narr/MyProject/myproject/__init__.py b/docs/narr/MyProject/myproject/__init__.py
index bfbbfd4df..580dfe546 100644
--- a/docs/narr/MyProject/myproject/__init__.py
+++ b/docs/narr/MyProject/myproject/__init__.py
@@ -5,11 +5,8 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
config = Configurator(root_factory=get_root, settings=settings)
- config.begin()
config.add_view('myproject.views.my_view',
context='myproject.models.MyModel',
renderer='myproject:templates/mytemplate.pt')
config.add_static_view('static', 'myproject:static')
- config.end()
return config.make_wsgi_app()
-
diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst
index 6c5bc614b..d55a190e2 100644
--- a/docs/narr/configuration.rst
+++ b/docs/narr/configuration.rst
@@ -46,20 +46,16 @@ imperatively:
if __name__ == '__main__':
config = Configurator()
- config.begin()
config.add_view(hello_world)
- config.end()
app = config.make_wsgi_app()
serve(app, host='0.0.0.0')
-We won't talk much about what this application does yet. Just note
-that the "configuration' statements take place underneath the ``if
-__name__ == '__main__':`` stanza in the form of method calls on a
-:term:`Configurator` object (e.g. ``config.begin()``,
-``config.add_view(...)``, and ``config.end()``). These statements take
-place one after the other, and are executed in order, so the full
-power of Python, including conditionals, can be employed in this mode
-of configuration.
+We won't talk much about what this application does yet. Just note that the
+"configuration' statements take place underneath the ``if __name__ ==
+'__main__':`` stanza in the form of method calls on a :term:`Configurator`
+object (e.g. ``config.add_view(...)``). These statements take place one
+after the other, and are executed in order, so the full power of Python,
+including conditionals, can be employed in this mode of configuration.
.. index::
single: view_config
@@ -122,9 +118,7 @@ and its subpackages. For example:
if __name__ == '__main__':
from pyramid.configuration import Configurator
config = Configurator()
- config.begin()
config.scan()
- config.end()
app = config.make_wsgi_app()
serve(app, host='0.0.0.0')
diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst
index ab1f86175..8835f395a 100644
--- a/docs/narr/firstapp.rst
+++ b/docs/narr/firstapp.rst
@@ -38,10 +38,8 @@ configured imperatively:
if __name__ == '__main__':
config = Configurator()
- config.begin()
config.add_view(hello_world)
config.add_view(goodbye_world, name='goodbye')
- config.end()
app = config.make_wsgi_app()
serve(app, host='0.0.0.0')
@@ -149,10 +147,8 @@ statement:
if __name__ == '__main__':
config = Configurator()
- config.begin()
config.add_view(hello_world)
config.add_view(goodbye_world, name='goodbye')
- config.end()
app = config.make_wsgi_app()
serve(app, host='0.0.0.0')
@@ -190,29 +186,6 @@ this particular :app:`Pyramid` application. Methods called on the
Configurator will cause registrations to be made in a
:term:`application registry` associated with the application.
-Beginning Configuration
-~~~~~~~~~~~~~~~~~~~~~~~
-
-.. ignore-next-block
-.. code-block:: python
-
- config.begin()
-
-The :meth:`pyramid.configuration.Configurator.begin` method tells
-the system that application configuration has begun. In particular,
-this causes the :term:`application registry` associated with this
-configurator to become the "current" application registry.
-Code that subsequently attempts to use the application registry
-:term:`thread local` will obtain the registry associated with this
-configurator. This is an explicit step because it's sometimes
-convenient to use a configurator without causing the registry
-associated with the configurator to become "current".
-
-.. note::
-
- See :ref:`threadlocals_chapter` for a discussion about what it
- means for an application registry to be "current".
-
.. _adding_configuration:
Adding Configuration
@@ -281,28 +254,6 @@ important. We can register ``goodbye_world`` first and
``hello_world`` second; :app:`Pyramid` will still give us the most
specific callable when a request is dispatched to it.
-Ending Configuration
-~~~~~~~~~~~~~~~~~~~~
-
-.. ignore-next-block
-.. code-block:: python
-
- config.end()
-
-The :meth:`pyramid.configuration.Configurator.end` method tells the
-system that application configuration has ended. It is the inverse of
-:meth:`pyramid.configuration.Configurator.begin`. In particular,
-this causes the :term:`application registry` associated with this
-configurator to no longer be the "current" application registry,
-meaning that code which attempts to use the application registry
-:term:`thread local` will no longer obtain the registry associated
-with the configurator.
-
-.. note::
-
- See :ref:`threadlocals_chapter` for a discussion about what it
- means for an application registry to be "current".
-
.. index::
single: make_wsgi_app
single: WSGI application
diff --git a/docs/narr/project.rst b/docs/narr/project.rst
index 15fe0fdb3..225ced59c 100644
--- a/docs/narr/project.rst
+++ b/docs/narr/project.rst
@@ -751,14 +751,14 @@ also informs Python that the directory which contains it is a *package*.
#. Line 2 imports the ``get_root`` function from
:mod:`myproject.models` that we use later.
-#. Lines 4-14 define a function that returns a :app:`Pyramid`
+#. Lines 4-12 define a function that returns a :app:`Pyramid`
WSGI application. This function is meant to be called
by the :term:`PasteDeploy` framework as a result of running
``paster serve``.
Within this function, configuration is performed.
- Lines 9-11 register a "default view" (a view that has no ``name``
+ Lines 8-10 register a "default view" (a view that has no ``name``
attribute). It is registered so that it will be found when the
:term:`context` of the request is an instance of the
:class:`myproject.models.MyModel` class. The first argument to
@@ -772,11 +772,11 @@ also informs Python that the directory which contains it is a *package*.
``templates`` directory of the ``myproject`` package. The template file
it actually points to is a :term:`Chameleon` ZPT template file.
- Line 12 registers a static view, which will serve up the files from the
+ Line 11 registers a static view, which will serve up the files from the
``mypackage:static`` :term:`resource specification` (the ``static``
directory of the ``mypackage`` package).
- Line 14 returns a :term:`WSGI` application to the caller of the function
+ Line 12 returns a :term:`WSGI` application to the caller of the function
(Paste).
``views.py``
diff --git a/docs/narr/unittesting.rst b/docs/narr/unittesting.rst
index 5cd8a0683..26035726b 100644
--- a/docs/narr/unittesting.rst
+++ b/docs/narr/unittesting.rst
@@ -1,4 +1,4 @@
-.. index::
+\.. index::
single: unit testing
single: integration testing
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst
index 4442be355..a86041e55 100644
--- a/docs/narr/urldispatch.rst
+++ b/docs/narr/urldispatch.rst
@@ -92,7 +92,12 @@ registry`. Here's an example:
# pyramid.configuration.Configurator class; "myview" is assumed
# to be a "view callable" function
from views import myview
- config.add_route('myroute', '/prefix/:one/:two', view=myview)
+ config.add_route('myroute', '/prefix/{one}/{two}', view=myview)
+
+.. versionchanged:: 1.0a4
+ Prior to 1.0a4, routes allow for a marker starting with a ``:``, for
+ example ``/prefix/{one}``. Starting in 1.0a4, this style is deprecated
+ in favor or ``{}`` usage which allows for additional functionality.
.. index::
single: route configuration; view callable
@@ -116,7 +121,7 @@ Here's an example route configuration that references a view callable:
# pyramid.configuration.Configurator class; "myview" is assumed
# to be a "view callable" function
from myproject.views import myview
- config.add_route('myroute', '/prefix/:one/:two', view=myview)
+ config.add_route('myroute', '/prefix/{one}/{two}', view=myview)
You can also pass a :term:`dotted Python name` as the ``view`` argument
rather than an actual callable:
@@ -128,7 +133,7 @@ rather than an actual callable:
# pyramid.configuration.Configurator class; "myview" is assumed
# to be a "view callable" function
from myproject.views import myview
- config.add_route('myroute', '/prefix/:one/:two',
+ config.add_route('myroute', '/prefix/{one}/{two}',
view='myproject.views.myview')
When a route configuration names a ``view`` attribute, the :term:`view
@@ -200,28 +205,28 @@ the following patterns are equivalent:
.. code-block:: text
- :foo/bar/baz
+ {foo}/bar/baz
and:
.. code-block:: text
- /:foo/bar/baz
+ /{foo}/bar/baz
-A patttern segment (an individual item between ``/`` characters in the
-pattern) may either be a literal string (e.g. ``foo``) *or* it may be
-a segment replacement marker (e.g. ``:foo``) or a certain combination
-of both.
+A pattern segment (an individual item between ``/`` characters in the pattern)
+may either be a literal string (e.g. ``foo``) *or* it may be a replacement
+marker (e.g. ``{foo}``) or a certain combination of both. A replacement marker
+does not need to be preceded by a ``/`` character.
-A segment replacement marker is in the format ``:name``, where this
-means "accept any characters up to the next nonalphaunumeric character
+A replacement marker is in the format ``{name}``, where this
+means "accept any characters up to the next non-alphanumeric character
and use this as the ``name`` matchdict value." For example, the
following pattern defines one literal segment ("foo") and two dynamic
-segments ("baz", and "bar"):
+replacement markers ("baz", and "bar"):
.. code-block:: text
- foo/:baz/:bar
+ foo/{baz}/{bar}
The above pattern will match these URLs, generating the following
matchdicts:
@@ -244,24 +249,36 @@ pattern. So, for instance, if this route pattern was used:
.. code-block:: text
- foo/:name.html
+ foo/{name}.html
The literal path ``/foo/biz.html`` will match the above route pattern,
and the match result will be ``{'name':u'biz'}``. However, the
literal path ``/foo/biz`` will not match, because it does not contain
a literal ``.html`` at the end of the segment represented by
-``:name.html`` (it only contains ``biz``, not ``biz.html``).
+``{name}.html`` (it only contains ``biz``, not ``biz.html``).
+
+To capture both segments, two replacement markers can be used:
+
+.. code-block:: text
+
+ foo/{name}.{ext}
-This does not mean, however, that you can use two segment replacement
-markers in the same segment. For instance, ``/:foo:bar`` is a
-nonsensical route pattern. It will never match anything.
+The literal path ``/foo/biz.html`` will match the above route pattern, and the
+match result will be ``{'name': 'biz', 'ext': 'html'}``. This occurs because
+the replacement marker ``{name}`` has a literal part of ``.`` between the other
+replacement marker ``:ext``.
+
+It is possible to use two replacement markers without any literal characters
+between them, for instance ``/{foo}{bar}``. This would be a nonsensical pattern
+without specifying a custom regular expression to restrict what a marker
+captures.
Segments must contain at least one character in order to match a
segment replacement marker. For example, for the URL ``/abc/``:
-- ``/abc/:foo`` will not match.
+- ``/abc/{foo}`` will not match.
-- ``/:foo/`` will match.
+- ``/{foo}/`` will match.
Note that values representing path segments matched with a
``:segment`` match will be url-unquoted and decoded from UTF-8 into
@@ -270,7 +287,7 @@ pattern:
.. code-block:: text
- foo/:bar
+ foo/{bar}
When matching the following URL:
@@ -292,7 +309,7 @@ need to be preceded by a slash. For example:
.. code-block:: text
- foo/:baz/:bar*fizzle
+ foo/{baz}/{bar}*fizzle
The above pattern will match these URLs, generating the following
matchdicts:
@@ -324,6 +341,24 @@ Will generate the following matchdict:
{'fizzle':(u'La Pe\xf1a', u'a', u'b', u'c')}
+By default, the ``*stararg`` will parse the remainder sections into a tuple
+split by segment. Changing the regular expression used to match a marker can
+also capture the remainder of the URL, for example:
+
+.. code-block:: text
+
+ foo/{baz}/{bar}{fizzle:.*}
+
+The above pattern will match these URLs, generating the following matchdicts:
+
+ foo/1/2/ -> {'baz':'1', 'bar':'2', 'fizzle':()}
+ foo/abc/def/a/b/c -> {'baz':'abc', 'bar':'def', 'fizzle': 'a/b/c')}
+
+This occurs because the default regular expression for a marker is ``[^/]+``
+which will match everything up to the first ``/``, while ``{filzzle:.*}`` will
+result in a regular expression match of ``.*`` capturing the remainder into
+a single value.
+
.. index::
single: route ordering
@@ -348,12 +383,12 @@ be added in the following order:
.. code-block:: text
- members/:def
+ members/{def}
members/abc
In such a configuration, the ``members/abc`` pattern would *never* be
matched; this is because the match ordering will always match
-``members/:def`` first; the route configuration with ``members/abc``
+``members/{def}`` first; the route configuration with ``members/abc``
will never be evaluated.
.. index::
@@ -434,8 +469,8 @@ represent neither predicates nor view configuration information.
The syntax of the ``traverse`` argument is the same as it is for
``pattern``. For example, if the ``pattern`` provided is
- ``articles/:article/edit``, and the ``traverse`` argument provided
- is ``/:article``, when a request comes in that causes the route to
+ ``articles/{article}/edit``, and the ``traverse`` argument provided
+ is ``/{article}``, when a request comes in that causes the route to
match in such a way that the ``article`` match value is '1' (when
the request URI is ``/articles/1/edit``), the traversal path will be
generated as ``/1``. This means that the root object's
@@ -462,7 +497,7 @@ represent neither predicates nor view configuration information.
**Predicate Arguments**
``pattern``
- The path of the route e.g. ``ideas/:idea``. This argument is
+ The path of the route e.g. ``ideas/{idea}``. This argument is
required. See :ref:`route_path_pattern_syntax` for information
about the syntax of route paths. If the path doesn't match the
current URL, route matching continues.
@@ -470,7 +505,7 @@ represent neither predicates nor view configuration information.
.. note:: In earlier releases of this framework, this argument existed
as ``path``. ``path`` continues to work as an alias for
``pattern``.
-
+
``xhr``
This value should be either ``True`` or ``False``. If this value is
specified and is ``True``, the :term:`request` must possess an
@@ -644,7 +679,7 @@ match. For example:
num_one_two_or_three = any_of('num', 'one', 'two', 'three')
- config.add_route('num', '/:num',
+ config.add_route('num', '/{num}',
custom_predicates=(num_one_two_or_three,))
The above ``any_of`` function generates a predicate which ensures that
@@ -675,7 +710,7 @@ For instance, a predicate might do some type conversion of values:
ymd_to_int = integers('year', 'month', 'day')
- config.add_route('num', '/:year/:month/:day',
+ config.add_route('num', '/{year}/{month}/{day}',
custom_predicates=(ymd_to_int,))
Note that a conversion predicate is still a predicate so it must
@@ -683,6 +718,29 @@ return ``True`` or ``False``; a predicate that does *only* conversion,
such as the one we demonstrate above should unconditionally return
``True``.
+To avoid the try/except uncertainty, the route pattern can contain regular
+expressions specifying requirements for that marker. For instance:
+
+.. code-block:: python
+ :linenos:
+
+ def integers(*segment_names):
+ def predicate(info, request):
+ match = info['match']
+ for segment_name in segment_names:
+ match[segment_name] = int(match[segment_name])
+ return True
+ return predicate
+
+ ymd_to_int = integers('year', 'month', 'day')
+
+ config.add_route('num', '/{year:\d+}/{month:\d+}/{day:\d+}',
+ custom_predicates=(ymd_to_int,))
+
+Now the try/except is no longer needed because the route will not match at
+all unless these markers match ``\d+`` which requires them to be valid digits
+for an ``int`` type conversion.
+
The ``match`` dictionary passed within ``info`` to each predicate
attached to a route will be the same dictionary. Therefore, when
registering a custom predicate which modifies the ``match`` dict, the
@@ -713,9 +771,9 @@ An example of using the route in a set of route predicates:
if info['route'].name in ('ymd', 'ym', 'y'):
return info['match']['year'] == '2010'
- config.add_route('y', '/:year', custom_predicates=(twenty_ten,))
- config.add_route('ym', '/:year/:month', custom_predicates=(twenty_ten,))
- config.add_route('ymd', '/:year/:month:/day',
+ config.add_route('y', '/{year}', custom_predicates=(twenty_ten,))
+ config.add_route('ym', '/{year}/{month}', custom_predicates=(twenty_ten,))
+ config.add_route('ymd', '/{year}/{month}/{day}',
custom_predicates=(twenty_ten,))
The above predicate, when added to a number of route configurations
@@ -814,7 +872,7 @@ The simplest route declaration which configures a route match to
.. code-block:: python
:linenos:
- config.add_route('idea', 'site/:id', view='mypackage.views.site_view')
+ config.add_route('idea', 'site/{id}', view='mypackage.views.site_view')
When a route configuration with a ``view`` attribute is added to the
system, and an incoming request matches the *pattern* of the route
@@ -822,12 +880,12 @@ configuration, the :term:`view callable` named as the ``view``
attribute of the route configuration will be invoked.
In the case of the above example, when the URL of a request matches
-``/site/:id``, the view callable at the Python dotted path name
+``/site/{id}``, the view callable at the Python dotted path name
``mypackage.views.site_view`` will be called with the request. In
other words, we've associated a view callable directly with a route
pattern.
-When the ``/site/:id`` route pattern matches during a request, the
+When the ``/site/{id}`` route pattern matches during a request, the
``site_view`` view callable is invoked with that request as its sole
argument. When this route matches, a ``matchdict`` will be generated
and attached to the request as ``request.matchdict``. If the specific
@@ -860,30 +918,30 @@ might add to your application:
.. code-block:: python
:linenos:
- config.add_route('idea', 'ideas/:idea', view='mypackage.views.idea_view')
- config.add_route('user', 'users/:user', view='mypackage.views.user_view')
- config.add_route('tag', 'tags/:tags', view='mypackage.views.tag_view')
+ config.add_route('idea', 'ideas/{idea}', view='mypackage.views.idea_view')
+ config.add_route('user', 'users/{user}', view='mypackage.views.user_view')
+ config.add_route('tag', 'tags/{tags}', view='mypackage.views.tag_view')
The above configuration will allow :app:`Pyramid` to service URLs
in these forms:
.. code-block:: text
- /ideas/:idea
- /users/:user
- /tags/:tag
+ /ideas/{idea}
+ /users/{user}
+ /tags/{tag}
-- When a URL matches the pattern ``/ideas/:idea``, the view callable
+- When a URL matches the pattern ``/ideas/{idea}``, the view callable
available at the dotted Python pathname ``mypackage.views.idea_view`` will
be called. For the specific URL ``/ideas/1``, the ``matchdict`` generated
and attached to the :term:`request` will consist of ``{'idea':'1'}``.
-- When a URL matches the pattern ``/users/:user``, the view callable
+- When a URL matches the pattern ``/users/{user}``, the view callable
available at the dotted Python pathname ``mypackage.views.user_view`` will
be called. For the specific URL ``/users/1``, the ``matchdict`` generated
and attached to the :term:`request` will consist of ``{'user':'1'}``.
-- When a URL matches the pattern ``/tags/:tag``, the view callable available
+- When a URL matches the pattern ``/tags/{tag}``, the view callable available
at the dotted Python pathname ``mypackage.views.tag_view`` will be called.
For the specific URL ``/tags/1``, the ``matchdict`` generated and attached
to the :term:`request` will consist of ``{'tag':'1'}``.
@@ -911,7 +969,7 @@ An example of using a route with a factory:
.. code-block:: python
:linenos:
- config.add_route('idea', 'ideas/:idea',
+ config.add_route('idea', 'ideas/{idea}',
view='myproject.views.idea_view',
factory='myproject.models.Idea')
@@ -939,7 +997,7 @@ a ``view`` declaration.
.. code-block:: python
:linenos:
- config.add_route('idea', 'site/:id')
+ config.add_route('idea', 'site/{id}')
config.add_view(route_name='idea', view='mypackage.views.site_view')
This set of configuration parameters creates a configuration
@@ -949,7 +1007,7 @@ completely equivalent to this example provided in
.. code-block:: python
:linenos:
- config.add_route('idea', 'site/:id', view='mypackage.views.site_view')
+ config.add_route('idea', 'site/{id}', view='mypackage.views.site_view')
In fact, the spelling which names a ``view`` attribute is just
syntactic sugar for the more verbose spelling which contains separate
@@ -990,7 +1048,7 @@ Generating Route URLs
Use the :func:`pyramid.url.route_url` function to generate URLs based on
route patterns. For example, if you've configured a route with the ``name``
-"foo" and the ``pattern`` ":a/:b/:c", you might do this.
+"foo" and the ``pattern`` "{a}/{b}/{c}", you might do this.
.. ignore-next-block
.. code-block:: python
@@ -1184,7 +1242,7 @@ Such a ``factory`` might look like so:
if article == '1':
self.__acl__ = [ (Allow, 'editor', 'view') ]
-If the route ``archives/:article`` is matched, and the article number
+If the route ``archives/{article}`` is matched, and the article number
is ``1``, :app:`Pyramid` will generate an ``Article``
:term:`context` with an ACL on it that allows the ``editor`` principal
the ``view`` permission. Obviously you can do more generic things
diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst
index c05a53831..a94a1632d 100644
--- a/docs/tutorials/wiki/basiclayout.rst
+++ b/docs/tutorials/wiki/basiclayout.rst
@@ -48,14 +48,11 @@ entry point happens to be the ``app`` function within the file named
factory` and the settings keywords parsed by PasteDeploy. The root
factory is named ``get_root``.
-#. *Lines 16-18*. Begin configuration using the ``begin`` method of
- the :meth:`pyramid.configuration.Configurator` class, load the
+#. *Line 16*. Load the
``configure.zcml`` file from our package using the
- :meth:`pyramid.configuration.Configurator.load_zcml` method, and
- end configuration using the
- :meth:`pyramid.configuration.Configurator.end` method.
+ :meth:`pyramid.configuration.Configurator.load_zcml` method.
-#. *Line 19*. Use the
+#. *Line 17*. Use the
:meth:`pyramid.configuration.Configurator.make_wsgi_app` method
to return a :term:`WSGI` application.
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
index 124647ba9..a8a3c513e 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
@@ -16,7 +16,5 @@ def main(global_config, **settings):
def get_root(request):
return finder(request.environ)
config = Configurator(root_factory=get_root, settings=settings)
- config.begin()
config.load_zcml('configure.zcml')
- config.end()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
index f6cf8b479..45e4d722b 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
@@ -13,8 +13,6 @@ def main(global_config, **settings):
def get_root(request):
return finder(request.environ)
config = Configurator(root_factory=get_root, settings=settings)
- config.begin()
config.load_zcml('configure.zcml')
- config.end()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py
index 7ef07e767..66e2f05ee 100644
--- a/docs/tutorials/wiki/src/models/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py
@@ -16,8 +16,6 @@ def main(global_config, **settings):
def get_root(request):
return finder(request.environ)
config = Configurator(root_factory=get_root, settings=settings)
- config.begin()
config.load_zcml('configure.zcml')
- config.end()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/__init__.py b/docs/tutorials/wiki/src/viewdecorators/tutorial/__init__.py
index 7ef07e767..66e2f05ee 100644
--- a/docs/tutorials/wiki/src/viewdecorators/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/viewdecorators/tutorial/__init__.py
@@ -16,8 +16,6 @@ def main(global_config, **settings):
def get_root(request):
return finder(request.environ)
config = Configurator(root_factory=get_root, settings=settings)
- config.begin()
config.load_zcml('configure.zcml')
- config.end()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py
index 7ef07e767..66e2f05ee 100644
--- a/docs/tutorials/wiki/src/views/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py
@@ -16,8 +16,6 @@ def main(global_config, **settings):
def get_root(request):
return finder(request.environ)
config = Configurator(root_factory=get_root, settings=settings)
- config.begin()
config.load_zcml('configure.zcml')
- config.end()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst
index 9aca94fe5..9bcc17e97 100644
--- a/docs/tutorials/wiki2/basiclayout.rst
+++ b/docs/tutorials/wiki2/basiclayout.rst
@@ -52,10 +52,7 @@ entry point happens to be the ``app`` function within the file named
deployment-related values such as ``reload_templates``,
``db_string``, etc.
-#. *Line 15*. We call :meth:`pyramid.configuration.Configurator.begin` which
- tells the configuration machinery we are starting configuration.
-
-#. *Line 16*. We call
+#. *Line 15*. We call
:meth:`pyramid.configuration.Configurator.add_static_view` with the
arguments ``static`` (the name), and ``tutorial:static`` (the path). This
registers a static resource view which will match any URL that starts with
@@ -67,7 +64,7 @@ entry point happens to be the ``app`` function within the file named
``/static/foo``) will be used to compose a path to a static file resource,
such as a CSS file.
-#. *Lines 17-18*. Register a :term:`route configuration` via the
+#. *Lines 16-17*. Register a :term:`route configuration` via the
:meth:`pyramid.configuration.Configurator.add_route` method that will be
used when the URL is ``/``. Since this route has an ``pattern`` equalling
``/`` it is the "default" route. The argument named ``view`` with the
@@ -81,10 +78,7 @@ entry point happens to be the ``app`` function within the file named
``tutorial.views.my_view`` view returns a dictionary, a :term:`renderer`
will use this template to create a response.
-#. *Line 19*. We call :meth:`pyramid.configuration.Configurator.end` which
- tells the configuration machinery we are ending configuration.
-
-#. *Line 20*. We use the
+#. *Line 18*. We use the
:meth:`pyramid.configuration.Configurator.make_wsgi_app` method to return
a :term:`WSGI` application.
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
index 4bddea74c..8269617f2 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
@@ -22,7 +22,6 @@ def main(global_config, **settings):
root_factory='tutorial.models.RootFactory',
authentication_policy=authn_policy,
authorization_policy=authz_policy)
- config.begin()
config.add_static_view('static', 'tutorial:static')
config.add_route('view_wiki', '/', view='tutorial.views.view_wiki')
config.add_route('login', '/login',
@@ -44,6 +43,5 @@ def main(global_config, **settings):
config.add_view('tutorial.login.login',
renderer='tutorial:templates/login.pt',
context='pyramid.exceptions.Forbidden')
- config.end()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py
index 7a701fc02..0ae61fccd 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py
@@ -12,11 +12,9 @@ def main(global_config, **settings):
db_echo = settings.get('db_echo', 'false')
initialize_sql(db_string, asbool(db_echo))
config = Configurator(settings=settings)
- config.begin()
config.add_static_view('static', 'tutorial:static')
config.add_route('home', '/', view='tutorial.views.my_view',
view_renderer='templates/mytemplate.pt')
- config.end()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki2/src/models/tutorial/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/__init__.py
index 10fcd0cbc..6e291ffdf 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/__init__.py
@@ -12,9 +12,7 @@ def main(global_config, **settings):
db_echo = settings.get('db_echo', 'false')
initialize_sql(db_string, asbool(db_echo))
config = Configurator(settings=settings)
- config.begin()
config.add_static_view('static', 'tutorial:static')
config.add_route('home', '/', view='tutorial.views.my_view',
view_renderer='templates/mytemplate.pt')
- config.end()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki2/src/views/tutorial/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/__init__.py
index 9ef923e0b..947ce9b93 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/__init__.py
@@ -12,7 +12,6 @@ def main(global_config, **settings):
db_echo = settings.get('db_echo', 'false')
initialize_sql(db_string, asbool(db_echo))
config = Configurator(settings=settings)
- config.begin()
config.add_static_view('static', 'tutorial:static')
config.add_route('home', '/', view='tutorial.views.view_wiki')
config.add_route('view_page', '/:pagename',
@@ -24,6 +23,5 @@ def main(global_config, **settings):
config.add_route('edit_page', '/:pagename/edit_page',
view='tutorial.views.edit_page',
view_renderer='tutorial:templates/edit.pt')
- config.end()
return config.make_wsgi_app()
diff --git a/pyramid/authentication.py b/pyramid/authentication.py
index 4849d2c41..86d725bcf 100644
--- a/pyramid/authentication.py
+++ b/pyramid/authentication.py
@@ -286,6 +286,7 @@ EXPIRE = object()
class AuthTktCookieHelper(object):
auth_tkt = auth_tkt # for tests
+ now = None # for tests
userid_type_decoders = {
'int':int,
@@ -373,7 +374,10 @@ class AuthTktCookieHelper(object):
except self.auth_tkt.BadTicket:
return None
- now = time.time()
+ now = self.now # service tests
+
+ if now is None:
+ now = time.time()
if self.timeout and ( (timestamp + self.timeout) < now ):
return None
diff --git a/pyramid/configuration.py b/pyramid/configuration.py
index 3f959aabf..63d09efe3 100644
--- a/pyramid/configuration.py
+++ b/pyramid/configuration.py
@@ -24,6 +24,8 @@ from pyramid.interfaces import IChameleonTranslate
from pyramid.interfaces import IDebugLogger
from pyramid.interfaces import IDefaultPermission
from pyramid.interfaces import IDefaultRootFactory
+from pyramid.interfaces import IException
+from pyramid.interfaces import IExceptionResponse
from pyramid.interfaces import IExceptionViewClassifier
from pyramid.interfaces import ILocaleNegotiator
from pyramid.interfaces import IMultiView
@@ -36,34 +38,39 @@ from pyramid.interfaces import IRootFactory
from pyramid.interfaces import IRouteRequest
from pyramid.interfaces import IRoutesMapper
from pyramid.interfaces import ISecuredView
+from pyramid.interfaces import ISessionFactory
from pyramid.interfaces import IStaticURLInfo
from pyramid.interfaces import ITranslationDirectories
from pyramid.interfaces import ITraverser
from pyramid.interfaces import IView
from pyramid.interfaces import IViewClassifier
-from pyramid.interfaces import IExceptionResponse
-from pyramid.interfaces import IException
-from pyramid.interfaces import ISessionFactory
-from pyramid import chameleon_text
-from pyramid import chameleon_zpt
-from pyramid.mako_templating import renderer_factory as mako_renderer_factory
+try:
+ from pyramid import chameleon_text
+except TypeError: # pragma: no cover
+ chameleon_text = None # pypy
+try:
+ from pyramid import chameleon_zpt
+except TypeError: # pragma: no cover
+ chameleon_zpt = None # pypy
+
from pyramid import renderers
-from pyramid.renderers import RendererHelper
from pyramid.authorization import ACLAuthorizationPolicy
from pyramid.compat import all
from pyramid.compat import md5
from pyramid.events import ApplicationCreated
+from pyramid.exceptions import ConfigurationError
from pyramid.exceptions import Forbidden
from pyramid.exceptions import NotFound
from pyramid.exceptions import PredicateMismatch
-from pyramid.exceptions import ConfigurationError
from pyramid.i18n import get_localizer
from pyramid.log import make_stream_logger
+from pyramid.mako_templating import renderer_factory as mako_renderer_factory
from pyramid.path import caller_package
-from pyramid.path import package_path
from pyramid.path import package_of
+from pyramid.path import package_path
from pyramid.registry import Registry
+from pyramid.renderers import RendererHelper
from pyramid.request import route_request_iface
from pyramid.resource import PackageOverrides
from pyramid.resource import resolve_resource_spec
@@ -72,25 +79,29 @@ from pyramid.static import StaticURLInfo
from pyramid.threadlocal import get_current_registry
from pyramid.threadlocal import get_current_request
from pyramid.threadlocal import manager
-from pyramid.traversal import traversal_path
from pyramid.traversal import DefaultRootFactory
from pyramid.traversal import find_interface
+from pyramid.traversal import traversal_path
from pyramid.urldispatch import RoutesMapper
-from pyramid.view import render_view_to_response
from pyramid.view import default_exceptionresponse_view
+from pyramid.view import render_view_to_response
MAX_ORDER = 1 << 30
DEFAULT_PHASH = md5().hexdigest()
DEFAULT_RENDERERS = (
- ('.pt', chameleon_zpt.renderer_factory),
- ('.txt', chameleon_text.renderer_factory),
('.mak', mako_renderer_factory),
('.mako', mako_renderer_factory),
('json', renderers.json_renderer_factory),
('string', renderers.string_renderer_factory),
)
+if chameleon_text:
+ DEFAULT_RENDERERS += (('.pt', chameleon_zpt.renderer_factory),)
+if chameleon_zpt:
+ DEFAULT_RENDERERS += (('.txt', chameleon_text.renderer_factory),)
+
+
class Configurator(object):
"""
A Configurator is used to configure a :app:`Pyramid`
@@ -1306,7 +1317,7 @@ class Configurator(object):
to this function will be used to represent the pattern
value if the ``pattern`` argument is ``None``. If both
``path`` and ``pattern`` are passed, ``pattern`` wins.
-
+
xhr
This value should be either ``True`` or ``False``. If this
diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py
index e2330f3ad..6356e0db4 100644
--- a/pyramid/mako_templating.py
+++ b/pyramid/mako_templating.py
@@ -65,7 +65,7 @@ def renderer_factory(info):
module_directory = settings.get('mako.module_directory')
input_encoding = settings.get('mako.input_encoding', 'utf-8')
error_handler = settings.get('mako.error_handler', None)
- default_filters = settings.get('mako.default_filters', [])
+ default_filters = settings.get('mako.default_filters', None)
imports = settings.get('mako.imports', [])
if directories is None:
raise ConfigurationError(
diff --git a/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl b/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl
index 748f58692..c20e7dd15 100755
--- a/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl
+++ b/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl
@@ -12,7 +12,6 @@ def main(global_config, **settings):
db_echo = settings.get('db_echo', 'false')
get_root = appmaker(db_string, asbool(db_echo))
config = Configurator(settings=settings, root_factory=get_root)
- config.begin()
config.add_static_view('static', '{{package}}:static')
config.add_view('{{package}}.views.view_root',
context='{{package}}.models.MyApp',
@@ -20,7 +19,6 @@ def main(global_config, **settings):
config.add_view('{{package}}.views.view_model',
context='{{package}}.models.MyModel',
renderer="templates/model.pt")
- config.end()
return config.make_wsgi_app()
diff --git a/pyramid/paster_templates/pylons_basic/+package+/__init__.py_tmpl b/pyramid/paster_templates/pylons_basic/+package+/__init__.py_tmpl
index 6a8beb1a7..dd6edd3cf 100644
--- a/pyramid/paster_templates/pylons_basic/+package+/__init__.py_tmpl
+++ b/pyramid/paster_templates/pylons_basic/+package+/__init__.py_tmpl
@@ -5,7 +5,6 @@ def main(global_config, **settings):
"""
from pyramid.configuration import Configurator
config = Configurator(settings=settings)
- config.begin()
session_factory = session_factory_from_settings(settings)
config.set_session_factory(session_factory)
config.add_static_view('static', '{{package}}:static/')
@@ -15,5 +14,4 @@ def main(global_config, **settings):
action='index')
config.add_subscriber('{{package}}.lib.subscribers.add_renderer_globals',
'pyramid.events.BeforeRender')
- config.end()
return config.make_wsgi_app()
diff --git a/pyramid/paster_templates/pylons_minimal/+package+/__init__.py_tmpl b/pyramid/paster_templates/pylons_minimal/+package+/__init__.py_tmpl
index a1a353aae..57371eec9 100644
--- a/pyramid/paster_templates/pylons_minimal/+package+/__init__.py_tmpl
+++ b/pyramid/paster_templates/pylons_minimal/+package+/__init__.py_tmpl
@@ -5,7 +5,6 @@ def main(global_config, **settings):
"""
from pyramid.configuration import Configurator
config = Configurator(settings=settings)
- config.begin()
session_factory = session_factory_from_settings(settings)
config.set_session_factory(session_factory)
config.add_static_view('static', '{{package}}:static/')
@@ -14,6 +13,5 @@ def main(global_config, **settings):
action='index')
config.add_subscriber('{{package}}.subscribers.add_renderer_globals',
'pyramid.events.BeforeRender')
- config.end()
return config.make_wsgi_app()
diff --git a/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl b/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl
index a8df81fd6..0a4313976 100644
--- a/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl
+++ b/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl
@@ -13,7 +13,6 @@ def main(global_config, **settings):
"configuration.")
initialize_sql(db_string, asbool(settings.get('db_echo')))
config = Configurator(settings=settings)
- config.begin()
session_factory = session_factory_from_settings(settings)
config.set_session_factory(session_factory)
config.add_static_view('static', '{{package}}:static/')
@@ -22,5 +21,4 @@ def main(global_config, **settings):
action='index')
config.add_subscriber('{{package}}.subscribers.add_renderer_globals',
'pyramid.events.BeforeRender')
- config.end()
return config.make_wsgi_app()
diff --git a/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl b/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl
index 8b13e3590..e68e0ed66 100644
--- a/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl
+++ b/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl
@@ -12,11 +12,9 @@ def main(global_config, **settings):
db_echo = settings.get('db_echo', 'false')
initialize_sql(db_string, asbool(db_echo))
config = Configurator(settings=settings)
- config.begin()
config.add_static_view('static', '{{package}}:static')
config.add_route('home', '/', view='{{package}}.views.my_view',
view_renderer='templates/mytemplate.pt')
- config.end()
return config.make_wsgi_app()
diff --git a/pyramid/paster_templates/starter/+package+/__init__.py_tmpl b/pyramid/paster_templates/starter/+package+/__init__.py_tmpl
index 5f170ad27..85cb7ec6a 100644
--- a/pyramid/paster_templates/starter/+package+/__init__.py_tmpl
+++ b/pyramid/paster_templates/starter/+package+/__init__.py_tmpl
@@ -5,11 +5,9 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
config = Configurator(root_factory=get_root, settings=settings)
- config.begin()
config.add_view('{{package}}.views.my_view',
context='{{package}}.models.MyModel',
renderer='{{package}}:templates/mytemplate.pt')
config.add_static_view('static', '{{package}}:static')
- config.end()
return config.make_wsgi_app()
diff --git a/pyramid/paster_templates/starter_zcml/+package+/__init__.py_tmpl b/pyramid/paster_templates/starter_zcml/+package+/__init__.py_tmpl
index 19f35bee9..3a0c6b7de 100644
--- a/pyramid/paster_templates/starter_zcml/+package+/__init__.py_tmpl
+++ b/pyramid/paster_templates/starter_zcml/+package+/__init__.py_tmpl
@@ -6,8 +6,6 @@ def main(global_config, **settings):
"""
zcml_file = settings.get('configure_zcml', 'configure.zcml')
config = Configurator(root_factory=get_root, settings=settings)
- config.begin()
config.load_zcml(zcml_file)
- config.end()
return config.make_wsgi_app()
diff --git a/pyramid/paster_templates/zodb/+package+/__init__.py_tmpl b/pyramid/paster_templates/zodb/+package+/__init__.py_tmpl
index 4e879d40f..6b05ac7ad 100644
--- a/pyramid/paster_templates/zodb/+package+/__init__.py_tmpl
+++ b/pyramid/paster_templates/zodb/+package+/__init__.py_tmpl
@@ -14,7 +14,5 @@ def main(global_config, **settings):
def get_root(request):
return finder(request.environ)
config = Configurator(root_factory=get_root, settings=settings)
- config.begin()
config.load_zcml(zcml_file)
- config.end()
return config.make_wsgi_app()
diff --git a/pyramid/testing.py b/pyramid/testing.py
index c6c999147..4acede879 100644
--- a/pyramid/testing.py
+++ b/pyramid/testing.py
@@ -741,9 +741,16 @@ def skip_on(*platforms):
for platform in platforms:
if skip_on.os_name.startswith(platform):
return
+ if platform == 'pypy' and skip_on.pypy: # pragma: no cover
+ return
return func(*args, **kw)
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
return decorator
skip_on.os_name = os.name # for testing
+try: # pragma: no cover
+ import __pypy__
+ skip_on.pypy = True
+except ImportError:
+ skip_on.pypy = False
diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py
index 69762fdb0..d9d0c2c97 100644
--- a/pyramid/tests/test_authentication.py
+++ b/pyramid/tests/test_authentication.py
@@ -411,8 +411,10 @@ class TestAuthTktCookieHelper(unittest.TestCase):
def test_identify_cookie_reissue(self):
import time
- plugin = self._makeOne('secret', timeout=5000, reissue_time=0)
- plugin.auth_tkt.timestamp = time.time()
+ plugin = self._makeOne('secret', timeout=10, reissue_time=0)
+ now = time.time()
+ plugin.auth_tkt.timestamp = now
+ plugin.now = now + 1
request = self._makeRequest({'HTTP_COOKIE':'auth_tkt=bogus'})
result = plugin.identify(request)
self.failUnless(result)
diff --git a/pyramid/tests/test_chameleon_text.py b/pyramid/tests/test_chameleon_text.py
index 654cfdf3b..9486bbc88 100644
--- a/pyramid/tests/test_chameleon_text.py
+++ b/pyramid/tests/test_chameleon_text.py
@@ -51,6 +51,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
klass = self._getTargetClass()
return klass(*arg, **kw)
+ @skip_on('pypy')
def test_instance_implements_ITemplate(self):
from zope.interface.verify import verifyObject
from pyramid.interfaces import ITemplateRenderer
@@ -58,12 +59,13 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
lookup = DummyLookup()
verifyObject(ITemplateRenderer, self._makeOne(path, lookup))
+ @skip_on('pypy')
def test_class_implements_ITemplate(self):
from zope.interface.verify import verifyClass
from pyramid.interfaces import ITemplateRenderer
verifyClass(ITemplateRenderer, self._getTargetClass())
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_template_reified(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -72,7 +74,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template, instance.__dict__['template'])
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_template_with_ichameleon_translate(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -81,7 +83,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.translate, lookup.translate)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_template_with_debug_templates(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -91,7 +93,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.debug, True)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_template_with_reload_templates(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -101,7 +103,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.auto_reload, True)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_template_without_reload_templates(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -111,7 +113,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.auto_reload, False)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_call(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -120,14 +122,14 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
self.failUnless(isinstance(result, str))
self.assertEqual(result, 'Hello.\n')
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_call_with_nondict_value(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
instance = self._makeOne(minimal, lookup)
self.assertRaises(ValueError, instance, None, {})
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_call_nonminimal(self):
nonminimal = self._getTemplatePath('nonminimal.txt')
lookup = DummyLookup()
@@ -136,7 +138,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase):
self.failUnless(isinstance(result, str))
self.assertEqual(result, 'Hello, Chris!\n')
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_implementation(self):
minimal = self._getTemplatePath('minimal.txt')
lookup = DummyLookup()
@@ -150,7 +152,7 @@ class RenderTemplateTests(Base, unittest.TestCase):
from pyramid.chameleon_text import render_template
return render_template(name, **kw)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_it(self):
minimal = self._getTemplatePath('minimal.txt')
result = self._callFUT(minimal)
@@ -162,7 +164,7 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase):
from pyramid.chameleon_text import render_template_to_response
return render_template_to_response(name, **kw)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_minimal(self):
minimal = self._getTemplatePath('minimal.txt')
result = self._callFUT(minimal)
@@ -172,7 +174,7 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase):
self.assertEqual(result.status, '200 OK')
self.assertEqual(len(result.headerlist), 2)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_iresponsefactory_override(self):
from webob import Response
class Response2(Response):
@@ -188,7 +190,7 @@ class GetRendererTests(Base, unittest.TestCase):
from pyramid.chameleon_text import get_renderer
return get_renderer(name)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_it(self):
from pyramid.interfaces import IRendererFactory
class Dummy:
@@ -206,7 +208,7 @@ class GetTemplateTests(Base, unittest.TestCase):
from pyramid.chameleon_text import get_template
return get_template(name)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_it(self):
from pyramid.interfaces import IRendererFactory
class Dummy:
diff --git a/pyramid/tests/test_chameleon_zpt.py b/pyramid/tests/test_chameleon_zpt.py
index 786e96129..802f4460f 100644
--- a/pyramid/tests/test_chameleon_zpt.py
+++ b/pyramid/tests/test_chameleon_zpt.py
@@ -44,6 +44,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
klass = self._getTargetClass()
return klass(*arg, **kw)
+ @skip_on('pypy')
def test_instance_implements_ITemplate(self):
from zope.interface.verify import verifyObject
from pyramid.interfaces import ITemplateRenderer
@@ -51,12 +52,13 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
lookup = DummyLookup()
verifyObject(ITemplateRenderer, self._makeOne(path, lookup))
+ @skip_on('pypy')
def test_class_implements_ITemplate(self):
from zope.interface.verify import verifyClass
from pyramid.interfaces import ITemplateRenderer
verifyClass(ITemplateRenderer, self._getTargetClass())
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_call(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -66,7 +68,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
self.assertEqual(result,
'<div xmlns="http://www.w3.org/1999/xhtml">\n</div>')
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_template_reified(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -75,7 +77,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template, instance.__dict__['template'])
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_template_with_ichameleon_translate(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -84,7 +86,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.translate, lookup.translate)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_template_with_debug_templates(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -94,7 +96,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.debug, True)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_template_without_debug_templates(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -104,7 +106,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.debug, False)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_template_with_reload_templates(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -114,7 +116,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.auto_reload, True)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_template_without_reload_templates(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -124,14 +126,14 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase):
template = instance.template
self.assertEqual(template.auto_reload, False)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_call_with_nondict_value(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
instance = self._makeOne(minimal, lookup)
self.assertRaises(ValueError, instance, None, {})
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_implementation(self):
minimal = self._getTemplatePath('minimal.pt')
lookup = DummyLookup()
@@ -147,7 +149,7 @@ class RenderTemplateTests(Base, unittest.TestCase):
from pyramid.chameleon_zpt import render_template
return render_template(name, **kw)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_it(self):
minimal = self._getTemplatePath('minimal.pt')
result = self._callFUT(minimal)
@@ -160,7 +162,7 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase):
from pyramid.chameleon_zpt import render_template_to_response
return render_template_to_response(name, **kw)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_it(self):
minimal = self._getTemplatePath('minimal.pt')
result = self._callFUT(minimal)
@@ -171,7 +173,7 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase):
self.assertEqual(result.status, '200 OK')
self.assertEqual(len(result.headerlist), 2)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_iresponsefactory_override(self):
from webob import Response
class Response2(Response):
@@ -187,7 +189,7 @@ class GetRendererTests(Base, unittest.TestCase):
from pyramid.chameleon_zpt import get_renderer
return get_renderer(name)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_it(self):
from pyramid.interfaces import IRendererFactory
class Dummy:
@@ -205,7 +207,7 @@ class GetTemplateTests(Base, unittest.TestCase):
from pyramid.chameleon_zpt import get_template
return get_template(name)
- @skip_on('java')
+ @skip_on('java', 'pypy')
def test_it(self):
from pyramid.interfaces import IRendererFactory
class Dummy:
diff --git a/pyramid/tests/test_configuration.py b/pyramid/tests/test_configuration.py
index ded17cb33..a8ea63f54 100644
--- a/pyramid/tests/test_configuration.py
+++ b/pyramid/tests/test_configuration.py
@@ -2,6 +2,11 @@ import unittest
from pyramid import testing
+try:
+ import __pypy__
+except:
+ __pypy__ = None
+
class ConfiguratorTests(unittest.TestCase):
def _makeOne(self, *arg, **kw):
from pyramid.configuration import Configurator
@@ -90,8 +95,9 @@ class ConfiguratorTests(unittest.TestCase):
self.assertEqual(config.package, this_pkg)
self.failUnless(config.registry.getUtility(IRendererFactory, 'json'))
self.failUnless(config.registry.getUtility(IRendererFactory, 'string'))
- self.failUnless(config.registry.getUtility(IRendererFactory, '.pt'))
- self.failUnless(config.registry.getUtility(IRendererFactory, '.txt'))
+ if not __pypy__:
+ self.failUnless(config.registry.getUtility(IRendererFactory, '.pt'))
+ self.failUnless(config.registry.getUtility(IRendererFactory,'.txt'))
self.failUnless(config.registry.getUtility(IRendererFactory, '.mak'))
self.failUnless(config.registry.getUtility(IRendererFactory, '.mako'))
@@ -2940,6 +2946,7 @@ class ConfiguratorTests(unittest.TestCase):
pyramid.tests)
def test_scan_integration(self):
+ import os
from zope.interface import alsoProvides
from pyramid.interfaces import IRequest
from pyramid.view import render_view_to_response
@@ -3011,8 +3018,12 @@ class ConfiguratorTests(unittest.TestCase):
result = render_view_to_response(ctx, req, 'another_stacked_class2')
self.assertEqual(result, 'another_stacked_class')
- self.assertRaises(TypeError,
- render_view_to_response, ctx, req, 'basemethod')
+ if not os.name.startswith('java'):
+ # on Jython, a class without an __init__ apparently accepts
+ # any number of arguments without raising a TypeError.
+
+ self.assertRaises(TypeError,
+ render_view_to_response, ctx, req, 'basemethod')
result = render_view_to_response(ctx, req, 'method1')
self.assertEqual(result, 'method1')
diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py
index 102a23f92..bd6a0825d 100644
--- a/pyramid/tests/test_renderers.py
+++ b/pyramid/tests/test_renderers.py
@@ -138,7 +138,7 @@ class TestTemplateRendererFactory(unittest.TestCase):
result = self._callFUT(info, factory)
self.failUnless(result is renderer)
path = os.path.abspath(__file__).split('$')[0] # jython
- if path.endswith('.pyc'):
+ if path.endswith('.pyc'): # pragma: no cover
path = path[:-1]
self.failUnless(factory.path.startswith(path))
self.assertEqual(factory.kw, {})
diff --git a/pyramid/tests/test_traversal.py b/pyramid/tests/test_traversal.py
index 2deb5982c..3245d6302 100644
--- a/pyramid/tests/test_traversal.py
+++ b/pyramid/tests/test_traversal.py
@@ -522,6 +522,32 @@ class FindModelTests(unittest.TestCase):
self.assertEqual(root.wascontext, True)
self.assertEqual(root.request.environ['PATH_INFO'], '/')
+ def test_absolute_unicode_found(self):
+ # test for bug wiggy found in wild, traceback stack:
+ # root = u'/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF'
+ # wiggy's code: section=find_model(page, root)
+ # find_model L76: D = traverse(model, path)
+ # traverse L291: return traverser(request)
+ # __call__ line 568: vpath_tuple = traversal_path(vpath)
+ # lru_cached line 91: f(*arg)
+ # traversal_path line 443: path.encode('ascii')
+ # UnicodeEncodeError: 'ascii' codec can't encode characters in
+ # position 1-12: ordinal not in range(128)
+ #
+ # solution: encode string to ascii in pyramid.traversal.traverse
+ # before passing it along to webob as path_info
+ from pyramid.traversal import ModelGraphTraverser
+ unprintable = DummyContext()
+ root = DummyContext(unprintable)
+ unprintable.__parent__ = root
+ unprintable.__name__ = unicode(
+ '/\xe6\xb5\x81\xe8\xa1\x8c\xe8\xb6\x8b\xe5\x8a\xbf', 'utf-8')
+ root.__parent__ = None
+ root.__name__ = None
+ traverser = ModelGraphTraverser
+ self._registerTraverser(traverser)
+ result = self._callFUT(root, u'/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF')
+ self.assertEqual(result, unprintable)
class ModelPathTests(unittest.TestCase):
def _callFUT(self, model, *elements):
diff --git a/pyramid/tests/test_urldispatch.py b/pyramid/tests/test_urldispatch.py
index 799f4986f..12c5cf220 100644
--- a/pyramid/tests/test_urldispatch.py
+++ b/pyramid/tests/test_urldispatch.py
@@ -251,6 +251,13 @@ class TestCompileRoute(unittest.TestCase):
from pyramid.exceptions import URLDecodeError
matcher, generator = self._callFUT('/:foo')
self.assertRaises(URLDecodeError, matcher, '/%FF%FE%8B%00')
+
+ def test_custom_regex(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')
class TestCompileRouteMatchFunctional(unittest.TestCase):
def matches(self, pattern, path, expected):
@@ -271,7 +278,6 @@ class TestCompileRouteMatchFunctional(unittest.TestCase):
self.matches('/:x', '', None)
self.matches('/:x', '/', None)
self.matches('/abc/:def', '/abc/', None)
- self.matches('/abc/:def:baz', '/abc/bleep', None) # bad pattern
self.matches('', '/', {})
self.matches('/', '/', {})
self.matches('/:x', '/a', {'x':'a'})
diff --git a/pyramid/traversal.py b/pyramid/traversal.py
index e928c33f7..fb73ad906 100644
--- a/pyramid/traversal.py
+++ b/pyramid/traversal.py
@@ -228,7 +228,7 @@ def traverse(model, path):
object supplied to the function as the ``model`` argument. If an
empty string is passed as ``path``, the ``model`` passed in will
be returned. Model path strings must be escaped in the following
- manner: each Unicode path segment must be encoded as UTF-8 and as
+ manner: each Unicode path segment must be encoded as UTF-8 and
each path segment must escaped via Python's :mod:`urllib.quote`.
For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or
``to%20the/La%20Pe%C3%B1a`` (relative). The
@@ -272,6 +272,17 @@ def traverse(model, path):
else:
path = ''
+ # The user is supposed to pass us a string object, never Unicode. In
+ # practice, however, users indeed pass Unicode to this API. If they do
+ # pass a Unicode object, its data *must* be entirely encodeable to ASCII,
+ # so we encode it here as a convenience to the user and to prevent
+ # second-order failures from cropping up (all failures will occur at this
+ # step rather than later down the line as the result of calling
+ # ``traversal_path``).
+
+ if isinstance(path, unicode):
+ path = path.encode('ascii')
+
if path and path[0] == '/':
model = find_root(model)
diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py
index aa0bddfe9..0f8691a07 100644
--- a/pyramid/urldispatch.py
+++ b/pyramid/urldispatch.py
@@ -74,8 +74,16 @@ class RoutesMapper(object):
return {'route':None, 'match':None}
# stolen from bobo and modified
-route_re = re.compile(r'(/:[a-zA-Z]\w*)')
+old_route_re = re.compile(r'(\:[a-zA-Z]\w*)')
+route_re = re.compile(r'(\{[a-zA-Z][^\}]*\})')
+def update_pattern(matchobj):
+ name = matchobj.group(0)
+ return '{%s}' % name[1:]
+
def _compile_route(route):
+ if old_route_re.search(route) and not route_re.search(route):
+ route = old_route_re.sub(update_pattern, route)
+
if not route.startswith('/'):
route = '/' + route
star = None
@@ -91,9 +99,13 @@ def _compile_route(route):
gen.append(prefix)
while pat:
name = pat.pop()
- name = name[2:]
- gen.append('/%%(%s)s' % name)
- name = '/(?P<%s>[^/]+)' % name
+ name = name[1:-1]
+ if ':' in name:
+ name, reg = name.split(':')
+ else:
+ reg = '[^/]+'
+ gen.append('%%(%s)s' % name)
+ name = '(?P<%s>%s)' % (name, reg)
rpat.append(name)
s = pat.pop()
if s:
diff --git a/setup.py b/setup.py
index c2d11384f..c1939f73c 100644
--- a/setup.py
+++ b/setup.py
@@ -47,7 +47,7 @@ install_requires=[
if platform.system() == 'Java':
tests_require = install_requires + ['WebTest']
else:
- tests_require= install_requires + ['Sphinx', 'docutils', 'coverage',
+ tests_require= install_requires + ['Sphinx', 'docutils',
'WebTest', 'repoze.sphinx.autointerface']
if sys.version_info[:2] < (2, 6):