Fixed #23346 -- Fixed lazy() to lookup methods on the real object, not resultclasses.
Co-Authored-By: Rocky Meza <rmeza@fusionbox.com>
This commit is contained in:
parent
c5fb34c47e
commit
b4e76f30d1
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue