Fixed #28358 -- Prevented LazyObject from mimicking nonexistent attributes.

Thanks Sergey Fedoseev for the initial patch.
This commit is contained in:
Theo Alexiou 2022-02-13 17:27:12 +01:00 committed by Mariusz Felisiak
parent 1d071ec1aa
commit 97d7990abd
3 changed files with 34 additions and 2 deletions

View File

@ -157,8 +157,9 @@ class LazySettings(LazyObject):
def USE_L10N(self): def USE_L10N(self):
stack = traceback.extract_stack() stack = traceback.extract_stack()
# Show a warning if the setting is used outside of Django. # Show a warning if the setting is used outside of Django.
# Stack index: -1 this line, -2 the caller. # Stack index: -1 this line, -2 the LazyObject __getattribute__(),
filename, _, _, _ = stack[-2] # -3 the caller.
filename, _, _, _ = stack[-3]
if not filename.startswith(os.path.dirname(django.__file__)): if not filename.startswith(os.path.dirname(django.__file__)):
warnings.warn( warnings.warn(
USE_L10N_DEPRECATED_MSG, USE_L10N_DEPRECATED_MSG,

View File

@ -266,6 +266,7 @@ def new_method_proxy(func):
self._setup() self._setup()
return func(self._wrapped, *args) return func(self._wrapped, *args)
inner._mask_wrapped = False
return inner return inner
@ -286,6 +287,14 @@ class LazyObject:
# override __copy__() and __deepcopy__() as well. # override __copy__() and __deepcopy__() as well.
self._wrapped = empty self._wrapped = empty
def __getattribute__(self, name):
value = super().__getattribute__(name)
# If attribute is a proxy method, raise an AttributeError to call
# __getattr__() and use the wrapped object method.
if not getattr(value, "_mask_wrapped", True):
raise AttributeError
return value
__getattr__ = new_method_proxy(getattr) __getattr__ = new_method_proxy(getattr)
def __setattr__(self, name, value): def __setattr__(self, name, value):

View File

@ -32,6 +32,28 @@ class LazyObjectTestCase(TestCase):
return AdHocLazyObject() return AdHocLazyObject()
def test_getattribute(self):
"""
Proxy methods don't exist on wrapped objects unless they're set.
"""
attrs = [
"__getitem__",
"__setitem__",
"__delitem__",
"__iter__",
"__len__",
"__contains__",
]
foo = Foo()
obj = self.lazy_wrap(foo)
for attr in attrs:
with self.subTest(attr):
self.assertFalse(hasattr(obj, attr))
setattr(foo, attr, attr)
obj_with_attr = self.lazy_wrap(foo)
self.assertTrue(hasattr(obj_with_attr, attr))
self.assertEqual(getattr(obj_with_attr, attr), attr)
def test_getattr(self): def test_getattr(self):
obj = self.lazy_wrap(Foo()) obj = self.lazy_wrap(Foo())
self.assertEqual(obj.foo, "bar") self.assertEqual(obj.foo, "bar")