summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Merickel <michael@merickel.org>2012-01-11 02:33:21 -0600
committerMichael Merickel <michael@merickel.org>2012-01-11 02:33:21 -0600
commitfda1a4d61293b3959f71558ff5429c89e7bb9d84 (patch)
treea4279e58a733f468781edb79644b4ae7c1b88310
parentcf3a11e990adda800e284effb006f1c28335da4d (diff)
parent6c2e8fb0f75939ebff014a7bf16500003ab9f1af (diff)
downloadpyramid-fda1a4d61293b3959f71558ff5429c89e7bb9d84.tar.gz
pyramid-fda1a4d61293b3959f71558ff5429c89e7bb9d84.tar.bz2
pyramid-fda1a4d61293b3959f71558ff5429c89e7bb9d84.zip
Merge branch 'feature.lazy-request-properties' into 1.3-branch
-rw-r--r--CHANGES.txt13
-rw-r--r--docs/api/request.rst14
-rw-r--r--docs/whatsnew-1.3.rst15
-rw-r--r--pyramid/config/factories.py61
-rw-r--r--pyramid/interfaces.py4
-rw-r--r--pyramid/tests/test_config/test_factories.py75
-rw-r--r--pyramid/util.py29
7 files changed, 182 insertions, 29 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/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/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 530b6cc28..eb4442e98 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,
)
@@ -70,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
@@ -85,3 +91,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))
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)