Skip to content

Commit

Permalink
Merge branch 'mmerickel.feature-instance-properties'
Browse files Browse the repository at this point in the history
  • Loading branch information
mcdonc committed Apr 11, 2012
2 parents 66c1722 + 15c40a1 commit 4d35316
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 49 deletions.
3 changes: 3 additions & 0 deletions CHANGES.txt
Expand Up @@ -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.
11 changes: 5 additions & 6 deletions pyramid/config/factories.py
Expand Up @@ -10,6 +10,7 @@
)

from pyramid.traversal import DefaultRootFactory
from pyramid.util import InstancePropertyMixin

class FactoriesConfiguratorMixin(object):
@action_method
Expand Down Expand Up @@ -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)
Expand All @@ -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),
Expand All @@ -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)
24 changes: 11 additions & 13 deletions pyramid/tests/test_config/test_factories.py
Expand Up @@ -73,23 +73,23 @@ 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
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)])
self.assertEqual(set(p[0] for p in plist), set(['foo']))

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)])
self.assertEqual(set(p[0] for p in plist), set(['foo']))

def test_set_multiple_request_properties(self):
from pyramid.interfaces import IRequestProperties
Expand All @@ -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
Expand All @@ -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)

27 changes: 25 additions & 2 deletions pyramid/tests/test_util.py
Expand Up @@ -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

Expand Down Expand Up @@ -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
cls = self._getTargetClass()
name, fn = cls._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
Expand Down
87 changes: 59 additions & 28 deletions pyramid/util.py
Expand Up @@ -20,6 +20,60 @@ class InstancePropertyMixin(object):
on the class itself.
"""

@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.
"""

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.
Expand All @@ -31,12 +85,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``
Expand Down Expand Up @@ -73,30 +126,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.
Expand Down

0 comments on commit 4d35316

Please sign in to comment.