Chris McDonough
2012-09-09 ebcdc7cccc977e388426f581abaf16e39c49cdbd
- 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.

- 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.
6 files modified
96 ■■■■ changed files
CHANGES.txt 29 ●●●●● patch | view | raw | blame | history
pyramid/request.py 9 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_request.py 12 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_traversal.py 30 ●●●● patch | view | raw | blame | history
pyramid/traversal.py 14 ●●●●● patch | view | raw | blame | history
setup.py 2 ●●● patch | view | raw | blame | history
CHANGES.txt
@@ -151,3 +151,32 @@
  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`` and
  ``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.
pyramid/request.py
@@ -454,13 +454,4 @@
    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)
pyramid/tests/test_request.py
@@ -549,18 +549,6 @@
        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:
pyramid/tests/test_traversal.py
@@ -71,8 +71,6 @@
        self.assertEqual(self._callFUT('../../bar'), (text_('bar'),))
    def test_segments_are_unicode(self):
        # breaks because lru_cached holds on to strings?  possibly from
        # other tests.  not good.
        result = self._callFUT('/foo/bar')
        self.assertEqual(type(result[0]), text_type)
        self.assertEqual(type(result[1]), text_type)
@@ -162,7 +160,7 @@
    def test_call_pathel_with_no_getitem(self):
        policy = self._makeOne(None)
        environ = self._getEnviron()
        request = DummyRequest(environ, path_info='/foo/bar')
        request = DummyRequest(environ, path_info=text_('/foo/bar'))
        result = policy(request)
        self.assertEqual(result['context'], None)
        self.assertEqual(result['view_name'], 'foo')
@@ -176,7 +174,7 @@
        root = DummyContext()
        policy = self._makeOne(root)
        environ = self._getEnviron()
        request = DummyRequest(environ, path_info='')
        request = DummyRequest(environ, path_info=text_(''))
        result = policy(request)
        self.assertEqual(result['context'], root)
        self.assertEqual(result['view_name'], '')
@@ -191,7 +189,7 @@
        root = DummyContext(foo)
        policy = self._makeOne(root)
        environ = self._getEnviron()
        request = DummyRequest(environ, path_info='/foo/bar')
        request = DummyRequest(environ, path_info=text_('/foo/bar'))
        result = policy(request)
        self.assertEqual(result['context'], foo)
        self.assertEqual(result['view_name'], 'bar')
@@ -206,7 +204,7 @@
        root = DummyContext(foo)
        policy = self._makeOne(root)
        environ = self._getEnviron()
        request = DummyRequest(environ, path_info='/foo/bar/baz/buz')
        request = DummyRequest(environ, path_info=text_('/foo/bar/baz/buz'))
        result = policy(request)
        self.assertEqual(result['context'], foo)
        self.assertEqual(result['view_name'], 'bar')
@@ -221,7 +219,7 @@
        root = DummyContext(foo)
        policy = self._makeOne(root)
        environ = self._getEnviron()
        request = DummyRequest(environ, path_info='/@@foo')
        request = DummyRequest(environ, path_info=text_('/@@foo'))
        result = policy(request)
        self.assertEqual(result['context'], root)
        self.assertEqual(result['view_name'], 'foo')
@@ -238,7 +236,7 @@
        foo = DummyContext(bar, 'foo')
        root = DummyContext(foo, 'root')
        policy = self._makeOne(root)
        request = DummyRequest(environ, path_info='/baz')
        request = DummyRequest(environ, path_info=text_('/baz'))
        result = policy(request)
        self.assertEqual(result['context'], baz)
        self.assertEqual(result['view_name'], '')
@@ -257,7 +255,7 @@
        foo = DummyContext(bar, 'foo')
        root = DummyContext(foo, 'root')
        policy = self._makeOne(root)
        request = DummyRequest(environ, path_info='/bar/baz')
        request = DummyRequest(environ, path_info=text_('/bar/baz'))
        result = policy(request)
        self.assertEqual(result['context'], baz)
        self.assertEqual(result['view_name'], '')
@@ -275,7 +273,7 @@
        foo = DummyContext(bar)
        root = DummyContext(foo)
        policy = self._makeOne(root)
        request = DummyRequest(environ, path_info='/foo/bar/baz')
        request = DummyRequest(environ, path_info=text_('/foo/bar/baz'))
        result = policy(request)
        self.assertEqual(result['context'], baz)
        self.assertEqual(result['view_name'], '')
@@ -293,7 +291,7 @@
        foo = DummyContext(bar, 'foo')
        root = DummyContext(foo, 'root')
        policy = self._makeOne(root)
        request = DummyRequest(environ, path_info='/')
        request = DummyRequest(environ, path_info=text_('/'))
        result = policy(request)
        self.assertEqual(result['context'], baz)
        self.assertEqual(result['view_name'], '')
@@ -308,7 +306,7 @@
    def test_call_with_vh_root_path_root(self):
        policy = self._makeOne(None)
        environ = self._getEnviron(HTTP_X_VHM_ROOT='/')
        request = DummyRequest(environ, path_info='/')
        request = DummyRequest(environ, path_info=text_('/'))
        result = policy(request)
        self.assertEqual(result['context'], None)
        self.assertEqual(result['view_name'], '')
@@ -329,7 +327,7 @@
        else:
            vhm_root = b'/Qu\xc3\xa9bec'
        environ = self._getEnviron(HTTP_X_VHM_ROOT=vhm_root)
        request = DummyRequest(environ, path_info='/bar')
        request = DummyRequest(environ, path_info=text_('/bar'))
        result = policy(request)
        self.assertEqual(result['context'], bar)
        self.assertEqual(result['view_name'], '')
@@ -351,7 +349,7 @@
        root = DummyContext(foo)
        policy = self._makeOne(root)
        environ = self._getEnviron()
        toraise = UnicodeDecodeError('ascii', 'a', 2, 3, '5')
        toraise = UnicodeDecodeError('ascii', b'a', 2, 3, '5')
        request = DummyRequest(environ, toraise=toraise)
        request.matchdict = None
        self.assertRaises(URLDecodeError, policy, request)
@@ -403,7 +401,7 @@
    def test_withroute_and_traverse_string(self):
        resource = DummyContext()
        traverser = self._makeOne(resource)
        matchdict =  {'traverse':'foo/bar'}
        matchdict =  {'traverse':text_('foo/bar')}
        request = DummyRequest({})
        request.matchdict = matchdict
        result = traverser(request)
@@ -1287,7 +1285,7 @@
    matchdict = None
    matched_route = None
    def __init__(self, environ=None, path_info='/', toraise=None):
    def __init__(self, environ=None, path_info=text_('/'), toraise=None):
        if environ is None:
            environ = {}
        self.environ = environ
pyramid/traversal.py
@@ -614,6 +614,7 @@
            _segment_cache[(segment, safe)] = result
            return result
    
slash = text_('/')
@implementer(ITraverser)
class ResourceTreeTraverser(object):
@@ -629,17 +630,17 @@
        self.root = root
    def __call__(self, request):
        matchdict = request.matchdict
        environ = request.environ
        matchdict = request.matchdict
        if matchdict is not None:
            path = matchdict.get('traverse', '/') or '/'
            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):
@@ -653,9 +654,10 @@
            subpath = ()
            try:
                # empty if mounted under a path in mod_wsgi, for example
                path = request.path_info
                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)
@@ -674,7 +676,7 @@
        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 = ()
setup.py
@@ -39,7 +39,7 @@
    '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