summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt9
-rw-r--r--pyramid/tests/test_urldispatch.py70
-rw-r--r--pyramid/urldispatch.py4
3 files changed, 71 insertions, 12 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 7488be4cb..ac3b9aafc 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,12 @@
+Next release
+============
+
+Features
+--------
+
+- Route pattern replacement marker names can now begin with an underscore.
+ See https://github.com/Pylons/pyramid/issues/276.
+
1.2b3 (2011-09-11)
==================
diff --git a/pyramid/tests/test_urldispatch.py b/pyramid/tests/test_urldispatch.py
index ec45cb71e..3c92e87be 100644
--- a/pyramid/tests/test_urldispatch.py
+++ b/pyramid/tests/test_urldispatch.py
@@ -334,7 +334,7 @@ class TestCompileRoute(unittest.TestCase):
self.assertEqual(generator({'buz':'2001-Nov-15'}), '/2001-Nov-15')
self.assertEqual(generator({'buz':'99-June-10'}), '/99-June-10')
-class TestCompileRouteMatchFunctional(unittest.TestCase):
+class TestCompileRouteFunctional(unittest.TestCase):
def matches(self, pattern, path, expected):
from pyramid.urldispatch import _compile_route
matcher = _compile_route(pattern)[0]
@@ -345,16 +345,41 @@ class TestCompileRouteMatchFunctional(unittest.TestCase):
from pyramid.urldispatch import _compile_route
self.assertEqual(_compile_route(pattern)[1](dict), result)
- def test_matcher_functional(self):
+ def test_matcher_functional_notdynamic(self):
self.matches('/', '', None)
self.matches('', '', None)
self.matches('/', '/foo', None)
self.matches('/foo/', '/foo', None)
+ self.matches('', '/', {})
+ self.matches('/', '/', {})
+
+ def test_matcher_functional_newstyle(self):
+ self.matches('/{x}', '', None)
+ self.matches('/{x}', '/', None)
+ self.matches('/abc/{def}', '/abc/', None)
+ self.matches('/{x}', '/a', {'x':'a'})
+ self.matches('zzz/{x}', '/zzz/abc', {'x':'abc'})
+ self.matches('zzz/{x}*traverse', '/zzz/abc', {'x':'abc', 'traverse':()})
+ self.matches('zzz/{x}*traverse', '/zzz/abc/def/g',
+ {'x':'abc', 'traverse':('def', 'g')})
+ self.matches('*traverse', '/zzz/abc', {'traverse':('zzz', 'abc')})
+ self.matches('*traverse', '/zzz/%20abc', {'traverse':('zzz', ' abc')})
+ self.matches('{x}', '/La%20Pe%C3%B1a', {'x':u'La Pe\xf1a'})
+ self.matches('*traverse', '/La%20Pe%C3%B1a/x',
+ {'traverse':(u'La Pe\xf1a', 'x')})
+ self.matches('/foo/{id}.html', '/foo/bar.html', {'id':'bar'})
+ self.matches('/{num:[0-9]+}/*traverse', '/555/abc/def',
+ {'num':'555', 'traverse':('abc', 'def')})
+ self.matches('/{num:[0-9]*}/*traverse', '/555/abc/def',
+ {'num':'555', 'traverse':('abc', 'def')})
+ self.matches('zzz/{_}', '/zzz/abc', {'_':'abc'})
+ self.matches('zzz/{_abc}', '/zzz/abc', {'_abc':'abc'})
+ self.matches('zzz/{abc_def}', '/zzz/abc', {'abc_def':'abc'})
+
+ def test_matcher_functional_oldstyle(self):
self.matches('/:x', '', None)
self.matches('/:x', '/', None)
self.matches('/abc/:def', '/abc/', None)
- self.matches('', '/', {})
- self.matches('/', '/', {})
self.matches('/:x', '/a', {'x':'a'})
self.matches('zzz/:x', '/zzz/abc', {'x':'abc'})
self.matches('zzz/:x*traverse', '/zzz/abc', {'x':'abc', 'traverse':()})
@@ -366,14 +391,36 @@ class TestCompileRouteMatchFunctional(unittest.TestCase):
self.matches('*traverse', '/La%20Pe%C3%B1a/x',
{'traverse':(u'La Pe\xf1a', 'x')})
self.matches('/foo/:id.html', '/foo/bar.html', {'id':'bar'})
- self.matches('/{num:[0-9]+}/*traverse', '/555/abc/def',
- {'num':'555', 'traverse':('abc', 'def')})
- self.matches('/{num:[0-9]*}/*traverse', '/555/abc/def',
- {'num':'555', 'traverse':('abc', 'def')})
-
- def test_generator_functional(self):
+ self.matches('/foo/:id_html', '/foo/bar_html', {'id_html':'bar_html'})
+ self.matches('zzz/:_', '/zzz/abc', {'_':'abc'})
+ self.matches('zzz/:_abc', '/zzz/abc', {'_abc':'abc'})
+ self.matches('zzz/:abc_def', '/zzz/abc', {'abc_def':'abc'})
+
+ def test_generator_functional_notdynamic(self):
self.generates('', {}, '/')
self.generates('/', {}, '/')
+
+ def test_generator_functional_newstyle(self):
+ self.generates('/{x}', {'x':''}, '/')
+ self.generates('/{x}', {'x':'a'}, '/a')
+ self.generates('zzz/{x}', {'x':'abc'}, '/zzz/abc')
+ self.generates('zzz/{x}*traverse', {'x':'abc', 'traverse':''},
+ '/zzz/abc')
+ self.generates('zzz/{x}*traverse', {'x':'abc', 'traverse':'/def/g'},
+ '/zzz/abc/def/g')
+ self.generates('/{x}', {'x':unicode('/La Pe\xc3\xb1a', 'utf-8')},
+ '/%2FLa%20Pe%C3%B1a')
+ self.generates('/{x}*y', {'x':unicode('/La Pe\xc3\xb1a', 'utf-8'),
+ 'y':'/rest/of/path'},
+ '/%2FLa%20Pe%C3%B1a/rest/of/path')
+ self.generates('*traverse', {'traverse':('a', u'La Pe\xf1a')},
+ '/a/La%20Pe%C3%B1a')
+ self.generates('/foo/{id}.html', {'id':'bar'}, '/foo/bar.html')
+ self.generates('/foo/{_}', {'_':'20'}, '/foo/20')
+ self.generates('/foo/{_abc}', {'_abc':'20'}, '/foo/20')
+ self.generates('/foo/{abc_def}', {'abc_def':'20'}, '/foo/20')
+
+ def test_generator_functional_oldstyle(self):
self.generates('/:x', {'x':''}, '/')
self.generates('/:x', {'x':'a'}, '/a')
self.generates('zzz/:x', {'x':'abc'}, '/zzz/abc')
@@ -389,6 +436,9 @@ class TestCompileRouteMatchFunctional(unittest.TestCase):
self.generates('*traverse', {'traverse':('a', u'La Pe\xf1a')},
'/a/La%20Pe%C3%B1a')
self.generates('/foo/:id.html', {'id':'bar'}, '/foo/bar.html')
+ self.generates('/foo/:_', {'_':'20'}, '/foo/20')
+ self.generates('/foo/:_abc', {'_abc':'20'}, '/foo/20')
+ self.generates('/foo/:abc_def', {'abc_def':'20'}, '/foo/20')
class DummyContext(object):
""" """
diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py
index 3530126eb..73318193c 100644
--- a/pyramid/urldispatch.py
+++ b/pyramid/urldispatch.py
@@ -74,14 +74,14 @@ class RoutesMapper(object):
return {'route':None, 'match':None}
# stolen from bobo and modified
-old_route_re = re.compile(r'(\:[a-zA-Z]\w*)')
+old_route_re = re.compile(r'(\:[_a-zA-Z]\w*)')
star_at_end = re.compile(r'\*\w*$')
# The torturous nature of the regex named ``route_re`` below is due to the
# fact that we need to support at least one level of "inner" squigglies
# inside the expr of a {name:expr} pattern. This regex used to be just
# (\{[a-zA-Z][^\}]*\}) but that choked when supplied with e.g. {foo:\d{4}}.
-route_re = re.compile(r'(\{[a-zA-Z][^{}]*(?:\{[^{}]*\}[^{}]*)*\})')
+route_re = re.compile(r'(\{[_a-zA-Z][^{}]*(?:\{[^{}]*\}[^{}]*)*\})')
def update_pattern(matchobj):
name = matchobj.group(0)