summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2013-08-28 12:52:20 -0400
committerChris McDonough <chrism@plope.com>2013-08-28 12:52:20 -0400
commit230157fcc3a3d3bd4658189aa5588da41b2da840 (patch)
tree9d4d149375fb02748568c8dbc6a7d2b4f82e1d99
parent8ac9ae83d7deb7909733e6490cad52beb88190ad (diff)
parent81870840254227b76f537d2f0003f072863aa436 (diff)
downloadpyramid-230157fcc3a3d3bd4658189aa5588da41b2da840.tar.gz
pyramid-230157fcc3a3d3bd4658189aa5588da41b2da840.tar.bz2
pyramid-230157fcc3a3d3bd4658189aa5588da41b2da840.zip
Merge branch 'master' of github.com:Pylons/pyramid
-rw-r--r--CHANGES.txt15
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--pyramid/path.py2
-rw-r--r--pyramid/scaffolds/copydir.py4
-rw-r--r--pyramid/scaffolds/template.py13
-rw-r--r--pyramid/tests/test_scaffolds/test_template.py25
-rw-r--r--pyramid/tests/test_traversal.py17
-rw-r--r--pyramid/tests/test_urldispatch.py10
-rw-r--r--pyramid/traversal.py2
-rw-r--r--pyramid/urldispatch.py6
10 files changed, 78 insertions, 18 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 1eeb0ce7b..41ecbde0f 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -4,6 +4,11 @@ Next Release
Features
--------
+- It is now possible to escape double braces in Pyramid scaffolds (unescaped,
+ these represent replacement values). You can use ``\{\{a\}\}`` to
+ represent a "bare" ``{{a}}``. See
+ https://github.com/Pylons/pyramid/pull/862
+
- Add ``localizer`` property (reified) to the request.
See https://github.com/Pylons/pyramid/issues/508.
@@ -149,6 +154,11 @@ Features
Bug Fixes
---------
+- Fix an obscure problem when combining a virtual root with a route with a
+ ``*traverse`` in its pattern. Now the traversal path generated in
+ such a configuration will be correct, instead of an element missing
+ a leading slash.
+
- Fixed a Mako renderer bug returning a tuple with a previous defname value
in some circumstances. See https://github.com/Pylons/pyramid/issues/1037
for more information.
@@ -202,6 +212,11 @@ Backwards Incompatibilities
previously returned the URL without the query string by default, it now does
attach the query string unless it is overriden.
+- The ``route_url`` and ``route_path`` APIs no longer quote ``/``
+ to ``%2F`` when a replacement value contains a ``/``. This was pointless,
+ as WSGI servers always unquote the slash anyway, and Pyramid never sees the
+ quoted value.
+
1.4 (2012-12-18)
================
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 591dcd7e4..0ddaebf15 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -218,3 +218,5 @@ Contributors
- Andreas Zeidler, 2013/08/15
- Matthew Wilkes, 2013/08/23
+
+- Takahiro Fujiwara, 2013/08/28
diff --git a/pyramid/path.py b/pyramid/path.py
index ab39a85d9..eb92ea62b 100644
--- a/pyramid/path.py
+++ b/pyramid/path.py
@@ -324,7 +324,7 @@ class DottedNameResolver(Resolver):
def _pkg_resources_style(self, value, package):
""" package.module:attr style """
- if value.startswith('.') or value.startswith(':'):
+ if value.startswith(('.', ':')):
if not package:
raise ValueError(
'relative name %r irresolveable without package' % (value,)
diff --git a/pyramid/scaffolds/copydir.py b/pyramid/scaffolds/copydir.py
index 7864dd1a1..3b871dc19 100644
--- a/pyramid/scaffolds/copydir.py
+++ b/pyramid/scaffolds/copydir.py
@@ -156,9 +156,9 @@ def should_skip_file(name):
"""
if name.startswith('.'):
return 'Skipping hidden file %(filename)s'
- if name.endswith('~') or name.endswith('.bak'):
+ if name.endswith(('~', '.bak')):
return 'Skipping backup file %(filename)s'
- if name.endswith('.pyc') or name.endswith('.pyo'):
+ if name.endswith(('.pyc', '.pyo')):
return 'Skipping %s file ' % os.path.splitext(name)[1] + '%(filename)s'
if name.endswith('$py.class'):
return 'Skipping $py.class file %(filename)s'
diff --git a/pyramid/scaffolds/template.py b/pyramid/scaffolds/template.py
index 39d0e4b3f..d88f5b2a6 100644
--- a/pyramid/scaffolds/template.py
+++ b/pyramid/scaffolds/template.py
@@ -35,7 +35,8 @@ class Template(object):
content = native_(content, fsenc)
try:
return bytes_(
- substitute_double_braces(content, TypeMapper(vars)), fsenc)
+ substitute_escaped_double_braces(
+ substitute_double_braces(content, TypeMapper(vars))), fsenc)
except Exception as e:
_add_except(e, ' in file %s' % filename)
raise
@@ -148,7 +149,15 @@ def substitute_double_braces(content, values):
value = match.group('braced').strip()
return values[value]
return double_brace_pattern.sub(double_bracerepl, content)
-
+
+escaped_double_brace_pattern = re.compile(r'\\{\\{(?P<escape_braced>[^\\]*?)\\}\\}')
+
+def substitute_escaped_double_braces(content):
+ def escaped_double_bracerepl(match):
+ value = match.group('escape_braced').strip()
+ return "{{%(value)s}}" % locals()
+ return escaped_double_brace_pattern.sub(escaped_double_bracerepl, content)
+
def _add_except(exc, info): # pragma: no cover
if not hasattr(exc, 'args') or exc.args is None:
return
diff --git a/pyramid/tests/test_scaffolds/test_template.py b/pyramid/tests/test_scaffolds/test_template.py
index d7cf638b6..2e961c516 100644
--- a/pyramid/tests/test_scaffolds/test_template.py
+++ b/pyramid/tests/test_scaffolds/test_template.py
@@ -11,7 +11,7 @@ class TestTemplate(unittest.TestCase):
inst = self._makeOne()
result = inst.render_template('{{a}} {{b}}', {'a':'1', 'b':'2'})
self.assertEqual(result, bytes_('1 2'))
-
+
def test_render_template_expr_failure(self):
inst = self._makeOne()
self.assertRaises(AttributeError, inst.render_template,
@@ -37,6 +37,21 @@ class TestTemplate(unittest.TestCase):
result = inst.render_template('{{a}}', {'a':None})
self.assertEqual(result, b'')
+ def test_render_template_with_escaped_double_braces(self):
+ inst = self._makeOne()
+ result = inst.render_template('{{a}} {{b}} \{\{a\}\} \{\{c\}\}', {'a':'1', 'b':'2'})
+ self.assertEqual(result, bytes_('1 2 {{a}} {{c}}'))
+
+ def test_render_template_with_breaking_escaped_braces(self):
+ inst = self._makeOne()
+ result = inst.render_template('{{a}} {{b}} \{\{a\} \{b\}\}', {'a':'1', 'b':'2'})
+ self.assertEqual(result, bytes_('1 2 \{\{a\} \{b\}\}'))
+
+ def test_render_template_with_escaped_single_braces(self):
+ inst = self._makeOne()
+ result = inst.render_template('{{a}} {{b}} \{a\} \{b', {'a':'1', 'b':'2'})
+ self.assertEqual(result, bytes_('1 2 \{a\} \{b'))
+
def test_module_dir(self):
import sys
import pkg_resources
@@ -90,7 +105,7 @@ class TestTemplate(unittest.TestCase):
'overwrite':False,
'interactive':False,
})
-
+
def test_write_files_path_missing(self):
L = []
inst = self._makeOne()
@@ -132,9 +147,9 @@ class DummyOptions(object):
simulate = False
overwrite = False
interactive = False
-
+
class DummyCommand(object):
options = DummyOptions()
verbosity = 1
-
-
+
+
diff --git a/pyramid/tests/test_traversal.py b/pyramid/tests/test_traversal.py
index 2e45ae1a9..ba0be7e06 100644
--- a/pyramid/tests/test_traversal.py
+++ b/pyramid/tests/test_traversal.py
@@ -456,6 +456,23 @@ class ResourceTreeTraverserTests(unittest.TestCase):
self.assertEqual(result['virtual_root'], resource)
self.assertEqual(result['virtual_root_path'], ())
+ def test_withroute_and_traverse_and_vroot(self):
+ abc = DummyContext()
+ resource = DummyContext(next=abc)
+ environ = self._getEnviron(HTTP_X_VHM_ROOT='/abc')
+ request = DummyRequest(environ)
+ traverser = self._makeOne(resource)
+ matchdict = {'traverse':text_('/foo/bar')}
+ request.matchdict = matchdict
+ result = traverser(request)
+ self.assertEqual(result['context'], abc)
+ self.assertEqual(result['view_name'], 'foo')
+ self.assertEqual(result['subpath'], ('bar',))
+ self.assertEqual(result['traversed'], ('abc', 'foo'))
+ self.assertEqual(result['root'], resource)
+ self.assertEqual(result['virtual_root'], abc)
+ self.assertEqual(result['virtual_root_path'], ('abc',))
+
class FindInterfaceTests(unittest.TestCase):
def _callFUT(self, context, iface):
from pyramid.traversal import find_interface
diff --git a/pyramid/tests/test_urldispatch.py b/pyramid/tests/test_urldispatch.py
index b2164717e..1755d9f47 100644
--- a/pyramid/tests/test_urldispatch.py
+++ b/pyramid/tests/test_urldispatch.py
@@ -295,7 +295,7 @@ class TestCompileRoute(unittest.TestCase):
'remainder':'/everything/else/here'})
self.assertEqual(matcher('foo/baz/biz/buz/bar'), None)
self.assertEqual(generator(
- {'baz':1, 'buz':2, 'remainder':'/a/b'}), '/foo/1/biz/2/bar%2Fa%2Fb')
+ {'baz':1, 'buz':2, 'remainder':'/a/b'}), '/foo/1/biz/2/bar/a/b')
def test_no_beginning_slash(self):
matcher, generator = self._callFUT('foo/:baz/biz/:buz/bar')
@@ -491,10 +491,10 @@ class TestCompileRouteFunctional(unittest.TestCase):
self.generates('zzz/{x}*traverse', {'x':'abc', 'traverse':'/def/g'},
'/zzz/abc/def/g')
self.generates('/{x}', {'x':text_(b'/La Pe\xc3\xb1a', 'utf-8')},
- '/%2FLa%20Pe%C3%B1a')
+ '//La%20Pe%C3%B1a')
self.generates('/{x}*y', {'x':text_(b'/La Pe\xc3\xb1a', 'utf-8'),
'y':'/rest/of/path'},
- '/%2FLa%20Pe%C3%B1a/rest/of/path')
+ '//La%20Pe%C3%B1a/rest/of/path')
self.generates('*traverse', {'traverse':('a', text_(b'La Pe\xf1a'))},
'/a/La%20Pe%C3%B1a')
self.generates('/foo/{id}.html', {'id':'bar'}, '/foo/bar.html')
@@ -511,10 +511,10 @@ class TestCompileRouteFunctional(unittest.TestCase):
self.generates('zzz/:x*traverse', {'x':'abc', 'traverse':'/def/g'},
'/zzz/abc/def/g')
self.generates('/:x', {'x':text_(b'/La Pe\xc3\xb1a', 'utf-8')},
- '/%2FLa%20Pe%C3%B1a')
+ '//La%20Pe%C3%B1a')
self.generates('/:x*y', {'x':text_(b'/La Pe\xc3\xb1a', 'utf-8'),
'y':'/rest/of/path'},
- '/%2FLa%20Pe%C3%B1a/rest/of/path')
+ '//La%20Pe%C3%B1a/rest/of/path')
self.generates('*traverse', {'traverse':('a', text_(b'La Pe\xf1a'))},
'/a/La%20Pe%C3%B1a')
self.generates('/foo/:id.html', {'id':'bar'}, '/foo/bar.html')
diff --git a/pyramid/traversal.py b/pyramid/traversal.py
index ed49d8743..469e77454 100644
--- a/pyramid/traversal.py
+++ b/pyramid/traversal.py
@@ -640,7 +640,7 @@ class ResourceTreeTraverser(object):
# this is a *traverse stararg (not a {traverse})
# routing has already decoded these elements, so we just
# need to join them
- path = slash.join(path) or slash
+ path = '/' + slash.join(path) or slash
subpath = matchdict.get('subpath', ())
if not is_nonstr_iter(subpath):
diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py
index 4182ea665..8090f07f2 100644
--- a/pyramid/urldispatch.py
+++ b/pyramid/urldispatch.py
@@ -213,7 +213,9 @@ def _compile_route(route):
if k == remainder:
# a stararg argument
if is_nonstr_iter(v):
- v = '/'.join([quote_path_segment(x) for x in v]) # native
+ v = '/'.join(
+ [quote_path_segment(x, safe='/') for x in v]
+ ) # native
else:
if v.__class__ not in string_types:
v = str(v)
@@ -222,7 +224,7 @@ def _compile_route(route):
if v.__class__ not in string_types:
v = str(v)
# v may be bytes (py2) or native string (py3)
- v = quote_path_segment(v)
+ v = quote_path_segment(v, safe='/')
# at this point, the value will be a native string
newdict[k] = v