summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2016-08-04 07:30:04 -0500
committerJason Madden <jamadden@gmail.com>2016-08-04 07:30:04 -0500
commit2c7af36ae6217a02e614d8af14ca06a20b7147b1 (patch)
tree436cdab2591fe776a6844c44b49d53f6da06ff98
parent1bf6305c14f8e7d4c683b30cd8a7e72c4cf2049e (diff)
downloadzope-traversing-handle-unicode-gracefully.tar.gz
Gracefully handle UnicodeEncodeError on python 2.handle-unicode-gracefully
This can be produced when doing an attribute lookup. Turn it into a LocationError (or the default value). zope.container.traversal.ContainerTraversable was doing this already, see https://github.com/zopefoundation/zope.container/blob/master/src/zope/container/traversal.py#L105
-rw-r--r--CHANGES.rst3
-rw-r--r--src/zope/traversing/adapters.py17
-rw-r--r--src/zope/traversing/tests/test_conveniencefunctions.py17
-rw-r--r--src/zope/traversing/tests/test_traverser.py4
4 files changed, 39 insertions, 2 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 9d97773..3b81f8c 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -8,6 +8,8 @@ Changes
- Drop support for Python 2.6.
+- Gracefully handle ``UnicodeEncodeError`` that can be produced when
+ doing attribute lookup on Python 2 by instead raising a ``LocationError``.
4.0.0 (2014-03-21)
------------------
@@ -283,4 +285,3 @@ No further changes since 3.4.0a1.
Initial release as a separate project, corresponds to ``zope.traversing``
from Zope 3.4.0a1
-
diff --git a/src/zope/traversing/adapters.py b/src/zope/traversing/adapters.py
index f1aeba0..a4985a2 100644
--- a/src/zope/traversing/adapters.py
+++ b/src/zope/traversing/adapters.py
@@ -38,7 +38,14 @@ class DefaultTraversable(object):
def traverse(self, name, furtherPath):
subject = self._subject
__traceback_info__ = (subject, name, furtherPath)
- attr = getattr(subject, name, _marker)
+ try:
+ attr = getattr(subject, name, _marker)
+ except UnicodeEncodeError:
+ # If we're on Python 2, and name was a unicode string the
+ # name would have been encoded using the system encoding
+ # (usually ascii). Failure to encode means invalid
+ # attribute name.
+ attr = _marker
if attr is not _marker:
return attr
if hasattr(subject, '__getitem__'):
@@ -133,6 +140,14 @@ def traversePathElement(obj, name, further_path, default=_marker,
try:
return traversable.traverse(nm, further_path)
+ except UnicodeEncodeError:
+ # If we're on Python 2, and nm was a unicode string, and the traversable
+ # tried to do an attribute lookup, the nm would have been encoded using the
+ # system encoding (usually ascii). Failure to encode means invalid attribute
+ # name.
+ if default is not _marker:
+ return default
+ raise LocationError(obj, name)
except LocationError:
if default is not _marker:
return default
diff --git a/src/zope/traversing/tests/test_conveniencefunctions.py b/src/zope/traversing/tests/test_conveniencefunctions.py
index 72c1861..b69d165 100644
--- a/src/zope/traversing/tests/test_conveniencefunctions.py
+++ b/src/zope/traversing/tests/test_conveniencefunctions.py
@@ -118,6 +118,23 @@ class Test(PlacelessSetup, TestCase):
self.folder, './item'
)
+ def testTraverseNameUnicode(self):
+ from zope.traversing.api import traverseName
+ from zope.interface import implementer
+
+ @implementer(ITraversable)
+ class BrokenTraversable(object):
+ def traverse(self, name, furtherPath):
+ getattr(self, u'\u2019', None)
+ # The above actually works on Python 3
+ raise LocationError()
+
+ self.assertRaises(
+ LocationError,
+ traverseName,
+ BrokenTraversable(), '')
+
+
def testGetName(self):
from zope.traversing.api import getName
self.assertEqual(
diff --git a/src/zope/traversing/tests/test_traverser.py b/src/zope/traversing/tests/test_traverser.py
index a6e638d..2c81ac3 100644
--- a/src/zope/traversing/tests/test_traverser.py
+++ b/src/zope/traversing/tests/test_traverser.py
@@ -271,6 +271,10 @@ class DefaultTraversableTests(unittest.TestCase):
self.assertRaises(LocationError, df.traverse, 'bar', [])
+ def testUnicodeTraversal(self):
+ df = DefaultTraversable(object())
+ self.assertRaises(LocationError, df.traverse, u'\u2019', ())
+
def test_suite():
loader = unittest.TestLoader()
suite = loader.loadTestsFromTestCase(TraverserTests)