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
|
called on the result of that function. The function is not evaluated
|
||||||
until one of the methods on the result is called.
|
until one of the methods on the result is called.
|
||||||
"""
|
"""
|
||||||
__dispatch = None
|
__prepared = False
|
||||||
|
|
||||||
def __init__(self, args, kw):
|
def __init__(self, args, kw):
|
||||||
self.__args = args
|
self.__args = args
|
||||||
self.__kw = kw
|
self.__kw = kw
|
||||||
if self.__dispatch is None:
|
if not self.__prepared:
|
||||||
self.__prepare_class__()
|
self.__prepare_class__()
|
||||||
|
self.__prepared = True
|
||||||
|
|
||||||
def __reduce__(self):
|
def __reduce__(self):
|
||||||
return (
|
return (
|
||||||
|
@ -101,18 +102,15 @@ def lazy(func, *resultclasses):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __prepare_class__(cls):
|
def __prepare_class__(cls):
|
||||||
cls.__dispatch = {}
|
|
||||||
for resultclass in resultclasses:
|
for resultclass in resultclasses:
|
||||||
cls.__dispatch[resultclass] = {}
|
for type_ in resultclass.mro():
|
||||||
for type_ in reversed(resultclass.mro()):
|
for method_name in type_.__dict__.keys():
|
||||||
for (k, v) in type_.__dict__.items():
|
# All __promise__ return the same wrapper method, they
|
||||||
# All __promise__ return the same wrapper method, but
|
# look up the correct implementation when called.
|
||||||
# they also do setup, inserting the method into the
|
if hasattr(cls, method_name):
|
||||||
# dispatch dict.
|
|
||||||
meth = cls.__promise__(resultclass, k, v)
|
|
||||||
if hasattr(cls, k):
|
|
||||||
continue
|
continue
|
||||||
setattr(cls, k, meth)
|
meth = cls.__promise__(method_name)
|
||||||
|
setattr(cls, method_name, meth)
|
||||||
cls._delegate_bytes = bytes in resultclasses
|
cls._delegate_bytes = bytes in resultclasses
|
||||||
cls._delegate_text = six.text_type in resultclasses
|
cls._delegate_text = six.text_type in resultclasses
|
||||||
assert not (cls._delegate_bytes and cls._delegate_text), (
|
assert not (cls._delegate_bytes and cls._delegate_text), (
|
||||||
|
@ -129,21 +127,13 @@ def lazy(func, *resultclasses):
|
||||||
cls.__str__ = cls.__bytes_cast
|
cls.__str__ = cls.__bytes_cast
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __promise__(cls, klass, funcname, method):
|
def __promise__(cls, method_name):
|
||||||
# Builds a wrapper around some magic method and registers that
|
# Builds a wrapper around some magic method
|
||||||
# magic method for the given type and method name.
|
|
||||||
def __wrapper__(self, *args, **kw):
|
def __wrapper__(self, *args, **kw):
|
||||||
# Automatically triggers the evaluation of a lazy value and
|
# Automatically triggers the evaluation of a lazy value and
|
||||||
# applies the given magic method of the result type.
|
# applies the given magic method of the result type.
|
||||||
res = func(*self.__args, **self.__kw)
|
res = func(*self.__args, **self.__kw)
|
||||||
for t in type(res).mro():
|
return getattr(res, method_name)(*args, **kw)
|
||||||
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 __wrapper__
|
return __wrapper__
|
||||||
|
|
||||||
def __text_cast(self):
|
def __text_cast(self):
|
||||||
|
|
|
@ -22,6 +22,20 @@ class FunctionalTestCase(unittest.TestCase):
|
||||||
t = lazy(lambda: Klazz(), Klazz)()
|
t = lazy(lambda: Klazz(), Klazz)()
|
||||||
self.assertIn('base_method', dir(t))
|
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):
|
def test_lazy_property(self):
|
||||||
|
|
||||||
class A(object):
|
class A(object):
|
||||||
|
|
Loading…
Reference in New Issue