From 1b113f772b48862c99e8269ca59365bc2acff85c Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 11 Jan 2012 02:04:22 -0600 Subject: Renamed the func to callable in the docs. --- docs/api/request.rst | 14 +++++++------- pyramid/util.py | 29 +++++++++++++++-------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/docs/api/request.rst b/docs/api/request.rst index 9596e5621..1ab84e230 100644 --- a/docs/api/request.rst +++ b/docs/api/request.rst @@ -208,9 +208,7 @@ body associated with this request, this property will raise an exception. See also :ref:`request_json_body`. - .. method:: set_property(func, name=None, reify=False) - - .. versionadded:: 1.3 + .. method:: set_property(callable, name=None, reify=False) Add a callable or a property descriptor to the request instance. @@ -225,15 +223,15 @@ cached. Thus the value of the property is only computed once for the lifetime of the object. - ``func`` can either be a callable that accepts the request as + ``callable`` can either be a callable that accepts the request as its single positional parameter, or it can be a property descriptor. - If the ``func`` is a property descriptor a ``ValueError`` will - be raised if ``name`` is ``None`` or ``reify`` is ``True``. + 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 ``func``. + from the name of the ``callable``. .. code-block:: python :linenos: @@ -259,6 +257,8 @@ without having to subclass it, which can be useful for extension authors. + .. versionadded:: 1.3 + .. note:: For information about the API of a :term:`multidict` structure (such as diff --git a/pyramid/util.py b/pyramid/util.py index 852689c4d..cca1872b7 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -20,7 +20,7 @@ class InstancePropertyMixin(object): on the class itself. """ - def set_property(self, func, name=None, reify=False): + def set_property(self, callable, name=None, reify=False): """ Add a callable or a property descriptor to the instance. Properties, unlike attributes, are lazily evaluated by executing @@ -34,17 +34,18 @@ class InstancePropertyMixin(object): cached. Thus the value of the property is only computed once for the lifetime of the object. - ``func`` can either be a callable that accepts the instance as + ``callable`` can either be a callable that accepts the instance + as its single positional parameter, or it can be a property descriptor. - If the ``func`` is a property descriptor, the ``name`` parameter - must be supplied or a ``ValueError`` will be raised. Also note - that a property descriptor cannot be reified, so ``reify`` must - be ``False``. + If the ``callable`` is a property descriptor, the ``name`` + parameter must be supplied or a ``ValueError`` will be raised. + Also note that a property descriptor cannot be reified, so + ``reify`` must be ``False``. If ``name`` is None, the name of the property will be computed - from the name of the ``func``. + from the name of the ``callable``. .. code-block:: python :linenos: @@ -73,20 +74,20 @@ class InstancePropertyMixin(object): 1 """ - is_property = isinstance(func, property) + is_property = isinstance(callable, property) if is_property: - fn = func + fn = callable if name is None: raise ValueError('must specify "name" for a property') if reify: raise ValueError('cannot reify a property') elif name is not None: - fn = lambda this: func(this) + fn = lambda this: callable(this) fn.__name__ = name - fn.__doc__ = func.__doc__ + fn.__doc__ = callable.__doc__ else: - name = func.__name__ - fn = func + name = callable.__name__ + fn = callable if reify: import pyramid.decorator fn = pyramid.decorator.reify(fn) @@ -234,7 +235,7 @@ def object_description(object): return text_('method %s of class %s.%s' % (object.__name__, modulename, oself.__class__.__name__)) - + if inspect.isclass(object): dottedname = '%s.%s' % (modulename, object.__name__) return text_('class %s' % dottedname) -- cgit v1.2.3 From 74d50369d99ce75d91aa73ee5d2ac336a94c44e1 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 11 Jan 2012 02:05:20 -0600 Subject: Added config.set_request_property(). This is a wrapper around request.set_property() which provides conflict detection for addons. --- pyramid/config/factories.py | 57 ++++++++++++++++++++++ pyramid/interfaces.py | 4 ++ pyramid/tests/test_config/test_factories.py | 75 ++++++++++++++++++++++++++++- 3 files changed, 134 insertions(+), 2 deletions(-) diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py index 530b6cc28..ca8d3f199 100644 --- a/pyramid/config/factories.py +++ b/pyramid/config/factories.py @@ -2,7 +2,9 @@ from pyramid.config.util import action_method from pyramid.interfaces import ( IDefaultRootFactory, + INewRequest, IRequestFactory, + IRequestProperties, IRootFactory, ISessionFactory, ) @@ -85,3 +87,58 @@ class FactoriesConfiguratorMixin(object): intr['factory'] = factory 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. + + ``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. + + 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. + + 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 + """ + callable = self.maybe_dotted(callable) + + if name is None: + name = callable.__name__ + + def register(): + plist = self.registry.queryUtility(IRequestProperties) + + if plist is None: + plist = [] + self.registry.registerUtility(plist, IRequestProperties) + self.registry.registerHandler(_set_request_properties, + (INewRequest,)) + + plist.append((name, callable, 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,)) + +def _set_request_properties(event): + request = event.request + plist = request.registry.queryUtility(IRequestProperties) + for prop in plist: + name, callable, reify = prop + request.set_property(callable, name=name, reify=reify) diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 6762d788d..8de5331b9 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -511,6 +511,10 @@ 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 IRouteRequest(Interface): """ *internal only* interface used as in a utility lookup to find route-specific interfaces. Not an API.""" diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py index 9cd13a435..d1a01568f 100644 --- a/pyramid/tests/test_config/test_factories.py +++ b/pyramid/tests/test_config/test_factories.py @@ -40,7 +40,7 @@ class TestFactoriesMixin(unittest.TestCase): config.commit() self.assertEqual(config.registry.getUtility(IRootFactory), DefaultRootFactory) - + def test_set_root_factory_dottedname(self): from pyramid.interfaces import IRootFactory config = self._makeOne() @@ -48,7 +48,7 @@ class TestFactoriesMixin(unittest.TestCase): self.assertEqual(config.registry.queryUtility(IRootFactory), None) config.commit() self.assertEqual(config.registry.getUtility(IRootFactory), dummyfactory) - + def test_set_session_factory(self): from pyramid.interfaces import ISessionFactory config = self._makeOne() @@ -67,4 +67,75 @@ class TestFactoriesMixin(unittest.TestCase): self.assertEqual(config.registry.getUtility(ISessionFactory), dummyfactory) + def test_set_request_property_with_callable(self): + from pyramid.interfaces import IRequestProperties + config = self._makeOne(autocommit=True) + callable = lambda x: None + config.set_request_property(callable, name='foo') + plist = config.registry.getUtility(IRequestProperties) + self.assertEqual(plist, [('foo', callable, False)]) + + def test_set_request_property_with_unnamed_callable(self): + from pyramid.interfaces import IRequestProperties + config = self._makeOne(autocommit=True) + def foo(self): pass + config.set_request_property(foo, reify=True) + plist = config.registry.getUtility(IRequestProperties) + self.assertEqual(plist, [('foo', foo, True)]) + + def test_set_request_property_with_property(self): + from pyramid.interfaces import IRequestProperties + config = self._makeOne(autocommit=True) + callable = property(lambda x: None) + config.set_request_property(callable, name='foo') + plist = config.registry.getUtility(IRequestProperties) + self.assertEqual(plist, [('foo', callable, False)]) + + def test_set_multiple_request_properties(self): + from pyramid.interfaces import IRequestProperties + 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(plist, [('foo', foo, True), + ('bar', bar, False)]) + + def test_set_multiple_request_properties_conflict(self): + from pyramid.exceptions import ConfigurationConflictError + config = self._makeOne() + def foo(self): pass + bar = property(lambda x: None) + config.set_request_property(foo, name='bar', reify=True) + config.set_request_property(bar, name='bar') + self.assertRaises(ConfigurationConflictError, config.commit) + + def test_set_request_property_subscriber(self): + from zope.interface import implementer + from pyramid.interfaces import INewRequest + config = self._makeOne() + def foo(r): pass + config.set_request_property(foo, name='foo') + config.set_request_property(foo, name='bar', reify=True) + config.commit() + @implementer(INewRequest) + class Event(object): + request = DummyRequest(config.registry) + event = Event() + config.registry.notify(event) + callables = event.request.callables + self.assertEqual(callables, [('foo', foo, False), + ('bar', foo, True)]) + +class DummyRequest(object): + callables = None + + def __init__(self, registry): + self.registry = registry + def set_property(self, callable, name, reify): + if self.callables is None: + self.callables = [] + self.callables.append((name, callable, reify)) -- cgit v1.2.3 From 6c2e8fb0f75939ebff014a7bf16500003ab9f1af Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 11 Jan 2012 02:30:57 -0600 Subject: Updated the changelogs. --- CHANGES.txt | 13 ++++++++++++- docs/whatsnew-1.3.rst | 15 ++++++++++----- pyramid/config/factories.py | 4 ++++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 8950e75a2..a755f6418 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,14 @@ +Unreleased +========== + +Features +-------- + +- New API: ``pyramid.config.Configurator.set_request_property``. Add lazy + property descriptors to a request without changing the request factory. + This method provides conflict detection and is the suggested way to add + properties to a request. + 1.3a5 (2012-01-09) ================== @@ -25,7 +36,7 @@ Features - New API: ``pyramid.request.Request.set_property``. Add lazy property descriptors to a request without changing the request factory. New properties may be reified, effectively caching the value for the lifetime - of the instance. Common use-cases for this would be to get a database + of the instance. Common use-cases for this would be to get a database connection for the request or identify the current user. - Use the ``waitress`` WSGI server instead of ``wsgiref`` in scaffolding. diff --git a/docs/whatsnew-1.3.rst b/docs/whatsnew-1.3.rst index eb8617ff1..ee4e2ccb5 100644 --- a/docs/whatsnew-1.3.rst +++ b/docs/whatsnew-1.3.rst @@ -198,11 +198,16 @@ Extending a Request without Subclassing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It is now possible to extend a :class:`pyramid.request.Request` object -with property descriptors without having to create a subclass via -:meth:`pyramid.request.Request.set_property`. New properties may be -reified, effectively caching the value for the lifetime of the instance. -Common use-cases for this would be to get a database connection for the -request or identify the current user. +with property descriptors without having to create a custom request factory. +The new method :meth:`pyramid.config.Configurator.set_request_property` +provides an entry point for addons to register properties which will be +added to each request. New properties may be reified, effectively caching +the return value for the lifetime of the instance. Common use-cases for this +would be to get a database connection for the request or identify the current +user. The new method :meth:`pyramid.request.Request.set_property` has been +added, as well, but the configurator method should be preferred as it +provides conflict detection and consistency in the lifetime of the +properties. Minor Feature Additions ----------------------- diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py index ca8d3f199..eb4442e98 100644 --- a/pyramid/config/factories.py +++ b/pyramid/config/factories.py @@ -72,6 +72,10 @@ class FactoriesConfiguratorMixin(object): :class:`pyramid.request.Request` class (particularly ``__call__``, and ``blank``). + See :meth:`pyramid.config.Configurator.set_request_property` + for a less intrusive way to extend the request objects with + custom properties. + .. note:: Using the ``request_factory`` argument to the -- cgit v1.2.3