I've run into autoflush issue while using AssociationProxy with
sqlalchemy-1.1.6. Appending to association list fails, because object is
flushed before it is appended (and appending sets primary key value). I
can avoid it with session.no_autoflush context manager, but I wonder if
it's not worth slightly changing proxy behaviour to make it work with
autoflush enabled.
Minimal example of this issue below:
|
engine =engine_from_config(CONFIG)
DBSession=scoped_session(sessionmaker())
DBSession.configure(bind=engine)
Base=declarative_base()
classUserAlias(Base):
__tablename__ ='user_aliases'
user_id =Column(Integer,ForeignKey('user.id'),primary_key=True)
alias_id =Column(Integer,ForeignKey('aliases.id'),primary_key=True)
valid =Column(Boolean)
alias=relationship('Alias',backref=backref('user_assocs',uselist=False))
user =relationship('User',backref=backref('alias_assocs'))
def__init__(self,alias,valid=True):
self.alias=alias
self.valid =valid
classAlias(Base):
__tablename__ ='aliases'
id =Column(Integer,primary_key=True)
name =Column('name',String(64))
user =association_proxy('user_assocs','user')
def__init__(self,name):
self.name =name
classUser(Base):
__tablename__ ='user'
id =Column(Integer,primary_key=True)
name =Column(String(64))
aliases =association_proxy('alias_assocs','alias')
def__init__(self,name):
self.name =name
deftest_assoc():
user =User(name='John')
DBSession.add(user)
DBSession.commit()
johny =Alias(name='Johny')
user.aliases.append(johny)
DBSession.commit()
jan =Alias(name='Jan')
DBSession.add(jan) # in my real use case proxied object is already
attached to session before appending, here I simulate it with DBSession.add
user.aliases.append(jan) # autoflush fails here
DBSession.commit()
returnuser
|
and stacktrace:
|
/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/sql/crud.py:692:SAWarning:Column'user_aliases.user_id'ismarked
asa member of the primary key fortable 'user_aliases',but has
noPython-side orserver-side defaultgenerator indicated,nor does it
indicate 'autoincrement=True'or'nullable=True',andnoexplicitvalue
ispassed. Primarykey columns typically may notstore NULL.Notethat asof
SQLAlchemy1.1,'autoincrement=True'must be indicated explicitly
forcomposite (e.g.multicolumn)primary keys
ifAUTO_INCREMENT/SERIAL/IDENTITY behavior isexpected forone of the
columns inthe primary key.CREATE TABLE statements are impacted
bythischange aswell on most backends.
util.warn(msg)
Traceback(most recent call last):
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/engine/base.py",line
1182,in_execute_context
context)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/engine/default.py",line
470,indo_execute
cursor.execute(statement,parameters)
psycopg2.IntegrityError:nullvalue incolumn "user_id"violates
not-nullconstraint
DETAIL: Failingrow contains (null,50,t).
Theabove exception was the direct cause of the following exception:
Traceback(most recent call last):
File"/home/bartek/dev/sqlalchemy-test/sqlalchemy_test/models.py",line
111,in<module>
test_assoc()
File"/home/bartek/dev/sqlalchemy-test/sqlalchemy_test/models.py",line
105,intest_assoc
user.aliases.append(jan)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/ext/associationproxy.py",line
610,inappend
self.col.append(item)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/ext/associationproxy.py",line
509,in<lambda>
col =property(lambdaself:self.lazy_collection())
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/ext/associationproxy.py",line
467,in__call__
returngetattr(obj,self.target)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/orm/attributes.py",line
237,in__get__
returnself.impl.get(instance_state(instance),dict_)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/orm/attributes.py",line
584,inget
value =self.callable_(state,passive)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/orm/strategies.py",line
557,in_load_for_state
returnself._emit_lazyload(session,state,ident_key,passive)
File"<string>",line 1,in<lambda>
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/orm/strategies.py",line
635,in_emit_lazyload
result =q.all()
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/orm/query.py",line
2679,inall
returnlist(self)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/orm/query.py",line
2830,in__iter__
self.session._autoflush()
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/orm/session.py",line
1375,in_autoflush
util.raise_from_cause(e)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/util/compat.py",line
203,inraise_from_cause
reraise(type(exception),exception,tb=exc_tb,cause=cause)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/util/compat.py",line
187,inreraise
raisevalue
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/orm/session.py",line
1365,in_autoflush
self.flush()
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/orm/session.py",line
2139,inflush
self._flush(objects)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/orm/session.py",line
2259,in_flush
transaction.rollback(_capture_exception=True)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/util/langhelpers.py",line
60,in__exit__
compat.reraise(exc_type,exc_value,exc_tb)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/util/compat.py",line
187,inreraise
raisevalue
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/orm/session.py",line
2223,in_flush
flush_context.execute()
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/orm/unitofwork.py",line
389,inexecute
rec.execute(self)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/orm/unitofwork.py",line
548,inexecute
uow
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/orm/persistence.py",line
181,insave_obj
mapper,table,insert)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/orm/persistence.py",line
835,in_emit_insert_statements
execute(statement,params)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/engine/base.py",line
945,inexecute
returnmeth(self,multiparams,params)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/sql/elements.py",line
263,in_execute_on_connection
returnconnection._execute_clauseelement(self,multiparams,params)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/engine/base.py",line
1053,in_execute_clauseelement
compiled_sql,distilled_params
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/engine/base.py",line
1189,in_execute_context
context)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/engine/base.py",line
1393,in_handle_dbapi_exception
exc_info
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/util/compat.py",line
203,inraise_from_cause
reraise(type(exception),exception,tb=exc_tb,cause=cause)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/util/compat.py",line
186,inreraise
raisevalue.with_traceback(tb)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/engine/base.py",line
1182,in_execute_context
context)
File"/home/bartek/envs/sqlalchemy-test/lib/python3.5/site-packages/sqlalchemy/engine/default.py",line
470,indo_execute
cursor.execute(statement,parameters)
sqlalchemy.exc.IntegrityError:(raised asa result of Query-invoked
autoflush;consider usinga session.no_autoflush block ifthisflush
isoccurring prematurely)(psycopg2.IntegrityError)nullvalue incolumn
"user_id"violates not-nullconstraint
DETAIL: Failingrow contains (null,50,t).
[SQL:'INSERT INTO user_aliases (alias_id, valid) VALUES (%(alias_id)s,
%(valid)s)'][parameters:{'alias_id':50,'valid':True}]
|
Autoflush occurs in _AssociationList append method:
|
defappend(self,value):
item =self._create(value)
self.col.append(item)
|
If _create was called before self.col is evaluted created object would
not be autoflushed prematurely. Maybe it is worth changing it if it does
not break anything else.
--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
http://www.sqlalchemy.org/
To post example code, please provide an MCVE: Minimal, Complete, and
Verifiable Example. See http://stackoverflow.com/help/mcve for a full
description.
---
You received this message because you are subscribed to the Google
Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send
an email to sqlalchemy+unsubscr...@googlegroups.com
<mailto:sqlalchemy+unsubscr...@googlegroups.com>.
To post to this group, send email to sqlalchemy@googlegroups.com
<mailto:sqlalchemy@googlegroups.com>.
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.