From 005603e2fb198c3f3328fa555e82a334f0f89d52 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 1 Sep 2006 16:25:20 +0000 Subject: [PATCH] insure that "parent" pointers are set up on objects that were lazily loaded --- CHANGES | 1 + lib/sqlalchemy/attributes.py | 10 ++++++++-- test/base/attributes.py | 30 ++++++++++++++++++++++++++++-- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index d0cd189409..c509caeb6d 100644 --- a/CHANGES +++ b/CHANGES @@ -167,6 +167,7 @@ relationships to an inheriting mapper (which is also self-referential) - added 'checkfirst' argument to table.create()/table.drop(), as well as table.exists() [ticket:234] - some other ongoing fixes to inheritance [ticket:245] +- attribute/backref/orphan/history-tracking tweaks as usual... 0.2.5 - fixed endless loop bug in select_by(), if the traversal hit diff --git a/lib/sqlalchemy/attributes.py b/lib/sqlalchemy/attributes.py index c709afe3dc..3aada1951a 100644 --- a/lib/sqlalchemy/attributes.py +++ b/lib/sqlalchemy/attributes.py @@ -134,7 +134,10 @@ class InstrumentedAttribute(object): if callable_ is not None: if passive: return InstrumentedAttribute.PASSIVE_NORESULT - l = InstrumentedList(self, obj, self._adapt_list(callable_()), init=False) + values = callable_() + l = InstrumentedList(self, obj, self._adapt_list(values), init=False) + if self.trackparent and values is not None: + [self.sethasparent(v, True) for v in values if v is not None] # if a callable was executed, then its part of the "committed state" # if any, so commit the newly loaded data orig = state.get('original', None) @@ -152,7 +155,10 @@ class InstrumentedAttribute(object): if callable_ is not None: if passive: return InstrumentedAttribute.PASSIVE_NORESULT - obj.__dict__[self.key] = callable_() + value = callable_() + obj.__dict__[self.key] = value + if self.trackparent and value is not None: + self.sethasparent(value, True) # if a callable was executed, then its part of the "committed state" # if any, so commit the newly loaded data orig = state.get('original', None) diff --git a/test/base/attributes.py b/test/base/attributes.py index d3ca349316..f281e487b5 100644 --- a/test/base/attributes.py +++ b/test/base/attributes.py @@ -131,8 +131,8 @@ class AttributesTest(PersistTest): class Post(object):pass class Blog(object):pass - manager.register_attribute(Post, 'blog', uselist=False, extension=attributes.GenericBackrefExtension('posts')) - manager.register_attribute(Blog, 'posts', uselist=True, extension=attributes.GenericBackrefExtension('blog')) + manager.register_attribute(Post, 'blog', uselist=False, extension=attributes.GenericBackrefExtension('posts'), trackparent=True) + manager.register_attribute(Blog, 'posts', uselist=True, extension=attributes.GenericBackrefExtension('blog'), trackparent=True) b = Blog() (p1, p2, p3) = (Post(), Post(), Post()) b.posts.append(p1) @@ -165,6 +165,32 @@ class AttributesTest(PersistTest): j.port = None self.assert_(p.jack is None) + def testlazytrackparent(self): + """test that the "hasparent" flag works properly when lazy loaders and backrefs are used""" + manager = attributes.AttributeManager() + + class Post(object):pass + class Blog(object):pass + + # set up instrumented attributes with backrefs + manager.register_attribute(Post, 'blog', uselist=False, extension=attributes.GenericBackrefExtension('posts'), trackparent=True) + manager.register_attribute(Blog, 'posts', uselist=True, extension=attributes.GenericBackrefExtension('blog'), trackparent=True) + + # create objects as if they'd been freshly loaded from the database (without history) + b = Blog() + p1 = Post() + Blog.posts.set_callable(b, lambda:[p1]) + Post.blog.set_callable(p1, lambda:b) + + # assert connections + assert p1.blog is b + assert p1 in b.posts + + # no orphans + assert getattr(Blog, 'posts').hasparent(p1) + assert getattr(Post, 'blog').hasparent(b) + + def testinheritance(self): """tests that attributes are polymorphic""" class Foo(object):pass -- 2.47.2