Added the InstancePropertyMixin to the Request.
The new mixin allows adding properties to the request object which are
lazily evaluated.
| | |
| | | from pyramid.decorator import reify |
| | | from pyramid.response import Response |
| | | from pyramid.url import URLMethodsMixin |
| | | from pyramid.util import InstancePropertyMixin |
| | | |
| | | class TemplateContext(object): |
| | | pass |
| | |
| | | |
| | | @implementer(IRequest) |
| | | class Request(BaseRequest, DeprecatedRequestMethodsMixin, URLMethodsMixin, |
| | | CallbackMethodsMixin): |
| | | CallbackMethodsMixin, InstancePropertyMixin): |
| | | """ |
| | | A subclass of the :term:`WebOb` Request class. An instance of |
| | | this class is created by the :term:`router` and is provided to a |
| | |
| | | request = self._makeOne({'REQUEST_METHOD':'GET'}) |
| | | self.assertRaises(ValueError, getattr, request, 'json_body') |
| | | |
| | | def test_set_property(self): |
| | | request = self._makeOne({}) |
| | | opts = [2, 1] |
| | | def connect(obj): |
| | | return opts.pop() |
| | | request.set_property(connect, name='db') |
| | | self.assertEqual(1, request.db) |
| | | self.assertEqual(2, request.db) |
| | | |
| | | def test_set_property_reify(self): |
| | | request = self._makeOne({}) |
| | | opts = [2, 1] |
| | | def connect(obj): |
| | | return opts.pop() |
| | | request.set_property(connect, name='db', reify=True) |
| | | self.assertEqual(1, request.db) |
| | | self.assertEqual(1, request.db) |
| | | |
| | | class TestRequestDeprecatedMethods(unittest.TestCase): |
| | | def setUp(self): |
| | | self.config = testing.setUp() |
| | |
| | | import unittest |
| | | from pyramid.compat import PY3 |
| | | |
| | | class Test_InstancePropertyMixin(unittest.TestCase): |
| | | def _makeOne(self): |
| | | cls = self._targetClass() |
| | | class Foo(cls): |
| | | pass |
| | | return Foo() |
| | | |
| | | def _targetClass(self): |
| | | from pyramid.util import InstancePropertyMixin |
| | | return InstancePropertyMixin |
| | | |
| | | def test_callable(self): |
| | | def worker(obj): |
| | | return obj.bar |
| | | foo = self._makeOne() |
| | | foo.set_property(worker) |
| | | foo.bar = 1 |
| | | self.assertEqual(1, foo.worker) |
| | | foo.bar = 2 |
| | | self.assertEqual(2, foo.worker) |
| | | |
| | | def test_callable_with_name(self): |
| | | def worker(obj): |
| | | return obj.bar |
| | | foo = self._makeOne() |
| | | foo.set_property(worker, name='x') |
| | | foo.bar = 1 |
| | | self.assertEqual(1, foo.x) |
| | | foo.bar = 2 |
| | | self.assertEqual(2, foo.x) |
| | | |
| | | def test_callable_with_reify(self): |
| | | def worker(obj): |
| | | return obj.bar |
| | | foo = self._makeOne() |
| | | foo.set_property(worker, reify=True) |
| | | foo.bar = 1 |
| | | self.assertEqual(1, foo.worker) |
| | | foo.bar = 2 |
| | | self.assertEqual(1, foo.worker) |
| | | |
| | | def test_callable_with_name_reify(self): |
| | | def worker(obj): |
| | | return obj.bar |
| | | foo = self._makeOne() |
| | | foo.set_property(worker, name='x') |
| | | foo.set_property(worker, name='y', reify=True) |
| | | foo.bar = 1 |
| | | self.assertEqual(1, foo.y) |
| | | self.assertEqual(1, foo.x) |
| | | foo.bar = 2 |
| | | self.assertEqual(2, foo.x) |
| | | self.assertEqual(1, foo.y) |
| | | |
| | | def test_property_without_name(self): |
| | | def worker(obj): pass |
| | | foo = self._makeOne() |
| | | self.assertRaises(ValueError, foo.set_property, property(worker)) |
| | | |
| | | def test_property_with_name(self): |
| | | def worker(obj): |
| | | return obj.bar |
| | | foo = self._makeOne() |
| | | foo.set_property(property(worker), name='x') |
| | | foo.bar = 1 |
| | | self.assertEqual(1, foo.x) |
| | | foo.bar = 2 |
| | | self.assertEqual(2, foo.x) |
| | | |
| | | def test_property_with_reify(self): |
| | | def worker(obj): pass |
| | | foo = self._makeOne() |
| | | self.assertRaises(ValueError, foo.set_property, |
| | | property(worker), name='x', reify=True) |
| | | |
| | | def test_override_property(self): |
| | | def worker(obj): pass |
| | | foo = self._makeOne() |
| | | foo.set_property(worker, name='x') |
| | | def doit(): |
| | | foo.x = 1 |
| | | self.assertRaises(AttributeError, doit) |
| | | |
| | | def test_override_reify(self): |
| | | def worker(obj): pass |
| | | foo = self._makeOne() |
| | | foo.set_property(worker, name='x', reify=True) |
| | | foo.x = 1 |
| | | self.assertEqual(1, foo.x) |
| | | foo.x = 2 |
| | | self.assertEqual(2, foo.x) |
| | | |
| | | def test_reset_property(self): |
| | | foo = self._makeOne() |
| | | foo.set_property(lambda _: 1, name='x') |
| | | self.assertEqual(1, foo.x) |
| | | foo.set_property(lambda _: 2, name='x') |
| | | self.assertEqual(2, foo.x) |
| | | |
| | | def test_reset_reify(self): |
| | | """ This is questionable behavior, but may as well get notified |
| | | if it changes.""" |
| | | foo = self._makeOne() |
| | | foo.set_property(lambda _: 1, name='x', reify=True) |
| | | self.assertEqual(1, foo.x) |
| | | foo.set_property(lambda _: 2, name='x', reify=True) |
| | | self.assertEqual(1, foo.x) |
| | | |
| | | class Test_WeakOrderedSet(unittest.TestCase): |
| | | def _makeOne(self): |
| | | from pyramid.config import WeakOrderedSet |
| | |
| | | def __init__(self, package=None): # default to package = None for bw compat |
| | | return _DottedNameResolver.__init__(self, package) |
| | | |
| | | class InstancePropertyMixin(object): |
| | | """ Mixin that will allow an instance to add properties at |
| | | run-time as if they had been defined via @property or @reify |
| | | on the class itself. |
| | | """ |
| | | |
| | | def set_property(self, func, name=None, reify=False): |
| | | """ Add a callable or a property descriptor to the instance. |
| | | |
| | | Properties, unlike attributes, are lazily evaluated by executing |
| | | an underlying callable when accessed. They can be useful for |
| | | adding features to an object without any cost if those features |
| | | go unused. |
| | | |
| | | A property may also be reified via the |
| | | :class:`pyramid.decorator.reify` decorator by setting |
| | | ``reify=True``, allowing the result of the evaluation to be |
| | | 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 |
| | | 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 ``name`` is None, the name of the property will be computed |
| | | from the name of the ``func``. |
| | | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | class Foo(InstancePropertyMixin): |
| | | _x = 1 |
| | | |
| | | def _get_x(self): |
| | | return _x |
| | | |
| | | def _set_x(self, value): |
| | | self._x = value |
| | | |
| | | foo = Foo() |
| | | foo.set_property(property(_get_x, _set_x), name='x') |
| | | foo.set_property(_get_x, name='y', reify=True) |
| | | |
| | | >>> foo.x |
| | | 1 |
| | | >>> foo.y |
| | | 1 |
| | | >>> foo.x = 5 |
| | | >>> foo.x |
| | | 5 |
| | | >>> foo.y # notice y keeps the original value |
| | | 1 |
| | | """ |
| | | |
| | | is_property = isinstance(func, property) |
| | | if is_property: |
| | | fn = func |
| | | 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.__name__ = name |
| | | fn.__doc__ = func.__doc__ |
| | | else: |
| | | name = func.__name__ |
| | | fn = func |
| | | if reify: |
| | | import pyramid.decorator |
| | | fn = pyramid.decorator.reify(fn) |
| | | elif not is_property: |
| | | fn = property(fn) |
| | | attrs = { name: fn } |
| | | parent = self.__class__ |
| | | cls = type(parent.__name__, (parent, object), attrs) |
| | | self.__class__ = cls |
| | | |
| | | class WeakOrderedSet(object): |
| | | """ Maintain a set of items. |
| | | |