This question already has answers here:
SQLAlchemy multiple foreign keys in one mapped class to the same primary key
(2 answers)
Closed 5 years ago.
I am about to create a certain 1:n-realtionship with SQLAlchey.
I have brought two pictures for you, so you can see what this is about. First you'll see a picture of the EER model. In this EER model, I created a 1: n relationship between the person table and the family table. Any person is stored in the person table. And we all know that people can be in a relationship with one another (mother, father, daughter, son, cousin, cousin, nephew, etc ...). For this reason I have set up the family table.
In order to play the whole time exemplary, I have filled the tables with fictitious data. With a look at the family table, we see in the first column, which is the central person, namely Hans Schmidt (with the ID 1). In the Family table the following persons are saved by their foreign key: Kurt Schmidt (son), Gerda Schmidt (wife), Gisela Schmidt (Hans' mother), and Julia Schmidt (daughter). I would like to point out Hans Schmidt's relationship.
And in my source code, I have mapped this ORM model as follows:
class FAMILY(Base):
__tablename__ = "family"
id = Column(Integer, primary_key=True, unique=True, autoincrement=True)
status = Column(String(255), nullable=False)
person_id = Column(Integer, ForeignKey('person.id'))
person = relationship("PERSON", backref='family', lazy='dynamic')
family_person_id = Column(Integer, ForeignKey('person.id'))
family_person = relationship("PERSON", backref='family', lazy='dynamic')
class PERSON(Base):
__tablename__ = "person"
id = Column(Integer, primary_key=True, unique=True, autoincrement=True)
nickname = Column(String(255))
alias_name = Column(String (255))
name_normally_used = Column(String(50), nullable=False)
first_middle_name = Column(String(255))
last_name = Column(String(100))
When creating the model, SQLAlchemy does not cause any problems (echo is true, so I could track it there were no problems.). However, SQLAlchemy throws an exception when I start the user interface, where you can manage the data of a person.
Traceback (most recent call last):
File "D:\Dan\Python\Xarphus\xarphus\subclass_master_data_load_data_item.py", line 140, in populate_item
self.populate_item_signal.emit(next(self._element))
File "D:\Dan\Python\Xarphus\xarphus\core\manage_data_manipulation_master_data.py", line 205, in select_all
for record in dict_store_session_query[category]():
File "D:\Dan\Python\Xarphus\xarphus\core\manage_data_manipulation_master_data.py", line 191, in <lambda>
'person_gender': lambda: self._session.query(PERSON_GENDER),
File "C:\Python27\lib\site-packages\sqlalchemy\orm\session.py", line 1362, in query
return self._query_cls(entities, self, **kwargs)
File "C:\Python27\lib\site-packages\sqlalchemy\orm\query.py", line 139, in __init__
self._set_entities(entities)
File "C:\Python27\lib\site-packages\sqlalchemy\orm\query.py", line 150, in _set_entities
self._set_entity_selectables(self._entities)
File "C:\Python27\lib\site-packages\sqlalchemy\orm\query.py", line 180, in _set_entity_selectables
ent.setup_entity(*d[entity])
File "C:\Python27\lib\site-packages\sqlalchemy\orm\query.py", line 3585, in setup_entity
self._with_polymorphic = ext_info.with_polymorphic_mappers
File "C:\Python27\lib\site-packages\sqlalchemy\util\langhelpers.py", line 764, in __get__
obj.__dict__[self.__name__] = result = self.fget(obj)
File "C:\Python27\lib\site-packages\sqlalchemy\orm\mapper.py", line 1948, in _with_polymorphic_mappers
configure_mappers()
File "C:\Python27\lib\site-packages\sqlalchemy\orm\mapper.py", line 2869, in configure_mappers
raise e
InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'Mapper|FAMILY|family'. Original exception was: Could not determine join condition between parent/child tables on relationship FAMILY.person - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
Traceback (most recent call last):
File "D:\Dan\Python\Xarphus\xarphus\subclass_master_data_load_data_item.py", line 140, in populate_item
self.populate_item_signal.emit(next(self._element))
File "D:\Dan\Python\Xarphus\xarphus\core\manage_data_manipulation_master_data.py", line 205, in select_all
for record in dict_store_session_query[category]():
File "D:\Dan\Python\Xarphus\xarphus\core\manage_data_manipulation_master_data.py", line 194, in <lambda>
'person_title': lambda: self._session.query(PERSON_TITLE),
File "C:\Python27\lib\site-packages\sqlalchemy\orm\session.py", line 1362, in query
return self._query_cls(entities, self, **kwargs)
File "C:\Python27\lib\site-packages\sqlalchemy\orm\query.py", line 139, in __init__
self._set_entities(entities)
File "C:\Python27\lib\site-packages\sqlalchemy\orm\query.py", line 150, in _set_entities
self._set_entity_selectables(self._entities)
File "C:\Python27\lib\site-packages\sqlalchemy\orm\query.py", line 180, in _set_entity_selectables
ent.setup_entity(*d[entity])
File "C:\Python27\lib\site-packages\sqlalchemy\orm\query.py", line 3585, in setup_entity
self._with_polymorphic = ext_info.with_polymorphic_mappers
File "C:\Python27\lib\site-packages\sqlalchemy\util\langhelpers.py", line 764, in __get__
obj.__dict__[self.__name__] = result = self.fget(obj)
File "C:\Python27\lib\site-packages\sqlalchemy\orm\mapper.py", line 1948, in _with_polymorphic_mappers
configure_mappers()
File "C:\Python27\lib\site-packages\sqlalchemy\orm\mapper.py", line 2872, in configure_mappers
mapper._post_configure_properties()
File "C:\Python27\lib\site-packages\sqlalchemy\orm\mapper.py", line 1765, in _post_configure_properties
prop.init()
File "C:\Python27\lib\site-packages\sqlalchemy\orm\interfaces.py", line 184, in init
self.do_init()
File "C:\Python27\lib\site-packages\sqlalchemy\orm\relationships.py", line 1654, in do_init
self._setup_join_conditions()
File "C:\Python27\lib\site-packages\sqlalchemy\orm\relationships.py", line 1729, in _setup_join_conditions
can_be_synced_fn=self._columns_are_mapped
File "C:\Python27\lib\site-packages\sqlalchemy\orm\relationships.py", line 1987, in __init__
self._determine_joins()
File "C:\Python27\lib\site-packages\sqlalchemy\orm\relationships.py", line 2114, in _determine_joins
% self.prop)
AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship FAMILY.person - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
And there we would be. SQLALchey apparently has a problem with the two foreign keys that come from the same table? Question to you: How should modeling be done best to show the interrelationship between people? Because after my reasoning, I need at least two foreign keys from the person table, in order to establish a relationship among the persons.
There are two main problems in your definition of mapper class FAMILY.
(1) You have referred PERSON as foreign key twice, so you have to explicitly tell SQLAlchemy how to join the PERSON, please set foreign_keys parameter for each relationship.
(2) You have used the same value as backref. The value of backref (FAMILY), would become the property name of PERSON class. Look in your FAMILY, there are two properties named person and family_person, then there should be two different properties for the PERSON class.
Also, in your code, the relationship between FAMILY and PERSON is MANY TO ONE, thus the dynamic value is not valid for the parameter lazy, you should use the default one. Here is the sample code:
class FAMILY(Base):
__tablename__ = "family"
id = Column(Integer, primary_key=True, unique=True, autoincrement=True)
status = Column(String(255), nullable=False)
person_id = Column(Integer, ForeignKey('person.id'))
person = relationship("PERSON", backref='family_to',
foreign_keys=[person_id],
)
family_person_id = Column(Integer, ForeignKey('person.id'))
family_person = relationship("PERSON",
foreign_keys=[family_person_id],
backref='family_from')
Here are some useful links related to your question: Refer a table twice, Relationship configration
Thanks!
Related
I have a table with two (M-1) relationships with respective ForeignKeys. I want to verify that ids match. I attempted to compare ids and am receiving: NameError: global name 'owner_id' is not defined
I am uncertain why this is the issue. I wasn't sure if I should use a join method per SQLAlchemy docs, but it is within the table. Calling self. also doesn't work.
The table is:
class Assessment_Results(Base):
__tablename__ = 'assessment_results'
id = Column(Integer, primary_key=True)
created_on = Column(DateTime, default=datetime.utcnow)
owner_id = Column(Integer, ForeignKey('users.user_id'))
owner = relationship('User', backref='assessment_results')
assessment_id = Column(Integer, ForeignKey('assessments.assessment_id'))
assessment = relationship('Assessment', backref='assessment_results')
def __init__(self, owner, assessment):
self.owner = owner
self.assessment = assessment
The query:
def retrieve_assessment_results(self, owner, assessment):
assess_results = self.session.query(Assessment_Results).\
filter(Assessment_Results.id == owner_id).\
filter(Assessment_Results.id == assessment_id).all()
return assess_results
Entire Traceback:
ERROR: notssdb.test.test.test1
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/local/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
self.test(*self.arg)
File "/Users/ack/code/venv/NotssDB/notssdb/test/test.py", line 86, in test1
api.create_category_rating(2, 'Decision-Making', 'baseball', 'Becoming a Leader')
File "/Users/ack/code/venv/NotssDB/notssdb/api/convenience.py", line 29, in create_category_rating
assessment_results = self.retrieve_assessment_results(owner, assessment)
File "/Users/ack/code/venv/NotssDB/notssdb/api/object.py", line 273, in retrieve_assessment_results
filter(Assessment_Results.id == owner_id).\
NameError: global name 'owner_id' is not defined
New Traceback Error after this new implementation:
def retrieve_assessment_results(self, owner, assessment):
assess_results = self.session.query(Assessment_Results).\
filter(Assessment_Results.owner_id == owner.id).\
filter(Assessment_Results.assessment_id == assessment.id).all()
return assess_results
Inheritance class of the method above:
def create_assessment_results(self, username, name):
owner = self.retrieve_user(username)
assessment = self.retrieve_assessment(name)
return super(ConvenienceAPI, self).create_assessment_results(owner, assessment)
Here is cat_rating :
def create_category_rating(self, category_rating_int, category_name, owner, assessment):
category = self.retrieve_category(category_name)
assessment_results = self.retrieve_assessment_results(owner, assessment)
return super(ConvenienceAPI, self).create_category_rating(category_rating_int, category, assessment_results)
Error:
Traceback (most recent call last):
File "/usr/local/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
self.test(*self.arg)
File "/Users/ack/code/venv/NotssDB/notssdb/test/test.py", line 86, in test1
api.create_category_rating(2, 'Decision-Making', 'baseball', 'Becoming a Leader')
File "/Users/ack/code/venv/NotssDB/notssdb/api/convenience.py", line 29, in create_category_rating
assessment_results = self.retrieve_assessment_results(owner, assessment)
File "/Users/ack/code/venv/NotssDB/notssdb/api/object.py", line 272, in retrieve_assessment_results
filter(Assessment_Results.owner_id == owner.id).\
AttributeError: 'str' object has no attribute 'id'
The problem is this line right here..
filter(Assessment_Results.id == owner_id).\
owner_id was not defined in this method and is not being accessed as an attribute of an Assessment_Results object, so the interpreter doesn't know what object you are trying to refer to by this name.
I think you probably want to do
filter(Assessment_Results.owner_id == owner.id).\
but that assumes that owner is an instance of the owner model.
You'll end up having the same problem with assessment_id (because it is also not defined within the method) once you figure out what variable you meant to use for owner_id.
I'm building a flask app and I want to use sqlalchemy observers to update a shipment status once all products inside that shipment become available.
Here's my datamodel:
from app import db
from sqlalchemy_utils import observes
class Shipment(db.Model):
__tablename__ = 'shipment'
id = db.Column(db.Integer, primary_key=True)
products = db.relationship('Product', backref='shipment', lazy='dynamic')
all_products_ready = db.Column(db.Boolean)
#observes('products')
def product_observer(self, products):
for p in self.products:
if p.status != 'ready':
self.all_products_ready = False
return False
self.all_products_ready = True
return True
class Product(db.Model):
__tablename__ = 'product'
id = db.Column(db.Integer, primary_key=True)
shipment_id = db.Column(db.Integer, db.ForeignKey('shipment.id'))
status = db.Column(db.String(120), index=True)
And here is some code I run to test it:
shipment = models.Shipment(products=[models.Product(status='ready'), models.Product(status='not_ready')])
db.session.add(shipment)
db.session.commit()
print(shipment.all_products_ready)
When I run this code I get an InvalidRequestError: Session is already flushing.
Here is the stack trace:
Traceback (most recent call last):
File "test.py", line 5, in <module>
db.session.commit()
File "U:\dev\observertest\flask\lib\site-packages\sqlalchemy\orm\scoping.py",
line 150, in do
return getattr(self.registry(), name)(*args, **kwargs)
File "U:\dev\observertest\flask\lib\site-packages\sqlalchemy\orm\session.py",
line 788, in commit
self.transaction.commit()
File "U:\dev\observertest\flask\lib\site-packages\sqlalchemy\orm\session.py",
line 384, in commit
self._prepare_impl()
File "U:\dev\observertest\flask\lib\site-packages\sqlalchemy\orm\session.py",
line 364, in _prepare_impl
self.session.flush()
File "U:\dev\observertest\flask\lib\site-packages\sqlalchemy\orm\session.py",
line 1985, in flush
self._flush(objects)
File "U:\dev\observertest\flask\lib\site-packages\sqlalchemy\orm\session.py",
line 2012, in _flush
self.dispatch.before_flush(self, flush_context, objects)
File "U:\dev\observertest\flask\lib\site-packages\sqlalchemy\event\attr.py", l
ine 221, in __call__
fn(*args, **kw)
File "U:\dev\observertest\flask\lib\site-packages\sqlalchemy_utils\observer.py
", line 272, in invoke_callbacks
for (root_obj, func, objects) in args:
File "U:\dev\observertest\flask\lib\site-packages\sqlalchemy_utils\observer.py
", line 252, in gather_callback_args
lambda obj: obj not in session.deleted
File "U:\dev\observertest\flask\lib\site-packages\sqlalchemy_utils\functions\o
rm.py", line 741, in getdotattr
last = [v for v in last if condition(v)]
File "U:\dev\observertest\flask\lib\site-packages\sqlalchemy\orm\dynamic.py",
line 245, in __iter__
sess = self.session
File "U:\dev\observertest\flask\lib\site-packages\sqlalchemy\orm\dynamic.py",
line 237, in session
sess.flush()
File "U:\dev\observertest\flask\lib\site-packages\sqlalchemy\orm\session.py",
line 1979, in flush
raise sa_exc.InvalidRequestError("Session is already flushing")
sqlalchemy.exc.InvalidRequestError: Session is already flushing
How can I use my models without getting this error?
I'm not completely sure why, but I think loading the relations using dynamic is causing problems here. In this line:
products = db.relationship('Product', backref='shipment', lazy='dynamic')
you need to change the lazy parameter to select instead of dynamic (or you can take out the lazy parameter alltogether as select is its default).
See the sqlalchemy reference for all the available options.
I have a product model with a many to many relationship to product_categories as described below:
class Product(Base):
""" The SQLAlchemy declarative model class for a Product object. """
__tablename__ = 'products'
id = Column(Integer, primary_key=True)
part_number = Column(String(10), nullable=False, unique=True)
name = Column(String(80), nullable=False, unique=True)
description = Column(String(2000), nullable=False)
categories = relationship('Category', secondary=product_categories,
backref=backref('categories', lazy='dynamic'))
class Category(Base):
""" The SQLAlchemy declarative model class for a Category object. """
__tablename__ = 'categories'
id = Column(Integer, primary_key=True)
lft = Column(Integer, nullable=False)
rgt = Column(Integer, nullable=False)
name = Column(String(80), nullable=False)
description = Column(String(2000), nullable=False)
order = Column(Integer)
products = relationship('Product', secondary=product_categories,
backref=backref('products', lazy='dynamic', order_by=name))
product_categories = Table('product_categories', Base.metadata,
Column('products_id', Integer, ForeignKey('products.id')),
Column('categories_id', Integer, ForeignKey('categories.id'))
)
Then, I'm trying to delete this object with:
product = DBSession.query(Product).filter_by(name = 'Algaecide').one()
DBSession().delete(product)
But, what I get is the error message StaleDataError: DELETE statement on table 'product_categories' expected to delete 1 row(s); Only 0 were matched.. If you notice, it appears to be running the DELETE statement twice. So, the first time it's successfully removes the product_id from the product_categories table, but then attempts to do it yet again for whatever reason, and then decides it needs to throw an exception because it's not there.
>>> Attempting to delete the product 'Algaecide' assigned to categories: [<myproject.models.category.Category object at 0x106fa9d90>]
21:39:50 INFO [sqlalchemy.engine.base.Engine][Dummy-2] SELECT categories.id AS categories_id, categories.lft AS categories_lft, categories.rgt AS categories_rgt, categories.name AS categories_name, categories.description AS categories_description, categories.`order` AS categories_order
FROM categories, product_categories
WHERE %s = product_categories.products_id AND categories.id = product_categories.categories_id ORDER BY categories.name
21:39:50 INFO [sqlalchemy.engine.base.Engine][Dummy-2] (109L,)
21:39:50 INFO [sqlalchemy.engine.base.Engine][Dummy-2] DELETE FROM product_categories WHERE product_categories.products_id = %s AND product_categories.categories_id = %s
21:39:50 INFO [sqlalchemy.engine.base.Engine][Dummy-2] (109L, 18L)
21:39:50 INFO [sqlalchemy.engine.base.Engine][Dummy-2] DELETE FROM product_categories WHERE product_categories.products_id = %s AND product_categories.categories_id = %s
21:39:50 INFO [sqlalchemy.engine.base.Engine][Dummy-2] (109L, 18L)
21:39:50 INFO [sqlalchemy.engine.base.Engine][Dummy-2] ROLLBACK
21:39:50 ERROR [pyramid_debugtoolbar][Dummy-2] Exception at http://0.0.0.0:6543/product/Algaecide/edit
traceback url: http://0.0.0.0:6543/_debug_toolbar/exception?token=6344937a98ee26992689&tb=4421411920
Traceback (most recent call last):
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid_debugtoolbar-2.3-py2.7.egg/pyramid_debugtoolbar/toolbar.py", line 178, in toolbar_tween
response = _handler(request)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid_debugtoolbar-2.3-py2.7.egg/pyramid_debugtoolbar/panels/performance.py", line 57, in resource_timer_handler
result = handler(request)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid/tweens.py", line 21, in excview_tween
response = handler(request)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid_tm-0.10-py2.7.egg/pyramid_tm/__init__.py", line 95, in tm_tween
reraise(*exc_info)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid_tm-0.10-py2.7.egg/pyramid_tm/__init__.py", line 83, in tm_tween
manager.commit()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_manager.py", line 111, in commit
return self.get().commit()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_transaction.py", line 280, in commit
reraise(t, v, tb)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_transaction.py", line 271, in commit
self._commitResources()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_transaction.py", line 417, in _commitResources
reraise(t, v, tb)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_transaction.py", line 389, in _commitResources
rm.tpc_begin(self)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/zope.sqlalchemy-0.7.5-py2.7.egg/zope/sqlalchemy/datamanager.py", line 90, in tpc_begin
self.session.flush()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/session.py", line 1919, in flush
self._flush(objects)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/session.py", line 2037, in _flush
transaction.rollback(_capture_exception=True)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/util/langhelpers.py", line 60, in __exit__
compat.reraise(exc_type, exc_value, exc_tb)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/session.py", line 2001, in _flush
flush_context.execute()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/unitofwork.py", line 372, in execute
rec.execute(self)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/unitofwork.py", line 479, in execute
self.dependency_processor.process_deletes(uow, states)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/dependency.py", line 1023, in process_deletes
secondary_update, secondary_delete)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/dependency.py", line 1111, in _run_crud
result.rowcount)
StaleDataError: DELETE statement on table 'product_categories' expected to delete 1 row(s); Only 0 were matched.
Am I missing some sort of a 'gotcha' in this case? Seems like a pretty standard implementation. Why is it attempting to execute the DELETE statement twice?
After a bit more searching, I found the following link which suggests it's a MySQL bug:
https://groups.google.com/forum/#!topic/sqlalchemy/ajYLEuhEB9k
Thankfully, disabling supports_san_multi_rowcount helped!
engine = engine_from_config(settings, 'sqlalchemy.')
engine.dialect.supports_sane_rowcount = engine.dialect.supports_sane_multi_rowcount = False
Good enough for me for now. Here's another interesting resources I found along the way dealing with postgresql:
https://bitbucket.org/zzzeek/sqlalchemy/issue/3015/deletes-executed-twice-when-using
I have the following model definitions
class Foo(Base):
__tablename__ = 'foo'
id = Column(Integer, primary_key=True)
name = Column(String(200))
class FooCycle(Base):
__tablename__ = 'foocycle'
foo_id = Column(
String(50),
ForeignKey('foo.id'),
primary_key=True
)
some_number = Column(
Integer,
primary_key=True,
)
foo = relationship("Foo", backref="cycles")
and the following test case
class HierarchicModelTest(unittest.TestCase):
def test_create_data_via_orm_save_twice(self):
# get_session is a convenience wrapper to access a scoped session object
s = get_session()
def create_foo():
foo = Foo(id="12345", name="fancy foo")
foo.cycles = [FooCycle(some_number=1)]
return foo
# initially create foo
foo = create_foo()
s.add(foo)
s.flush()
# recreating foo, using merge to update into database
foo = create_foo()
s.merge(foo)
# raises Exception: Dependency rule tried to blank-out primary key
# column 'foocycle.foo_id' on instance '<FooCycle at 0x32e6b10>'
s.flush()
The test fails with a neat little stack trace and the final assertion error, telling me that the "Dependency rule tried to blank-out primary key column 'foocycle.foo_id". I'm assuming SQLAlchemy cannot, or doesn't want to calculate the value for foo_id on FooCycle itself. I can explicitly set this value myself in create_foo:
def create_foo():
foo = Foo(id="12345", name="fancy foo")
foo.cycles = [FooCycle(some_number=1, foo_id="12345")]
return foo
But, due to conciseness, architectural considerations and admittedly personal pride I don't want to. Is there a simple way to get SQLAlchemy to resolve this issue. I haven't quite grasped the purpose of the dependency rule. Any pointers/information on that issue?
Stack Trace:
# Test 1 of 7:
# test_core.HierarchicModelTest.test_create_data_via_orm_save_twice
===============
HierarchicModelTest: test_create_data_via_orm_save_twice (tests.test_core.HierarchicModelTest)
Failed test "test_create_data_via_orm_save_twice (tests.test_core.HierarchicModelTest)"! Reason: Dependency rule tried to blank-out primary key column 'foocycle.foo_id' on instance '<FooCycle at 0x39cda10>'
Traceback (most recent call last):
File "/home/xxx/xxx/xxx/backend/tests/test_core.py", line 115, in test_create_data_via_orm_save_twice
s.flush()
File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/orm/scoping.py", line 149, in do
return getattr(self.registry(), name)(*args, **kwargs)
File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1879, in flush
self._flush(objects)
File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1997, in _flush
transaction.rollback(_capture_exception=True)
File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/util/langhelpers.py", line 57, in __exit__
compat.reraise(exc_type, exc_value, exc_tb)
File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1961, in _flush
flush_context.execute()
File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.py", line 370, in execute
rec.execute(self)
File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.py", line 479, in execute
self.dependency_processor.process_saves(uow, states)
File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/orm/dependency.py", line 552, in process_saves
uowcommit, False)
File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/orm/dependency.py", line 569, in _synchronize
sync.clear(dest, self.mapper, self.prop.synchronize_pairs)
File "/home/xxx/xxx/xxx/.env/lib/python2.7/site-packages/sqlalchemy/orm/sync.py", line 53, in clear
(r, orm_util.state_str(dest))
AssertionError: Dependency rule tried to blank-out primary key column 'foocycle.foo_id' on instance '<FooCycle at 0x39cda10>'
Based on the comment by van I was able to work out a solution. The default relationship cascade is "save-update, merge". I had to set this to "save-update, merge, delete, delete-orphan".
Adding delete by itself did not change the behavior, delete-orphan was necessary.
Adding only delete-orphan made a deletion testcase fail, with the "dependency rule" assertion error, mentioned in the question:
class HierarchicModelTest(unittest.TestCase):
def test_delete_parent_object(self):
foo = Foo(**foo_data).save()
self.assertEqual(Foo.query.count(), 1)
self.assertEqual(FooCycle.query.count(), 1)
s = get_session()
s.delete(foo)
s.flush()
self.assertEqual(Foo.query.count(), 0)
self.assertEqual(FooCycle.query.count(), 0)
--
File "/home/xxx/xxx/xxx/backend/tests/test_core.py", line 128, in test_delete_parent_object
s.flush()
[...]
AssertionError: Dependency rule tried to blank-out primary key column 'foocycle.foo_id' on instance '<FooCycle at 0x37a1710>'
From the SQLAlchemy docs:
delete-orphan cascade adds behavior to the delete cascade, such that a child object will be marked for deletion when it is de-associated from the parent, not just when the parent is marked for deletion.
So, the correct definition of the FooCycle Model is
class FooCycle(Base):
__tablename__ = 'foocycle'
foo_id = Column(
String(50),
ForeignKey('foo.id'),
primary_key=True
)
some_number = Column(
Integer,
primary_key=True,
)
foo = relationship("Foo",
backref=backref("cycles",
cascade="save-update, merge, "
"delete, delete-orphan"))
I have set up a simple system for adding comments to items in my database using a one-to-many relation (one item can have many comments), but I can't seem to get it to work. It should be simple, I know. It's probably something simple that I've overlooked. I would very much appriciate a second set of eyes on this. Can anyone help?
I have a model defined with SQLAlchemy that looks like this:
class Item(Base):
__tablename__ = 'items' # Parent
id = Column(Integer, primary_key=True)
comments = relationship("Comment", backref='items')
class Comment(Base):
__tablename__ = 'comments' # Child
id = Column(Integer, primary_key=True)
comment_text = Column(Text, nullable=False)
item_id = Column(Integer, ForeignKey('items.id'), nullable=False)
def __init__(self, comment_text, item_id):
self.comment_text = comment_text
self.item_id = item_id
In my view, I do some work, then try to add a comment object:
item = DBSession.query(Item).filter(Item.id == item_id).first()
try:
print('Item id:', item.id, 'Comment text:', comment)
print('Item Comments:', item.comments)
cm = Comment(comment_text=comment,
item_id=item.id)
print('a')
item.comments.append(cm)
#DBSession.add(cm)
print('b')
DBSession.commit()
except:
DBSession.rollback()
print('c')
Note that I tried both item.comments.append(cm) and DBSession.add(cm) with identical results. That's why one of the two is commented out in the above code block. I also tried item.comments.append(Comment(...)) with identical results.
Now, when I try to add a comment, I get a stacktrace, culminating in:
sqlalchemy.exc.ResourceClosedError: This transaction is closed
The whole trace, including the debug prints, look like this:
Item id: 1 Comment text: test
Item Comments: []
a
c
Traceback (most recent call last):
File "C:\Python33\lib\wsgiref\handlers.py", line 137, in run
self.result = application(self.environ, self.start_response)
File "C:\Users\[user]\PYTHON~1.5\lib\site-packages\pyramid\router.py", line 251, in __call__
response = self.invoke_subrequest(request, use_tweens=True)
File "C:\Users\[user]\PYTHON~1.5\lib\site-packages\pyramid\router.py", line 227, in invoke_subrequest
response = handle_request(request)
File "C:\Users\[user]\PYTHON~1.5\lib\site-packages\pyramid\tweens.py", line 21, in excview_tween
response = handler(request)
File "C:\Users\[user]\PYTHON~1.5\lib\site-packages\pyramid_tm\__init__.py", line 82, in tm_tween
reraise(*exc_info)
File "C:\Users\[user]\PYTHON~1.5\lib\site-packages\pyramid_tm\compat.py", line 13, in reraise
raise value
File "C:\Users\[user]\PYTHON~1.5\lib\site-packages\pyramid_tm\__init__.py", line 70, in tm_tween
manager.commit()
File "C:\Users\[user]\PYTHON~1.5\lib\site-packages\transaction\_manager.py", line 111, in commit
return self.get().commit()
File "C:\Users\[user]\PYTHON~1.5\lib\site-packages\transaction\_transaction.py", line 280, in commit
reraise(t, v, tb)
File "C:\Users\[user]\PYTHON~1.5\lib\site-packages\transaction\_compat.py", line 55, in reraise
raise value
File "C:\Users\[user]\PYTHON~1.5\lib\site-packages\transaction\_transaction.py", line 271, in commit
self._commitResources()
File "C:\Users\[user]\PYTHON~1.5\lib\site-packages\transaction\_transaction.py", line 417, in _commitResources
reraise(t, v, tb)
File "C:\Users\[user]\PYTHON~1.5\lib\site-packages\transaction\_compat.py", line 55, in reraise
raise value
File "C:\Users\[user]\PYTHON~1.5\lib\site-packages\transaction\_transaction.py", line 394, in _commitResources
rm.tpc_vote(self)
File "C:\Users\[user]\PYTHON~1.5\lib\site-packages\zope\sqlalchemy\datamanager.py", line 100, in tpc_vote
self.tx.commit()
File "C:\Users\[user]\PYTHON~1.5\lib\site-packages\sqlalchemy\orm\session.py", line 352, in commit
self._assert_active(prepared_ok=True)
File "C:\Users\[user]\PYTHON~1.5\lib\site-packages\sqlalchemy\orm\session.py", line 203, in _assert_active
raise sa_exc.ResourceClosedError(closed_msg)
sqlalchemy.exc.ResourceClosedError: This transaction is closed
Well, turns out the problem was a few corrupt files, not a programming error. Sigh. Well, problem solved. :)