diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-05-18 07:07:12 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-05-18 07:07:12 +0000 |
| commit | 916f88578ad68470a35a4b7afd223e9dbf5fd20d (patch) | |
| tree | 52913d78e5876ca2612da56c6d066227001d9160 | |
| parent | 8e2f6eaae104df8bf13678a67f4690294f982e2d (diff) | |
| download | pyramid-916f88578ad68470a35a4b7afd223e9dbf5fd20d.tar.gz pyramid-916f88578ad68470a35a4b7afd223e9dbf5fd20d.tar.bz2 pyramid-916f88578ad68470a35a4b7afd223e9dbf5fd20d.zip | |
Features
--------
- Added a ``traverse`` function to the ``repoze.bfg.traversal``
module. This function may be used to retrieve certain values
computed during path resolution. See the Traversal API chapter of
the documentation for more information about this function.
Deprecations
------------
- Internal: ``ITraverser`` callables should now return a dictionary
rather than a tuple. Up until 0.7.0, all ITraversers were assumed
to return a 3-tuple. In 0.7.1, ITraversers were assumed to return a
6-tuple. As (by evidence) it's likely we'll need to add further
information to the return value of an ITraverser callable, 0.8
assumes that an ITraverser return a dictionary with certain elements
in it. See the ``repoze.bfg.interfaces.ITraverser`` interface for
the list of keys that should be present in the dictionary.
``ITraversers`` which return tuples will still work, although a
deprecation warning will be issued.
Backwards Incompatibilities
---------------------------
- If your code used the ITraverser interface directly (not via an API
function such as ``find_model``) via an adapter lookup, you'll need
to change your code to expect a dictionary rather than a 3- or
6-tuple if your code ever gets return values from the default
ModelGraphTraverser or RoutesModelTraverser adapters.
| -rw-r--r-- | CHANGES.txt | 31 | ||||
| -rw-r--r-- | docs/api/traversal.rst | 2 | ||||
| -rw-r--r-- | repoze/bfg/interfaces.py | 29 | ||||
| -rw-r--r-- | repoze/bfg/router.py | 42 | ||||
| -rw-r--r-- | repoze/bfg/testing.py | 5 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_router.py | 168 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_testing.py | 16 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_traversal.py | 282 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_urldispatch.py | 38 | ||||
| -rw-r--r-- | repoze/bfg/traversal.py | 213 | ||||
| -rw-r--r-- | repoze/bfg/urldispatch.py | 4 |
11 files changed, 563 insertions, 267 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 3e4dac521..fd4a82cb2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,36 @@ 0.8dev (unreleased) =================== -... +Features +-------- + +- Added a ``traverse`` function to the ``repoze.bfg.traversal`` + module. This function may be used to retrieve certain values + computed during path resolution. See the Traversal API chapter of + the documentation for more information about this function. + +Deprecations +------------ + +- Internal: ``ITraverser`` callables should now return a dictionary + rather than a tuple. Up until 0.7.0, all ITraversers were assumed + to return a 3-tuple. In 0.7.1, ITraversers were assumed to return a + 6-tuple. As (by evidence) it's likely we'll need to add further + information to the return value of an ITraverser callable, 0.8 + assumes that an ITraverser return a dictionary with certain elements + in it. See the ``repoze.bfg.interfaces.ITraverser`` interface for + the list of keys that should be present in the dictionary. + ``ITraversers`` which return tuples will still work, although a + deprecation warning will be issued. + +Backwards Incompatibilities +--------------------------- + +- If your code used the ITraverser interface directly (not via an API + function such as ``find_model``) via an adapter lookup, you'll need + to change your code to expect a dictionary rather than a 3- or + 6-tuple if your code ever gets return values from the default + ModelGraphTraverser or RoutesModelTraverser adapters. 0.8a7 (2009-05-16) ================== diff --git a/docs/api/traversal.rst b/docs/api/traversal.rst index 89d9135a0..2f57fd3aa 100644 --- a/docs/api/traversal.rst +++ b/docs/api/traversal.rst @@ -19,6 +19,8 @@ .. autofunction:: virtual_root + .. autofunction:: traverse + .. note:: A function named ``model_url`` used to be present in this module. It was moved to :ref:`url_module` in version 0.6.1. diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py index 5dadd252a..cecc3a397 100644 --- a/repoze/bfg/interfaces.py +++ b/repoze/bfg/interfaces.py @@ -59,20 +59,21 @@ deprecated('IRootPolicy', class ITraverser(Interface): def __call__(environ): - """ Return a tuple in the form ``(context, view_name, subpath, - traversed, virtual_root, virtual_root_path)`` , typically the - result of an object graph traversal. ``context`` will be a - model object, ``view_name`` will be the view name used (a - Unicode name), ``subpath`` will be a sequence of Unicode names - that followed the view name but were not traversed, - ``traversed`` will be a sequence of Unicode names that were - traversed (including the virtual root path, if any) or - ``None`` if no traversal was performed, ``virtual_root`` will - be a model object representing the virtual root (or the - physical root if traversal was not performed), and - ``virtual_root_path`` will be a sequence representing the - virtual root path (a sequence of Unicode names) or None if - traversal was not performed.""" + """ Return a dictionary with the keys ``root``, ``context``, + ``view_name``, ``subpath``, ``traversed``, ``vroot``, and + ``vroot_path``. These values are typically the result of an + object graph traversal. ``root`` is the physical root object, + ``context`` will be a model object, ``view_name`` will be the + view name used (a Unicode name), ``subpath`` will be a + sequence of Unicode names that followed the view name but were + not traversed, ``traversed`` will be a sequence of Unicode + names that were traversed (including the virtual root path, if + any) or ``None`` if no traversal was performed, + ``virtual_root`` will be a model object representing the + virtual root (or the physical root if traversal was not + performed), and ``virtual_root_path`` will be a sequence + representing the virtual root path (a sequence of Unicode + names) or None if traversal was not performed.""" class ITraverserFactory(Interface): def __call__(context): diff --git a/repoze/bfg/router.py b/repoze/bfg/router.py index 5278da892..614d32aa2 100644 --- a/repoze/bfg/router.py +++ b/repoze/bfg/router.py @@ -15,7 +15,6 @@ from repoze.bfg.interfaces import IRequestFactory from repoze.bfg.interfaces import IRootFactory from repoze.bfg.interfaces import IRouter from repoze.bfg.interfaces import IRoutesMapper -from repoze.bfg.interfaces import ITraverserFactory from repoze.bfg.interfaces import ISecurityPolicy from repoze.bfg.interfaces import ISettings from repoze.bfg.interfaces import IUnauthorizedAppFactory @@ -35,6 +34,7 @@ from repoze.bfg.settings import Settings from repoze.bfg.urldispatch import RoutesRootFactory +from repoze.bfg.traversal import _traverse from repoze.bfg.view import _view_execution_permitted from repoze.bfg.wsgi import Unauthorized from repoze.bfg.wsgi import NotFound @@ -99,25 +99,15 @@ class Router(object): registry.has_listeners and registry.notify(NewRequest(request)) root = self.root_factory(environ) - traverser = registry.getAdapter(root, ITraverserFactory) - vals = traverser(environ) - - try: - context, view_name, subpath, traversed, vroot, vroot_path = vals - except ValueError: - if not (traverser.__class__ in self.traverser_warned): - self.logger and self.logger.warn( - '%s is an pre-0.7.1-style ITraverser returning only ' - '3 arguments; please update it to the new ' - '6-argument-returning interface for improved ' - 'functionality. See the repoze.bfg.interfaces module ' - 'for the new ITraverser interface ' - 'definition' % traverser) - self.traverser_warned[traverser.__class__] = True - context, view_name, subpath = vals - traversed = [] - vroot = root - vroot_path = [] + tdict = _traverse(root, environ) + if '_deprecation_warning' in tdict: + warning = tdict.pop('_deprecation_warning') + if not warning in self.traverser_warned: + self.logger and self.logger.warn(warning) + context, view_name, subpath, traversed, vroot, vroot_path = ( + tdict['context'], tdict['view_name'], tdict['subpath'], + tdict['traversed'], tdict['virtual_root'], + tdict['virtual_root_path']) if isinstance(request, WebObRequest): # webob.Request's __setattr__ (as of 0.9.5 and lower) @@ -125,13 +115,7 @@ class Router(object): # webob.Request, go around its back and set stuff into # the environ directly attrs = environ.setdefault('webob.adhoc_attrs', {}) - attrs['root'] = root - attrs['context'] = context - attrs['view_name'] = view_name - attrs['subpath'] = subpath - attrs['traversed'] = traversed - attrs['virtual_root'] = vroot - attrs['virtual_root_path'] = vroot_path + attrs.update(tdict) else: request.root = root request.context = context @@ -181,9 +165,9 @@ class Router(object): msg = ( 'debug_notfound of url %s; path_info: %r, context: %r, ' 'view_name: %r, subpath: %r, traversed: %r, ' - 'vroot: %r, vroot_path: %r' % ( + 'root: %r, vroot: %r, vroot_path: %r' % ( request.url, request.path_info, context, view_name, - subpath, traversed, vroot, vroot_path) + subpath, traversed, root, vroot, vroot_path) ) logger and logger.debug(msg) else: diff --git a/repoze/bfg/testing.py b/repoze/bfg/testing.py index 0ff1dbade..d4ff6fe4d 100644 --- a/repoze/bfg/testing.py +++ b/repoze/bfg/testing.py @@ -205,7 +205,10 @@ def make_traverser_factory(root): def __call__(self, environ): path = environ['PATH_INFO'] ob = root[path] - return ob, '', [] + from repoze.bfg.traversal import traversal_path + traversed = list(traversal_path(path)) + return {'context':ob, 'view_name':'','subpath':[], + 'traversed':traversed, 'vroot':ob, 'vroot_path':[]} return DummyTraverserFactory diff --git a/repoze/bfg/tests/test_router.py b/repoze/bfg/tests/test_router.py index 95defb7e7..db47f832e 100644 --- a/repoze/bfg/tests/test_router.py +++ b/repoze/bfg/tests/test_router.py @@ -38,9 +38,37 @@ class RouterTests(unittest.TestCase): settings = Settings(**defaultkw) self.registry.registerUtility(settings, ISettings) - def _registerTraverserFactory(self, app, name, *for_): + def _registerTraverserFactory(self, context, view_name='', subpath=None, + traversed=None, virtual_root=None, + virtual_root_path=None, **kw): from repoze.bfg.interfaces import ITraverserFactory - self.registry.registerAdapter(app, for_, ITraverserFactory, name) + + if virtual_root is None: + virtual_root = context + if subpath is None: + subpath = [] + if traversed is None: + traversed = [] + if virtual_root_path is None: + virtual_root_path = [] + + class DummyTraverserFactory: + def __init__(self, root): + self.root = root + + def __call__(self, path): + values = {'root':self.root, + 'context':context, + 'view_name':view_name, + 'subpath':subpath, + 'traversed':traversed, + 'virtual_root':virtual_root, + 'virtual_root_path':virtual_root_path} + kw.update(values) + return kw + + self.registry.registerAdapter(DummyTraverserFactory, (None,), + ITraverserFactory, name='') def _registerView(self, app, name, *for_): from repoze.bfg.interfaces import IView @@ -79,6 +107,7 @@ class RouterTests(unittest.TestCase): 'SERVER_NAME':'localhost', 'SERVER_PORT':'8080', 'REQUEST_METHOD':'GET', + 'PATH_INFO':'/', } environ.update(extras) return environ @@ -87,41 +116,16 @@ class RouterTests(unittest.TestCase): rootfactory = make_rootfactory(None) environ = self._makeEnviron() context = DummyContext() - traversalfactory = make_traversal_factory(context, '', []) - self._registerTraverserFactory(traversalfactory, '', None) - logger = self._registerLogger() + self._registerTraverserFactory(context) self._registerRootFactory(rootfactory) router = self._makeOne() self.assertEqual(router.root_policy, rootfactory) - def test_3arg_policy(self): - rootfactory = make_rootfactory(None) - environ = self._makeEnviron() - context = DummyContext() - traversalfactory = make_3arg_traversal_factory(context, '', []) - self._registerTraverserFactory(traversalfactory, '', None) - logger = self._registerLogger() - self._registerRootFactory(rootfactory) - router = self._makeOne() - start_response = DummyStartResponse() - result = router(environ, start_response) - self.failUnless(traversalfactory in router.traverser_warned) - headers = start_response.headers - self.assertEqual(len(headers), 2) - status = start_response.status - self.assertEqual(status, '404 Not Found') - self.failUnless('http://localhost:8080' in result[0], result) - self.failIf('debug_notfound' in result[0]) - self.assertEqual(len(logger.messages), 1) - message = logger.messages[0] - self.failUnless('is an pre-0.7.1-style ITraverser ' in message) - def test_call_no_view_registered_no_isettings(self): rootfactory = make_rootfactory(None) environ = self._makeEnviron() context = DummyContext() - traversalfactory = make_traversal_factory(context, '', []) - self._registerTraverserFactory(traversalfactory, '', None) + self._registerTraverserFactory(context) logger = self._registerLogger() self._registerRootFactory(rootfactory) router = self._makeOne() @@ -141,8 +145,7 @@ class RouterTests(unittest.TestCase): class NotFound(object): implements(IContextNotFound) context = NotFound() - traversalfactory = make_traversal_factory(context, '', []) - self._registerTraverserFactory(traversalfactory, '', None) + self._registerTraverserFactory(context) environ = self._makeEnviron() start_response = DummyStartResponse() rootfactory = make_rootfactory(NotFound()) @@ -157,8 +160,7 @@ class RouterTests(unittest.TestCase): rootfactory = make_rootfactory(None) environ = self._makeEnviron() context = DummyContext() - traversalfactory = make_traversal_factory(context, '', []) - self._registerTraverserFactory(traversalfactory, '', None) + self._registerTraverserFactory(context) logger = self._registerLogger() self._registerSettings(debug_notfound=False) self._registerRootFactory(rootfactory) @@ -177,8 +179,7 @@ class RouterTests(unittest.TestCase): rootfactory = make_rootfactory(None) environ = self._makeEnviron() context = DummyContext() - traversalfactory = make_traversal_factory(context, '', []) - self._registerTraverserFactory(traversalfactory, '', None) + self._registerTraverserFactory(context) self._registerSettings(debug_notfound=True) logger = self._registerLogger() self._registerRootFactory(rootfactory) @@ -190,7 +191,7 @@ class RouterTests(unittest.TestCase): status = start_response.status self.assertEqual(status, '404 Not Found') self.failUnless( - "debug_notfound of url http://localhost:8080; path_info: '', " + "debug_notfound of url http://localhost:8080/; path_info: '/', " "context:" in result[0]) self.failUnless( "view_name: '', subpath: []" in result[0]) @@ -198,7 +199,7 @@ class RouterTests(unittest.TestCase): self.assertEqual(len(logger.messages), 1) message = logger.messages[0] self.failUnless('of url http://localhost:8080' in message) - self.failUnless("path_info: ''" in message) + self.failUnless("path_info: '/'" in message) self.failUnless('DummyContext instance at' in message) self.failUnless("view_name: ''" in message) self.failUnless("subpath: []" in message) @@ -206,9 +207,8 @@ class RouterTests(unittest.TestCase): def test_call_view_returns_nonresponse(self): rootfactory = make_rootfactory(None) context = DummyContext() - traversalfactory = make_traversal_factory(context, '', []) + self._registerTraverserFactory(context) environ = self._makeEnviron() - self._registerTraverserFactory(traversalfactory, '', None) view = make_view('abc') self._registerView(view, '', None, None) self._registerRootFactory(rootfactory) @@ -219,12 +219,11 @@ class RouterTests(unittest.TestCase): def test_call_view_registered_nonspecific_default_path(self): rootfactory = make_rootfactory(None) context = DummyContext() - traversalfactory = make_traversal_factory(context, '', []) + self._registerTraverserFactory(context) response = DummyResponse() response.app_iter = ['Hello world'] view = make_view(response) environ = self._makeEnviron() - self._registerTraverserFactory(traversalfactory, '', None) self._registerView(view, '', None, None) self._registerRootFactory(rootfactory) router = self._makeOne() @@ -238,15 +237,34 @@ class RouterTests(unittest.TestCase): self.assertEqual(environ['webob.adhoc_attrs']['context'], context) self.assertEqual(environ['webob.adhoc_attrs']['root'], None) + def test_call_deprecation_warning(self): + rootfactory = make_rootfactory(None) + context = DummyContext() + self._registerTraverserFactory(context, _deprecation_warning='abc') + response = DummyResponse() + response.app_iter = ['Hello world'] + view = make_view(response) + environ = self._makeEnviron() + self._registerView(view, '', None, None) + self._registerRootFactory(rootfactory) + router = self._makeOne() + logger = self._registerLogger() + router.logger = logger + start_response = DummyStartResponse() + router(environ, start_response) + self.assertEqual(len(logger.messages), 1) + self.assertEqual(logger.messages[0], 'abc') + def test_call_view_registered_nonspecific_nondefault_path_and_subpath(self): rootfactory = make_rootfactory(None) context = DummyContext() - traversalfactory = make_traversal_factory(context, 'foo', ['bar']) + self._registerTraverserFactory(context, view_name='foo', + subpath=['bar'], + traversed=['context']) response = DummyResponse() response.app_iter = ['Hello world'] view = make_view(response) environ = self._makeEnviron() - self._registerTraverserFactory(traversalfactory, '', None) self._registerView(view, 'foo', None, None) self._registerRootFactory(rootfactory) router = self._makeOne() @@ -269,12 +287,11 @@ class RouterTests(unittest.TestCase): from repoze.bfg.interfaces import IRequest context = DummyContext() directlyProvides(context, IContext) - traversalfactory = make_traversal_factory(context, '', []) + self._registerTraverserFactory(context) response = DummyResponse() response.app_iter = ['Hello world'] view = make_view(response) environ = self._makeEnviron() - self._registerTraverserFactory(traversalfactory, '', None) self._registerView(view, '', IContext, IRequest) self._registerRootFactory(rootfactory) router = self._makeOne() @@ -299,11 +316,10 @@ class RouterTests(unittest.TestCase): from repoze.bfg.interfaces import IRequest context = DummyContext() directlyProvides(context, INotContext) - traversalfactory = make_traversal_factory(context, '', ['']) + self._registerTraverserFactory(context, subpath=['']) response = DummyResponse() view = make_view(response) environ = self._makeEnviron() - self._registerTraverserFactory(traversalfactory, '', None) self._registerView(view, '', IContext, IRequest) self._registerRootFactory(rootfactory) router = self._makeOne() @@ -321,11 +337,10 @@ class RouterTests(unittest.TestCase): from repoze.bfg.interfaces import IRequest context = DummyContext() directlyProvides(context, IContext) - traversalfactory = make_traversal_factory(context, '', ['']) + self._registerTraverserFactory(context, subpath=['']) response = DummyResponse() view = make_view(response) environ = self._makeEnviron() - self._registerTraverserFactory(traversalfactory, '', None) self._registerView(view, '', IContext, IRequest) secpol = DummySecurityPolicy() self._registerSecurityPolicy(secpol) @@ -344,13 +359,12 @@ class RouterTests(unittest.TestCase): from repoze.bfg.interfaces import IRequest context = DummyContext() directlyProvides(context, IContext) - traversalfactory = make_traversal_factory(context, '', ['']) + self._registerTraverserFactory(context, subpath=['']) response = DummyResponse() view = make_view(response) secpol = DummySecurityPolicy() permissionfactory = make_permission_factory(True) environ = self._makeEnviron() - self._registerTraverserFactory(traversalfactory, '', None) self._registerView(view, '', IContext, IRequest) self._registerSecurityPolicy(secpol) self._registerPermission(permissionfactory, '', IContext, IRequest) @@ -370,7 +384,7 @@ class RouterTests(unittest.TestCase): from repoze.bfg.interfaces import IRequest context = DummyContext() directlyProvides(context, IContext) - traversalfactory = make_traversal_factory(context, '', ['']) + self._registerTraverserFactory(context, subpath=['']) response = DummyResponse() view = make_view(response) secpol = DummySecurityPolicy() @@ -379,7 +393,6 @@ class RouterTests(unittest.TestCase): ACLDenied('ace', 'acl', 'permission', ['principals'], context) ) environ = self._makeEnviron() - self._registerTraverserFactory(traversalfactory, '', None) self._registerView(view, '', IContext, IRequest) self._registerSecurityPolicy(secpol) self._registerPermission(permissionfactory, '', IContext, IRequest) @@ -401,7 +414,7 @@ class RouterTests(unittest.TestCase): from repoze.bfg.interfaces import IRequest context = DummyContext() directlyProvides(context, IContext) - traversalfactory = make_traversal_factory(context, '', ['']) + self._registerTraverserFactory(context, subpath=['']) response = DummyResponse() view = make_view(response) secpol = DummySecurityPolicy() @@ -410,7 +423,6 @@ class RouterTests(unittest.TestCase): ACLDenied('ace', 'acl', 'permission', ['principals'], context) ) environ = self._makeEnviron() - self._registerTraverserFactory(traversalfactory, '', None) self._registerView(view, '', IContext, IRequest) self._registerSecurityPolicy(secpol) self._registerPermission(permissionfactory, '', IContext, IRequest) @@ -433,7 +445,7 @@ class RouterTests(unittest.TestCase): from repoze.bfg.interfaces import IRequest context = DummyContext() directlyProvides(context, IContext) - traversalfactory = make_traversal_factory(context, '', ['']) + self._registerTraverserFactory(context, subpath=['']) response = DummyResponse() view = make_view(response) secpol = DummySecurityPolicy() @@ -442,7 +454,6 @@ class RouterTests(unittest.TestCase): ACLDenied('ace', 'acl', 'permission', ['principals'], context) ) environ = self._makeEnviron() - self._registerTraverserFactory(traversalfactory, '', None) self._registerView(view, '', IContext, IRequest) self._registerSecurityPolicy(secpol) self._registerPermission(permissionfactory, '', IContext, IRequest) @@ -462,7 +473,7 @@ class RouterTests(unittest.TestCase): self.assertEqual(len(logger.messages), 1) logged = logger.messages[0] self.failUnless( - "debug_authorization of url http://localhost:8080 (view name " + "debug_authorization of url http://localhost:8080/ (view name " "'' against context" in logged) self.failUnless( "ACLDenied permission 'permission' via ACE 'ace' in ACL 'acl' on " @@ -473,12 +484,11 @@ class RouterTests(unittest.TestCase): def test_call_eventsends(self): rootfactory = make_rootfactory(None) context = DummyContext() - traversalfactory = make_traversal_factory(context, '', []) + self._registerTraverserFactory(context) response = DummyResponse() response.app_iter = ['Hello world'] view = make_view(response) environ = self._makeEnviron() - self._registerTraverserFactory(traversalfactory, '', None) self._registerView(view, '', None, None) from repoze.bfg.interfaces import INewRequest from repoze.bfg.interfaces import INewResponse @@ -500,12 +510,11 @@ class RouterTests(unittest.TestCase): from repoze.bfg.interfaces import IRequest rootfactory = make_rootfactory(None) context = DummyContext() - traversalfactory = make_traversal_factory(context, '', []) + self._registerTraverserFactory(context) response = DummyResponse() response.app_iter = ['Hello world'] view = make_view(response) environ = self._makeEnviron(REQUEST_METHOD='POST') - self._registerTraverserFactory(traversalfactory, '', None) self._registerView(view, '', None, None) self._registerRootFactory(rootfactory) router = self._makeOne() @@ -524,12 +533,11 @@ class RouterTests(unittest.TestCase): from repoze.bfg.interfaces import IRequest rootfactory = make_rootfactory(None) context = DummyContext() - traversalfactory = make_traversal_factory(context, '', []) + self._registerTraverserFactory(context) response = DummyResponse() response.app_iter = ['Hello world'] view = make_view(response) environ = self._makeEnviron(REQUEST_METHOD='PUT') - self._registerTraverserFactory(traversalfactory, '', None) self._registerView(view, '', None, None) self._registerRootFactory(rootfactory) router = self._makeOne() @@ -546,12 +554,11 @@ class RouterTests(unittest.TestCase): from repoze.bfg.interfaces import IRequest rootfactory = make_rootfactory(None) context = DummyContext() - traversalfactory = make_traversal_factory(context, '', []) + self._registerTraverserFactory(context) response = DummyResponse() response.app_iter = ['Hello world'] view = make_view(response) environ = self._makeEnviron(REQUEST_METHOD='UNKNOWN') - self._registerTraverserFactory(traversalfactory, '', None) self._registerView(view, '', None, None) self._registerRootFactory(rootfactory) router = self._makeOne() @@ -568,12 +575,11 @@ class RouterTests(unittest.TestCase): self.registry.registerUtility(DummyRequest, IRequestFactory) rootfactory = make_rootfactory(None) context = DummyContext() - traversalfactory = make_traversal_factory(context, '', []) + self._registerTraverserFactory(context) response = DummyResponse() response.app_iter = ['Hello world'] view = make_view(response) environ = self._makeEnviron() - self._registerTraverserFactory(traversalfactory, '', None) self._registerView(view, '', None, None) self._registerRootFactory(rootfactory) router = self._makeOne() @@ -594,12 +600,11 @@ class RouterTests(unittest.TestCase): self.registry.registerUtility(app, INotFoundAppFactory) rootfactory = make_rootfactory(None) context = DummyContext() - traversalfactory = make_traversal_factory(context, '', []) + self._registerTraverserFactory(context) response = DummyResponse() response.app_iter = ['Hello world'] view = make_view(response) environ = self._makeEnviron() - self._registerTraverserFactory(traversalfactory, '', None) self._registerView(view, '', None, None) self._registerRootFactory(rootfactory) router = self._makeOne() @@ -612,12 +617,11 @@ class RouterTests(unittest.TestCase): self.registry.registerUtility(app, IUnauthorizedAppFactory) rootfactory = make_rootfactory(None) context = DummyContext() - traversalfactory = make_traversal_factory(context, '', []) + self._registerTraverserFactory(context) response = DummyResponse() response.app_iter = ['Hello world'] view = make_view(response) environ = self._makeEnviron() - self._registerTraverserFactory(traversalfactory, '', None) self._registerView(view, '', None, None) self._registerRootFactory(rootfactory) router = self._makeOne() @@ -740,26 +744,6 @@ def make_view(response): return response return view -def make_traversal_factory(context, name, subpath, vroot=None, - vroot_path=(), traversed=()): - class DummyTraversalFactory: - def __init__(self, root): - self.root = root - - def __call__(self, path): - return context, name, subpath, traversed, vroot, vroot_path - return DummyTraversalFactory - -def make_3arg_traversal_factory(context, name, subpath): - class DummyTraversalFactory: - def __init__(self, root): - self.root = root - - def __call__(self, path): - return context, name, subpath - return DummyTraversalFactory - - def make_permission_factory(result): class DummyPermissionFactory: def __init__(self, context, request): diff --git a/repoze/bfg/tests/test_testing.py b/repoze/bfg/tests/test_testing.py index d61092065..504151ce2 100644 --- a/repoze/bfg/tests/test_testing.py +++ b/repoze/bfg/tests/test_testing.py @@ -41,8 +41,20 @@ class TestTestingFunctions(unittest.TestCase): from zope.component import getAdapter from repoze.bfg.interfaces import ITraverserFactory adapter = getAdapter(None, ITraverserFactory) - self.assertEqual(adapter({'PATH_INFO':'/ob1'}), (ob1, '', [])) - self.assertEqual(adapter({'PATH_INFO':'/ob2'}), (ob2, '', [])) + result = adapter({'PATH_INFO':'/ob1'}) + self.assertEqual(result['context'], ob1) + self.assertEqual(result['view_name'], '') + self.assertEqual(result['subpath'], []) + self.assertEqual(result['traversed'], [u'ob1']) + self.assertEqual(result['vroot'], ob1) + self.assertEqual(result['vroot_path'], []) + result = adapter({'PATH_INFO':'/ob2'}) + self.assertEqual(result['context'], ob2) + self.assertEqual(result['view_name'], '') + self.assertEqual(result['subpath'], []) + self.assertEqual(result['traversed'], [u'ob2']) + self.assertEqual(result['vroot'], ob2) + self.assertEqual(result['vroot_path'], []) self.assertRaises(KeyError, adapter, {'PATH_INFO':'/ob3'}) from repoze.bfg.traversal import find_model self.assertEqual(find_model(None, '/ob1'), ob1) diff --git a/repoze/bfg/tests/test_traversal.py b/repoze/bfg/tests/test_traversal.py index e0ab8d938..41cf667b9 100644 --- a/repoze/bfg/tests/test_traversal.py +++ b/repoze/bfg/tests/test_traversal.py @@ -55,7 +55,7 @@ class ModelGraphTraverserTests(unittest.TestCase): def tearDown(self): cleanUp() - + def _getTargetClass(self): from repoze.bfg.traversal import ModelGraphTraverser return ModelGraphTraverser @@ -83,75 +83,75 @@ class ModelGraphTraverserTests(unittest.TestCase): def test_call_with_no_pathinfo(self): policy = self._makeOne(None) environ = self._getEnviron() - ctx, name, subpath, traversed, vroot, vroot_path = policy(environ) - self.assertEqual(ctx, None) - self.assertEqual(name, '') - self.assertEqual(subpath, []) - self.assertEqual(traversed, []) - self.assertEqual(vroot, policy.root) - self.assertEqual(vroot_path, []) + result = policy(environ) + self.assertEqual(result['context'], None) + self.assertEqual(result['view_name'], '') + self.assertEqual(result['subpath'], []) + self.assertEqual(result['traversed'], []) + 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') - ctx, name, subpath, traversed, vroot, vroot_path = policy(environ) - self.assertEqual(ctx, None) - self.assertEqual(name, 'foo') - self.assertEqual(subpath, ['bar']) - self.assertEqual(traversed, []) - self.assertEqual(vroot, policy.root) - self.assertEqual(vroot_path, []) + result = policy(environ) + self.assertEqual(result['context'], None) + self.assertEqual(result['view_name'], 'foo') + self.assertEqual(result['subpath'], ['bar']) + self.assertEqual(result['traversed'], []) + self.assertEqual(result['virtual_root'], policy.root) + self.assertEqual(result['virtual_root_path'], []) def test_call_withconn_getitem_emptypath_nosubpath(self): root = DummyContext() policy = self._makeOne(root) environ = self._getEnviron(PATH_INFO='') - ctx, name, subpath, traversed, vroot, vroot_path = policy(environ) - self.assertEqual(ctx, root) - self.assertEqual(name, '') - self.assertEqual(subpath, []) - self.assertEqual(traversed, []) - self.assertEqual(vroot, root) - self.assertEqual(vroot_path, []) + result = policy(environ) + self.assertEqual(result['context'], root) + self.assertEqual(result['view_name'], '') + self.assertEqual(result['subpath'], []) + self.assertEqual(result['traversed'], []) + self.assertEqual(result['virtual_root'], root) + self.assertEqual(result['virtual_root_path'], []) def test_call_withconn_getitem_withpath_nosubpath(self): foo = DummyContext() root = DummyContext(foo) policy = self._makeOne(root) environ = self._getEnviron(PATH_INFO='/foo/bar') - ctx, name, subpath, traversed, vroot, vroot_path = policy(environ) - self.assertEqual(ctx, foo) - self.assertEqual(name, 'bar') - self.assertEqual(subpath, []) - self.assertEqual(traversed, [u'foo']) - self.assertEqual(vroot, root) - self.assertEqual(vroot_path, []) + result = policy(environ) + self.assertEqual(result['context'], foo) + self.assertEqual(result['view_name'], 'bar') + self.assertEqual(result['subpath'], []) + self.assertEqual(result['traversed'], [u'foo']) + self.assertEqual(result['virtual_root'], root) + self.assertEqual(result['virtual_root_path'], []) def test_call_withconn_getitem_withpath_withsubpath(self): foo = DummyContext() root = DummyContext(foo) policy = self._makeOne(root) environ = self._getEnviron(PATH_INFO='/foo/bar/baz/buz') - ctx, name, subpath, traversed, vroot, vroot_path = policy(environ) - self.assertEqual(ctx, foo) - self.assertEqual(name, 'bar') - self.assertEqual(subpath, ['baz', 'buz']) - self.assertEqual(traversed, [u'foo']) - self.assertEqual(vroot, root) - self.assertEqual(vroot_path, []) + result = policy(environ) + self.assertEqual(result['context'], foo) + self.assertEqual(result['view_name'], 'bar') + self.assertEqual(result['subpath'], ['baz', 'buz']) + self.assertEqual(result['traversed'], [u'foo']) + self.assertEqual(result['virtual_root'], root) + self.assertEqual(result['virtual_root_path'], []) def test_call_with_explicit_viewname(self): foo = DummyContext() root = DummyContext(foo) policy = self._makeOne(root) environ = self._getEnviron(PATH_INFO='/@@foo') - ctx, name, subpath, traversed, vroot, vroot_path = policy(environ) - self.assertEqual(ctx, root) - self.assertEqual(name, 'foo') - self.assertEqual(subpath, []) - self.assertEqual(traversed, []) - self.assertEqual(vroot, root) - self.assertEqual(vroot_path, []) + result = policy(environ) + self.assertEqual(result['context'], root) + self.assertEqual(result['view_name'], 'foo') + self.assertEqual(result['subpath'], []) + self.assertEqual(result['traversed'], []) + self.assertEqual(result['virtual_root'], root) + self.assertEqual(result['virtual_root_path'], []) def test_call_with_vh_root(self): environ = self._getEnviron(PATH_INFO='/baz', @@ -165,13 +165,13 @@ class ModelGraphTraverserTests(unittest.TestCase): root = DummyContext(foo) root.name = 'root' policy = self._makeOne(root) - ctx, name, subpath, traversed, vroot, vroot_path = policy(environ) - self.assertEqual(ctx, baz) - self.assertEqual(name, '') - self.assertEqual(subpath, []) - self.assertEqual(traversed, [u'foo', u'bar', u'baz']) - self.assertEqual(vroot, bar) - self.assertEqual(vroot_path, [u'foo', u'bar']) + result = policy(environ) + self.assertEqual(result['context'], baz) + self.assertEqual(result['view_name'], '') + self.assertEqual(result['subpath'], []) + self.assertEqual(result['traversed'], [u'foo', u'bar', u'baz']) + self.assertEqual(result['virtual_root'], bar) + self.assertEqual(result['virtual_root_path'], [u'foo', u'bar']) def test_non_utf8_path_segment_unicode_path_segments_fails(self): foo = DummyContext() @@ -245,7 +245,7 @@ class FindModelTests(unittest.TestCase): def test_list(self): model = DummyContext() - traverser = make_traverser(model, '', []) + traverser = make_traverser({'context':model, 'view_name':''}) self._registerTraverserFactory(traverser) result = self._callFUT(model, ['']) self.assertEqual(result, model) @@ -253,7 +253,7 @@ class FindModelTests(unittest.TestCase): def test_generator(self): model = DummyContext() - traverser = make_traverser(model, '', []) + traverser = make_traverser({'context':model, 'view_name':''}) self._registerTraverserFactory(traverser) def foo(): yield '' @@ -263,7 +263,7 @@ class FindModelTests(unittest.TestCase): def test_self_string_found(self): model = DummyContext() - traverser = make_traverser(model, '', []) + traverser = make_traverser({'context':model, 'view_name':''}) self._registerTraverserFactory(traverser) result = self._callFUT(model, '') self.assertEqual(result, model) @@ -271,7 +271,7 @@ class FindModelTests(unittest.TestCase): def test_self_tuple_found(self): model = DummyContext() - traverser = make_traverser(model, '', []) + traverser = make_traverser({'context':model, 'view_name':''}) self._registerTraverserFactory(traverser) result = self._callFUT(model, ()) self.assertEqual(result, model) @@ -280,7 +280,7 @@ class FindModelTests(unittest.TestCase): def test_relative_string_found(self): model = DummyContext() baz = DummyContext() - traverser = make_traverser(baz, '', []) + traverser = make_traverser({'context':baz, 'view_name':''}) self._registerTraverserFactory(traverser) result = self._callFUT(model, 'baz') self.assertEqual(result, baz) @@ -289,7 +289,7 @@ class FindModelTests(unittest.TestCase): def test_relative_tuple_found(self): model = DummyContext() baz = DummyContext() - traverser = make_traverser(baz, '', []) + traverser = make_traverser({'context':baz, 'view_name':''}) self._registerTraverserFactory(traverser) result = self._callFUT(model, ('baz',)) self.assertEqual(result, baz) @@ -298,7 +298,7 @@ class FindModelTests(unittest.TestCase): def test_relative_string_notfound(self): model = DummyContext() baz = DummyContext() - traverser = make_traverser(baz, 'bar', []) + traverser = make_traverser({'context':baz, 'view_name':'bar'}) self._registerTraverserFactory(traverser) self.assertRaises(KeyError, self._callFUT, model, 'baz') self.assertEqual(model.environ['PATH_INFO'], 'baz') @@ -306,7 +306,7 @@ class FindModelTests(unittest.TestCase): def test_relative_tuple_notfound(self): model = DummyContext() baz = DummyContext() - traverser = make_traverser(baz, 'bar', []) + traverser = make_traverser({'context':baz, 'view_name':'bar'}) self._registerTraverserFactory(traverser) self.assertRaises(KeyError, self._callFUT, model, ('baz',)) self.assertEqual(model.environ['PATH_INFO'], 'baz') @@ -316,7 +316,7 @@ class FindModelTests(unittest.TestCase): model = DummyContext() model.__parent__ = root model.__name__ = 'baz' - traverser = make_traverser(root, '', []) + traverser = make_traverser({'context':root, 'view_name':''}) self._registerTraverserFactory(traverser) result = self._callFUT(model, '/') self.assertEqual(result, root) @@ -328,7 +328,7 @@ class FindModelTests(unittest.TestCase): model = DummyContext() model.__parent__ = root model.__name__ = 'baz' - traverser = make_traverser(root, '', []) + traverser = make_traverser({'context':root, 'view_name':''}) self._registerTraverserFactory(traverser) result = self._callFUT(model, ('',)) self.assertEqual(result, root) @@ -340,7 +340,7 @@ class FindModelTests(unittest.TestCase): model = DummyContext() model.__parent__ = root model.__name__ = 'baz' - traverser = make_traverser(root, 'fuz', []) + traverser = make_traverser({'context':root, 'view_name':'fuz'}) self._registerTraverserFactory(traverser) self.assertRaises(KeyError, self._callFUT, model, '/') self.assertEqual(root.wascontext, True) @@ -351,12 +351,13 @@ class FindModelTests(unittest.TestCase): model = DummyContext() model.__parent__ = root model.__name__ = 'baz' - traverser = make_traverser(root, 'fuz', []) + traverser = make_traverser({'context':root, 'view_name':'fuz'}) self._registerTraverserFactory(traverser) self.assertRaises(KeyError, self._callFUT, model, ('',)) self.assertEqual(root.wascontext, True) self.assertEqual(root.environ['PATH_INFO'], '/') + class ModelPathTests(unittest.TestCase): def _callFUT(self, model, *elements): from repoze.bfg.traversal import model_path @@ -569,7 +570,7 @@ class TraversalContextURLTests(unittest.TestCase): self.assertEqual(result, 'http://example.com:5432/La%20Pe%C3%B1a/La%20Pe%C3%B1a/') - def test_call_with_vroot_path(self): + def test_call_with_virtual_root_path(self): from repoze.bfg.interfaces import VH_ROOT_KEY root = DummyContext() root.__parent__ = None @@ -590,7 +591,7 @@ class TraversalContextURLTests(unittest.TestCase): result = context_url() self.assertEqual(result, 'http://example.com:5432/') - def test_virtual_root_no_vroot_path(self): + def test_virtual_root_no_virtual_root_path(self): root = DummyContext() root.__name__ = None root.__parent__ = None @@ -601,7 +602,7 @@ class TraversalContextURLTests(unittest.TestCase): context_url = self._makeOne(one, request) self.assertEqual(context_url.virtual_root(), root) - def test_virtual_root_no_vroot_path_with_root_on_request(self): + def test_virtual_root_no_virtual_root_path_with_root_on_request(self): context = DummyContext() context.__parent__ = None request = DummyRequest() @@ -609,14 +610,14 @@ class TraversalContextURLTests(unittest.TestCase): context_url = self._makeOne(context, request) self.assertEqual(context_url.virtual_root(), request.root) - def test_virtual_root_with_vroot_path(self): + def test_virtual_root_with_virtual_root_path(self): from repoze.bfg.interfaces import VH_ROOT_KEY context = DummyContext() context.__parent__ = None traversed_to = DummyContext() environ = {VH_ROOT_KEY:'/one'} request = DummyRequest(environ) - traverser = make_traverser(traversed_to, '', []) + traverser = make_traverser({'context':traversed_to, 'view_name':''}) self._registerTraverserFactory(traverser) context_url = self._makeOne(context, request) self.assertEqual(context_url.virtual_root(), traversed_to) @@ -661,14 +662,157 @@ class TestVirtualRoot(unittest.TestCase): result = self._callFUT(context, request) self.assertEqual(result, '123') -def make_traverser(*args): +class TraverseTests(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def _callFUT(self, context, name): + from repoze.bfg.traversal import traverse + return traverse(context, name) + + def _registerTraverserFactory(self, traverser): + import zope.component + gsm = zope.component.getGlobalSiteManager() + from repoze.bfg.interfaces import ITraverserFactory + from zope.interface import Interface + gsm.registerAdapter(traverser, (Interface,), ITraverserFactory) + + def test_list(self): + model = DummyContext() + traverser = make_traverser({'context':model, 'view_name':''}) + self._registerTraverserFactory(traverser) + self._callFUT(model, ['']) + self.assertEqual(model.environ['PATH_INFO'], '/') + + def test_generator(self): + model = DummyContext() + traverser = make_traverser({'context':model, 'view_name':''}) + self._registerTraverserFactory(traverser) + def foo(): + yield '' + self._callFUT(model, foo()) + self.assertEqual(model.environ['PATH_INFO'], '/') + + def test_self_string_found(self): + model = DummyContext() + traverser = make_traverser({'context':model, 'view_name':''}) + self._registerTraverserFactory(traverser) + self._callFUT(model, '') + self.assertEqual(model.environ['PATH_INFO'], '') + + def test_self_tuple_found(self): + model = DummyContext() + traverser = make_traverser({'context':model, 'view_name':''}) + self._registerTraverserFactory(traverser) + self._callFUT(model, ()) + self.assertEqual(model.environ['PATH_INFO'], '') + + def test_relative_string_found(self): + model = DummyContext() + baz = DummyContext() + traverser = make_traverser({'context':baz, 'view_name':''}) + self._registerTraverserFactory(traverser) + self._callFUT(model, 'baz') + self.assertEqual(model.environ['PATH_INFO'], 'baz') + + def test_relative_tuple_found(self): + model = DummyContext() + baz = DummyContext() + traverser = make_traverser({'context':baz, 'view_name':''}) + self._registerTraverserFactory(traverser) + self._callFUT(model, ('baz',)) + self.assertEqual(model.environ['PATH_INFO'], 'baz') + + def test_absolute_string_found(self): + root = DummyContext() + model = DummyContext() + model.__parent__ = root + model.__name__ = 'baz' + traverser = make_traverser({'context':root, 'view_name':''}) + self._registerTraverserFactory(traverser) + self._callFUT(model, '/') + self.assertEqual(root.wascontext, True) + self.assertEqual(root.environ['PATH_INFO'], '/') + + def test_absolute_tuple_found(self): + root = DummyContext() + model = DummyContext() + model.__parent__ = root + model.__name__ = 'baz' + traverser = make_traverser({'context':root, 'view_name':''}) + self._registerTraverserFactory(traverser) + self._callFUT(model, ('',)) + self.assertEqual(root.wascontext, True) + self.assertEqual(root.environ['PATH_INFO'], '/') + +class UnderTraverseTests(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def _callFUT(self, context, environ): + from repoze.bfg.traversal import _traverse + return _traverse(context, environ) + + def _registerTraverserFactory(self, traverser): + import zope.component + gsm = zope.component.getGlobalSiteManager() + from repoze.bfg.interfaces import ITraverserFactory + from zope.interface import Interface + gsm.registerAdapter(traverser, (Interface,), ITraverserFactory) + + def test_isdict(self): + traverser = make_traverser({}) + self._registerTraverserFactory(traverser) + context = DummyContext() + result = self._callFUT(context, None) + self.assertEqual(result, {}) + + def test_issixtuple(self): + traverser = make_traverser((1,2,3,4,5,6)) + self._registerTraverserFactory(traverser) + context = DummyContext() + result = self._callFUT(context, None) + self.assertEqual(result['context'], 1) + self.assertEqual(result['view_name'], 2) + self.assertEqual(result['subpath'], 3) + self.assertEqual(result['traversed'], 4) + self.assertEqual(result['virtual_root'], 5) + self.assertEqual(result['virtual_root_path'], 6) + self.assertEqual(result['root'], None) + self.failUnless(result['_deprecation_warning'].startswith( + "<class 'repoze.bfg.tests.test_traversal.DummyTraverser'>")) + self.failUnless("6-argument tuple" in result['_deprecation_warning']) + + def test_isthreetuple(self): + traverser = make_traverser((1,2,3)) + self._registerTraverserFactory(traverser) + context = DummyContext() + result = self._callFUT(context, None) + self.assertEqual(result['context'], 1) + self.assertEqual(result['view_name'], 2) + self.assertEqual(result['subpath'], 3) + self.assertEqual(result['traversed'], None) + self.assertEqual(result['virtual_root'], None) + self.assertEqual(result['virtual_root_path'], None) + self.assertEqual(result['root'], None) + self.failUnless(result['_deprecation_warning'].startswith( + "<class 'repoze.bfg.tests.test_traversal.DummyTraverser'>")) + self.failUnless("3-argument tuple" in result['_deprecation_warning']) + +def make_traverser(result): class DummyTraverser(object): def __init__(self, context): self.context = context context.wascontext = True def __call__(self, environ): self.context.environ = environ - return args + return result return DummyTraverser class DummyContext(object): diff --git a/repoze/bfg/tests/test_urldispatch.py b/repoze/bfg/tests/test_urldispatch.py index e5ae483e7..92564146e 100644 --- a/repoze/bfg/tests/test_urldispatch.py +++ b/repoze/bfg/tests/test_urldispatch.py @@ -232,12 +232,12 @@ class RoutesModelTraverserTests(unittest.TestCase): route = DummyRoute('yo') environ = {'wsgiorg.routing_args': routing_args, 'bfg.route': route} result = traverser(environ) - self.assertEqual(result[0], model) - self.assertEqual(result[1], 'yo') - self.assertEqual(result[2], []) - self.assertEqual(result[3], None) - self.assertEqual(result[4], model) - self.assertEqual(result[5], None) + self.assertEqual(result['context'], model) + self.assertEqual(result['view_name'], 'yo') + self.assertEqual(result['subpath'], []) + self.assertEqual(result['traversed'], None) + self.assertEqual(result['virtual_root'], model) + self.assertEqual(result['virtual_root_path'], None) def test_call_with_subpath(self): model = DummyContext() @@ -246,12 +246,12 @@ class RoutesModelTraverserTests(unittest.TestCase): route = DummyRoute('yo') environ = {'wsgiorg.routing_args':routing_args, 'bfg.route': route} result = traverser(environ) - self.assertEqual(result[0], model) - self.assertEqual(result[1], 'yo') - self.assertEqual(result[2], ['a', 'b','c']) - self.assertEqual(result[3], None) - self.assertEqual(result[4], model) - self.assertEqual(result[5], None) + self.assertEqual(result['context'], model) + self.assertEqual(result['view_name'], 'yo') + self.assertEqual(result['subpath'], ['a', 'b','c']) + self.assertEqual(result['traversed'], None) + self.assertEqual(result['virtual_root'], model) + self.assertEqual(result['virtual_root_path'], None) def test_with_path_info(self): model = DummyContext() @@ -261,12 +261,12 @@ class RoutesModelTraverserTests(unittest.TestCase): environ = {'wsgiorg.routing_args': routing_args, 'bfg.route': route, 'PATH_INFO':'/a/b/foo/bar', 'SCRIPT_NAME':''} result = traverser(environ) - self.assertEqual(result[0], model) - self.assertEqual(result[1], 'yo') - self.assertEqual(result[2], []) - self.assertEqual(result[3], None) - self.assertEqual(result[4], model) - self.assertEqual(result[5], None) + self.assertEqual(result['context'], model) + self.assertEqual(result['view_name'], 'yo') + self.assertEqual(result['subpath'], []) + self.assertEqual(result['traversed'], None) + self.assertEqual(result['virtual_root'], model) + self.assertEqual(result['virtual_root_path'], None) self.assertEqual(environ['PATH_INFO'], '/foo/bar') self.assertEqual(environ['SCRIPT_NAME'], '/a/b') @@ -277,7 +277,7 @@ class RoutesModelTraverserTests(unittest.TestCase): route = DummyRoute('yo') environ = {'wsgiorg.routing_args': routing_args, 'bfg.route':route, 'PATH_INFO':'/a/b//foo/bar', 'SCRIPT_NAME':''} - result = traverser(environ) + traverser(environ) self.assertEqual(environ['PATH_INFO'], '/foo/bar') self.assertEqual(environ['SCRIPT_NAME'], '/a/b') diff --git a/repoze/bfg/traversal.py b/repoze/bfg/traversal.py index 49017a93b..f21f27a39 100644 --- a/repoze/bfg/traversal.py +++ b/repoze/bfg/traversal.py @@ -2,7 +2,6 @@ import re import urllib from zope.component import getMultiAdapter -from zope.component import queryUtility from zope.deprecation import deprecated @@ -17,7 +16,6 @@ from repoze.bfg.interfaces import IContextURL from repoze.bfg.interfaces import ITraverser from repoze.bfg.interfaces import ITraverserFactory from repoze.bfg.interfaces import VH_ROOT_KEY -from repoze.bfg.interfaces import ILogger def find_root(model): """ Find the root node in the graph to which ``model`` @@ -74,39 +72,12 @@ def find_model(model, path): object representing a model name). Model path tuples generated by ``model_path_tuple`` can always be resolved by ``find_model``. """ - - if hasattr(path, '__iter__'): # it's a tuple or some other iterable - # the traverser factory expects PATH_INFO to be a string, not - # unicode and it expects path segments to be utf-8 and - # urlencoded (it's the same traverser which accepts PATH_INFO - # from user agents; user agents always send strings). - path = [quote_path_segment(name) for name in path] - if path: - path = '/'.join(path) or '/' - else: - path = '' - - if path and path[0] == '/': - model = find_root(model) - - traverser = ITraverserFactory(model) - val = traverser({'PATH_INFO':path}) - try: - ob, name, _, _, _, _ = val - except ValueError: - # b/w compat for three-value-returning ITraverser (prior to 0.7.1) - logger = queryUtility(ILogger, 'repoze.bfg.debug') - logger and logger.warn( - '%s is an pre-0.7.1-style ITraverser returning only ' - '3 arguments; please update it to the new ' - '6-argument-returning interface for improved ' - 'functionality. See the repoze.bfg.interfaces module ' - 'for the new ITraverser interface ' - 'definition' % traverser) - ob, name, _ = val - if name: - raise KeyError('%r has no subelement %s' % (ob, name)) - return ob + D = traverse(model, path) + view_name = D['view_name'] + context = D['context'] + if view_name: + raise KeyError('%r has no subelement %s' % (context, view_name)) + return context def find_interface(model, interface): """ @@ -162,6 +133,162 @@ def model_path(model, *elements): path = _model_path_list(model, *elements) return path and '/'.join([quote_path_segment(x) for x in path]) or '/' +def traverse(model, path): + """Given a model object as ``model` and a string or tuple + representing a path as ``path`` (such as the return value of + ``repoze.bfg.traversal.model_path`` or + ``repoze.bfg.traversal.model_path_tuple`` or the value of + ``request.environ['PATH_INFO']``), return a dictionary with the + keys ``context``, ``root``, ``view_name``, ``subpath``, + ``traversed``, ``virtual_root``, and ``virtual_root_path``. + + A definition of each value in the returned dictionary: + + - ``context``: The context (a 'model' object) found via traversal + or url dispatch. If the ``path`` passed in is the empty string, + the value of the ``model`` argument passed to this function is + returned. + + - ``root``: The root model object found via traversal or url + dispatch. If the ``model`` passed in was found via url + dispatch, the value of the ``model`` argument passed to this + function is returned. + + - ``view_name``: The 'view name' found during traversal or url + dispatch; if the ``model`` was found via traversal, this is + usually a representation of the path segment which directly + follows the path to the ``context`` in the ``path``. The + ``view_name`` will be a Unicode object or the empty string. The + ``view_name`` will be the empty string if there is no element + which follows the ``context`` path. An example: if the path + passed is ``/foo/bar``, and a context object is found at + ``/foo`` (but not at ``/foo/bar``), the 'view name' will be + ``u'bar'``. If the ``model`` was found via urldispatch, the + view_name will be the name the route found was registered with. + + - ``subpath``: For a ``model`` found via traversal, this is a + sequence of path segments found in the ``path`` that follow the + ``view_name`` (if any). Each of these items is a Unicode + object. If no path segments follow the ``view_name``, the + subpath will be the empty list. An example: if the path passed + is ``/foo/bar/baz/buz``, and a context object is found at + ``/foo`` (but not ``/foo/bar``), the 'view name' will be + ``u'bar'`` and the subpath will be ``[u'baz', u'buz']``. For a + ``model`` found via url dispatch, the subpath will be a sequence + of values discerned from ``*subpath`` in the route pattern + matched or the empty sequence. + + - ``traversed``: The sequence of path elements traversed to find + the ``context`` object. Each of these items is a Unicode + object. If no path segments were traversed to find the + ``context`` object (e.g. if the ``path`` provided is the empty + string), the ``traversed`` value will be the empty list. If the + ``model`` is a model found via urldispatch, traversed will be None. + + - ``virtual_root``: A model object representing the 'virtual' root + of the object graph being traversed. See + :ref:`vhosting_chapter` for a definition of the virtual root + object. If no virtual hosting is in effect or if the ``model`` + passed in was found via URL dispatch, the ``virtual_root`` will + always be the physical root of the object graph (the object at + which traversal begins). + + - ``virtual_root_path`` -- If traversal was used to find the + ``model``, this will be the sequence of path elements traversed + to find the ``virtual_root`` object. Each of these items is a + Unicode object. If no path segments were traversed to find the + ``virtual_root`` object (e.g. if virtual hosting is not in + effect), the ``traversed`` value will be the empty list. If url + dispatch was used to find the ``model``, this will be None. + + If the path cannot be resolved, a KeyError will be raised. + + Rules for passing a *string* as the ``path`` argument: if the + first character in the path string is the with the ``/`` + character, the path will considered absolute and the graph + traversal will start at the root object. If the first character + of the path string is *not* the ``/`` character, the path is + considered relative and graph traversal will begin at the model + 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 + each path segment must escaped via Python's ``urllib.quote``. For + example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or + ``to%20the/La%20Pe%C3%B1a`` (relative). The ``model_path`` + function generates strings which follow these rules (albeit only + absolute ones). + + Rules for passing a *tuple* as the ``path`` argument: if the first + element in the path tuple is the empty string (for example ``('', + 'a', 'b', 'c')``, the path is considered absolute and the graph + traversal will start at the graph root object. If the first + element in the path tuple is not the empty string (for example + ``('a', 'b', 'c')``), the path is considered relative and graph + traversal will begin at the model object supplied to the function + as the ``model`` argument. If an empty sequence is passed as + ``path``, the ``model`` passed in itself will be returned. No + URL-quoting or UTF-8-encoding of individual path segments within + the tuple is required (each segment may be any string or unicode + object representing a model name). Model path tuples generated by + ``model_path_tuple`` can always be resolved by ``find_model``. + + Explanation of the conversion of ``path`` segment values to + Unicode during traversal: Each segment is URL-unquoted, and + decoded into Unicode. Each segment is assumed to be encoded using + the UTF-8 encoding (or a subset, such as ASCII); a TypeError is + raised if a segment cannot be decoded. If a segment name is empty + or if it is ``.``, it is ignored. If a segment name is ``..``, + the previous segment is deleted, and the ``..`` is ignored. As a + result of this process, the return values ``view_name``, each + element in the ``subpath``, each element in ``traversed``, and + each element in the ``virtual_root_path`` will be Unicode as + opposed to a string, and will be URL-decoded. + """ + if hasattr(path, '__iter__'): # it's a tuple or some other iterable + # the traverser factory expects PATH_INFO to be a string, not + # unicode and it expects path segments to be utf-8 and + # urlencoded (it's the same traverser which accepts PATH_INFO + # from user agents; user agents always send strings). + path = [quote_path_segment(name) for name in path] + if path: + path = '/'.join(path) or '/' + else: + path = '' + + if path and path[0] == '/': + model = find_root(model) + + return _traverse(model, {'PATH_INFO':path}) + +def _traverse(model, environ): + traverser = ITraverserFactory(model) + result = traverser(environ) + deprecation_warning = None + if not isinstance(result, dict): + try: + # b/w compat for 6-arg returning ITraversers (0.7.1 til 0.8a7) + ctx, view_name, subpath, traversed, vroot, vroot_path = result + except ValueError: + # b/w compat for 3-arg returning ITraversers (0.7.0-style + # and below) + ctx, view_name, subpath = result + traversed = None + vroot = None + vroot_path = None + deprecation_warning = ( + '%r is an pre-0.8-style ITraverser returning a %s-argument tuple; ' + 'please update it to the new ITraverser interface which returns ' + 'a dictionary for improved functionality. See the ' + '"repoze.bfg.interfaces" module for the new ITraverser interface ' + 'definition.' % (traverser.__class__, len(result))) + + result = {'context':ctx, 'view_name':view_name, 'subpath':subpath, + 'traversed':traversed, 'virtual_root':vroot, + 'virtual_root_path':vroot_path, 'root':None, + '_deprecation_warning':deprecation_warning} + return result + def model_path_tuple(model, *elements): """ Return a tuple representing the absolute physical path of the @@ -394,22 +521,30 @@ class ModelGraphTraverser(object): for segment in path: if segment[:2] =='@@': - return ob, segment[2:], path[i:], traversed, vroot, vroot_path + return dict(context=ob, view_name=segment[2:], subpath=path[i:], + traversed=traversed, virtual_root=vroot, + virtual_root_path=vroot_path, root=self.root) try: getitem = ob.__getitem__ except AttributeError: - return ob, segment, path[i:], traversed, vroot, vroot_path + return dict(context=ob, view_name=segment, subpath=path[i:], + traversed=traversed, virtual_root=vroot, + virtual_root_path=vroot_path, root=self.root) try: next = getitem(segment) except KeyError: - return ob, segment, path[i:], traversed, vroot, vroot_path + return dict(context=ob, view_name=segment, subpath=path[i:], + traversed=traversed, virtual_root=vroot, + virtual_root_path=vroot_path, root=self.root) if vroot_idx == i-1: vroot = ob traversed.append(segment) ob = next i += 1 - return ob, '', [], traversed, vroot, vroot_path + return dict(context=ob, view_name=u'', subpath=[], traversed=traversed, + virtual_root=vroot, virtual_root_path=vroot_path, + root=self.root) class TraversalContextURL(object): """ The IContextURL adapter used to generate URLs for a context diff --git a/repoze/bfg/urldispatch.py b/repoze/bfg/urldispatch.py index 4c3bc2ed3..3e60c0ba5 100644 --- a/repoze/bfg/urldispatch.py +++ b/repoze/bfg/urldispatch.py @@ -154,7 +154,9 @@ class RoutesModelTraverser(object): if environ['SCRIPT_NAME'].endswith('/'): environ['SCRIPT_NAME'] = environ['SCRIPT_NAME'][:-1] - return self.context, route.name, subpath, None, self.context, None + return dict(context=self.context, view_name=route.name, + subpath=subpath, traversed=None, virtual_root=self.context, + virtual_root_path=None, root=self.context) class RoutesContextURL(object): """ The IContextURL adapter used to generate URLs for a context |
