Chris McDonough
2010-11-18 a66593d25e77f1a0e749f5590b45498bbaa66755
- Fix apparent failures when calling ``pyramid.traversal.find_model(root,
path)`` or ``pyramid.traversal.traverse(path)`` when ``path`` is
(erroneously) a Unicode object. The user is meant to pass these APIs a
string object, never a Unicode object. In practice, however, users indeed
pass Unicode. Because the string that is passed must be ASCII encodeable,
now, if they pass a Unicode object, its data is eagerly converted to an
ASCII string rather than being passed along to downstream code as a
convenience to the user and to prevent puzzling second-order failures from
cropping up (all failures will occur within ``pyramid.traversal.traverse``
rather than later down the line as the result of calling
``traversal_path``).
3 files modified
51 ■■■■■ changed files
CHANGES.txt 12 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_traversal.py 26 ●●●●● patch | view | raw | blame | history
pyramid/traversal.py 13 ●●●●● patch | view | raw | blame | history
CHANGES.txt
@@ -28,6 +28,18 @@
- The ``pyramid_alchemy`` paster template had a typo, preventing an import
  from working.
- Fix apparent failures when calling ``pyramid.traversal.find_model(root,
  path)`` or ``pyramid.traversal.traverse(path)`` when ``path`` is
  (erroneously) a Unicode object. The user is meant to pass these APIs a
  string object, never a Unicode object.  In practice, however, users indeed
  pass Unicode.  Because the string that is passed must be ASCII encodeable,
  now, if they pass a Unicode object, its data is eagerly converted to an
  ASCII string rather than being passed along to downstream code as a
  convenience to the user and to prevent puzzling second-order failures from
  cropping up (all failures will occur within ``pyramid.traversal.traverse``
  rather than later down the line as the result of calling
  ``traversal_path``).
Backwards Incompatibilities
---------------------------
pyramid/tests/test_traversal.py
@@ -522,6 +522,32 @@
        self.assertEqual(root.wascontext, True)
        self.assertEqual(root.request.environ['PATH_INFO'], '/')
    def test_absolute_unicode_found(self):
        # test for bug wiggy found in wild, traceback stack:
        # root = u'/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF'
        # wiggy's code: section=find_model(page, root)
        # find_model L76: D = traverse(model, path)
        # traverse L291: return traverser(request)
        # __call__ line 568: vpath_tuple = traversal_path(vpath)
        # lru_cached line 91: f(*arg)
        # traversal_path line 443: path.encode('ascii')
        # UnicodeEncodeError: 'ascii' codec can't encode characters in
        #     position 1-12: ordinal not in range(128)
        #
        # solution: encode string to ascii in pyramid.traversal.traverse
        # before passing it along to webob as path_info
        from pyramid.traversal import ModelGraphTraverser
        unprintable = DummyContext()
        root = DummyContext(unprintable)
        unprintable.__parent__ = root
        unprintable.__name__ = unicode(
            '/\xe6\xb5\x81\xe8\xa1\x8c\xe8\xb6\x8b\xe5\x8a\xbf', 'utf-8')
        root.__parent__ = None
        root.__name__ = None
        traverser = ModelGraphTraverser
        self._registerTraverser(traverser)
        result = self._callFUT(root, u'/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF')
        self.assertEqual(result, unprintable)
class ModelPathTests(unittest.TestCase):
    def _callFUT(self, model, *elements):
pyramid/traversal.py
@@ -228,7 +228,7 @@
    object supplied to the function as the ``model`` argument.  If an
    empty string is passed as ``path``, the ``model`` passed in will
    be returned.  Model path strings must be escaped in the following
    manner: each Unicode path segment must be encoded as UTF-8 and as
    manner: each Unicode path segment must be encoded as UTF-8 and
    each path segment must escaped via Python's :mod:`urllib.quote`.
    For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or
    ``to%20the/La%20Pe%C3%B1a`` (relative).  The
@@ -272,6 +272,17 @@
        else:
            path = ''
    # The user is supposed to pass us a string object, never Unicode.  In
    # practice, however, users indeed pass Unicode to this API.  If they do
    # pass a Unicode object, its data *must* be entirely encodeable to ASCII,
    # so we encode it here as a convenience to the user and to prevent
    # second-order failures from cropping up (all failures will occur at this
    # step rather than later down the line as the result of calling
    # ``traversal_path``).
    if isinstance(path, unicode):
        path = path.encode('ascii')
    if path and path[0] == '/':
        model = find_root(model)