diff options
| author | Chris McDonough <chrism@plope.com> | 2012-08-16 11:12:24 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2012-08-16 11:12:24 -0400 |
| commit | 14f69c57c4d83355e769db46692cfb36e87a4f70 (patch) | |
| tree | 2e7e9de785050c7b5276029d1fbed12d0b15a097 | |
| parent | 8c7b0f14b2c55e653bb0d4a18e3de299b7abe208 (diff) | |
| parent | 2c25342383eed7b10e349b2396eed08d332cfb80 (diff) | |
| download | pyramid-14f69c57c4d83355e769db46692cfb36e87a4f70.tar.gz pyramid-14f69c57c4d83355e769db46692cfb36e87a4f70.tar.bz2 pyramid-14f69c57c4d83355e769db46692cfb36e87a4f70.zip | |
Merge branch 'feature.instance-properties' of git://github.com/mmerickel/pyramid into mmerickel-feature.instance-properties
| -rw-r--r-- | CHANGES.txt | 24 | ||||
| -rw-r--r-- | docs/api/config.rst | 4 | ||||
| -rw-r--r-- | docs/narr/advconfig.rst | 2 | ||||
| -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 |
6 files changed, 201 insertions, 61 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index f02925585..b2f37355b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -64,13 +64,17 @@ Features HEAD is a variant of GET that omits the body, and WebOb has special support to return an empty body when a HEAD is used. -- ``config.set_request_property`` now causes less code to be executed at - request construction time. - -- Don't add a ``?`` to URLs generated by request.resource_url if the +- ``config.set_request_method`` has been introduced to support extending + request objects with arbitrary callables. This method expands on the + previous ``config.set_request_property`` by supporting methods as well as + properties. This method now causes less code to be executed at + request construction time than ``config.set_request_property`` in + version 1.3. + +- Don't add a ``?`` to URLs generated by ``request.resource_url`` if the ``query`` argument is provided but empty. -- Don't add a ``?`` to URLs generated by request.route_url if the +- Don't add a ``?`` to URLs generated by ``request.route_url`` if the ``_query`` argument is provided but empty. - The static view machinery now raises (rather than returns) ``HTTPNotFound`` @@ -100,3 +104,13 @@ Features config = Configurator() config.add_permission('view') + +Deprecations +------------ + +- The documentation for + ``pyramid.config.Configurator.set_request_property`` has been removed. + The method remains usable but the more featureful + ``pyramid.config.Configurator.set_request_method`` should be used in its + place. It has all of the same capabilities but can also extend the + request object with methods. diff --git a/docs/api/config.rst b/docs/api/config.rst index 1b887988a..8dbe6de91 100644 --- a/docs/api/config.rst +++ b/docs/api/config.rst @@ -38,9 +38,9 @@ .. automethod:: set_default_permission .. automethod:: add_permission - :methodcategory:`Setting Request Properties` + :methodcategory:`Extending the Request Object` - .. automethod:: set_request_property + .. automethod:: set_request_method :methodcategory:`Using I18N` diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst index 2949dc808..0ad2fdc95 100644 --- a/docs/narr/advconfig.rst +++ b/docs/narr/advconfig.rst @@ -296,7 +296,7 @@ These are the methods of the configurator which provide conflict detection: :meth:`~pyramid.config.Configurator.add_renderer`, :meth:`~pyramid.config.Configurator.set_request_factory`, :meth:`~pyramid.config.Configurator.set_session_factory`, -:meth:`~pyramid.config.Configurator.set_request_property`, +:meth:`~pyramid.config.Configurator.set_request_method`, :meth:`~pyramid.config.Configurator.set_root_factory`, :meth:`~pyramid.config.Configurator.set_view_mapper`, :meth:`~pyramid.config.Configurator.set_authentication_policy`, diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py index ccbf3bbe9..e3637cbd2 100644 --- a/pyramid/config/factories.py +++ b/pyramid/config/factories.py @@ -1,10 +1,13 @@ +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 +96,116 @@ 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. + + .. warning:: + + 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 = fn.__get__(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) |
