diff options
| author | Bert JW Regeer <xistence@0x58.com> | 2020-01-05 20:02:26 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-01-05 20:02:26 -0800 |
| commit | 5702c3c3a4357a6071c9ba624a89655209548336 (patch) | |
| tree | bc8d03a5c7927ea3ae2a868fd5af8e1a2cff7c6b | |
| parent | 148cf5138638ce6b1b92b4e13fe1444df9451e34 (diff) | |
| parent | 5cad7ad7ce47f1fe151b40ae9398fb5cbbfd3806 (diff) | |
| download | pyramid-5702c3c3a4357a6071c9ba624a89655209548336.tar.gz pyramid-5702c3c3a4357a6071c9ba624a89655209548336.tar.bz2 pyramid-5702c3c3a4357a6071c9ba624a89655209548336.zip | |
Merge pull request #3559 from mmerickel/settable-properties
allow overriding synthesized properties
| -rw-r--r-- | CHANGES.rst | 7 | ||||
| -rw-r--r-- | src/pyramid/util.py | 57 | ||||
| -rw-r--r-- | tests/test_util.py | 30 |
3 files changed, 63 insertions, 31 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 383906e00..70ee43b96 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -71,6 +71,13 @@ Features - Fix ``DeprecationWarning`` emitted by using the ``imp`` module. See https://github.com/Pylons/pyramid/pull/3553 +- Properties created via ``config.add_request_method(..., property=True)`` or + ``request.set_property`` used to be readonly. They can now be overridden + via ``request.foo = ...`` and until the value is deleted it will return + the overridden value. This is most useful when mocking request properties + in testing. + See https://github.com/Pylons/pyramid/pull/3559 + Deprecations ------------ diff --git a/src/pyramid/util.py b/src/pyramid/util.py index e552b37de..504516631 100644 --- a/src/pyramid/util.py +++ b/src/pyramid/util.py @@ -73,6 +73,26 @@ def as_sorted_tuple(val): return val +class SettableProperty(object): + def __init__(self, wrapped): + self.wrapped = wrapped + functools.update_wrapper(self, wrapped) + + def __get__(self, obj, type=None): + if obj is None: # pragma: no cover + return self + value = obj.__dict__.get(self.wrapped.__name__, _marker) + if value is _marker: + value = self.wrapped(obj) + return value + + def __set__(self, obj, value): + obj.__dict__[self.wrapped.__name__] = value + + def __delete__(self, obj): + del obj.__dict__[self.wrapped.__name__] + + class InstancePropertyHelper(object): """A helper object for assigning properties and descriptors to instances. It is not normally possible to do this because descriptors must be @@ -94,26 +114,29 @@ class InstancePropertyHelper(object): (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__ = get_callable_name(name) - fn.__doc__ = callable.__doc__ - else: + if name is None: + if not hasattr(callable, '__name__'): + raise ValueError( + 'missing __name__, must specify "name" for property' + ) name = callable.__name__ + name = get_callable_name(name) + is_data_descriptor = hasattr(callable, '__set__') + if reify and is_data_descriptor: + raise ValueError('cannot reify a data descriptor') + if is_data_descriptor: fn = callable - if reify: - import pyramid.decorator # avoid circular import + else: + wrapped = lambda this: callable(this) + wrapped.__name__ = name + wrapped.__doc__ = callable.__doc__ + + if reify: + import pyramid.decorator # avoid circular import - fn = pyramid.decorator.reify(fn) - elif not is_property: - fn = property(fn) + fn = pyramid.decorator.reify(wrapped) + else: + fn = SettableProperty(wrapped) return name, fn diff --git a/tests/test_util.py b/tests/test_util.py index 293036c10..1553d8e60 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -103,25 +103,26 @@ class Test_InstancePropertyHelper(unittest.TestCase): ) def test_override_property(self): - def worker(obj): # pragma: no cover + def worker(obj): pass foo = Dummy() helper = self._getTargetClass() helper.set_property(foo, worker, name='x') - - def doit(): - foo.x = 1 - - self.assertRaises(AttributeError, doit) + self.assertIsNone(foo.x) + foo.x = 1 + self.assertEqual(foo.x, 1) + del foo.x + self.assertIsNone(foo.x) def test_override_reify(self): - def worker(obj): # pragma: no cover + def worker(obj): pass foo = Dummy() helper = self._getTargetClass() helper.set_property(foo, worker, name='x', reify=True) + self.assertIsNone(foo.x) foo.x = 1 self.assertEqual(1, foo.x) foo.x = 2 @@ -301,23 +302,24 @@ class Test_InstancePropertyMixin(unittest.TestCase): ) def test_override_property(self): - def worker(obj): # pragma: no cover + def worker(obj): pass foo = self._makeOne() foo.set_property(worker, name='x') - - def doit(): - foo.x = 1 - - self.assertRaises(AttributeError, doit) + self.assertIsNone(foo.x) + foo.x = 1 + self.assertEqual(foo.x, 1) + del foo.x + self.assertIsNone(foo.x) def test_override_reify(self): - def worker(obj): # pragma: no cover + def worker(obj): pass foo = self._makeOne() foo.set_property(worker, name='x', reify=True) + self.assertIsNone(foo.x) foo.x = 1 self.assertEqual(1, foo.x) foo.x = 2 |
