summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2010-11-19 22:14:18 -0500
committerChris McDonough <chrism@plope.com>2010-11-19 22:14:18 -0500
commitff3811e4a776f096869d860309d4cff04dcb99ba (patch)
tree3e3806b0d9cadde44a97a777c7d27654b25cfb86
parentb5e5018c9b948929548615139533e9a8a504ff11 (diff)
parent15c258849aa54eb99d272fbbef0e9c3d911d2cca (diff)
downloadpyramid-ff3811e4a776f096869d860309d4cff04dcb99ba.tar.gz
pyramid-ff3811e4a776f096869d860309d4cff04dcb99ba.tar.bz2
pyramid-ff3811e4a776f096869d860309d4cff04dcb99ba.zip
Merge branch 'master' of github.com:Pylons/pyramid
-rw-r--r--CHANGES.txt8
-rw-r--r--docs/narr/urldispatch.rst145
-rw-r--r--pyramid/configuration.py10
-rw-r--r--pyramid/tests/test_urldispatch.py7
-rw-r--r--pyramid/urldispatch.py30
5 files changed, 121 insertions, 79 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index c1e89734d..ed68770fd 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -7,9 +7,11 @@ Features
- URL Dispatch now allows for replacement markers to be located anywhere
in the pattern, instead of immediately following a ``/``.
-- Added ``marker_pattern`` option to ``add_route`` to supply a dict of
- regular expressions to be used for markers in the pattern instead of the
- default regular expression that matched everything except 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
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst
index 0170b36e2..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,20 +205,20 @@ 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 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
+marker (e.g. ``{foo}``) or a certain combination of both. A replacement marker
does not need to be preceded by a ``/`` character.
-A replacement marker is in the format ``:name``, where this
+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
@@ -221,7 +226,7 @@ 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,36 +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
+ foo/{name}.{ext}
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
+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 any ``pattern_regexes`` to restrict valid values of each
-replacement marker.
+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
@@ -282,7 +287,7 @@ pattern:
.. code-block:: text
- foo/:bar
+ foo/{bar}
When matching the following URL:
@@ -304,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:
@@ -336,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
@@ -360,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::
@@ -446,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
@@ -474,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.
@@ -482,13 +505,6 @@ 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``.
-
-``marker_pattern``
- A dict of regular expression replacements for replacement markers in the
- pattern to use when generating the complete regular expression used to
- match the route. By default, every replacement marker in the pattern is
- replaced with the regular expression ``[^/]+``. Values in this dict will
- be used instead if present.
``xhr``
This value should be either ``True`` or ``False``. If this value is
@@ -663,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
@@ -694,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
@@ -702,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
@@ -732,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
@@ -833,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
@@ -841,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
@@ -879,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'}``.
@@ -930,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')
@@ -958,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
@@ -968,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
@@ -1009,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
@@ -1203,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/pyramid/configuration.py b/pyramid/configuration.py
index d861292b6..63d09efe3 100644
--- a/pyramid/configuration.py
+++ b/pyramid/configuration.py
@@ -1199,7 +1199,6 @@ class Configurator(object):
def add_route(self,
name,
pattern=None,
- marker_pattern=None,
view=None,
view_for=None,
permission=None,
@@ -1319,12 +1318,6 @@ class Configurator(object):
value if the ``pattern`` argument is ``None``. If both
``path`` and ``pattern`` are passed, ``pattern`` wins.
- marker_pattern
-
- A dict of regular expression's that will be used in the place
- of the default ``[^/]+`` regular expression for all replacement
- markers in the route pattern.
-
xhr
This value should be either ``True`` or ``False``. If this
@@ -1547,8 +1540,7 @@ class Configurator(object):
raise ConfigurationError('"pattern" argument may not be None')
return mapper.connect(name, pattern, factory, predicates=predicates,
- pregenerator=pregenerator,
- marker_pattern=marker_pattern)
+ pregenerator=pregenerator)
def get_routes_mapper(self):
""" Return the :term:`routes mapper` object associated with
diff --git a/pyramid/tests/test_urldispatch.py b/pyramid/tests/test_urldispatch.py
index f77ebbbb4..12c5cf220 100644
--- a/pyramid/tests/test_urldispatch.py
+++ b/pyramid/tests/test_urldispatch.py
@@ -218,9 +218,9 @@ class RoutesMapperTests(unittest.TestCase):
self.assertEqual(mapper.generate('abc', {}), 123)
class TestCompileRoute(unittest.TestCase):
- def _callFUT(self, pattern, marker_pattern=None):
+ def _callFUT(self, pattern):
from pyramid.urldispatch import _compile_route
- return _compile_route(pattern, marker_pattern)
+ return _compile_route(pattern)
def test_no_star(self):
matcher, generator = self._callFUT('/foo/:baz/biz/:buz/bar')
@@ -253,8 +253,7 @@ class TestCompileRoute(unittest.TestCase):
self.assertRaises(URLDecodeError, matcher, '/%FF%FE%8B%00')
def test_custom_regex(self):
- matcher, generator = self._callFUT('foo/:baz/biz/:buz.:bar',
- {'buz': '[^/\.]+'})
+ 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)
diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py
index 06ec647e5..0f8691a07 100644
--- a/pyramid/urldispatch.py
+++ b/pyramid/urldispatch.py
@@ -17,10 +17,10 @@ _marker = object()
class Route(object):
implements(IRoute)
def __init__(self, name, pattern, factory=None, predicates=(),
- pregenerator=None, marker_pattern=None):
+ pregenerator=None):
self.pattern = pattern
self.path = pattern # indefinite b/w compat, not in interface
- self.match, self.generate = _compile_route(pattern, marker_pattern)
+ self.match, self.generate = _compile_route(pattern)
self.name = name
self.factory = factory
self.predicates = predicates
@@ -42,12 +42,11 @@ class RoutesMapper(object):
return self.routes.get(name)
def connect(self, name, pattern, factory=None, predicates=(),
- pregenerator=None, marker_pattern=None):
+ pregenerator=None):
if name in self.routes:
oldroute = self.routes[name]
self.routelist.remove(oldroute)
- route = Route(name, pattern, factory, predicates, pregenerator,
- marker_pattern)
+ route = Route(name, pattern, factory, predicates, pregenerator)
self.routelist.append(route)
self.routes[name] = route
return route
@@ -75,9 +74,16 @@ class RoutesMapper(object):
return {'route':None, 'match':None}
# stolen from bobo and modified
-route_re = re.compile(r'(:[a-zA-Z]\w*)')
-def _compile_route(route, marker_pattern=None):
- marker_pattern = marker_pattern or {}
+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
@@ -93,9 +99,13 @@ def _compile_route(route, marker_pattern=None):
gen.append(prefix)
while pat:
name = pat.pop()
- name = name[1:]
+ name = name[1:-1]
+ if ':' in name:
+ name, reg = name.split(':')
+ else:
+ reg = '[^/]+'
gen.append('%%(%s)s' % name)
- name = '(?P<%s>%s)' % (name, marker_pattern.get(name, '[^/]+'))
+ name = '(?P<%s>%s)' % (name, reg)
rpat.append(name)
s = pat.pop()
if s: