>
> 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.
>
its because accessing the lazy collection on c.phone_numbers doesn't
trigger autoflush in 0.4, while it does in 0.5. Below is a test case
which fails only on 0.4 due to this behavior. 0.5 IMO is the one
which is more "correct", but its an unfortunate feature since this is
a frequent inconvenience. The issue demonstrated below is probably
not as common of a use case as that of just appending a non-
orphanable, pending object to a collection. But, an argument in favor
of 0.5's behavior is that the orphan error, while inconvenient,
complains loudly and is easily remedied (assuming one understands lazy
collections and autoflush), whereas the failure in 0.4 is silent.
a possible workaround would be to make the autoflush on the relation()
optional based on a configuration option, although that seems like yet
another obscure flag nobody would ever find out about and it also
dilutes the consistency of autoflush's behavior.
What I've advised to people in the past, and have also used myself, is
to temporarily disable autoflush on the session during code sections
that a lot of object manipulation is to occur, such as a form-to-model
population method in a web app. I made a decorator which
accomplishes this and it also fits nicely into a "with:" construct
(i.e. with no_autoflush():)".
using a "dynamic" relation (i.e. lazy="dynamic") would also alleviate
this problem since those collections don't request data unless they
are iterated. "dynamic" relations are the style of collection most
other Python ORMs use in all cases.
from sqlalchemy import *
from sqlalchemy.orm import *
engine = create_engine('sqlite:///', echo=True)
metadata = MetaData()
users = Table('users', metadata,
Column('id', Integer, primary_key=True),
Column('name', String(30), nullable=False),
)
addresses = Table('addresses', metadata,
Column('id', Integer, primary_key=True),
Column('user_id', None, ForeignKey('users.id')),
Column('email_address', String(50), nullable=False))
metadata.create_all(engine)
engine.execute(users.insert(),dict(id=8, name='ed'))
engine.execute(addresses.insert(),
[dict(id=x, user_id=y, email_address=z) for x, y, z in [
(2, 8, "e...@wood.com"),
(3, 8, "e...@bettyboop.com"),
(4, 8, "e...@lala.com"),
]])
class User(object):
pass
class Address(object):
pass
mapper(User, users, properties={
'addresses':relation(Address, order_by=addresses.c.email_address)
})
mapper(Address, addresses)
s = create_session(autoflush=True, transactional=True, bind=engine)
u = s.query(User).get(8)
ad2 = s.query(Address).get(2)
ad2.email_address = 'aaaaa'
assert [a.email_address for a in u.addresses] == ['aaaaa', 'e...@bettyboop.com
', 'e...@lala.com']