summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2008-12-17 16:04:45 +0000
committerChris McDonough <chrism@agendaless.com>2008-12-17 16:04:45 +0000
commit7b1c3ac42c148b8ef5fdd47fe90542f254d29663 (patch)
tree9515e92511b2b946bdfddafe3ec2fc74a682f312
parent4e75b9f4fe1479b5cbde3a8224e386e3392f6b43 (diff)
downloadpyramid-7b1c3ac42c148b8ef5fdd47fe90542f254d29663.tar.gz
pyramid-7b1c3ac42c148b8ef5fdd47fe90542f254d29663.tar.bz2
pyramid-7b1c3ac42c148b8ef5fdd47fe90542f254d29663.zip
- In the past, during traversal, the ModelGraphTraverser (the
default traverser) always passed each URL path segment to any ``__getitem__`` method of a model object as a byte string (a ``str`` object). Now, by default the ModelGraphTraverser attempts to decode the path segment to Unicode (a ``unicode`` object) using the UTF-8 encoding before passing it to the ``__getitem__`` method of a model object. This makes it possible for model objects to be dumber in ``__getitem__`` when trying to resolve a subobject, as model objects themselves no longer need to try to divine whether or not to try to decode the path segment passed by the traverser. Note that since 0.5.4, URLs generated by repoze.bfg's ``model_url`` API will contain UTF-8 encoded path segments as necessary, so any URL generated by BFG itself will be decodeable by the traverser. If another application generates URLs to a BFG application, to be resolved successully, it should generate the URL with UTF-8 encoded path segments to be successfully resolved. The decoder is not at all magical: if a non-UTF-8-decodeable path segment (e.g. one encoded using UTF-16 or some other insanity) is passed in the URL, BFG will raise a ``TypeError`` with a message indicating it could not decode the path segment. To turn on the older behavior, where path segments were not decoded to Unicode before being passed to model object ``__getitem__`` by the traverser, and were passed as a raw byte string, set the ``unicode_path_segments`` configuration setting to a false value in your BFG application's section of the paste .ini file, for example:: unicode_path_segments = False Or start the application using the ``BFG_UNICODE_PATH_SEGMENT`` envvar set to a false value:: BFG_UNICODE_PATH_SEGMENTS=0
-rw-r--r--CHANGES.txt41
-rw-r--r--docs/narr/environment.rst50
-rw-r--r--repoze/bfg/registry.py7
-rw-r--r--repoze/bfg/tests/test_registry.py18
-rw-r--r--repoze/bfg/tests/test_traversal.py39
-rw-r--r--repoze/bfg/traversal.py20
6 files changed, 151 insertions, 24 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index f7271114c..c41c1090b 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,44 @@
+Next release
+
+ Backwards Incompatbilities
+
+ - In the past, during traversal, the ModelGraphTraverser (the
+ default traverser) always passed each URL path segment to any
+ ``__getitem__`` method of a model object as a byte string (a
+ ``str`` object). Now, by default the ModelGraphTraverser attempts
+ to decode the path segment to Unicode (a ``unicode`` object) using
+ the UTF-8 encoding before passing it to the ``__getitem__`` method
+ of a model object. This makes it possible for model objects to be
+ dumber in ``__getitem__`` when trying to resolve a subobject, as
+ model objects themselves no longer need to try to divine whether
+ or not to try to decode the path segment passed by the
+ traverser.
+
+ Note that since 0.5.4, URLs generated by repoze.bfg's
+ ``model_url`` API will contain UTF-8 encoded path segments as
+ necessary, so any URL generated by BFG itself will be decodeable
+ by the traverser. If another application generates URLs to a BFG
+ application, to be resolved successully, it should generate the
+ URL with UTF-8 encoded path segments to be successfully resolved.
+ The decoder is not at all magical: if a non-UTF-8-decodeable path
+ segment (e.g. one encoded using UTF-16 or some other insanity) is
+ passed in the URL, BFG will raise a ``TypeError`` with a message
+ indicating it could not decode the path segment.
+
+ To turn on the older behavior, where path segments were not
+ decoded to Unicode before being passed to model object
+ ``__getitem__`` by the traverser, and were passed as a raw byte
+ string, set the ``unicode_path_segments`` configuration setting to
+ a false value in your BFG application's section of the paste .ini
+ file, for example::
+
+ unicode_path_segments = False
+
+ Or start the application using the ``BFG_UNICODE_PATH_SEGMENT``
+ envvar set to a false value::
+
+ BFG_UNICODE_PATH_SEGMENTS=0
+
0.5.4 (12/13/2008)
Backwards Incompatibilities
diff --git a/docs/narr/environment.rst b/docs/narr/environment.rst
index 3fe3457c0..69dc98e25 100644
--- a/docs/narr/environment.rst
+++ b/docs/narr/environment.rst
@@ -19,26 +19,36 @@ setting names documented in this chapter are reserved for
:mod:`repoze.bfg` use. You should not use them to indicate
application-specific configuration settings.
-+-----------------------------+--------------------------+-------------------------------------+
-| Environment Variable Name | Config File Setting Name | Further Information |
-+=============================+==========================+=====================================+
-| ``BFG_RELOAD_TEMPLATES`` | ``reload_templates`` | Reload templates without restart |
-| | | when true |
-| | | See also: |
-| | | :ref:`reload_templates_section` |
-+-----------------------------+--------------------------+-------------------------------------+
-| ``BFG_DEBUG_AUTHORIZATION`` | ``debug_authorization`` | Print view authorization failure & |
-| | | success info to stderr when true |
-| | | See also: |
-| | | :ref:`debug_authorization_section` |
-+-----------------------------+--------------------------+-------------------------------------+
-| ``BFG_DEBUG_NOTFOUND`` | ``debug_notfound`` | Print view-related NotFound debug |
-| | | messages to stderr when true |
-| | | See also: |
-| | | :ref:`debug_notfound_section` |
-+-----------------------------+--------------------------+-------------------------------------+
-| ``BFG_DEBUG_ALL`` | ``debug_all`` | Turns all debug_* settings on. |
-+-----------------------------+--------------------------+-------------------------------------+
++---------------------------------+-----------------------------+----------------------------------------+
+| Environment Variable Name | Config File Setting Name | Further Information |
++=================================+=============================+========================================+
+| ``BFG_RELOAD_TEMPLATES`` | ``reload_templates`` | Reload templates without restart |
+| | | when true |
+| | | See also: |
+| | | :ref:`reload_templates_section` |
++---------------------------------+-----------------------------+----------------------------------------+
+| ``BFG_DEBUG_AUTHORIZATION`` | ``debug_authorization`` | Print view authorization failure & |
+| | | success info to stderr when true |
+| | | See also: |
+| | | :ref:`debug_authorization_section` |
++---------------------------------+-----------------------------+----------------------------------------+
+| ``BFG_DEBUG_NOTFOUND`` | ``debug_notfound`` | Print view-related NotFound debug |
+| | | messages to stderr when true |
+| | | See also: |
+| | | :ref:`debug_notfound_section` |
++---------------------------------+-----------------------------+----------------------------------------+
+| ``BFG_DEBUG_ALL`` | ``debug_all`` | Turns all debug_* settings on. |
++---------------------------------+-----------------------------+----------------------------------------+
+| ``BFG_UNICODE_PATH_SEGMENTS`` | ``unicode_path_segments`` | Defaults to ``true``. When ``true``, |
+| | | URL path segment names will be passed |
+| | | to model object ``__getitem__`` |
+| | | methods by the BFG model graph |
+| | | traverser as ``unicode`` types rather |
+| | | than as ``str`` types; path segments |
+| | | will be assumed to be UTF-8 encoded. |
+| | | When ``false``, pass path segments |
+| | | as undecoded ``str`` types. |
++---------------------------------+-----------------------------+----------------------------------------+
Examples
--------
diff --git a/repoze/bfg/registry.py b/repoze/bfg/registry.py
index b76453f36..5dda3f7a6 100644
--- a/repoze/bfg/registry.py
+++ b/repoze/bfg/registry.py
@@ -74,6 +74,7 @@ class Settings(object):
reload_templates = False
debug_notfound = False
debug_authorization = False
+ unicode_path_segments = True
def __init__(self, options):
self.__dict__.update(options)
@@ -102,13 +103,17 @@ def get_options(kw, environ=os.environ):
config_debug_notfound = kw.get('debug_notfound', '')
effective_debug_notfound = asbool(eget('BFG_DEBUG_NOTFOUND',
config_debug_notfound))
- config_reload_templates = kw.get('reload_templates')
+ config_reload_templates = kw.get('reload_templates', '')
effective_reload_templates = asbool(eget('BFG_RELOAD_TEMPLATES',
config_reload_templates))
+ config_unicode_path_segments = kw.get('unicode_path_segments', '')
+ effective_unicode_path_segments = asbool(eget('BFG_UNICODE_PATH_SEGMENTS',
+ config_unicode_path_segments))
return {
'debug_authorization': effective_debug_all or effective_debug_auth,
'debug_notfound': effective_debug_all or effective_debug_notfound,
'reload_templates': effective_reload_templates,
+ 'unicode_path_segments': effective_unicode_path_segments,
}
from zope.testing.cleanup import addCleanUp
diff --git a/repoze/bfg/tests/test_registry.py b/repoze/bfg/tests/test_registry.py
index 0b7eda586..f10fe6a52 100644
--- a/repoze/bfg/tests/test_registry.py
+++ b/repoze/bfg/tests/test_registry.py
@@ -117,6 +117,22 @@ class TestGetOptions(unittest.TestCase):
self.assertEqual(result['debug_notfound'], True)
self.assertEqual(result['debug_authorization'], True)
+ def test_unicode_path_segments(self):
+ get_options = self._getFUT()
+ result = get_options({})
+ self.assertEqual(result['unicode_path_segments'], False)
+ result = get_options({'unicode_path_segments':'false'})
+ self.assertEqual(result['unicode_path_segments'], False)
+ result = get_options({'unicode_path_segments':'t'})
+ self.assertEqual(result['unicode_path_segments'], True)
+ result = get_options({'unicode_path_segments':'1'})
+ self.assertEqual(result['unicode_path_segments'], True)
+ result = get_options({}, {'BFG_UNICODE_PATH_SEGMENTS':'1'})
+ self.assertEqual(result['unicode_path_segments'], True)
+ result = get_options({'unicode_path_segments':'false'},
+ {'BFG_UNICODE_PATH_SEGMENTS':'1'})
+ self.assertEqual(result['unicode_path_segments'], True)
+
class TestSettings(unittest.TestCase):
def _getTargetClass(self):
from repoze.bfg.registry import Settings
@@ -131,12 +147,14 @@ class TestSettings(unittest.TestCase):
self.assertEqual(settings.reload_templates, False)
self.assertEqual(settings.debug_notfound, False)
self.assertEqual(settings.debug_authorization, False)
+ self.assertEqual(settings.unicode_path_segments, True)
def test_with_option(self):
settings = self._makeOne(reload_templates=True)
self.assertEqual(settings.reload_templates, True)
self.assertEqual(settings.debug_notfound, False)
self.assertEqual(settings.debug_authorization, False)
+ self.assertEqual(settings.unicode_path_segments, True)
class TestThreadLocalRegistryManager(unittest.TestCase, PlacelessSetup):
def setUp(self):
diff --git a/repoze/bfg/tests/test_traversal.py b/repoze/bfg/tests/test_traversal.py
index 32be1ebbb..21e2b6180 100644
--- a/repoze/bfg/tests/test_traversal.py
+++ b/repoze/bfg/tests/test_traversal.py
@@ -162,6 +162,42 @@ class ModelGraphTraverserTests(unittest.TestCase, PlacelessSetup):
self.assertEqual(ctx.__parent__, bar)
self.failIf(isProxy(ctx.__parent__))
+ def test_non_utf8_path_segment_unicode_path_segments_fails(self):
+ foo = DummyContext()
+ root = DummyContext(foo)
+ policy = self._makeOne(root)
+ segment = unicode('LaPe\xc3\xb1a', 'utf-8').encode('utf-16')
+ environ = self._getEnviron(PATH_INFO='/%s' % segment)
+ self.assertRaises(TypeError, policy, environ)
+
+ def test_non_utf8_path_segment_settings_unicode_path_segments_fails(self):
+ defaultkw = {'unicode_path_segments':True}
+ settings = DummySettings(**defaultkw)
+ from repoze.bfg.interfaces import ISettings
+ import zope.component
+ gsm = zope.component.getGlobalSiteManager()
+ gsm.registerUtility(settings, ISettings)
+ foo = DummyContext()
+ root = DummyContext(foo)
+ policy = self._makeOne(root)
+ segment = unicode('LaPe\xc3\xb1a', 'utf-8').encode('utf-16')
+ environ = self._getEnviron(PATH_INFO='/%s' % segment)
+ self.assertRaises(TypeError, policy, environ)
+
+ def test_non_utf8_path_segment_str_path_segments_succeeds(self):
+ defaultkw = {'unicode_path_segments':False}
+ settings = DummySettings(**defaultkw)
+ from repoze.bfg.interfaces import ISettings
+ import zope.component
+ gsm = zope.component.getGlobalSiteManager()
+ gsm.registerUtility(settings, ISettings)
+ foo = DummyContext()
+ root = DummyContext(foo)
+ policy = self._makeOne(root)
+ segment = unicode('LaPe\xc3\xb1a', 'utf-8').encode('utf-16')
+ environ = self._getEnviron(PATH_INFO='/%s' % segment)
+ ctx, name, subpath = policy(environ) # test is: this doesn't fail
+
class FindInterfaceTests(unittest.TestCase):
def _callFUT(self, context, iface):
from repoze.bfg.traversal import find_interface
@@ -422,3 +458,6 @@ class DummyContext(object):
class DummyRequest:
application_url = 'http://example.com:5432/'
+class DummySettings:
+ def __init__(self, **kw):
+ self.__dict__.update(kw)
diff --git a/repoze/bfg/traversal.py b/repoze/bfg/traversal.py
index e599ee6c1..89495a223 100644
--- a/repoze/bfg/traversal.py
+++ b/repoze/bfg/traversal.py
@@ -1,5 +1,7 @@
import urllib
import urlparse
+
+from zope.component import queryUtility
from zope.interface import classProvides
from zope.interface import implements
@@ -9,6 +11,7 @@ from repoze.bfg.location import lineage
from repoze.bfg.interfaces import ILocation
from repoze.bfg.interfaces import ITraverser
from repoze.bfg.interfaces import ITraverserFactory
+from repoze.bfg.interfaces import ISettings
def split_path(path):
while path.startswith('/'):
@@ -26,7 +29,13 @@ def split_path(path):
clean.append(segment)
return clean
-def step(ob, name, default):
+def step(ob, name, default, as_unicode=True):
+ if as_unicode:
+ try:
+ name = name.decode('utf-8')
+ except UnicodeDecodeError:
+ raise TypeError('Could not decode path segment "%s" using the '
+ 'UTF-8 decoding scheme' % name)
if name.startswith('@@'):
return name[2:], default
if not hasattr(ob, '__getitem__'):
@@ -36,7 +45,7 @@ def step(ob, name, default):
except KeyError:
return name, default
-_marker = ()
+_marker = []
class ModelGraphTraverser(object):
classProvides(ITraverserFactory)
@@ -44,8 +53,13 @@ class ModelGraphTraverser(object):
def __init__(self, root):
self.root = root
self.locatable = ILocation.providedBy(root)
+ self.unicode_path_segments = True
+ settings = queryUtility(ISettings)
+ if settings is not None:
+ self.unicode_path_segments = settings.unicode_path_segments
def __call__(self, environ):
+ unicode_path_segments = self.unicode_path_segments
path = environ.get('PATH_INFO', '/')
path = split_path(path)
ob = self.root
@@ -54,7 +68,7 @@ class ModelGraphTraverser(object):
while path:
segment = path.pop(0)
- segment, next = step(ob, segment, _marker)
+ segment, next = step(ob, segment, _marker, unicode_path_segments)
if next is _marker:
name = segment
break