Merge branch 'feature.lazy-request-properties' into 1.3-branch
| | |
| | | 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) |
| | | ================== |
| | | |
| | |
| | | - 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. |
| | |
| | | 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. |
| | | |
| | |
| | | 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: |
| | |
| | | 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 |
| | |
| | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| | | |
| | | 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 |
| | | ----------------------- |
| | |
| | | |
| | | from pyramid.interfaces import ( |
| | | IDefaultRootFactory, |
| | | INewRequest, |
| | | IRequestFactory, |
| | | IRequestProperties, |
| | | IRootFactory, |
| | | ISessionFactory, |
| | | ) |
| | |
| | | :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 |
| | |
| | | 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) |
| | |
| | | |
| | | 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.""" |
| | |
| | | config.commit() |
| | | self.assertEqual(config.registry.getUtility(IRootFactory), |
| | | DefaultRootFactory) |
| | | |
| | | |
| | | def test_set_root_factory_dottedname(self): |
| | | from pyramid.interfaces import IRootFactory |
| | | config = self._makeOne() |
| | |
| | | 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() |
| | |
| | | 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)) |
| | |
| | | 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 |
| | |
| | | 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: |
| | |
| | | 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) |
| | |
| | | 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) |