if ourUser.addressesrelationship does not havedeletecascade, SQLAlchemy’s default behavior is to instead de-associateaddress1andaddress2fromuser1by setting their foreign key reference toNULL.
from sqlalchemy import Column, FetchedValue, ForeignKey, Integer, MetaData, String, Table, create_engine, eventfrom sqlalchemy.ext.declarative import declarative_basefrom sqlalchemy.orm import backref, relationship, sessionmaker
Base = declarative_base()
class House(Base): __tablename__ = "house"
id = Column('id', Integer, primary_key=True) name = Column(String)
dogs = relationship('Dog', backref=backref("house", uselist=False))
def __repr__(self): return "House({})".format(self.name)
class Cat(Base): ''' Cat has a many-to-many relationship via a secondary table, but constrained to be unique and single-valued on both sides. ''' __tablename__ = "cat"
id = Column('id', Integer, primary_key=True) name = Column(String)
house_cat = Table("house_cat", Base.metadata, Column("id_house", Integer, ForeignKey(House.id), primary_key=True), Column("id_cat", Integer, ForeignKey(id), unique=True))
house = relationship(House, secondary=house_cat, uselist=False, backref=backref("cat", uselist=False))
def __repr__(self): return "Cat({})".format(self.name)
class Dog(Base): ''' Dog has a foreign key and a many-to-one to House, with a one-to-many backref. ''' __tablename__ = "dog"
id = Column('id', Integer, primary_key=True) name = Column(String) id_house = Column(Integer, ForeignKey(House.id), unique=True)
# house = relationship(House, uselist=False, backref=backref("dogs"))
def __repr__(self): return "Dog({})".format(self.name)
engine = create_engine('sqlite://')Session = sessionmaker(bind=engine)Base.metadata.create_all(engine)
session = Session()# session.bind.echo = True
def debug(desc, boolean, left, right): print "{:<40} {:<10} {:<20} {:<20}".format(desc, str(boolean), left, right)
bedsit = House(name='bedsit')snoopy = Dog(name='snoopy', house=bedsit)session.add(bedsit)session.flush()debug("bedsit.dogs == [snoopy]", bedsit.dogs == [snoopy], bedsit.dogs, [snoopy])debug("snoopy.house is bedsit", snoopy.house is bedsit, snoopy.house, bedsit)
session.delete(snoopy)debug("bedsit.dogs == []", bedsit.dogs == [], bedsit.dogs, [])
flat = House(name='flat')muffin = Dog(name='muffin', house=flat)session.add(flat)session.flush()debug("flat.dogs == [muffin]", flat.dogs == [muffin], flat.dogs, [muffin])debug("muffin.house == flat", muffin.house == flat, muffin.house, flat)
session.delete(flat)session.flush()debug("muffin.id_house is None", muffin.id_house is None, muffin.id_house, None)debug("muffin.house is None", muffin.house is None, muffin.house, None)
mansion = House(name='mansion')koshka = Cat(name='koshka', house=mansion)session.add(mansion)session.flush()debug("koshka.house == mansion", koshka.house == mansion, koshka.house, mansion)
session.delete(koshka)
session.rollback()Assertion Passes Actual value Expected value
========= ====== ============ ==============
bedsit.dogs == [snoopy] True [Dog(snoopy)] [Dog(snoopy)]
snoopy.id_house == bedsit.id True 1 1
snoopy.house is bedsit True House(bedsit) House(bedsit)
bedsit.dogs == [] False [Dog(snoopy)] []
flat.dogs == [muffin] True [Dog(muffin)] [Dog(muffin)]
muffin.id_house == flat.id True 2 2
muffin.house == flat True House(flat) House(flat)
muffin.id_house is None True None None
muffin.house is None False House(flat) None
mansion.cat is koshka True Cat(koshka) Cat(koshka)
koshka.house == mansion True House(mansion) House(mansion)
mansion.cat is None False Cat(koshka) None
self.transaction.__exit__(a_type, value, traceback)
File "R:\sw\external\20151014-0\python27\lib\site-packages\sqlalchemy-1.0.8-py2.7-win32.egg\sqlalchemy\orm\session.py", line 490, in __exit__
self.rollback()
File "R:\sw\external\20151014-0\python27\lib\site-packages\sqlalchemy-1.0.8-py2.7-win32.egg\sqlalchemy\util\langhelpers.py", line 60, in __exit__
compat.reraise(exc_type, exc_value, exc_tb)
File "R:\sw\external\20151014-0\python27\lib\site-packages\sqlalchemy-1.0.8-py2.7-win32.egg\sqlalchemy\orm\session.py", line 487, in __exit__
self.commit()
File "R:\sw\external\20151014-0\python27\lib\site-packages\sqlalchemy-1.0.8-py2.7-win32.egg\sqlalchemy\orm\session.py", line 392, in commit
self._prepare_impl()
File "R:\sw\external\20151014-0\python27\lib\site-packages\sqlalchemy-1.0.8-py2.7-win32.egg\sqlalchemy\orm\session.py", line 372, in _prepare_impl
self.session.flush()
File "R:\sw\external\20151014-0\python27\lib\site-packages\sqlalchemy-1.0.8-py2.7-win32.egg\sqlalchemy\orm\session.py", line 2004, in flush
self._flush(objects)
File "R:\sw\external\20151014-0\python27\lib\site-packages\sqlalchemy-1.0.8-py2.7-win32.egg\sqlalchemy\orm\session.py", line 2122, in _flush
transaction.rollback(_capture_exception=True)
File "R:\sw\external\20151014-0\python27\lib\site-packages\sqlalchemy-1.0.8-py2.7-win32.egg\sqlalchemy\util\langhelpers.py", line 60, in __exit__
compat.reraise(exc_type, exc_value, exc_tb)
File "R:\sw\external\20151014-0\python27\lib\site-packages\sqlalchemy-1.0.8-py2.7-win32.egg\sqlalchemy\orm\session.py", line 2086, in _flush
flush_context.execute()
File "R:\sw\external\20151014-0\python27\lib\site-packages\sqlalchemy-1.0.8-py2.7-win32.egg\sqlalchemy\orm\unitofwork.py", line 373, in execute
rec.execute(self)
File "R:\sw\external\20151014-0\python27\lib\site-packages\sqlalchemy-1.0.8-py2.7-win32.egg\sqlalchemy\orm\unitofwork.py", line 485, in execute
self.dependency_processor.process_deletes(uow, states)
File "R:\sw\external\20151014-0\python27\lib\site-packages\sqlalchemy-1.0.8-py2.7-win32.egg\sqlalchemy\orm\dependency.py", line 1023, in process_deletes
secondary_update, secondary_delete)
File "R:\sw\external\20151014-0\python27\lib\site-packages\sqlalchemy-1.0.8-py2.7-win32.egg\sqlalchemy\orm\dependency.py", line 1111, in _run_crud
result.rowcount)
StaleDataError: DELETE statement on table 'blah' expected to delete 2 row(s); Only 0 were matched.
OK, sorry, you've misunderstood the documentation. When it says
"delete", it refers to the *parent* object that refers to the *child*,
that is, the parent is the one that the foreign key refers *to*, and the
child is the one that has a foreign key reference to the parent. This
is the opposite case. You're deleting one of the objects that
references the parent, that does not inherently affect the parent until
it is freshly re-loaded from the database, such as after a commit.
This is why it's better to deal with manipulation of the collection,
instead of using session.delete(). See the section
http://docs.sqlalchemy.org/en/rel_1_0/orm/session_basics.html#deleting-from-collections.
delete-orphan cascade implies that each child object can only have one parent at a time,