diff options
| author | Chris McDonough <chrism@plope.com> | 2013-08-28 12:52:20 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2013-08-28 12:52:20 -0400 |
| commit | 230157fcc3a3d3bd4658189aa5588da41b2da840 (patch) | |
| tree | 9d4d149375fb02748568c8dbc6a7d2b4f82e1d99 | |
| parent | 8ac9ae83d7deb7909733e6490cad52beb88190ad (diff) | |
| parent | 81870840254227b76f537d2f0003f072863aa436 (diff) | |
| download | pyramid-230157fcc3a3d3bd4658189aa5588da41b2da840.tar.gz pyramid-230157fcc3a3d3bd4658189aa5588da41b2da840.tar.bz2 pyramid-230157fcc3a3d3bd4658189aa5588da41b2da840.zip | |
Merge branch 'master' of github.com:Pylons/pyramid
| -rw-r--r-- | CHANGES.txt | 15 | ||||
| -rw-r--r-- | CONTRIBUTORS.txt | 2 | ||||
| -rw-r--r-- | pyramid/path.py | 2 | ||||
| -rw-r--r-- | pyramid/scaffolds/copydir.py | 4 | ||||
| -rw-r--r-- | pyramid/scaffolds/template.py | 13 | ||||
| -rw-r--r-- | pyramid/tests/test_scaffolds/test_template.py | 25 | ||||
| -rw-r--r-- | pyramid/tests/test_traversal.py | 17 | ||||
| -rw-r--r-- | pyramid/tests/test_urldispatch.py | 10 | ||||
| -rw-r--r-- | pyramid/traversal.py | 2 | ||||
| -rw-r--r-- | pyramid/urldispatch.py | 6 |
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 |
