On 9/22/2010 10:27 AM, Michael Bayer wrote:

Michael, thanks so much for taking the time to compose a very thorough answer. If you could indulge a few clarifications/suggestions ...

So here, the value of None for car.auction, merges into the session which 
becomes a pending change.   The flush overwrites car.auct_id with None because 
car.auction has been set to None.

The merge() process takes everything that is present on the incoming object and 
assigns it to the object that's in the session.  So here when merge sets 
old.auction = None, this is the effect.

So you want to merge an object where every attribute is either exactly the value that you want it to be, or 
it is not loaded or assigned to in any way (i.e. not present in __dict__).   If you pop "auction" 
from __dict__ before the merge, or just don't assign to "auction" in the contructor of Car and also 
dont issue a "print car.auction" later on, the program succeeds.

I have been putting more and more things in the constructors for 2 reasons:

  1) It's really convenient esp in unit tests to be able to spec everything
     on 1 line when creating a lot of objects at once.

  2) It has always been "good business" in Python to make sure all
     instance vars are given a default value as early as possible.

But here, that "harmless" act of setting auction=None actually triggers things to happen that go considerably beyond my simplistic notion of just making sure things have a default value.

This is the 2nd time in as many days that I've been tripped-up by having things in the constructor that didn't *have* to be there. But only now am I coming to realize why.

Some explanation of or warning about this in the docs would seem appropriate. As I look over the declarative tutorial, it is somewhat implied that every column should be set in the constructor:
http://www.sqlalchemy.org/docs/orm/tutorial.html#creating-table-class-and-mapper-all-at-once-declaratively

Here, the issue is that you're mixing the usage of merge() with the usage of objects that 
are already in the session.   "new" is added to the session via cascade:

new = Car()
new.id_ = old.id_
new.lane = old.lane
new.auct_id = old.auct_id
new.auction = old.auction
assert new in sess   # passes

The ways to get around that effect are:

- pass "cascade=None" to your 'cars' backref - this means, when you set 
somecar.auction = someauction, someauction is already in the session, 'somecar' doesn't 
get added automatically.   cascade also affects what merge() does along relationships so 
when changing this make sure it has the cascades that you still want.
- expunge "new" before you merge() it, but that's kind of messy.
- don't set any relationships that are going to cascade it into the session

On that last note I found that if I do:
  new = Car()
  new.id_ = old.id_
  new = sess.merge(new)
  new.auction = old.auction  # do this *after* merge
  sess.commit()

This seems to work and avoids me having to deal with the cascade stuff (which I don't understand) just yet. Any worries with this approach?

I definitely want to add a note about what "the state of the given instance is 
copied" means, regarding things in __dict__.

Some explanation of how things get in __dict__ and what their presence there means would help us noobs.

Also, is it really a good idea to go hacking on __dict__ (e.g. popping things out as mentioned above)?

Again, thanks,
Michael

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

Reply via email to