Michael Merickel
2012-01-11 fda1a4d61293b3959f71558ff5429c89e7bb9d84
Merge branch 'feature.lazy-request-properties' into 1.3-branch
7 files modified
211 ■■■■ changed files
CHANGES.txt 13 ●●●●● patch | view | raw | blame | history
docs/api/request.rst 14 ●●●● patch | view | raw | blame | history
docs/whatsnew-1.3.rst 15 ●●●●● patch | view | raw | blame | history
pyramid/config/factories.py 61 ●●●●● patch | view | raw | blame | history
pyramid/interfaces.py 4 ●●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_factories.py 75 ●●●●● patch | view | raw | blame | history
pyramid/util.py 29 ●●●● patch | view | raw | blame | history
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 @@
- 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.
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
docs/whatsnew-1.3.rst
@@ -198,11 +198,16 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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
-----------------------
pyramid/config/factories.py
@@ -2,7 +2,9 @@
from pyramid.interfaces import (
    IDefaultRootFactory,
    INewRequest,
    IRequestFactory,
    IRequestProperties,
    IRootFactory,
    ISessionFactory,
    )
@@ -70,6 +72,10 @@
        :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
@@ -85,3 +91,58 @@
        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)
pyramid/interfaces.py
@@ -511,6 +511,10 @@
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."""
pyramid/tests/test_config/test_factories.py
@@ -40,7 +40,7 @@
        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 @@
        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 @@
        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))
pyramid/util.py
@@ -20,7 +20,7 @@
    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 @@
        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 @@
           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 @@
        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)