dan -
heres a more rudimental program that uses only the attributes
package, and three classes in an A->B->C relationship the way yours
are. See if you can get your head around this and try to illustrate
the behavior youre seeing.
i think you understand this already; the way hasparent works is:
if a class "A" has a property "bs", managed by InstrumentedAttribute
"Bs" that points to instances of class "B", then
"B" is an orphan if its _state does not contain a record
"('hasparent',id(Bs))".
So im not sure what is going wrong in your app, symptomatically it
feels like your instances are getting connected together improperly.
but im guessing that youre not doing anything like that. one way we
might test is to add some logic to InstrumentedAttribute to check
that the class of the instances being attached is correct (i.e. make
all the collections type-checked). it is most likely the backrefs
are getting tripped up in some way (i.e. if we removed the backrefs
the problem might go away).
anyway, see if you can get your pdb sessions to illustrate the same
symptom with this scaled down test program, so that we have something
very tangible and narrowly scoped to work with and theres a greater
chance that i can reproduce the issue. (or im hoping just that the
test program gives you the insight you need to narrow down what
causes your error. )
import sqlalchemy.attributes as attributes
# relationship:
#
# Blog -- (contains many) --> Post -- (contains many) --> Comment
#
manager = attributes.AttributeManager()
class Post(object):pass
class Blog(object):pass
class Comment(object):pass
# orphan rules
delete_orphans = {
Post : [('posts', Blog)], # a Post must be in a 'posts' collection on Blog, or its an orphan
Blog : [],
Comment : [('comments', Post)] # a Comment must be in a 'comments' collection on a Post, or its an orphan
}
# check the "hasparent" property of all of an object's orphan rules to determine orphan status
def _is_orphan(obj):
d = delete_orphans[obj.__class__]
for (key,klass) in d:
if not getattr(klass, key).hasparent(obj):
return True
else:
return False
# 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)
manager.register_attribute(Post, 'comments', uselist=True, extension=attributes.GenericBackrefExtension('post'), trackparent=True)
manager.register_attribute(Comment, 'post', uselist=False, extension=attributes.GenericBackrefExtension('comments'), trackparent=True)
# print out the integer ids of all the InstrumentedAttribute objects
print "Blog.posts", id(Blog.posts)
print "Post.blog", id(Post.blog)
print "Post.comments", id(Post.comments)
print "Comment.post", id(Comment.post)
# create objects and connect
b = Blog()
p1 = Post()
c1 = Comment()
b.posts.append(p1)
p1.comments.append(c1)
# assert connections
assert p1.blog is b
assert p1 in b.posts
assert c1.post is p1
assert c1 in p1.comments
print "Blog:", b._state
print "Post", p1._state
print "Comment", c1._state
# no orphans
assert not _is_orphan(c1)
assert not _is_orphan(p1)
assert not _is_orphan(b)
# detach stuff
c1.post = None
assert c1 not in p1.comments
# one orphan
assert _is_orphan(c1)
assert not _is_orphan(p1)
assert not _is_orphan(b)
# detach more
b.posts.remove(p1)
assert p1.blog is None
assert p1 not in b.posts
# two orphans
assert _is_orphan(c1)
assert _is_orphan(p1)
assert not _is_orphan(b)
On Sep 1, 2006, at 12:16 AM, Daniel Miller wrote:
I'm having a very strange issue with delete-orphan where I insert
an item and its parent becomes and orphan and gets deleted on
session.flush(). I tried to make a test case that reproduces the
error, but was not successful. I also tried to trace the problem to
see where its originating. I payed particular attention to
InstrumentedAttribute.sethasparent(),
InstrumentedAttribute.hasparent() and Mapper._is_orphan().
First some background:
<class 'orderentry.model.Order'> is the parent type of
<OrderLineItem id=82>, which is the parent instance of
<OrderLineItemSize id=None>, which is the new item being inserted
Here's the pdb session where things start to get strange:
.../lib/sqlalchemy/attributes.py(42)sethasparent()
-> if item is not None:
(pdb) n
.../lib/sqlalchemy/attributes.py(43)sethasparent()
-> item._state[('hasparent', id(self))] = value
(pdb) item
<OrderLineItem id=82>
(pdb) id(self)
21105808 <----- NOTE THIS IS THE child's backref PROPERTY ID
(pdb) u
.../lib/sqlalchemy/attributes.py(186)set()
-> self.sethasparent(value, True)
(pdb) obj
<OrderLineItemSize id=None>
Explanation: <OrderLineItem id=82>._state gets a ('hasparent',
21105808): True, which refers to its sizes[0].lineItem UOWProperty
instance. I.E. the child's backref property is registered as the
parent's parent property. I believe that's a bug...
Here's the debug session where errors pop up during flush() due to
<OrderLineItem id=82> being flagged as an orphan even though it is
not an orphan at all
.../lib/sqlalchemy/orm/mapper.py(144)_is_orphan()
-> for (key,klass) in self.delete_orphans:
(pdb) n
.../lib/sqlalchemy/orm/mapper.py(145)_is_orphan()
-> if not getattr(klass, key).hasparent(obj):
(pdb)
.../lib/sqlalchemy/orm/mapper.py(146)_is_orphan()
-> return True
(pdb) obj
<OrderLineItem id=82>
(pdb) key
'lineItems'
(pdb) klass
<class 'orderentry.model.Order'>
(pdb) obj.sizes[0]
<OrderLineItemSize id=None>
(pdb) obj.sizes[0].lineItem
<OrderLineItem id=82>
(pdb) obj._state
{('hasparent', 21105808): True, ... )}
(pdb) id(type(obj.sizes[0]).lineItem)
21105808 <----- NOTE THIS IS THE child's backref PROPERTY ID
Explanation: <OrderLineItem id=82> is checked for orphan-hood using
the 'lineItems' property of Order (i.e. the correct parent
property). The line item has a 'hasparent' key in its _state, but
its the wrong one (i.e. the hasparent entry in _state points to the
child's backref property, not the parent property).
I realize this is very complicated and hard to follow. Is there
something else I can do to help find the problem?
Thanks,
~ Daniel
----------------------------------------------------------------------
---
Using Tomcat but need to do more? Need to support web services,
security?
Get stuff done quickly with pre-integrated technology to make your
job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache
Geronimo
http://sel.as-us.falkag.net/sel?
cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
Sqlalchemy-users mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/sqlalchemy-users
-------------------------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
Sqlalchemy-users mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/sqlalchemy-users