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:
Gavin Wahl 2014-08-22 10:31:26 -06:00 committed by Tim Graham
parent c5fb34c47e
commit b4e76f30d1
2 changed files with 27 additions and 23 deletions

View File

@ -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):

View File

@ -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):