From 5473f06c9c51ce4bd5f391a240e7b9b278137c53 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 10 Apr 2012 23:20:11 -0500 Subject: [PATCH 1/5] refactored set_property to support multiple properties in the same subclass --- pyramid/util.py | 86 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/pyramid/util.py b/pyramid/util.py index cca1872b76..ff6ac8f4a7 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -20,6 +20,59 @@ class InstancePropertyMixin(object): on the class itself. """ + def _make_property(self, callable, name=None, reify=False): + """ Convert a callable into one suitable for adding to the + instance. This will return a 2-tuple containing the computed + (name, property) pair. + """ + + is_property = isinstance(callable, property) + if is_property: + 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: callable(this) + fn.__name__ = name + fn.__doc__ = callable.__doc__ + else: + name = callable.__name__ + fn = callable + if reify: + import pyramid.decorator # avoid circular import + fn = pyramid.decorator.reify(fn) + elif not is_property: + fn = property(fn) + + return name, fn + + def _set_properties(self, properties): + """ Create several properties on the instance at once. + + This is a more efficient version of + :meth:`pyramid.util.InstancePropertyMixin.set_property` which + can accept multiple ``(name, property)`` pairs generated via + :meth:`pyramid.util.InstancePropertyMixin._make_property`. + + ``attrs`` is a sequence of 2-tuples *or* a data structure with + an ``.items()`` method which returns a sequence of 2-tuples + (presumably a dictionary). It will be used used to add several + properties to the instance in a manner that is more efficient + than simply calling ``set_property`` repeatedly. + """ + + if hasattr(properties, 'items'): + attrs = properties.items() + else: + attrs = properties + attrs = dict(properties) + + parent = self.__class__ + cls = type(parent.__name__, (parent, object), attrs) + self.__class__ = cls + def set_property(self, callable, name=None, reify=False): """ Add a callable or a property descriptor to the instance. @@ -31,12 +84,11 @@ def set_property(self, callable, name=None, reify=False): 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. + cached. Using this method, the value of the property is only + computed once for the lifetime of the object. ``callable`` can either be a callable that accepts the instance - as - its single positional parameter, or it can be a property + as its single positional parameter, or it can be a property descriptor. If the ``callable`` is a property descriptor, the ``name`` @@ -73,30 +125,8 @@ def _set_x(self, value): >>> foo.y # notice y keeps the original value 1 """ - - is_property = isinstance(callable, property) - if is_property: - 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: callable(this) - fn.__name__ = name - fn.__doc__ = callable.__doc__ - else: - name = callable.__name__ - fn = callable - 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 + prop = self._make_property(callable, name=name, reify=reify) + self._set_properties([prop]) class WeakOrderedSet(object): """ Maintain a set of items. From 3f310683151e4d6285e9bfa3f74883a6521e62b3 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 10 Apr 2012 23:34:06 -0500 Subject: [PATCH 2/5] tests for bulk property creation --- pyramid/tests/test_util.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 824ee329f4..e0998d81dd 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -109,6 +109,29 @@ def test_reset_reify(self): foo.set_property(lambda _: 2, name='x', reify=True) self.assertEqual(1, foo.x) + def test__make_property(self): + from pyramid.decorator import reify + foo = self._makeOne() + name, fn = foo._make_property(lambda x: 1, name='x', reify=True) + self.assertEqual(name, 'x') + self.assertTrue(isinstance(fn, reify)) + + def test__set_properties_with_iterable(self): + foo = self._makeOne() + x = foo._make_property(lambda _: 1, name='x', reify=True) + y = foo._make_property(lambda _: 2, name='y') + foo._set_properties([x, y]) + self.assertEqual(1, foo.x) + self.assertEqual(2, foo.y) + + def test__set_properties_with_dict(self): + foo = self._makeOne() + x_name, x_fn = foo._make_property(lambda _: 1, name='x', reify=True) + y_name, y_fn = foo._make_property(lambda _: 2, name='y') + foo._set_properties({x_name: x_fn, y_name: y_fn}) + self.assertEqual(1, foo.x) + self.assertEqual(2, foo.y) + class Test_WeakOrderedSet(unittest.TestCase): def _makeOne(self): from pyramid.config import WeakOrderedSet From 6d2187e8706618861c3a5cc9e66c8f11bfc62364 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 10 Apr 2012 23:41:12 -0500 Subject: [PATCH 3/5] changed to classmethod --- pyramid/tests/test_util.py | 4 ++-- pyramid/util.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index e0998d81dd..2a69190f7c 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -111,8 +111,8 @@ def test_reset_reify(self): def test__make_property(self): from pyramid.decorator import reify - foo = self._makeOne() - name, fn = foo._make_property(lambda x: 1, name='x', reify=True) + cls = self._getTargetClass() + name, fn = cls._make_property(lambda x: 1, name='x', reify=True) self.assertEqual(name, 'x') self.assertTrue(isinstance(fn, reify)) diff --git a/pyramid/util.py b/pyramid/util.py index ff6ac8f4a7..7d5c978145 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -20,7 +20,8 @@ class InstancePropertyMixin(object): on the class itself. """ - def _make_property(self, callable, name=None, reify=False): + @classmethod + def _make_property(cls, callable, name=None, reify=False): """ Convert a callable into one suitable for adding to the instance. This will return a 2-tuple containing the computed (name, property) pair. From 49a3582d9e8a1345cfcc49ecb362a4dc14564040 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 11 Apr 2012 00:00:16 -0500 Subject: [PATCH 4/5] request properties are now added in bulk --- pyramid/config/factories.py | 11 +++++----- pyramid/tests/test_config/test_factories.py | 24 ++++++++++----------- pyramid/tests/test_util.py | 4 ++-- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py index eb4442e983..ccbf3bbe9c 100644 --- a/pyramid/config/factories.py +++ b/pyramid/config/factories.py @@ -10,6 +10,7 @@ ) from pyramid.traversal import DefaultRootFactory +from pyramid.util import InstancePropertyMixin class FactoriesConfiguratorMixin(object): @action_method @@ -118,8 +119,8 @@ def set_request_property(self, callable, name=None, reify=False): """ callable = self.maybe_dotted(callable) - if name is None: - name = callable.__name__ + name, callable = InstancePropertyMixin._make_property( + callable, name=name, reify=reify) def register(): plist = self.registry.queryUtility(IRequestProperties) @@ -130,7 +131,7 @@ def register(): self.registry.registerHandler(_set_request_properties, (INewRequest,)) - plist.append((name, callable, reify)) + plist.append((name, callable)) intr = self.introspectable('request properties', name, self.object_description(callable), @@ -143,6 +144,4 @@ def register(): 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) + request._set_properties(plist) diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py index 0930f96036..1dfeda34c4 100644 --- a/pyramid/tests/test_config/test_factories.py +++ b/pyramid/tests/test_config/test_factories.py @@ -73,7 +73,7 @@ def test_set_request_property_with_callable(self): callable = lambda x: None config.set_request_property(callable, name='foo') plist = config.registry.getUtility(IRequestProperties) - self.assertEqual(plist, [('foo', callable, False)]) + self.assertEqual(set(p[0] for p in plist), set(['foo'])) def test_set_request_property_with_unnamed_callable(self): from pyramid.interfaces import IRequestProperties @@ -81,7 +81,7 @@ def test_set_request_property_with_unnamed_callable(self): def foo(self): pass config.set_request_property(foo, reify=True) plist = config.registry.getUtility(IRequestProperties) - self.assertEqual(plist, [('foo', foo, True)]) + self.assertEqual(set(p[0] for p in plist), set(['foo'])) def test_set_request_property_with_property(self): from pyramid.interfaces import IRequestProperties @@ -89,7 +89,7 @@ def test_set_request_property_with_property(self): callable = property(lambda x: None) config.set_request_property(callable, name='foo') plist = config.registry.getUtility(IRequestProperties) - self.assertEqual(plist, [('foo', callable, False)]) + self.assertEqual(set(p[0] for p in plist), set(['foo'])) def test_set_multiple_request_properties(self): from pyramid.interfaces import IRequestProperties @@ -100,8 +100,7 @@ def foo(self): pass config.set_request_property(bar, name='bar') config.commit() plist = config.registry.getUtility(IRequestProperties) - self.assertEqual(plist, [('foo', foo, True), - ('bar', bar, False)]) + self.assertEqual(set(p[0] for p in plist), set(['foo', 'bar'])) def test_set_multiple_request_properties_conflict(self): from pyramid.exceptions import ConfigurationConflictError @@ -125,20 +124,19 @@ 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)]) + plist = event.request.plist + self.assertEqual(set(p[0] for p in plist), set(['foo', 'bar'])) class DummyRequest(object): - callables = None + plist = 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)) + def _set_properties(self, properties): + if self.plist is None: + self.plist = [] + self.plist.extend(properties) diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 2a69190f7c..e83ad59220 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -3,12 +3,12 @@ class Test_InstancePropertyMixin(unittest.TestCase): def _makeOne(self): - cls = self._targetClass() + cls = self._getTargetClass() class Foo(cls): pass return Foo() - def _targetClass(self): + def _getTargetClass(self): from pyramid.util import InstancePropertyMixin return InstancePropertyMixin From 15c40a14c41d90dada89a20085226d74406dfa92 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 11 Apr 2012 01:50:26 -0400 Subject: [PATCH 5/5] readme --- CHANGES.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 8f82866280..de4714bf22 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -26,3 +26,6 @@ Features ``@view_config(request_method=('GET', 'HEAD', 'POST')``. This is because HEAD is a variant of GET that omits the body, and WebOb has special support to return an empty body when a HEAD is used. + +- ``config.set_request_properties`` now causes less code to be executed at + request construction time.