diff options
| -rw-r--r-- | docs/narr/introspector.rst | 22 | ||||
| -rw-r--r-- | pyramid/config/adapters.py | 147 | ||||
| -rw-r--r-- | pyramid/config/factories.py | 143 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_adapters.py | 122 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_factories.py | 114 |
5 files changed, 289 insertions, 259 deletions
diff --git a/docs/narr/introspector.rst b/docs/narr/introspector.rst index 08cc430f6..d465c47d9 100644 --- a/docs/narr/introspector.rst +++ b/docs/narr/introspector.rst @@ -540,10 +540,30 @@ introspectables in categories not described here. The (resolved) interface or class object that represents the return value of a root factory that this traverser will be used for. - ``factory`` + ``adapter`` The (resolved) traverser class. +``resource url adapters`` + + Each introspectable in the ``resource url adapters`` category represents a + call to :meth:`pyramid.config.Configurator.add_resource_url_adapter`; each + will have the following data. + + ``adapter`` + + The (resolved) resource URL adapter class. + + ``resource_iface`` + + The (resolved) interface or class object that represents the resource + interface that this url adapter is registered for. + + ``request_iface`` + + The (resolved) interface or class object that represents the request + interface that this url adapter is registered for. + Introspection in the Toolbar ---------------------------- diff --git a/pyramid/config/adapters.py b/pyramid/config/adapters.py index 04571bec3..b6dfdaf41 100644 --- a/pyramid/config/adapters.py +++ b/pyramid/config/adapters.py @@ -1,6 +1,10 @@ from zope.interface import Interface -from pyramid.interfaces import IResponse +from pyramid.interfaces import ( + IResponse, + ITraverser, + IResourceURL, + ) from pyramid.config.util import action_method @@ -72,3 +76,144 @@ class AdaptersConfiguratorMixin(object): # cope with WebOb response objects that aren't decorated with IResponse from webob import Response as WebobResponse self.registry.registerSelfAdapter((WebobResponse,), IResponse) + + @action_method + def add_traverser(self, adapter, iface=None): + """ + The superdefault :term:`traversal` algorithm that :app:`Pyramid` uses + is explained in :ref:`traversal_algorithm`. Though it is rarely + necessary, this default algorithm can be swapped out selectively for + a different traversal pattern via configuration. The section + entitled :ref:`changing_the_traverser` details how to create a + traverser class. + + For example, to override the superdefault traverser used by Pyramid, + you might do something like this: + + .. code-block:: python + + from myapp.traversal import MyCustomTraverser + config.add_traverser(MyCustomTraverser) + + This would cause the Pyramid superdefault traverser to never be used; + intead all traversal would be done using your ``MyCustomTraverser`` + class, no matter which object was returned by the :term:`root + factory` of this application. Note that we passed no arguments to + the ``iface`` keyword parameter. The default value of ``iface``, + ``None`` represents that the registered traverser should be used when + no other more specific traverser is available for the object returned + by the root factory. + + However, more than one traversal algorithm can be active at the same + time. The traverser used can depend on the result of the :term:`root + factory`. For instance, if your root factory returns more than one + type of object conditionally, you could claim that an alternate + traverser adapter should be used agsinst one particular class or + interface returned by that root factory. When the root factory + returned an object that implemented that class or interface, a custom + traverser would be used. Otherwise, the default traverser would be + used. The ``iface`` argument represents the class of the object that + the root factory might return or an :term:`interface` that the object + might implement. + + To use a particular traverser only when the root factory returns a + particular class: + + .. code-block:: python + + config.add_traverser(MyCustomTraverser, MyRootClass) + + When more than one traverser is active, the "most specific" traverser + will be used (the one that matches the class or interface of the + value returned by the root factory most closely). + + Note that either ``adapter`` or ``iface`` can be a :term:`dotted + Python name` or a Python object. + + See :ref:`changing_the_traverser` for more information. + """ + iface = self.maybe_dotted(iface) + adapter= self.maybe_dotted(adapter) + def register(iface=iface): + if iface is None: + iface = Interface + self.registry.registerAdapter(adapter, (iface,), ITraverser) + discriminator = ('traverser', iface) + intr = self.introspectable( + 'traversers', + discriminator, + 'traverser for %r' % iface, + 'traverser', + ) + intr['adapter'] = adapter + intr['iface'] = iface + self.action(discriminator, register, introspectables=(intr,)) + + @action_method + def add_resource_url_adapter(self, adapter, resource_iface=None, + request_iface=None): + """ + When you add a traverser as described in + :ref:`changing_the_traverser`, it's convenient to continue to use the + :meth:`pyramid.request.Request.resource_url` API. However, since the + way traversal is done may have been modified, the URLs that + ``resource_url`` generates by default may be incorrect when resources + are returned by a custom traverser. + + If you've added a traverser, you can change how + :meth:`~pyramid.request.Request.resource_url` generates a URL for a + specific type of resource by calling this method. + + The ``adapter`` argument represents a class that implements the + :class:`~pyramid.interfaces.IResourceURL` interface. The class + constructor should accept two arguments in its constructor (the + resource and the request) and the resulting instance should provide + the attributes detailed in that interface (``virtual_path`` and + ``physical_path``, in particular). + + The ``resource_iface`` argument represents a class or interface that + the resource should possess for this url adapter to be used when + :meth:`pyramid.request.Request.resource_url` looks up a resource url + adapter. If ``resource_iface`` is not passed, or it is passed as + ``None``, the adapter will be used for every type of resource. + + The ``request_iface`` argument represents a class or interface that + the request should possess for this url adapter to be used when + :meth:`pyramid.request.Request.resource_url` looks up a resource url + adapter. If ``request_iface`` is not epassed, or it is passed as + ``None``, the adapter will be used for every type of request. + + See :ref:`changing_resource_url` for more information. + + .. note:: + + This API is new in Pyramid 1.3. + """ + adapter = self.maybe_dotted(adapter) + resource_iface = self.maybe_dotted(resource_iface) + request_iface = self.maybe_dotted(request_iface) + def register(resource_iface=resource_iface, + request_iface=request_iface): + if resource_iface is None: + resource_iface = Interface + if request_iface is None: + request_iface = Interface + self.registry.registerAdapter( + adapter, + (resource_iface, request_iface), + IResourceURL, + ) + discriminator = ('resource url adapter', resource_iface, request_iface) + intr = self.introspectable( + 'resource url adapters', + discriminator, + 'resource url adapter for resource iface %r, request_iface %r' % ( + resource_iface, request_iface), + 'resource url adapter', + ) + intr['adapter'] = adapter + intr['resource_iface'] = resource_iface + intr['request_iface'] = request_iface + self.action(discriminator, register, introspectables=(intr,)) + + diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py index 76f8d86ed..eb4442e98 100644 --- a/pyramid/config/factories.py +++ b/pyramid/config/factories.py @@ -1,5 +1,3 @@ -from zope.interface import Interface - from pyramid.config.util import action_method from pyramid.interfaces import ( @@ -9,8 +7,6 @@ from pyramid.interfaces import ( IRequestProperties, IRootFactory, ISessionFactory, - ITraverser, - IResourceURL, ) from pyramid.traversal import DefaultRootFactory @@ -144,145 +140,6 @@ class FactoriesConfiguratorMixin(object): self.action(('request properties', name), register, introspectables=(intr,)) - @action_method - def add_traverser(self, factory, iface=None): - """ - The superdefault :term:`traversal` algorithm that :app:`Pyramid` uses - is explained in :ref:`traversal_algorithm`. Though it is rarely - necessary, this default algorithm can be swapped out selectively for - a different traversal pattern via configuration. The section - entitled :ref:`changing_the_traverser` details how to create a - traverser class. - - For example, to override the superdefault traverser used by Pyramid, - you might do something like this: - - .. code-block:: python - - from myapp.traversal import MyCustomTraverser - config.add_traverser(MyCustomTraverser) - - This would cause the Pyramid superdefault traverser to never be used; - intead all traversal would be done using your ``MyCustomTraverser`` - class, no matter which object was returned by the :term:`root - factory` of this application. Note that we passed no arguments to - the ``iface`` keyword parameter. The default value of ``iface``, - ``None`` represents that the registered traverser should be used when - no other more specific traverser is available for the object returned - by the root factory. - - However, more than one traversal algorithm can be active at the same - time. The traverser used can depend on the result of the :term:`root - factory`. For instance, if your root factory returns more than one - type of object conditionally, you could claim that an alternate - traverser adapter should be used agsinst one particular class or - interface returned by that root factory. When the root factory - returned an object that implemented that class or interface, a custom - traverser would be used. Otherwise, the default traverser would be - used. The ``iface`` argument represents the class of the object that - the root factory might return or an :term:`interface` that the object - might implement. - - To use a particular traverser only when the root factory returns a - particular class: - - .. code-block:: python - - config.add_traverser(MyCustomTraverser, MyRootClass) - - When more than one traverser is active, the "most specific" traverser - will be used (the one that matches the class or interface of the - value returned by the root factory most closely). - - Note that either ``factory`` or ``iface`` can be a :term:`dotted - Python name` or a Python object. - - See :ref:`changing_the_traverser` for more information. - """ - iface = self.maybe_dotted(iface) - factory = self.maybe_dotted(factory) - def register(iface=iface): - if iface is None: - iface = Interface - self.registry.registerAdapter(factory, (iface,), ITraverser) - discriminator = ('traverser', iface) - intr = self.introspectable( - 'traversers', - discriminator, - 'traverser for %r' % iface, - 'traverser', - ) - intr['factory'] = factory - intr['iface'] = iface - self.action(discriminator, register, introspectables=(intr,)) - - @action_method - def add_resource_url_adapter(self, factory, resource_iface=None, - request_iface=None): - """ - When you add a traverser as described in - :ref:`changing_the_traverser`, it's convenient to continue to use the - :meth:`pyramid.request.Request.resource_url` API. However, since the - way traversal is done may have been modified, the URLs that - ``resource_url`` generates by default may be incorrect when resources - are returned by a custom traverser. - - If you've added a traverser, you can change how - :meth:`~pyramid.request.Request.resource_url` generates a URL for a - specific type of resource by calling this method. - - The ``factory`` argument represents a class that implements the - :class:`~pyramid.interfaces.IResourceURL` interface. The class - constructor should accept two arguments in its constructor (the - resource and the request) and the resulting instance should provide - the attributes detailed in that interface (``virtual_path`` and - ``physical_path``, in particular). - - The ``resource_iface`` argument represents a class or interface that - the resource should possess for this url adapter to be used when - :meth:`pyramid.request.Request.resource_url` looks up a resource url - adapter. If ``resource_iface`` is not passed, or it is passed as - ``None``, the adapter will be used for every type of resource. - - The ``request_iface`` argument represents a class or interface that - the request should possess for this url adapter to be used when - :meth:`pyramid.request.Request.resource_url` looks up a resource url - adapter. If ``request_iface`` is not epassed, or it is passed as - ``None``, the adapter will be used for every type of request. - - See :ref:`changing_resource_url` for more information. - - .. note:: - - This API is new in Pyramid 1.3. - """ - factory = self.maybe_dotted(factory) - resource_iface = self.maybe_dotted(resource_iface) - request_iface = self.maybe_dotted(request_iface) - def register(resource_iface=resource_iface, - request_iface=request_iface): - if resource_iface is None: - resource_iface = Interface - if request_iface is None: - request_iface = Interface - self.registry.registerAdapter( - factory, - (resource_iface, request_iface), - IResourceURL, - ) - discriminator = ('resource url adapter', resource_iface, request_iface) - intr = self.introspectable( - 'resource url adapters', - discriminator, - 'resource url adapter for resource iface %r, request_iface %r' % ( - resource_iface, request_iface), - 'resource url adapter', - ) - intr['factory'] = factory - intr['resource_iface'] = resource_iface - intr['request_iface'] = request_iface - self.action(discriminator, register, introspectables=(intr,)) - def _set_request_properties(event): request = event.request plist = request.registry.queryUtility(IRequestProperties) diff --git a/pyramid/tests/test_config/test_adapters.py b/pyramid/tests/test_config/test_adapters.py index 84b7119cf..b89571639 100644 --- a/pyramid/tests/test_config/test_adapters.py +++ b/pyramid/tests/test_config/test_adapters.py @@ -112,3 +112,125 @@ class AdaptersConfiguratorMixinTests(unittest.TestCase): result = config.registry.queryAdapter('foo', IResponse) self.assertTrue(result.body, b'foo') + def test_add_traverser_dotted_names(self): + from pyramid.interfaces import ITraverser + config = self._makeOne(autocommit=True) + config.add_traverser( + 'pyramid.tests.test_config.test_adapters.DummyTraverser', + 'pyramid.tests.test_config.test_adapters.DummyIface') + iface = DummyIface() + traverser = config.registry.getAdapter(iface, ITraverser) + self.assertEqual(traverser.__class__, DummyTraverser) + self.assertEqual(traverser.root, iface) + + def test_add_traverser_default_iface_means_Interface(self): + from pyramid.interfaces import ITraverser + config = self._makeOne(autocommit=True) + config.add_traverser(DummyTraverser) + traverser = config.registry.getAdapter(None, ITraverser) + self.assertEqual(traverser.__class__, DummyTraverser) + + def test_add_traverser_nondefault_iface(self): + from pyramid.interfaces import ITraverser + config = self._makeOne(autocommit=True) + config.add_traverser(DummyTraverser, DummyIface) + iface = DummyIface() + traverser = config.registry.getAdapter(iface, ITraverser) + self.assertEqual(traverser.__class__, DummyTraverser) + self.assertEqual(traverser.root, iface) + + def test_add_traverser_introspectables(self): + config = self._makeOne() + config.add_traverser(DummyTraverser, DummyIface) + actions = config.action_state.actions + self.assertEqual(len(actions), 1) + intrs = actions[0]['introspectables'] + self.assertEqual(len(intrs), 1) + intr = intrs[0] + self.assertEqual(intr.type_name, 'traverser') + self.assertEqual(intr.discriminator, ('traverser', DummyIface)) + self.assertEqual(intr.category_name, 'traversers') + self.assertEqual(intr.title, 'traverser for %r' % DummyIface) + self.assertEqual(intr['adapter'], DummyTraverser) + self.assertEqual(intr['iface'], DummyIface) + + def test_add_resource_url_adapter_dotted_names(self): + from pyramid.interfaces import IResourceURL + config = self._makeOne(autocommit=True) + config.add_resource_url_adapter( + 'pyramid.tests.test_config.test_adapters.DummyResourceURL', + 'pyramid.tests.test_config.test_adapters.DummyIface', + 'pyramid.tests.test_config.test_adapters.DummyIface', + ) + iface = DummyIface() + adapter = config.registry.getMultiAdapter((iface, iface), + IResourceURL) + self.assertEqual(adapter.__class__, DummyResourceURL) + self.assertEqual(adapter.resource, iface) + self.assertEqual(adapter.request, iface) + + def test_add_resource_url_default_interfaces_mean_Interface(self): + from pyramid.interfaces import IResourceURL + config = self._makeOne(autocommit=True) + config.add_resource_url_adapter(DummyResourceURL) + iface = DummyIface() + adapter = config.registry.getMultiAdapter((iface, iface), + IResourceURL) + self.assertEqual(adapter.__class__, DummyResourceURL) + self.assertEqual(adapter.resource, iface) + self.assertEqual(adapter.request, iface) + + def test_add_resource_url_nodefault_interfaces(self): + from zope.interface import Interface + from pyramid.interfaces import IResourceURL + config = self._makeOne(autocommit=True) + config.add_resource_url_adapter(DummyResourceURL, DummyIface, + DummyIface) + iface = DummyIface() + adapter = config.registry.getMultiAdapter((iface, iface), + IResourceURL) + self.assertEqual(adapter.__class__, DummyResourceURL) + self.assertEqual(adapter.resource, iface) + self.assertEqual(adapter.request, iface) + bad_result = config.registry.queryMultiAdapter( + (Interface, Interface), + IResourceURL, + ) + self.assertEqual(bad_result, None) + + def test_add_resource_url_adapter_introspectables(self): + config = self._makeOne() + config.add_resource_url_adapter(DummyResourceURL, DummyIface) + actions = config.action_state.actions + self.assertEqual(len(actions), 1) + intrs = actions[0]['introspectables'] + self.assertEqual(len(intrs), 1) + intr = intrs[0] + self.assertEqual(intr.type_name, 'resource url adapter') + self.assertEqual(intr.discriminator, + ('resource url adapter', DummyIface, None)) + self.assertEqual(intr.category_name, 'resource url adapters') + self.assertEqual( + intr.title, + "resource url adapter for resource iface " + "<class 'pyramid.tests.test_config.test_adapters.DummyIface'>, " + "request_iface None" + ) + self.assertEqual(intr['adapter'], DummyResourceURL) + self.assertEqual(intr['resource_iface'], DummyIface) + self.assertEqual(intr['request_iface'], None) + + +class DummyTraverser(object): + def __init__(self, root): + self.root = root + +class DummyIface(object): + pass + +class DummyResourceURL(object): + def __init__(self, resource, request): + self.resource = resource + self.request = request + + diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py index 5f300a73e..0930f9603 100644 --- a/pyramid/tests/test_config/test_factories.py +++ b/pyramid/tests/test_config/test_factories.py @@ -129,108 +129,6 @@ class TestFactoriesMixin(unittest.TestCase): self.assertEqual(callables, [('foo', foo, False), ('bar', foo, True)]) - def test_add_traverser_dotted_names(self): - from pyramid.interfaces import ITraverser - config = self._makeOne(autocommit=True) - config.add_traverser( - 'pyramid.tests.test_config.test_factories.DummyTraverser', - 'pyramid.tests.test_config.test_factories.DummyIface') - iface = DummyIface() - traverser = config.registry.getAdapter(iface, ITraverser) - self.assertEqual(traverser.__class__, DummyTraverser) - self.assertEqual(traverser.root, iface) - - def test_add_traverser_default_iface_means_Interface(self): - from pyramid.interfaces import ITraverser - config = self._makeOne(autocommit=True) - config.add_traverser(DummyTraverser) - traverser = config.registry.getAdapter(None, ITraverser) - self.assertEqual(traverser.__class__, DummyTraverser) - - def test_add_traverser_nondefault_iface(self): - from pyramid.interfaces import ITraverser - config = self._makeOne(autocommit=True) - config.add_traverser(DummyTraverser, DummyIface) - iface = DummyIface() - traverser = config.registry.getAdapter(iface, ITraverser) - self.assertEqual(traverser.__class__, DummyTraverser) - self.assertEqual(traverser.root, iface) - - def test_add_traverser_introspectables(self): - config = self._makeOne() - config.add_traverser(DummyTraverser, DummyIface) - actions = config.action_state.actions - self.assertEqual(len(actions), 1) - intrs = actions[0]['introspectables'] - self.assertEqual(len(intrs), 1) - intr = intrs[0] - self.assertEqual(intr.type_name, 'traverser') - self.assertEqual(intr.discriminator, ('traverser', DummyIface)) - self.assertEqual(intr.category_name, 'traversers') - self.assertEqual(intr.title, 'traverser for %r' % DummyIface) - - def test_add_resource_url_adapter_dotted_names(self): - from pyramid.interfaces import IResourceURL - config = self._makeOne(autocommit=True) - config.add_resource_url_adapter( - 'pyramid.tests.test_config.test_factories.DummyResourceURL', - 'pyramid.tests.test_config.test_factories.DummyIface', - 'pyramid.tests.test_config.test_factories.DummyIface', - ) - iface = DummyIface() - adapter = config.registry.getMultiAdapter((iface, iface), - IResourceURL) - self.assertEqual(adapter.__class__, DummyResourceURL) - self.assertEqual(adapter.resource, iface) - self.assertEqual(adapter.request, iface) - - def test_add_resource_url_default_interfaces_mean_Interface(self): - from pyramid.interfaces import IResourceURL - config = self._makeOne(autocommit=True) - config.add_resource_url_adapter(DummyResourceURL) - iface = DummyIface() - adapter = config.registry.getMultiAdapter((iface, iface), - IResourceURL) - self.assertEqual(adapter.__class__, DummyResourceURL) - self.assertEqual(adapter.resource, iface) - self.assertEqual(adapter.request, iface) - - def test_add_resource_url_nodefault_interfaces(self): - from zope.interface import Interface - from pyramid.interfaces import IResourceURL - config = self._makeOne(autocommit=True) - config.add_resource_url_adapter(DummyResourceURL, DummyIface, - DummyIface) - iface = DummyIface() - adapter = config.registry.getMultiAdapter((iface, iface), - IResourceURL) - self.assertEqual(adapter.__class__, DummyResourceURL) - self.assertEqual(adapter.resource, iface) - self.assertEqual(adapter.request, iface) - bad_result = config.registry.queryMultiAdapter( - (Interface, Interface), - IResourceURL, - ) - self.assertEqual(bad_result, None) - - def test_add_resource_url_adapter_introspectables(self): - config = self._makeOne() - config.add_resource_url_adapter(DummyResourceURL, DummyIface) - actions = config.action_state.actions - self.assertEqual(len(actions), 1) - intrs = actions[0]['introspectables'] - self.assertEqual(len(intrs), 1) - intr = intrs[0] - self.assertEqual(intr.type_name, 'resource url adapter') - self.assertEqual(intr.discriminator, - ('resource url adapter', DummyIface, None)) - self.assertEqual(intr.category_name, 'resource url adapters') - self.assertEqual( - intr.title, - "resource url adapter for resource iface " - "<class 'pyramid.tests.test_config.test_factories.DummyIface'>, " - "request_iface None" - ) class DummyRequest(object): @@ -244,15 +142,3 @@ class DummyRequest(object): self.callables = [] self.callables.append((name, callable, reify)) -class DummyTraverser(object): - def __init__(self, root): - self.root = root - -class DummyIface(object): - pass - -class DummyResourceURL(object): - def __init__(self, resource, request): - self.resource = resource - self.request = request - |
