oh man, i missed the biggest part of this one, after I noticed that you weren't doing anything to put the PhoneNumber in the session. Don't use Session.mapper. That is the main problem you're having in this specific case. Auto-adding free-standing objects to the session is a bad idea and I've downplayed the crap out of this legacy feature for years now.
On Jul 2, 2009, at 7:21 PM, Brad Wells wrote: > > In the process of upgrading from 0.4 to 0.5 I've come across a > troubling issue. With the following setup: > > #################################################################################### > > from sqlalchemy import Table, Column, Integer, String, MetaData, > create_engine, ForeignKey > from sqlalchemy.orm import relation, sessionmaker, scoped_session > > engine = create_engine('sqlite:///mystuff.sqlite', echo=True) > > Session = scoped_session(sessionmaker(autoflush=True, > transactional=True, bind=engine)) > metadata = MetaData() > mapper = Session.mapper > > time_zones = Table('time_zones', metadata, > Column('id', Integer, primary_key=True), > Column('name', String(35))) > > contacts = Table('contacts', metadata, > Column('id', Integer, primary_key=True), > Column('display_as', String(35)), > Column('time_zone_id', Integer, ForeignKey > ('time_zones.id'))) > > phone_numbers = Table('phone_numbers', metadata, > Column('id', Integer, primary_key=True), > Column('number', String(35)), > Column('contact_id', Integer, ForeignKey > ('contacts.id'))) > > class TimeZone(object): pass > class Contact(object): pass > class PhoneNumber(object): pass > > mapper(TimeZone, time_zones) > mapper(Contact, contacts, properties={ > 'time_zone': relation(TimeZone, backref='contacts'), > 'phone_numbers': relation(PhoneNumber, backref='contact', > cascade='all, delete-orphan') > }) > mapper(PhoneNumber, phone_numbers) > > metadata.create_all(bind=engine) > > ############################################################################## > > Under 0.4 the following code executes fine: > > > ############ > > c = Contact(display_as='Fake, User') > c.time_zone = TimeZone.query.filter_by(name='Africa/Algiers').first() > ph = PhoneNumber(full='1234567890') > c.phone_numbers.append(ph) > Session.commit() > > ############ > > But under 0.5 I receive an orphaned object error (see below for full > output). I understand that the TimeZone query causes a flush in > between the creation of the Contact and of the PhoneNumber. Without > the flush in between (if the TimeZone query line is removed) SA 0.5 is > correctly able execute the sample script. > > As per this thread (http://groups.google.com/group/sqlalchemy/ > browse_thread/thread/6c71c61bc59223f?tvc=2) I see that a suggested > remedy is to change the relation to cascade='all' rather than > cascade='all, delete-orphan'. > I would prefer not to do this as it really does make no sense in this > case to have a PhoneNumber without a Contact. I could also set the > relation via 'ph.contact = contact' but I would prefer to not have to > comb all of our existing code for this new class of bug. > > What doesn't make sense to me is why 0.4 was able to correctly delay > the insert of the new phone number record until after the query for > the collection and now 0.5 can't. > > Thank you for the assistance > -Brad > > > Below is the full echo output of 0.4 and 0.5 > > 0.4.8dev_r5095: > 2009-07-02 18:51:17,391 INFO sqlalchemy.engine.base.Engine.0x..70 > BEGIN > 2009-07-02 18:51:17,391 INFO sqlalchemy.engine.base.Engine.0x..70 > INSERT INTO contacts (display_as, time_zone_id) VALUES (?, ?) > 2009-07-02 18:51:17,391 INFO sqlalchemy.engine.base.Engine.0x..70 > ['Fake, User', None] > 2009-07-02 18:51:17,391 INFO sqlalchemy.engine.base.Engine.0x..70 > SELECT time_zones.id AS time_zones_id, time_zones.name AS > time_zones_name FROM time_zones WHERE time_zones.name = ? ORDER BY > time_zones.oid LIMIT 1 OFFSET 0 > 2009-07-02 18:51:17,391 INFO sqlalchemy.engine.base.Engine.0x..70 > ['Africa/Algiers'] > 2009-07-02 18:51:17,407 INFO sqlalchemy.engine.base.Engine.0x..70 > SELECT phone_numbers.id AS phone_numbers_id, phone_numbers.number AS > phone_numbers_number, phone_numbers.contact_id AS > phone_numbers_contact_id FROM phone_numbers WHERE > phone_numbers.contact_id = ? ORDER BY phone_numbers.oid > 2009-07-02 18:51:17,407 INFO sqlalchemy.engine.base.Engine.0x..70 [4] > 2009-07-02 18:51:17,407 INFO sqlalchemy.engine.base.Engine.0x..70 > INSERT INTO phone_numbers (number, contact_id) VALUES (?, ?) > 2009-07-02 18:51:17,407 INFO sqlalchemy.engine.base.Engine.0x..70 > [None, 4] > 2009-07-02 18:51:17,407 INFO sqlalchemy.engine.base.Engine.0x..70 > COMMIT > > > > > 0.5.4p2: > sa_test.py:8: SADeprecationWarning: The 'transactional' argument to > sessionmaker() is deprecated; use autocommit=True|False instead. > Session = scoped_session(sessionmaker(autoflush=True, > transactional=True, bind=engine)) > 2009-07-02 18:54:41,658 INFO sqlalchemy.engine.base.Engine.0x...2f30 > PRAGMA table_info("time_zones") > 2009-07-02 18:54:41,658 INFO sqlalchemy.engine.base.Engine.0x...2f30 > () > 2009-07-02 18:54:41,658 INFO sqlalchemy.engine.base.Engine.0x...2f30 > PRAGMA table_info("contacts") > 2009-07-02 18:54:41,658 INFO sqlalchemy.engine.base.Engine.0x...2f30 > () > 2009-07-02 18:54:41,658 INFO sqlalchemy.engine.base.Engine.0x...2f30 > PRAGMA table_info("phone_numbers") > 2009-07-02 18:54:41,658 INFO sqlalchemy.engine.base.Engine.0x...2f30 > () > 2009-07-02 18:54:41,658 INFO sqlalchemy.engine.base.Engine.0x...2f30 > BEGIN > 2009-07-02 18:54:41,658 INFO sqlalchemy.engine.base.Engine.0x...2f30 > INSERT INTO contacts (display_as, time_zone_id) VALUES (?, ?) > 2009-07-02 18:54:41,658 INFO sqlalchemy.engine.base.Engine.0x...2f30 > ['Fake, User', None] > 2009-07-02 18:54:41,674 INFO sqlalchemy.engine.base.Engine.0x...2f30 > SELECT time_zones.id AS time_zones_id, time_zones.name AS > time_zones_name FROM time_zones WHERE time_zones.name = ? LIMIT 1 > OFFSET 0 > 2009-07-02 18:54:41,674 INFO sqlalchemy.engine.base.Engine.0x...2f30 > ['Africa/Algiers'] > Traceback (most recent call last): > File "sa_test.py", line 47, in <module> > c.phone_numbers.append(ph) > File "c:\program files\python\lib\site-packages\sqlalchemy-0.5.4p2- > py2.5.egg\sqlalchemy\orm\attributes.py", line 158, > in __get__ > return self.impl.get(instance_state(instance), instance_dict > (instance)) > File "c:\program files\python\lib\site-packages\sqlalchemy-0.5.4p2- > py2.5.egg\sqlalchemy\orm\attributes.py", line 374, > in get > value = callable_() > File "c:\program files\python\lib\site-packages\sqlalchemy-0.5.4p2- > py2.5.egg\sqlalchemy\orm\strategies.py", line 568, > in __call__ > result = q.all() > File "c:\program files\python\lib\site-packages\sqlalchemy-0.5.4p2- > py2.5.egg\sqlalchemy\orm\query.py", line 1193, in a > ll > return list(self) > File "c:\program files\python\lib\site-packages\sqlalchemy-0.5.4p2- > py2.5.egg\sqlalchemy\orm\query.py", line 1286, in _ > _iter__ > self.session._autoflush() > File "c:\program files\python\lib\site-packages\sqlalchemy-0.5.4p2- > py2.5.egg\sqlalchemy\orm\session.py", line 899, in > _autoflush > self.flush() > File "c:\program files\python\lib\site-packages\sqlalchemy-0.5.4p2- > py2.5.egg\sqlalchemy\orm\session.py", line 1354, in > flush > self._flush(objects) > File "c:\program files\python\lib\site-packages\sqlalchemy-0.5.4p2- > py2.5.egg\sqlalchemy\orm\session.py", line 1414, in > _flush > mapperutil.state_str(state), path)) > sqlalchemy.orm.exc.FlushError: Instance <PhoneNumber at 0xf8ae30> is > an unsaved, pending instance and is an orphan (is n > ot attached to any parent 'Contact' instance via that classes' > 'phone_numbers' attribute) > > --~--~---------~--~----~------------~-------~--~----~ 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 -~----------~----~----~----~------~----~------~--~---