diff options
| -rw-r--r-- | pyramid/config/factories.py | 126 | ||||
| -rw-r--r-- | pyramid/interfaces.py | 10 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_factories.py | 96 |
3 files changed, 179 insertions, 53 deletions
diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py index ccbf3bbe9..e8b70e9e6 100644 --- a/pyramid/config/factories.py +++ b/pyramid/config/factories.py @@ -1,10 +1,15 @@ +import types + +from zope.interface import implementer + +from pyramid.compat import iteritems_ from pyramid.config.util import action_method from pyramid.interfaces import ( IDefaultRootFactory, INewRequest, IRequestFactory, - IRequestProperties, + IRequestExtensions, IRootFactory, ISessionFactory, ) @@ -93,55 +98,114 @@ class FactoriesConfiguratorMixin(object): self.action(IRequestFactory, register, introspectables=(intr,)) @action_method - def set_request_property(self, callable, name=None, reify=False): - """ Add a property to the request object. + def set_request_method(self, + callable=None, + name=None, + property=False, + reify=False): + """ Add a property or method to the request object. + + When adding a method to the request, ``callable`` may be any + function that receives the request object as the first + parameter. If ``name`` is ``None`` then it will be computed + from the name of the ``callable``. - ``callable`` can either be a callable that accepts the request - as its single positional parameter, or it can be a property - descriptor. It may also be a :term:`dotted Python name` which - refers to either a callable or a property descriptor. + When adding a property to the request, ``callable`` can either + be a callable that accepts the request as its single positional + parameter, or it can be a property descriptor. If ``name`` is + ``None``, the name of the property will be computed from the + name of the ``callable``. If the ``callable`` is a property descriptor a ``ValueError`` will be raised if ``name`` is ``None`` or ``reify`` is ``True``. - If ``name`` is None, the name of the property will be computed - from the name of the ``callable``. - See :meth:`pyramid.request.Request.set_property` for more - information on its usage. + details on ``property`` vs ``reify``. When ``reify`` is + ``True``, the value of ``property`` is assumed to also be + ``True``. + + In all cases, ``callable`` may also be a + :term:`dotted Python name` which refers to either a callable or + a property descriptor. + + If ``callable`` is ``None`` then the method is only used to + assist in conflict detection between different addons requesting + the same attribute on the request object. This is the recommended method for extending the request object and should be used in favor of providing a custom request factory via :meth:`pyramid.config.Configurator.set_request_factory`. - .. versionadded:: 1.3 + .. versionadded:: 1.4 """ - callable = self.maybe_dotted(callable) + if callable is not None: + callable = self.maybe_dotted(callable) - name, callable = InstancePropertyMixin._make_property( - callable, name=name, reify=reify) + property = property or reify + if property: + name, callable = InstancePropertyMixin._make_property( + callable, name=name, reify=reify) + elif name is None: + name = callable.__name__ def register(): - plist = self.registry.queryUtility(IRequestProperties) + exts = self.registry.queryUtility(IRequestExtensions) - if plist is None: - plist = [] - self.registry.registerUtility(plist, IRequestProperties) - self.registry.registerHandler(_set_request_properties, + if exts is None: + exts = _RequestExtensions() + self.registry.registerUtility(exts, IRequestExtensions) + self.registry.registerHandler(_set_request_extensions, (INewRequest,)) - plist.append((name, callable)) + plist = exts.descriptors if property else exts.methods + plist[name] = callable + + if callable is None: + self.action(('request extensions', name), None) + elif property: + intr = self.introspectable('request extensions', name, + self.object_description(callable), + 'request property') + intr['callable'] = callable + intr['property'] = True + intr['reify'] = reify + self.action(('request extensions', name), register, + introspectables=(intr,)) + else: + intr = self.introspectable('request extensions', name, + self.object_description(callable), + 'request method') + intr['callable'] = callable + intr['property'] = False + intr['reify'] = False + self.action(('request extensions', name), register, + introspectables=(intr,)) + + @action_method + def set_request_property(self, callable, name=None, reify=False): + """ Add a property to the request object. + + This method has been replaced by + :meth:`pyramid.config.Configurator.set_request_method` in + version :app:`Pyramid` version 1.4, more details can be found + there. + + .. versionadded:: 1.3 + """ + self.set_request_method( + callable, name=name, property=not reify, reify=reify) - intr = self.introspectable('request properties', name, - self.object_description(callable), - 'request property') - intr['callable'] = callable - intr['reify'] = reify - self.action(('request properties', name), register, - introspectables=(intr,)) +@implementer(IRequestExtensions) +class _RequestExtensions(object): + def __init__(self): + self.descriptors = {} + self.methods = {} -def _set_request_properties(event): +def _set_request_extensions(event): request = event.request - plist = request.registry.queryUtility(IRequestProperties) - request._set_properties(plist) + exts = request.registry.queryUtility(IRequestExtensions) + for name, fn in iteritems_(exts.methods): + method = types.MethodType(fn, request, request.__class__) + setattr(request, name, method) + request._set_properties(exts.descriptors) diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 114a01854..042b4487b 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -513,9 +513,13 @@ class IRequestHandler(Interface): IRequest.combined = IRequest # for exception view lookups -class IRequestProperties(Interface): - """ Marker interface for storing a list of request properties which - will be added to the request object.""" +class IRequestExtensions(Interface): + """ Marker interface for storing request extensions (properties and + methods) which will be added to the request object.""" + descriptors = Attribute( + """A list of descriptors that will be added to each request.""") + methods = Attribute( + """A list of methods to be added to each request.""") class IRouteRequest(Interface): """ *internal only* interface used as in a utility lookup to find diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py index 1dfeda34c..a95326772 100644 --- a/pyramid/tests/test_config/test_factories.py +++ b/pyramid/tests/test_config/test_factories.py @@ -68,39 +68,40 @@ class TestFactoriesMixin(unittest.TestCase): dummyfactory) def test_set_request_property_with_callable(self): - from pyramid.interfaces import IRequestProperties + from pyramid.interfaces import IRequestExtensions config = self._makeOne(autocommit=True) callable = lambda x: None config.set_request_property(callable, name='foo') - plist = config.registry.getUtility(IRequestProperties) - self.assertEqual(set(p[0] for p in plist), set(['foo'])) + exts = config.registry.getUtility(IRequestExtensions) + self.assertTrue('foo' in exts.descriptors) def test_set_request_property_with_unnamed_callable(self): - from pyramid.interfaces import IRequestProperties + from pyramid.interfaces import IRequestExtensions config = self._makeOne(autocommit=True) def foo(self): pass config.set_request_property(foo, reify=True) - plist = config.registry.getUtility(IRequestProperties) - self.assertEqual(set(p[0] for p in plist), set(['foo'])) + exts = config.registry.getUtility(IRequestExtensions) + self.assertTrue('foo' in exts.descriptors) def test_set_request_property_with_property(self): - from pyramid.interfaces import IRequestProperties + from pyramid.interfaces import IRequestExtensions config = self._makeOne(autocommit=True) callable = property(lambda x: None) config.set_request_property(callable, name='foo') - plist = config.registry.getUtility(IRequestProperties) - self.assertEqual(set(p[0] for p in plist), set(['foo'])) + exts = config.registry.getUtility(IRequestExtensions) + self.assertTrue('foo' in exts.descriptors) def test_set_multiple_request_properties(self): - from pyramid.interfaces import IRequestProperties + from pyramid.interfaces import IRequestExtensions config = self._makeOne() def foo(self): pass bar = property(lambda x: None) config.set_request_property(foo, reify=True) config.set_request_property(bar, name='bar') config.commit() - plist = config.registry.getUtility(IRequestProperties) - self.assertEqual(set(p[0] for p in plist), set(['foo', 'bar'])) + exts = config.registry.getUtility(IRequestExtensions) + self.assertTrue('foo' in exts.descriptors) + self.assertTrue('bar' in exts.descriptors) def test_set_multiple_request_properties_conflict(self): from pyramid.exceptions import ConfigurationConflictError @@ -124,19 +125,76 @@ class TestFactoriesMixin(unittest.TestCase): request = DummyRequest(config.registry) event = Event() config.registry.notify(event) - plist = event.request.plist - self.assertEqual(set(p[0] for p in plist), set(['foo', 'bar'])) + exts = event.request.extensions + self.assertTrue('foo' in exts[0]) + self.assertTrue('bar' in exts[1]) + + def test_set_request_method_subscriber(self): + from zope.interface import implementer + from pyramid.interfaces import INewRequest + config = self._makeOne(autocommit=True) + def foo(r): return 'bar' + config.set_request_method(foo, name='foo') + @implementer(INewRequest) + class Event(object): + request = DummyRequest(config.registry) + event = Event() + config.registry.notify(event) + self.assertEqual('bar', event.request.foo()) + + def test_set_request_method_with_callable(self): + from pyramid.interfaces import IRequestExtensions + config = self._makeOne(autocommit=True) + callable = lambda x: None + config.set_request_method(callable, name='foo') + exts = config.registry.getUtility(IRequestExtensions) + self.assertTrue('foo' in exts.methods) + + def test_set_request_method_with_unnamed_callable(self): + from pyramid.interfaces import IRequestExtensions + config = self._makeOne(autocommit=True) + def foo(self): pass + config.set_request_method(foo) + exts = config.registry.getUtility(IRequestExtensions) + self.assertTrue('foo' in exts.methods) + + def test_set_multiple_request_methods_conflict(self): + from pyramid.exceptions import ConfigurationConflictError + config = self._makeOne() + def foo(self): pass + def bar(self): pass + config.set_request_method(foo, name='bar') + config.set_request_method(bar, name='bar') + self.assertRaises(ConfigurationConflictError, config.commit) + + def test_set_request_method_with_None_callable(self): + from pyramid.interfaces import IRequestExtensions + config = self._makeOne(autocommit=True) + config.set_request_method(name='foo') + exts = config.registry.queryUtility(IRequestExtensions) + self.assertTrue(exts is None) + + def test_set_request_method_with_None_callable_conflict(self): + from pyramid.exceptions import ConfigurationConflictError + config = self._makeOne() + def bar(self): pass + config.set_request_method(name='foo') + config.set_request_method(bar, name='foo') + self.assertRaises(ConfigurationConflictError, config.commit) + + def test_set_request_method_with_None_callable_and_no_name(self): + config = self._makeOne(autocommit=True) + self.assertRaises(AttributeError, config.set_request_method) - class DummyRequest(object): - plist = None + extensions = None def __init__(self, registry): self.registry = registry def _set_properties(self, properties): - if self.plist is None: - self.plist = [] - self.plist.extend(properties) + if self.extensions is None: + self.extensions = [] + self.extensions.extend(properties) |
