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.