Re: [sqlalchemy] changes flushed for expunged relationships

2012-10-18 Thread Michael Bayer

On Oct 17, 2012, at 9:55 PM, Kent wrote:

 The attached script fails with sqlalchemy.exc.InvalidRequestError: Instance 
 'Bug at 0x1e6f3d10' has been deleted.  Use the make_transient() function to 
 send this object back to the transient state.
 
 While this example is somewhat convoluted, I have a few questions about 
 sqlalchemy behavior here:
 
 1) At the session.flush(), even though the Rock and the bugs relationship 
 have been expunged, the pending delete still is issued to the database.  
 Would you expect/intend sqlalchemy to delete even after the expunge()?

no, because the Rock you have merged has established that the Bug is no longer 
associated with it.   You expunge the Rock, there's no Bug attached to it to be 
expunged.

 
 2) After the flush(), shouldn't the history of the 'bugs' relationship have 
 been updated to reflect the statement issued to the database?  (See print 
 statement)

yes, because merged is not in that Session anymore.The flush() is what 
resets the history.  Clearly it's not going to go out to find objects that 
aren't in that Session.

 
 3) The InvalidRequestError is only raised if the 'bugs' relationship has a 
 backref, otherwise it isn't raised.  Any idea why?

removing the Bug.rock path means that when you expunge merged, there is 
nothing left in the flush process to handle the orphan cascade you're looking 
for here.   The DELETE does not occur so the Bug that's present on merged can 
go right back in.

 
 4) Don't hate me for asking: is there a work around?  I'm trying to 
 understand this scenario since in a rare case, it presents.

The workaround is don't use expunge().This is not a method I ever use for 
anything, actually. And especially, if you manipulate the state within the 
Session, then expunge() random segments of that state, you can hardly expect 
flush() to have a clear idea of what you intend.

-- 
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.



Re: [sqlalchemy] changes flushed for expunged relationships

2012-10-18 Thread Kent

Thank you for the clarifications.

On 10/18/2012 11:43 AM, Michael Bayer wrote:

On Oct 17, 2012, at 9:55 PM, Kent wrote:


The attached script fails with sqlalchemy.exc.InvalidRequestError: Instance 'Bug at 
0x1e6f3d10' has been deleted.  Use the make_transient() function to send this object back 
to the transient state.

While this example is somewhat convoluted, I have a few questions about 
sqlalchemy behavior here:

1) At the session.flush(), even though the Rock and the bugs relationship have 
been expunged, the pending delete still is issued to the database.  Would you 
expect/intend sqlalchemy to delete even after the expunge()?

no, because the Rock you have merged has established that the Bug is no longer 
associated with it.   You expunge the Rock, there's no Bug attached to it to be 
expunged.


2) After the flush(), shouldn't the history of the 'bugs' relationship have 
been updated to reflect the statement issued to the database?  (See print 
statement)

yes, because merged is not in that Session anymore.The flush() is what 
resets the history.  Clearly it's not going to go out to find objects that 
aren't in that Session.


3) The InvalidRequestError is only raised if the 'bugs' relationship has a 
backref, otherwise it isn't raised.  Any idea why?

removing the Bug.rock path means that when you expunge merged, there is 
nothing left in the flush process to handle the orphan cascade you're looking for here.   
The DELETE does not occur so the Bug that's present on merged can go right back in.


4) Don't hate me for asking: is there a work around?  I'm trying to understand 
this scenario since in a rare case, it presents.

The workaround is don't use expunge().This is not a method I ever use for 
anything, actually. And especially, if you manipulate the state within the 
Session, then expunge() random segments of that state, you can hardly expect 
flush() to have a clear idea of what you intend.



--
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.



Re: [sqlalchemy] changes flushed for expunged relationships

2012-10-18 Thread Kent
On Thursday, October 18, 2012 11:43:50 AM UTC-4, Michael Bayer wrote:


 On Oct 17, 2012, at 9:55 PM, Kent wrote: 

  The attached script fails with sqlalchemy.exc.InvalidRequestError: 
 Instance 'Bug at 0x1e6f3d10' has been deleted.  Use the make_transient() 
 function to send this object back to the transient state. 
  
  While this example is somewhat convoluted, I have a few questions about 
 sqlalchemy behavior here: 
  
  1) At the session.flush(), even though the Rock and the bugs 
 relationship have been expunged, the pending delete still is issued to the 
 database.  Would you expect/intend sqlalchemy to delete even after the 
 expunge()? 

 no, because the Rock you have merged has established that the Bug is no 
 longer associated with it.   You expunge the Rock, there's no Bug attached 
 to it to be expunged. 


I am still slightly unclear on this: since sqlalchemy *does *delete the 
Bug, I assume it is marked for deletion in some way when the 'bugs' 
relationship is merged to the empty list [].  Is that accurate?  Please 
help me understand why it does not show up in session.deleted, which is 
IdentitySet([])?

-- 
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To view this discussion on the web visit 
https://groups.google.com/d/msg/sqlalchemy/-/pnnq3lOodtYJ.
To post to this group, send email to sqlalchemy@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.



Re: [sqlalchemy] changes flushed for expunged relationships

2012-10-18 Thread Michael Bayer

On Oct 18, 2012, at 2:46 PM, Kent wrote:

 On Thursday, October 18, 2012 11:43:50 AM UTC-4, Michael Bayer wrote:
 
 On Oct 17, 2012, at 9:55 PM, Kent wrote: 
 
  The attached script fails with sqlalchemy.exc.InvalidRequestError: 
  Instance 'Bug at 0x1e6f3d10' has been deleted.  Use the make_transient() 
  function to send this object back to the transient state. 
  
  While this example is somewhat convoluted, I have a few questions about 
  sqlalchemy behavior here: 
  
  1) At the session.flush(), even though the Rock and the bugs relationship 
  have been expunged, the pending delete still is issued to the database.  
  Would you expect/intend sqlalchemy to delete even after the expunge()? 
 
 no, because the Rock you have merged has established that the Bug is no 
 longer associated with it.   You expunge the Rock, there's no Bug attached to 
 it to be expunged. 
 
 
 I am still slightly unclear on this: since sqlalchemy does delete the Bug, I 
 assume it is marked for deletion in some way when the 'bugs' relationship 
 is merged to the empty list [].  Is that accurate?  Please help me understand 
 why it does not show up in session.deleted, which is IdentitySet([])?

This particular mark for deletion occurs on line 1817 of session.py, within 
flush(), as it iterates through the current list of dirty objects and tests 
the object to see if it's an orphan.  For Bug, this holds true and it's 
marked for deletion.

The mark for deletion due to orphan step always occurs within the flush 
process.   It is ultimately based on a test for the parents of the target 
object, which in the course of operation can be set and unset multiple times, 
so it is easier to track just this one flag and then convert to a delete 
within the flush, rather than to delete/undelete continuously, especially 
considering that the delete operation itself needs to continue cascading.

So to correct what I said about backrefs earlier, what is specifically 
happening here is, the backref is what enables Bug to be marked as dirty in 
the first place, that it's Bug.rock attribute has been modified.  flush() then 
sees its an orphan and marks it as such.   Without the backref, bug is in the 
Session, but is not marked as modified.   Rock is gone so nothing else happens, 
the Session is clean.

If Rock is not expunged, and the backref is not present on Bug, *then* the 
mark for deletion occurs in a different place, on line 473 of dependency.py 
where the presort_saves method of OneToManyDP (which is managing Rock.bugs) 
iterates through those records marked as removed from Rock.bugs, tests them 
for orphan, marks them as deleted.  So this is a later path for mark for 
deletion, but is still based on the orphan flag status of the object.

-- 
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.



[sqlalchemy] changes flushed for expunged relationships

2012-10-17 Thread Kent
The attached script fails with sqlalchemy.exc.InvalidRequestError: 
Instance 'Bug at 0x1e6f3d10' has been deleted.  Use the make_transient() 
function to send this object back to the transient state.

While this example is somewhat convoluted, I have a few questions about 
sqlalchemy behavior here:

1) At the session.flush(), even though the Rock and the bugs relationship 
have been expunged, the pending delete still is issued to the database.  
Would you expect/intend sqlalchemy to delete even after the expunge()?

2) After the flush(), shouldn't the history of the 'bugs' relationship have 
been updated to reflect the statement issued to the database?  (See print 
statement)

3) The InvalidRequestError is only raised if the 'bugs' relationship has a 
backref, otherwise it isn't raised.  Any idea why?

4) Don't hate me for asking: is there a work around?  I'm trying to 
understand this scenario since in a rare case, it presents.

Thanks very much!
Kent

-- 
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To view this discussion on the web visit 
https://groups.google.com/d/msg/sqlalchemy/-/6oYSFMbpnEsJ.
To post to this group, send email to sqlalchemy@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.orm import attributes

engine = create_engine('sqlite:///', echo=True)
metadata = MetaData(engine)
Session = sessionmaker(bind=engine)

rocks_table = Table(rocks, metadata,
Column(id, Integer, primary_key=True),
)

bugs_table = Table(bugs, metadata,
Column(id, Integer, primary_key=True),
Column(rockid, Integer, ForeignKey('rocks.id')),
)

class Object(object):
def __init__(self, **attrs):
self.__dict__.update(attrs)

class Rock(Object):
def __repr__(self):
return 'Rock: id=[%s]' % self.__dict__.get('id')

class Bug(Object):
def __repr__(self):
return 'Bug: id=[%s]' % self.__dict__.get('id')

mapper(Rock, rocks_table,
properties={'bugs': relationship(Bug,
cascade='all,delete-orphan',
backref=backref('rock',cascade='refresh-expire,expunge'))
})

mapper(Bug, bugs_table)

metadata.create_all()
try:
session = Session()
r = Rock(id=1)
r.bugs=[Bug(id=1)]
session.add(r)
session.commit()

session = Session()
r = Rock(id=1)
r.bugs=[]
merged = session.merge(r)
session.expunge(merged)
# if merged is now detached, should flush() still delete Bug?
session.flush()
# should history still have deleted Bug?
print \n\nadd: %r\nunchanged: %r\ndelete: %r\n % attributes.get_history(merged, 'bugs')

# this only fails if the backref 'rock' is present in relationship
session.add(merged)

finally:
metadata.drop_all()