From 14c0dc7b4a6ae0723b692d67b912ecd2c3847bb8 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 8 Aug 2008 15:37:41 +0000 Subject: [PATCH] - cleaned up the attributes scan for reconstitute hooks - added more careful check for "_should_exclude", guard against possible heisenbug activity --- lib/sqlalchemy/orm/attributes.py | 13 +++---------- lib/sqlalchemy/orm/mapper.py | 7 +++++-- lib/sqlalchemy/util.py | 14 ++++++++++++++ test/orm/utils.py | 2 +- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index dd470f358c..5076775bce 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -1056,16 +1056,9 @@ class ClassManager(dict): self._instantiable = False self.events = self.event_registry_factory() - # TODO: generalize (and document the rationalization for) this traversal. - # TODO: figure out why getattr(cls, key) for all attributes - # causes test failures - for cls in class_.__mro__[0:-1]: - for key, meth in cls.__dict__.iteritems(): - if isinstance(meth, types.FunctionType) and \ - hasattr(meth, '__sa_reconstitute__') and \ - hasattr(getattr(class_, key), '__sa_reconstitute__'): - self.events.add_listener('on_load', getattr(class_, key)) - break + for key, meth in util.iterate_attributes(class_): + if isinstance(meth, types.FunctionType) and hasattr(meth, '__sa_reconstitute__'): + self.events.add_listener('on_load', meth) def instantiable(self, boolean): # experiment, probably won't stay in this form diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 06f4f3dadf..52acdcb339 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -644,15 +644,18 @@ class Mapper(object): """ + def is_userland_descriptor(obj): + return not isinstance(obj, attributes.InstrumentedAttribute) and hasattr(obj, '__get__') + # check for descriptors, either local or from # an inherited class if local: if self.class_.__dict__.get(name, None)\ - and hasattr(self.class_.__dict__[name], '__get__'): + and is_userland_descriptor(self.class_.__dict__[name]): return True else: if getattr(self.class_, name, None)\ - and hasattr(getattr(self.class_, name), '__get__'): + and is_userland_descriptor(getattr(self.class_, name)): return True if (self.include_properties is not None and diff --git a/lib/sqlalchemy/util.py b/lib/sqlalchemy/util.py index a9e7d22380..76c73ca6ae 100644 --- a/lib/sqlalchemy/util.py +++ b/lib/sqlalchemy/util.py @@ -410,6 +410,20 @@ def class_hierarchy(cls): hier.add(s) return list(hier) +def iterate_attributes(cls): + """iterate all the keys and attributes associated with a class, without using getattr(). + + Does not use getattr() so that class-sensitive descriptors (i.e. property.__get__()) + are not called. + + """ + keys = dir(cls) + for key in keys: + for c in cls.__mro__: + if key in c.__dict__: + yield (key, c.__dict__[key]) + break + # from paste.deploy.converters def asbool(obj): if isinstance(obj, (str, unicode)): diff --git a/test/orm/utils.py b/test/orm/utils.py index 4bb2464b3e..1f2cbe13a7 100644 --- a/test/orm/utils.py +++ b/test/orm/utils.py @@ -201,7 +201,7 @@ class AliasedClassTest(TestBase): assert_table(Point.left_of(p2), table) assert_table(alias.left_of(p2), alias_table) - + if __name__ == '__main__': testenv.main() -- 2.47.3