the stack trace tells all for autoflush situations. Note this is an 0.7
stacktrace, 0.6 is slightly different but the same series of steps:
File "test.py", line 107, in <module>
group = Group([item1, item2])
File "<string>", line 4, in __init__
File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/state.py", line 105,
in initialize_instance
return manager.original_init(*mixed[1:], **kwargs)
File "test.py", line 68, in __init__
----> 1. self.items_by_owner[item.owner] = item
File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/ext/associationproxy.py",
line 600, in __setitem__
self.col[key] = self._create(key, value)
File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/ext/associationproxy.py",
line 585, in _create
----> 2. return self.creator(key, value)
File "test.py", line 62, in create_for_proxy
---> 3. return cls(None, owner, item)
File "<string>", line 4, in __init__
File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/state.py", line 105,
in initialize_instance
return manager.original_init(*mixed[1:], **kwargs)
File "test.py", line 57, in __init__
---> 4, 5. self.item = item
File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/attributes.py", line
149, in __set__
instance_dict(instance), value, None)
File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/attributes.py", line
642, in set
value = self.fire_replace_event(state, dict_, value, old, initiator)
File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/attributes.py", line
662, in fire_replace_event
value = fn(state, value, previous, initiator or self)
File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/attributes.py", line
932, in set_
---> 6. passive=PASSIVE_NO_FETCH)
File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/attributes.py", line
436, in append
self.set(state, dict_, value, initiator, passive=passive)
File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/attributes.py", line
638, in set
----> 7. old = self.get(state, dict_, passive=PASSIVE_ONLY_PERSISTENT)
File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/attributes.py", line
414, in get
value = self.callable_(state, passive)
File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/strategies.py", line
542, in _load_for_state
---> 8. result = q.all()
File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/query.py", line 1636,
in all
return list(self)
File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/query.py", line 1745,
in __iter__
self.session._autoflush()
File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/session.py", line 901,
in _autoflush
---> 9. self.flush()
1. items_by_owner assignment
2. association proxy receives assignment, calls creator
3. creator is GroupOwner.create_for_proxy
4. GroupOwner constructor
5. assign self.item =item
6. group_owners backref must assign
7. group_owners backref is uselist=False, old value must be placed in the
"deleted" collection since new value replaces. "deleted" collection is so
session knows to assign NULL to the item's foreign key.
8. Item.group_owners is not present in __dict__. Old value must be loaded.
There's no old value in this case, but SQLA doesn't know that until it loads
9. autoflush
Solution 1:
initialize group_owners to None:
class Item(object):
def __init__(self, name, owner):
self.name = name
self.owner = owner
self.group_owners = None
solution 2:
disable autoflush in the GroupOwner constructor. See
http://www.sqlalchemy.org/trac/wiki/UsageRecipes/DisableAutoflush .
On Feb 17, 2011, at 5:56 AM, Julien Demoor wrote:
> Hi,
>
> I'm running into a problem illustrated by the code below. The result
> is an IntegrityError:
>
> sqlalchemy.exc.IntegrityError: (IntegrityError) null value in column
> "group_id" violates not-null constraint
> 'INSERT INTO groups_owners (owner_id, item_id) VALUES (%(owner_id)s, %
> (item_id)s) RETURNING groups_owners.group_id' {'item_id': 1,
> 'owner_id': 1}
>
> Looking at the stack trace, autoflush is triggerred by the assignments
> in GroupOwner.__init__(), but I fail to see why or what to do about
> it.
>
> The error appeared when I set a backref with uselist=False on the
> GroupOwner.item relationship. I can work around the problem by using a
> list instead, although that would make less sense since there's at
> most one group per item. Is there an error in my relationship
> configuration?
>
> I'm running SA 0.6.6.
>
> ==== Code to reproduce the error ====
>
> from sqlalchemy.ext.associationproxy import association_proxy
> from sqlalchemy.orm.collections import attribute_mapped_collection
> from sqlalchemy import Table, Column, Integer, Numeric, String,
> ForeignKey, MetaData
> from sqlalchemy.engine import create_engine
> from sqlalchemy.orm import sessionmaker, mapper, relationship, backref
>
> meta = MetaData()
> engine = create_engine('postgresql://test:test@localhost/test')
> Session = sessionmaker(bind=engine)
> session = Session()
>
> owners = Table("owners", meta,
> Column('id', Integer, primary_key=True),
> Column('name', String)
> )
>
> items = Table("items", meta,
> Column('id', Integer, primary_key=True),
> Column('name', String),
> Column('owner_id', Integer, ForeignKey('owners.id'))
> )
>
> groups = Table("groups", meta,
> Column('id', Integer,primary_key=True),
> )
>
> groups_owners = Table("groups_owners", meta,
> Column('group_id', Integer,
> ForeignKey('groups.id'), primary_key=True),
> Column('owner_id', Integer,
> ForeignKey('owners.id'), primary_key=True),
> Column('item_id', Integer,
> ForeignKey('items.id'), nullable=False, unique=True) # notice
> unique=True; items can belong to one group at most
> )
>
> def _create_holding(stock, shares):
> """A creator function, constructs Holdings from Stock and share
> quantity."""
> return Holding(stock=stock, shares=shares)
>
> class Owner(object):
> def __init__(self, name):
> self.name = name
>
> holdings = association_proxy('by_stock', 'shares',
> creator=_create_holding)
>
> class Item(object):
> def __init__(self, name, owner):
> self.name = name
> self.owner = owner
>
>
> class GroupOwner(object):
> def __init__(self, group, owner, item):
> self.group = group
> self.owner = owner
> self.item = item
> #assert item.owner is owner
>
> @classmethod
> def create_for_proxy(cls, owner, item):
> return cls(None, owner, item)
>
> class Group(object):
> def __init__(self, items):
> if items:
> for item in items:
> self.items_by_owner[item.owner] = item
>
> items_by_owner = association_proxy('group_owners', 'item',
> creator=GroupOwner.create_for_proxy)
>
>
> mapper(Owner, owners)
> mapper(Item, items, properties={
> 'owner': relationship(Owner),
> })
> mapper(Group, groups, properties={
> 'group_owners': relationship(GroupOwner,
> collection_class=attribute_mapped_collection('owner')),
> })
> mapper(GroupOwner, groups_owners, properties={
> 'group': relationship(Group),
> # Fails
> 'item': relationship(Item, backref=backref('group_owners',
> uselist=False), single_parent=True),
> # Doesn't fail
> #'item': relationship(Item, backref=backref('group_owners'),
> single_parent=True),
> 'owner': relationship(Owner),
> })
>
> meta.drop_all(bind=engine, checkfirst=True)
> meta.create_all(bind=engine)
>
> owner1 = Owner('owner 1')
> item1 = Item('item 1', owner1)
>
> owner2 = Owner('owner 2')
> item2 = Item('item 2', owner2)
>
> session.add(owner1)
> session.add(owner2)
> session.add(item1)
> session.add(item2)
>
> session.flush()
>
> # Fails:
> group = Group([item1, item2])
> # Doesn't fail
> #group = Group([item1])
>
>
> session.add(group)
> session.commit()
>
> assert session.query(GroupOwner).filter_by(group_id=1, item_id=1,
> owner_id=1).first() is not None
> print 'Done.'
>
> --
> 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.
>
--
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.