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

Reply via email to