If there is a way to avoid those updates for orphaned children and just ignore them if they aren't included in the incoming data by primary key, that would really help me, if not, I can look into adding them all to the incoming entity so they'll be ignored.
from sqlalchemy import Column, Integer, String, ForeignKey, create_engine, select
from sqlalchemy.orm import registry, declarative_base, relationship, sessionmaker
from sqlalchemy.sql.sqltypes import Numeric
import unittest
Base = declarative_base()
Mapper_Registry = registry()
### MODELS
@Mapper_Registry.mapped
class Item:
__tablename__ = 'item'
id = Column(Integer, primary_key=True)
model_number = Column(String)
item_prices = relationship("Item_Price", back_populates="item", lazy="joined")
@Mapper_Registry.mapped
class Item_Price:
__tablename__ = 'item_price'
id = Column(Integer, primary_key=True)
item_id = Column(Integer, ForeignKey('
item.id'))
price = Column(Numeric)
item = relationship("Item", back_populates="item_prices", lazy="joined", viewonly=True)
### TESTS
class Test_OrphanRecordFKMerge(unittest.TestCase):
engine = create_engine('sqlite:///:memory:', echo=True, echo_pool='debug', future=True)
Session = sessionmaker(bind=engine)
session = Session()
def setUp(self):
Base2 = Mapper_Registry.generate_base()
Base2.metadata.create_all(self.engine)
# Create a base item to run tests on
t_item = Item()
t_item.model_number = 'TestItem'
t_price1 = Item_Price()
t_price1.price = 1.00
t_item.item_prices.append(t_price1)
t_price2 = Item_Price()
t_price2.price = 4.00
t_item.item_prices.append(t_price2)
self.session.add(t_item)
self.session.commit()
def tearDown(self):
Base.metadata.drop_all(self.engine)
def test_item_update(self):
self.session.expunge_all()
# Incoming item data from remote api or flat file
incoming_item = Item()
incoming_item.model_number = 'TestItem'
incoming_price1 = Item_Price()
incoming_price1.price = 777.00
incoming_item.item_prices.append(incoming_price1)
# Now we have an incoming item, we need to query the database for the existing item and reconcile the primary keys
# so that it can be updated correctly
persisted_item = self.session.execute(select(Item).where(Item.model_number == 'TestItem')).scalars().first()
# let us imagine that the new price should not overwrite either old price
self.session.merge(incoming_item)
self.session.commit()
self.session.expunge_all()
final_result = self.session.execute(select(Item).where(Item.model_number == 'TestItem')).scalars().first()
# the following test fails as both the other price records have had their foreign keys set to null after the merge
# so the len(final_result.item_prices) == 1
self.assertEqual(len(final_result.item_prices), 3)
if __name__ == '__main__':
unittest.main()
2021-12-08 09:34:46,053 INFO sqlalchemy.engine.Engine UPDATE item_price SET item_id=? WHERE
2021-12-08 09:34:46,053 INFO sqlalchemy.engine.Engine [generated in 0.00056s] ((None, 1), (None, 2))
2021-12-08 09:34:46,055 INFO sqlalchemy.engine.Engine INSERT INTO item_price (item_id, price) VALUES (?, ?)
2021-12-08 09:34:46,055 INFO sqlalchemy.engine.Engine [cached since 0.03964s ago] (1, 777.0)