diff options
| -rw-r--r-- | CHANGES.txt | 29 | ||||
| -rw-r--r-- | TODO.txt | 2 | ||||
| -rw-r--r-- | pyramid/request.py | 9 | ||||
| -rw-r--r-- | pyramid/router.py | 10 | ||||
| -rw-r--r-- | pyramid/tests/test_request.py | 12 | ||||
| -rw-r--r-- | pyramid/tests/test_router.py | 4 | ||||
| -rw-r--r-- | pyramid/tests/test_traversal.py | 155 | ||||
| -rw-r--r-- | pyramid/traversal.py | 32 | ||||
| -rw-r--r-- | setup.py | 2 |
9 files changed, 115 insertions, 140 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 9e911ce7e..dd5f66e5b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -152,3 +152,32 @@ Deprecations featureful ``pyramid.config.Configurator.set_request_method`` should be used in its place (it has all of the same capabilities but can also extend the request object with methods). + +Backwards Incompatibilities +--------------------------- + +- The Pyramid router no longer adds the values ``bfg.routes.route`` or + ``bfg.routes.matchdict`` to the request's WSGI environment dictionary. + These values were docs-deprecated in ``repoze.bfg`` 1.0 (effectively seven + minor releases ago). If your code depended on these values, use + request.matched_route and request.matchdict instead. + +- It is no longer possible to pass an environ dictionary directly to + ``pyramid.traversal.ResourceTreeTraverser.__call__`` (aka + ``ModelGraphTraverser.__call__``). Instead, you must pass a request + object. Passing an environment instead of a request has generated a + deprecation warning since Pyramid 1.1. + +- Pyramid will no longer work properly if you use the + ``webob.request.LegacyRequest`` as a request factory. Instances of the + LegacyRequest class have a ``request.path_info`` which return a string. + This Pyramid release assumes that ``request.path_info`` will + unconditionally be Unicode. + +Dependencies +------------ + +- Pyramid now requires WebOb 1.2b3+ (the prior Pyramid release only relied on + 1.2dev+). This is to ensure that we obtain a version of WebOb that returns + ``request.path_info`` as text. + @@ -98,8 +98,6 @@ Nice-to-Have Future ------ -- 1.4: Kill off ``bfg.routes`` envvars in router. - - 1.4: Remove ``chameleon_text`` / ``chameleon_zpt`` deprecated functions (render_*) diff --git a/pyramid/request.py b/pyramid/request.py index 37fac6a46..af3310829 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -454,13 +454,4 @@ def call_app_with_subpath_as_path_info(request, app): new_request.environ['SCRIPT_NAME'] = new_script_name new_request.environ['PATH_INFO'] = new_path_info - # In case downstream WSGI app is a Pyramid app, hack around existence of - # these envars until we can safely remove them (see router.py); in any - # case, even if these get removed, it might be better to not copy the - # existing environ but to create a new one instead. - if 'bfg.routes.route' in new_request.environ: - del new_request.environ['bfg.routes.route'] - if 'bfg.routes.matchdict' in new_request.environ: - del new_request.environ['bfg.routes.matchdict'] - return new_request.get_response(app) diff --git a/pyramid/router.py b/pyramid/router.py index 0c115a1ac..3491884b8 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -84,13 +84,6 @@ class Router(object): request.url) logger and logger.debug(msg) else: - # TODO: kill off bfg.routes.* environ keys - # when traverser requires request arg, and - # cant cope with environ anymore (they are - # docs-deprecated as of BFG 1.3) - environ = request.environ - environ['bfg.routes.route'] = route - environ['bfg.routes.matchdict'] = match attrs['matchdict'] = match attrs['matched_route'] = route @@ -105,7 +98,8 @@ class Router(object): request.url, route.name, request.path_info, - route.pattern, match, + route.pattern, + match, ', '.join([p.__text__ for p in route.predicates])) ) logger and logger.debug(msg) diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py index a95d614f9..86cfd8b09 100644 --- a/pyramid/tests/test_request.py +++ b/pyramid/tests/test_request.py @@ -549,18 +549,6 @@ class Test_call_app_with_subpath_as_path_info(unittest.TestCase): self.assertEqual(request.environ['SCRIPT_NAME'], '/' + encoded) self.assertEqual(request.environ['PATH_INFO'], '/' + encoded) - def test_it_removes_bfg_routes_info(self): - request = DummyRequest({}) - request.environ['bfg.routes.route'] = True - request.environ['bfg.routes.matchdict'] = True - response = self._callFUT(request, 'app') - self.assertTrue(request.copied) - self.assertEqual(response, 'app') - self.assertEqual(request.environ['SCRIPT_NAME'], '') - self.assertEqual(request.environ['PATH_INFO'], '/') - self.assertFalse('bfg.routes.route' in request.environ) - self.assertFalse('bfg.routes.matchdict' in request.environ) - class DummyRequest: def __init__(self, environ=None): if environ is None: diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index eb9b7285d..6c68d548c 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -646,8 +646,6 @@ class TestRouter(unittest.TestCase): self.assertEqual(request.context, context) self.assertEqual(request.root, root) matchdict = {'action':'action1', 'article':'article1'} - self.assertEqual(environ['bfg.routes.matchdict'], matchdict) - self.assertEqual(environ['bfg.routes.route'].name, 'foo') self.assertEqual(request.matchdict, matchdict) self.assertEqual(request.matched_route.name, 'foo') self.assertEqual(len(logger.messages), 1) @@ -712,8 +710,6 @@ class TestRouter(unittest.TestCase): self.assertEqual(request.context, context) self.assertEqual(request.root, root) matchdict = {'action':'action1', 'article':'article1'} - self.assertEqual(environ['bfg.routes.matchdict'], matchdict) - self.assertEqual(environ['bfg.routes.route'].name, 'foo') self.assertEqual(request.matchdict, matchdict) self.assertEqual(request.matched_route.name, 'foo') self.assertTrue(IFoo.providedBy(request)) diff --git a/pyramid/tests/test_traversal.py b/pyramid/tests/test_traversal.py index 8e0bb2494..237064871 100644 --- a/pyramid/tests/test_traversal.py +++ b/pyramid/tests/test_traversal.py @@ -142,31 +142,25 @@ class ResourceTreeTraverserTests(unittest.TestCase): self.assertEqual(result['virtual_root_path'], ()) def test_call_with_pathinfo_highorder(self): - foo = DummyContext(None, text_(b'Qu\xc3\xa9bec', 'utf-8')) + path = text_(b'/Qu\xc3\xa9bec', 'utf-8') + foo = DummyContext(None, path) root = DummyContext(foo, 'root') policy = self._makeOne(root) - if PY3: # pragma: no cover - path_info = b'/Qu\xc3\xa9bec'.decode('latin-1') - else: - path_info = b'/Qu\xc3\xa9bec' - environ = self._getEnviron(PATH_INFO=path_info) - request = DummyRequest(environ) + environ = self._getEnviron() + request = DummyRequest(environ, path_info=path) result = policy(request) self.assertEqual(result['context'], foo) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) - self.assertEqual( - result['traversed'], - (text_(b'Qu\xc3\xa9bec', 'utf-8'),) - ) + self.assertEqual(result['traversed'], (path[1:],)) self.assertEqual(result['root'], policy.root) self.assertEqual(result['virtual_root'], policy.root) self.assertEqual(result['virtual_root_path'], ()) def test_call_pathel_with_no_getitem(self): policy = self._makeOne(None) - environ = self._getEnviron(PATH_INFO='/foo/bar') - request = DummyRequest(environ) + environ = self._getEnviron() + request = DummyRequest(environ, path_info=text_('/foo/bar')) result = policy(request) self.assertEqual(result['context'], None) self.assertEqual(result['view_name'], 'foo') @@ -179,8 +173,8 @@ class ResourceTreeTraverserTests(unittest.TestCase): def test_call_withconn_getitem_emptypath_nosubpath(self): root = DummyContext() policy = self._makeOne(root) - environ = self._getEnviron(PATH_INFO='') - request = DummyRequest(environ) + environ = self._getEnviron() + request = DummyRequest(environ, path_info=text_('')) result = policy(request) self.assertEqual(result['context'], root) self.assertEqual(result['view_name'], '') @@ -194,8 +188,8 @@ class ResourceTreeTraverserTests(unittest.TestCase): foo = DummyContext() root = DummyContext(foo) policy = self._makeOne(root) - environ = self._getEnviron(PATH_INFO='/foo/bar') - request = DummyRequest(environ) + environ = self._getEnviron() + request = DummyRequest(environ, path_info=text_('/foo/bar')) result = policy(request) self.assertEqual(result['context'], foo) self.assertEqual(result['view_name'], 'bar') @@ -209,8 +203,8 @@ class ResourceTreeTraverserTests(unittest.TestCase): foo = DummyContext() root = DummyContext(foo) policy = self._makeOne(root) - environ = self._getEnviron(PATH_INFO='/foo/bar/baz/buz') - request = DummyRequest(environ) + environ = self._getEnviron() + request = DummyRequest(environ, path_info=text_('/foo/bar/baz/buz')) result = policy(request) self.assertEqual(result['context'], foo) self.assertEqual(result['view_name'], 'bar') @@ -224,8 +218,8 @@ class ResourceTreeTraverserTests(unittest.TestCase): foo = DummyContext() root = DummyContext(foo) policy = self._makeOne(root) - environ = self._getEnviron(PATH_INFO='/@@foo') - request = DummyRequest(environ) + environ = self._getEnviron() + request = DummyRequest(environ, path_info=text_('/@@foo')) result = policy(request) self.assertEqual(result['context'], root) self.assertEqual(result['view_name'], 'foo') @@ -236,14 +230,13 @@ class ResourceTreeTraverserTests(unittest.TestCase): self.assertEqual(result['virtual_root_path'], ()) def test_call_with_vh_root(self): - environ = self._getEnviron(PATH_INFO='/baz', - HTTP_X_VHM_ROOT='/foo/bar') + environ = self._getEnviron(HTTP_X_VHM_ROOT='/foo/bar') baz = DummyContext(None, 'baz') bar = DummyContext(baz, 'bar') foo = DummyContext(bar, 'foo') root = DummyContext(foo, 'root') policy = self._makeOne(root) - request = DummyRequest(environ) + request = DummyRequest(environ, path_info=text_('/baz')) result = policy(request) self.assertEqual(result['context'], baz) self.assertEqual(result['view_name'], '') @@ -256,14 +249,13 @@ class ResourceTreeTraverserTests(unittest.TestCase): (text_('foo'), text_('bar'))) def test_call_with_vh_root2(self): - environ = self._getEnviron(PATH_INFO='/bar/baz', - HTTP_X_VHM_ROOT='/foo') + environ = self._getEnviron(HTTP_X_VHM_ROOT='/foo') baz = DummyContext(None, 'baz') bar = DummyContext(baz, 'bar') foo = DummyContext(bar, 'foo') root = DummyContext(foo, 'root') policy = self._makeOne(root) - request = DummyRequest(environ) + request = DummyRequest(environ, path_info=text_('/bar/baz')) result = policy(request) self.assertEqual(result['context'], baz) self.assertEqual(result['view_name'], '') @@ -275,14 +267,13 @@ class ResourceTreeTraverserTests(unittest.TestCase): self.assertEqual(result['virtual_root_path'], (text_('foo'),)) def test_call_with_vh_root3(self): - environ = self._getEnviron(PATH_INFO='/foo/bar/baz', - HTTP_X_VHM_ROOT='/') + environ = self._getEnviron(HTTP_X_VHM_ROOT='/') baz = DummyContext() bar = DummyContext(baz) foo = DummyContext(bar) root = DummyContext(foo) policy = self._makeOne(root) - request = DummyRequest(environ) + request = DummyRequest(environ, path_info=text_('/foo/bar/baz')) result = policy(request) self.assertEqual(result['context'], baz) self.assertEqual(result['view_name'], '') @@ -294,14 +285,13 @@ class ResourceTreeTraverserTests(unittest.TestCase): self.assertEqual(result['virtual_root_path'], ()) def test_call_with_vh_root4(self): - environ = self._getEnviron(PATH_INFO='/', - HTTP_X_VHM_ROOT='/foo/bar/baz') + environ = self._getEnviron(HTTP_X_VHM_ROOT='/foo/bar/baz') baz = DummyContext(None, 'baz') bar = DummyContext(baz, 'bar') foo = DummyContext(bar, 'foo') root = DummyContext(foo, 'root') policy = self._makeOne(root) - request = DummyRequest(environ) + request = DummyRequest(environ, path_info=text_('/')) result = policy(request) self.assertEqual(result['context'], baz) self.assertEqual(result['view_name'], '') @@ -315,9 +305,8 @@ class ResourceTreeTraverserTests(unittest.TestCase): def test_call_with_vh_root_path_root(self): policy = self._makeOne(None) - environ = self._getEnviron(HTTP_X_VHM_ROOT='/', - PATH_INFO='/') - request = DummyRequest(environ) + environ = self._getEnviron(HTTP_X_VHM_ROOT='/') + request = DummyRequest(environ, path_info=text_('/')) result = policy(request) self.assertEqual(result['context'], None) self.assertEqual(result['view_name'], '') @@ -328,57 +317,48 @@ class ResourceTreeTraverserTests(unittest.TestCase): self.assertEqual(result['virtual_root_path'], ()) def test_call_with_vh_root_highorder(self): + path = text_(b'Qu\xc3\xa9bec', 'utf-8') bar = DummyContext(None, 'bar') - foo = DummyContext(bar, text_(b'Qu\xc3\xa9bec', 'utf-8')) + foo = DummyContext(bar, path) root = DummyContext(foo, 'root') policy = self._makeOne(root) if PY3: # pragma: no cover vhm_root = b'/Qu\xc3\xa9bec'.decode('latin-1') else: vhm_root = b'/Qu\xc3\xa9bec' - environ = self._getEnviron(HTTP_X_VHM_ROOT=vhm_root, - PATH_INFO='/bar') - request = DummyRequest(environ) + environ = self._getEnviron(HTTP_X_VHM_ROOT=vhm_root) + request = DummyRequest(environ, path_info=text_('/bar')) result = policy(request) self.assertEqual(result['context'], bar) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual( result['traversed'], - (text_(b'Qu\xc3\xa9bec', 'utf-8'), text_('bar')) + (path, text_('bar')) ) self.assertEqual(result['root'], policy.root) self.assertEqual(result['virtual_root'], foo) self.assertEqual( result['virtual_root_path'], - (text_(b'Qu\xc3\xa9bec', 'utf-8'),) + (path,) ) - def test_non_utf8_path_segment_unicode_path_segments_fails(self): + def test_path_info_raises_unicodedecodeerror(self): from pyramid.exceptions import URLDecodeError foo = DummyContext() root = DummyContext(foo) policy = self._makeOne(root) - segment = native_(text_(b'LaPe\xc3\xb1a', 'utf-8'), 'utf-16') - environ = self._getEnviron(PATH_INFO='/%s' % segment) - request = DummyRequest(environ) - self.assertRaises(URLDecodeError, policy, request) - - def test_non_utf8_path_segment_settings_unicode_path_segments_fails(self): - from pyramid.exceptions import URLDecodeError - foo = DummyContext() - root = DummyContext(foo) - policy = self._makeOne(root) - segment = native_(text_(b'LaPe\xc3\xb1a', 'utf-8'), 'utf-16') - environ = self._getEnviron(PATH_INFO='/%s' % segment) - request = DummyRequest(environ) + environ = self._getEnviron() + toraise = UnicodeDecodeError('ascii', b'a', 2, 3, '5') + request = DummyRequest(environ, toraise=toraise) + request.matchdict = None self.assertRaises(URLDecodeError, policy, request) def test_withroute_nothingfancy(self): resource = DummyContext() traverser = self._makeOne(resource) - environ = {'bfg.routes.matchdict': {}} - request = DummyRequest(environ) + request = DummyRequest({}) + request.matchdict = {} result = traverser(request) self.assertEqual(result['context'], resource) self.assertEqual(result['view_name'], '') @@ -391,8 +371,9 @@ class ResourceTreeTraverserTests(unittest.TestCase): def test_withroute_with_subpath_string(self): resource = DummyContext() traverser = self._makeOne(resource) - environ = {'bfg.routes.matchdict': {'subpath':'/a/b/c'}} - request = DummyRequest(environ) + matchdict = {'subpath':'/a/b/c'} + request = DummyRequest({}) + request.matchdict = matchdict result = traverser(request) self.assertEqual(result['context'], resource) self.assertEqual(result['view_name'], '') @@ -405,8 +386,9 @@ class ResourceTreeTraverserTests(unittest.TestCase): def test_withroute_with_subpath_tuple(self): resource = DummyContext() traverser = self._makeOne(resource) - environ = {'bfg.routes.matchdict': {'subpath':('a', 'b', 'c')}} - request = DummyRequest(environ) + matchdict = {'subpath':('a', 'b', 'c')} + request = DummyRequest({}) + request.matchdict = matchdict result = traverser(request) self.assertEqual(result['context'], resource) self.assertEqual(result['view_name'], '') @@ -419,8 +401,9 @@ class ResourceTreeTraverserTests(unittest.TestCase): def test_withroute_and_traverse_string(self): resource = DummyContext() traverser = self._makeOne(resource) - environ = {'bfg.routes.matchdict': {'traverse':'foo/bar'}} - request = DummyRequest(environ) + matchdict = {'traverse':text_('foo/bar')} + request = DummyRequest({}) + request.matchdict = matchdict result = traverser(request) self.assertEqual(result['context'], resource) self.assertEqual(result['view_name'], 'foo') @@ -433,8 +416,9 @@ class ResourceTreeTraverserTests(unittest.TestCase): def test_withroute_and_traverse_tuple(self): resource = DummyContext() traverser = self._makeOne(resource) - environ = {'bfg.routes.matchdict': {'traverse':('foo', 'bar')}} - request = DummyRequest(environ) + matchdict = {'traverse':('foo', 'bar')} + request = DummyRequest({}) + request.matchdict = matchdict result = traverser(request) self.assertEqual(result['context'], resource) self.assertEqual(result['view_name'], 'foo') @@ -447,8 +431,9 @@ class ResourceTreeTraverserTests(unittest.TestCase): def test_withroute_and_traverse_empty(self): resource = DummyContext() traverser = self._makeOne(resource) - environ = {'bfg.routes.matchdict': {'traverse':''}} - request = DummyRequest(environ) + matchdict = {'traverse':''} + request = DummyRequest({}) + request.matchdict = matchdict result = traverser(request) self.assertEqual(result['context'], resource) self.assertEqual(result['view_name'], '') @@ -458,21 +443,6 @@ class ResourceTreeTraverserTests(unittest.TestCase): self.assertEqual(result['virtual_root'], resource) self.assertEqual(result['virtual_root_path'], ()) - def test_call_with_environ(self): - with warnings.catch_warnings(record=True) as w: - warnings.filterwarnings('always') - policy = self._makeOne(None) - environ = self._getEnviron() - result = policy(environ) - self.assertEqual(result['context'], None) - self.assertEqual(result['view_name'], '') - self.assertEqual(result['subpath'], ()) - self.assertEqual(result['traversed'], ()) - self.assertEqual(result['root'], policy.root) - self.assertEqual(result['virtual_root'], policy.root) - self.assertEqual(result['virtual_root_path'], ()) - self.assertEqual(len(w), 1) - class FindInterfaceTests(unittest.TestCase): def _callFUT(self, context, iface): from pyramid.traversal import find_interface @@ -1310,11 +1280,28 @@ class DummyContext(object): return '<DummyContext with name %s at id %s>'%(self.__name__, id(self)) class DummyRequest: + application_url = 'http://example.com:5432' # app_url never ends with slash - def __init__(self, environ=None): + matchdict = None + matched_route = None + + def __init__(self, environ=None, path_info=text_('/'), toraise=None): if environ is None: environ = {} self.environ = environ + self._set_path_info(path_info) + self.toraise = toraise + + def _get_path_info(self): + if self.toraise: + raise self.toraise + return self._path_info + + def _set_path_info(self, v): + self._path_info = v + + path_info = property(_get_path_info, _set_path_info) + class DummyContextURL: def __init__(self, context, request): diff --git a/pyramid/traversal.py b/pyramid/traversal.py index b514d4c16..6832ce69a 100644 --- a/pyramid/traversal.py +++ b/pyramid/traversal.py @@ -614,6 +614,7 @@ else: _segment_cache[(segment, safe)] = result return result +slash = text_('/') @implementer(ITraverser) class ResourceTreeTraverser(object): @@ -629,27 +630,17 @@ class ResourceTreeTraverser(object): self.root = root def __call__(self, request): - try: - environ = request.environ - except AttributeError: - # In BFG 1.0 and before, this API expected an environ - # rather than a request; some bit of code may still be - # passing us an environ. If so, deal. - environ = request - depwarn = ('Passing an environ dictionary directly to a traverser ' - 'is deprecated in Pyramid 1.1. Pass a request object ' - 'instead.') - warnings.warn(depwarn, DeprecationWarning, 2) - - if 'bfg.routes.matchdict' in environ: - matchdict = environ['bfg.routes.matchdict'] - - path = matchdict.get('traverse', '/') or '/' + environ = request.environ + matchdict = request.matchdict + + if matchdict is not None: + + path = matchdict.get('traverse', slash) or slash if is_nonstr_iter(path): # this is a *traverse stararg (not a {traverse}) # routing has already decoded these elements, so we just # need to join them - path = '/'.join(path) or '/' + path = slash.join(path) or slash subpath = matchdict.get('subpath', ()) if not is_nonstr_iter(subpath): @@ -663,9 +654,10 @@ class ResourceTreeTraverser(object): subpath = () try: # empty if mounted under a path in mod_wsgi, for example - path = decode_path_info(environ['PATH_INFO'] or '/') + path = request.path_info or slash except KeyError: - path = '/' + # if environ['PATH_INFO'] is just not there + path = slash except UnicodeDecodeError as e: raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason) @@ -684,7 +676,7 @@ class ResourceTreeTraverser(object): root = self.root ob = vroot = root - if vpath == '/': # invariant: vpath must not be empty + if vpath == slash: # invariant: vpath must not be empty # prevent a call to traversal_path if we know it's going # to return the empty tuple vpath_tuple = () @@ -39,7 +39,7 @@ install_requires=[ 'setuptools', 'Chameleon >= 1.2.3', 'Mako >= 0.3.6', # strict_undefined - 'WebOb >= 1.2dev', # response.text / py3 compat + 'WebOb >= 1.2b3', # request.path_info is unicode 'repoze.lru >= 0.4', # py3 compat 'zope.interface >= 3.8.0', # has zope.interface.registry 'zope.deprecation >= 3.5.0', # py3 compat |
