On Sun, Jan 18, 2009 at 03:16, Jason R. Coombs <[email protected]> wrote:
>
> This problem has emerged with sqlalchemy 0.5. I create a child
> object, then attempt to assign it to the parent object, but before I
> can, I get a flush error. I've created two test cases. One is Elixir-
> based and the other is pure sqlalchemy. I've been unable to get the
> pure sqlalchemy test case to reproduce the problem.
>
> The Elixir test case: http://paste.turbogears.org/paste/27865
> The SqlAlchemy test case: http://paste.turbogears.org/paste/27866
> The traceback (from Elixir case): http://paste.turbogears.org/paste/27867
>
> One might suggest an obvious workaround to assign the parent to the
> child instead of vice-versa, but this isn't an option in my real-world
> case, where the attributes are generated without knowledge of the
> parent (so the attribute referring to the parent is not readily
> known).
>
> In any case, the test case works with sqlalchemy==0.4.8, but fails
> with sqlalchemy>=0.5.0.
>
> I appreciate any suggestions on this matter. I bring it up here
> because the sa test seems to pass without any problems. I suspect the
> problem actually lies with SA, and if so, I would appreciate some help
> recreating the problem with a pure sa implementation.
>
> I've been able to work around the problem in my real-world code by
> caching a reference to the .attributes property before the children
> are created.
First (and as you probably guessed), your SA test case is not
equivalent to your Elixir test case. The main difference lies in the
fact you didn't use the scoped session to map your entities. Attached
is the corrected SA test case, which crashes as the Elixir one.
Now on to the actual problem. It is caused by a combination of using a
mapped collection (which triggers at query when you access it), using
the default elixir session which is a scoped session and use the
default argument to session, (and they changed in SA 0.5 to be
autoflush=True), which means that when the query for the mapped
collection is issued, a flush is issued just before it. Now since the
default elixir session scoped, your PersonAttribute instance is added
to the session as soon as it is created, which implies that SA tries
to flush that instance when you access your "mapped collection". Why
does it break? Because your relationship is configured as
"delete-orphan", which forbids an instance of PersonAttribute to exist
without being attached to a Person.
There are several ways to fix this:
- do not use a mapped collection
- do not use the default elixir session:
* either configure the session as autoflush=False
* or configure it as not scoped. In that case you'll need to do
manual session management everywhere (ie session.add(your_instance))
* the least invading solution (but ugliest one) is to remove the new
PersonAttribute from the session just after it is created.
Hope it helps,
--
Gaƫtan de Menten
http://openhex.org
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"SQLElixir" 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/sqlelixir?hl=en
-~----------~----~----~----~------~----~------~--~---
#!/usr/bin/env python
from sqlalchemy.orm.collections import mapped_collection
import sqlalchemy as sa
from sqlalchemy.orm import mapper, relation
from sqlalchemy.orm import sessionmaker, scoped_session
import os
engine = sa.create_engine('sqlite:///', echo=True)
metadata = sa.MetaData(engine)
session = scoped_session(sessionmaker())
person_table = sa.Table('person', metadata,
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('name', sa.String(128)),
)
person_attribute_table = sa.Table('person_attribute', metadata,
sa.Column('key', sa.String(128), primary_key=True),
sa.Column('value', sa.String(128)),
sa.Column('person', None, sa.ForeignKey('person.id')),
)
metadata.create_all()
class Person(object):
def __init__(self, **kwargs):
for name, value in kwargs.items():
setattr(self, name, value)
class PersonAttribute(object):
def __init__(self, **kwargs):
for name, value in kwargs.items():
setattr(self, name, value)
#mapper(Person, person_table, properties={
session.mapper(Person, person_table, properties={
'attributes': relation(
PersonAttribute,
collection_class=mapped_collection(lambda o: o.key),
cascade='all, delete-orphan',
)
})
session.mapper(PersonAttribute, person_attribute_table)
#mapper(PersonAttribute, person_attribute_table)
p = Person(name='Homer')
#session.add(p)
session.flush()
# accessing p.attributes before creating PersonAttribute will
# alleviate the exception
#p.attributes
attr1 = PersonAttribute(key='happy', value='yes')
#session.add(attr1)
# when one attempts to query p.attributes (even to set the
# attribute), a FlushError is thrown:
# sqlalchemy.orm.exc.FlushError: Instance <PersonAttribute at 0x2ce8250> is an unsaved, pending instance and is an orphan (is not attached to any parent 'Person' instance via that classes' 'attributes' attribute)
p.attributes['happy'] = attr1