From b4e76f30d12bfa8a53cc297c60055c6f4629cc4c Mon Sep 17 00:00:00 2001 From: Gavin Wahl Date: Fri, 22 Aug 2014 10:31:26 -0600 Subject: [PATCH] Fixed #23346 -- Fixed lazy() to lookup methods on the real object, not resultclasses. Co-Authored-By: Rocky Meza --- django/utils/functional.py | 36 ++++++++++------------------ tests/utils_tests/test_functional.py | 14 +++++++++++ 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/django/utils/functional.py b/django/utils/functional.py index ceb8b05055..aa0d6f850a 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -85,13 +85,14 @@ def lazy(func, *resultclasses): called on the result of that function. The function is not evaluated until one of the methods on the result is called. """ - __dispatch = None + __prepared = False def __init__(self, args, kw): self.__args = args self.__kw = kw - if self.__dispatch is None: + if not self.__prepared: self.__prepare_class__() + self.__prepared = True def __reduce__(self): return ( @@ -101,18 +102,15 @@ def lazy(func, *resultclasses): @classmethod def __prepare_class__(cls): - cls.__dispatch = {} for resultclass in resultclasses: - cls.__dispatch[resultclass] = {} - for type_ in reversed(resultclass.mro()): - for (k, v) in type_.__dict__.items(): - # All __promise__ return the same wrapper method, but - # they also do setup, inserting the method into the - # dispatch dict. - meth = cls.__promise__(resultclass, k, v) - if hasattr(cls, k): + for type_ in resultclass.mro(): + for method_name in type_.__dict__.keys(): + # All __promise__ return the same wrapper method, they + # look up the correct implementation when called. + if hasattr(cls, method_name): continue - setattr(cls, k, meth) + meth = cls.__promise__(method_name) + setattr(cls, method_name, meth) cls._delegate_bytes = bytes in resultclasses cls._delegate_text = six.text_type in resultclasses assert not (cls._delegate_bytes and cls._delegate_text), ( @@ -129,21 +127,13 @@ def lazy(func, *resultclasses): cls.__str__ = cls.__bytes_cast @classmethod - def __promise__(cls, klass, funcname, method): - # Builds a wrapper around some magic method and registers that - # magic method for the given type and method name. + def __promise__(cls, method_name): + # Builds a wrapper around some magic method def __wrapper__(self, *args, **kw): # Automatically triggers the evaluation of a lazy value and # applies the given magic method of the result type. res = func(*self.__args, **self.__kw) - for t in type(res).mro(): - if t in self.__dispatch: - return self.__dispatch[t][funcname](res, *args, **kw) - raise TypeError("Lazy object returned unexpected type.") - - if klass not in cls.__dispatch: - cls.__dispatch[klass] = {} - cls.__dispatch[klass][funcname] = method + return getattr(res, method_name)(*args, **kw) return __wrapper__ def __text_cast(self): diff --git a/tests/utils_tests/test_functional.py b/tests/utils_tests/test_functional.py index 7d71f32415..e8956a7a29 100644 --- a/tests/utils_tests/test_functional.py +++ b/tests/utils_tests/test_functional.py @@ -22,6 +22,20 @@ class FunctionalTestCase(unittest.TestCase): t = lazy(lambda: Klazz(), Klazz)() self.assertIn('base_method', dir(t)) + def test_lazy_base_class_override(self): + """Test that lazy finds the correct (overridden) method implementation""" + + class Base(object): + def method(self): + return 'Base' + + class Klazz(Base): + def method(self): + return 'Klazz' + + t = lazy(lambda: Klazz(), Base)() + self.assertEqual(t.method(), 'Klazz') + def test_lazy_property(self): class A(object):