On Fri, May 18, 2018 at 9:06 PM, Diego Quintana <daquinta...@gmail.com> wrote: > So I watched the video, made a lot of stuff clear. What I'm not certain > about is the symmetry of relationships. > > > I used to think that, in many to many relationships (perhaps for all of the > relationships available) > > parent.children.append(child) > and > > child.parents.append(parent)
well in the simple case with backrefs + secondary, they are. with this specific thing you're doing with multiple association tables and events firing off additional mutations, I have no idea. this case is deeply complicated. > > > were equivalent. I have improved to test this but it is working for only one > side: > > import logging > import sys I'll have to find time to step through this as these tests are very long. if you can narrow down the thing you want to illustrate to just a few lines that would make it easier. > > import sqlalchemy as sa > from sqlalchemy.ext.declarative import declarative_base > > # setup logger > stdout_handler = logging.StreamHandler(sys.stdout) > formatter = > logging.Formatter('%(asctime)s:%(filename)s:%(lineno)d\t%(levelname)s\t%(message)s') > stdout_handler.setFormatter(formatter) > > logger = logging.getLogger('sqlalchemy.engine') > logger.setLevel(logging.DEBUG) > logger.addHandler(stdout_handler) > > Base = declarative_base() > > # many to many relationship between parents and children > parents_children_relationship = sa.Table('parents_children_relationship', > Base.metadata, > sa.Column('parent_id', sa.Integer, sa.ForeignKey('parents.id')), > sa.Column('child_id', sa.Integer, sa.ForeignKey('children.id')), > sa.UniqueConstraint('parent_id', 'child_id')) > > # many to many relationship between User and Pet > parents_pets_relationship = sa.Table('parents_pets_relationship', > Base.metadata, > sa.Column('parent_id', sa.Integer, sa.ForeignKey('parents.id')), > sa.Column('pet_id', sa.Integer, sa.ForeignKey('pets.id')), > sa.UniqueConstraint('parent_id', 'pet_id')) > > class Parent(Base): > __tablename__ = 'parents' > > id = sa.Column(sa.Integer, primary_key=True) > name = sa.Column(sa.String(64)) > > # many to many relationship between parent and children > # my case allows for a children to have many parents. Don't ask. > children = sa.orm.relationship('Child', > secondary=parents_children_relationship, > backref=sa.orm.backref('parents', lazy='dynamic' > ), > lazy='dynamic') > > # many to many relationship between parents and pets > pets = sa.orm.relationship('Pet', > secondary=parents_pets_relationship, > backref=sa.orm.backref('parents', lazy='dynamic' > ), # > lazy='dynamic') > > > def __repr__(self): > return '<Parent (name=%r)>' % (self.name) > > class Child(Base): > __tablename__ = 'children' > id = sa.Column(sa.Integer, primary_key=True) > name = sa.Column(sa.String(64)) > # parents = <backref relationship with User model> > > # one to many relationship with pets > pets = sa.orm.relationship('Pet', backref='child', lazy='dynamic') > > def __repr__(self): > return '<Child (name=%r)>' % (self.name) > > class Pet(Base): > __tablename__ = 'pets' > id = sa.Column(sa.Integer, primary_key=True) > name = sa.Column(sa.String(64)) > # child = backref relationship with cities > child_id = sa.Column(sa.Integer, sa.ForeignKey('children.id'), nullable= > True) > # parents = <relationship backref from User> > > def __repr__(self): > return '<Pet (name=%r)>' % (self.name) > > > from sqlalchemy.orm import object_session > > @sa.event.listens_for(Parent.children, 'append') > def _on_append_children(parent, child, initiator): > """ > If a new child is appended to the parent, this listener > will also add the pets bound to the child being bound to the parent. > """ > # appends also the pets bound to the child that the > # parent is being appended to > > logger.debug(f'**********1. adding the pets of {child} to > {parent}***************') > > object_session(parent).execute( > "INSERT INTO parents_pets_relationship VALUES " > "(:parent_id, :pet_id)", > [ > {"parent_id": parent.id, "pet_id": pet.id} > for pet in child.pets > ] > ) > > logger.debug('**********1. done!***************') > > @sa.event.listens_for(Parent.children, 'remove') > def _on_remove_children(parent, child, initiator, *args, **kwargs): > """ > If a child is removed from the parent, this listener > will also remove only remove_single_pet --> <Pet> > """ > > object_session(parent).execute( > "DELETE FROM parents_pets_relationship WHERE " > "parent_id=:parent_id AND pet_id=:pet_id", > [ > {"parent_id": parent.id, "pet_id": pet.id} > for pet in child.pets > ] > ) > > > @sa.event.listens_for(Parent.pets, 'remove') > def _on_remove_pets(parent, pet, initiator, *args, **kwargs): > """ > If a pet is removed from the parent, and the parent also is related > to the child that has access to that pet, then > > * removes relationship with the child, and > * keeps relationship with the remaining pets, except the one that was > removed > """ > > object_session(parent).execute( > "DELETE FROM parents_children_relationship WHERE " > "parent_id=:parent_id AND child_id=:child_id", > {"parent_id": parent.id, "child_id": pet.child.id} > ) > > #### test ### > > import unittest > from sqlalchemy import create_engine > from sqlalchemy.orm import sessionmaker > > class BasicTestModelCase(unittest.TestCase): > > def setUp(self): > self.engine = create_engine("sqlite://", echo=False) > Base.metadata.create_all(self.engine) > > Session = sessionmaker(bind=self.engine) > self.session = Session() > > > def tearDown(self): > Base.metadata.drop_all(bind=self.engine) > > def test_child_pet_relationship_on_parents_combined(self): > """ > Test that a parent can be hold children and pets that don't > belong necessary to the child, given the behaviour tested in the > previous test. > """ > > # create new parent > test_parent = Parent(name='test_parent') > > child1 = Child(id=1, > name='FakeChild1') > > child2 = Child(id=2, > name='FakeChild2') > > pet1 = Pet(id=1, > name='FakePet1', > child_id=1) > > pet2 = Pet(id=2, > name='FakePet2', > child_id=2) > > pet3 = Pet(id=3, > name='FakePet3', > child_id=1) > > self.session.add(test_parent) > self.session.add(child1) > self.session.add(child2) > self.session.add(pet1) > self.session.add(pet2) > self.session.add(pet3) > self.session.commit() > > # add parent to the child > logger.debug('************A - add test_parent to > child1***************') > child1.parents.append(test_parent) > self.session.add(child1) > self.session.commit() > logger.debug('**********A - done!***************') > > # add parent to the child > pet2.parents.append(test_parent) > logger.debug('************B - add test_parent to > child1***************') > > # persist changes in the db > self.session.add(pet2) > self.session.commit() > logger.debug('**********B - done!***************') > > print(test_parent.pets.all()) > print(child2.pets.all()) > > # check that previous relationships are intact > self.assertTrue(child1.pets.all() == [pet1, pet3]) > self.assertTrue(child2.pets.all() == [pet2]) > > # resultant elements should be only child1, its pets and the single > Pet > self.assertTrue(test_parent.children.all() == [child1]) > self.assertTrue(test_parent.pets.all() == [pet1, pet2, pet3]) > > # remove child from parent > logger.debug('***********C - remove test_parent from > pet3****************') > pet3.parents.remove(test_parent) ## ERROR here > logger.debug('**********C - done!***************') > > # resultant elements should be remaining pets, and no child > self.assertTrue(test_parent.children.all() == []) > self.assertTrue(test_parent.pets.all() == [pet1, pet2]) # pet2 was > not touched, > # but pet1 > should remain > # since only > # pet3 was > removed > # child1 > should be also removed since > # > relationship is unbalanced, i.e. > # user can't > have access to a child if it > # does not > have access to all of the child's pets > > def test_child_pet_relationship_on_parents_combined_reversed(self): > """ > Test that a parent can be hold children and pets that don't > belong necessary to the child, given the behaviour tested in the > previous test. > """ > > # create new parent > test_parent = Parent(name='test_parent') > > child1 = Child(id=1, > name='FakeChild1') > > child2 = Child(id=2, > name='FakeChild2') > > pet1 = Pet(id=1, > name='FakePet1', > child_id=1) > > pet2 = Pet(id=2, > name='FakePet2', > child_id=2) > > pet3 = Pet(id=3, > name='FakePet3', > child_id=1) > > self.session.add(test_parent) > self.session.add(child1) > self.session.add(child2) > self.session.add(pet1) > self.session.add(pet2) > self.session.add(pet3) > self.session.commit() > > > logger.debug('************A` - add child1 to > test_parent***************') > # add parent to the child > test_parent.children.append(child1) > self.session.add(test_parent) > self.session.commit() > logger.debug('**********A` - done!***************') > > > logger.debug('************B` - add pet2 to > test_parent***************') > # add parent to the child > test_parent.pets.append(pet2) > > # persist changes in the db > self.session.add(test_parent) > self.session.commit() > logger.debug('**********B` - done!***************') > > # check that previous relationships are intact > self.assertTrue(child1.pets.all() == [pet1, pet3]) > self.assertTrue(child2.pets.all() == [pet2]) > > # resultant elements should be only child1, its pets and the single > Pet > self.assertTrue(test_parent.children.all() == [child1]) > self.assertTrue(test_parent.pets.all() == [pet1, pet2, pet3]) > > # remove child from parent > logger.debug('***********C` - remove pet3 from > test_parent****************') > test_parent.pets.remove(pet3) > logger.debug('**********C` - done!***************') > > # resultant elements should be remaining pets, and no child > self.assertTrue(test_parent.children.all() == []) > self.assertTrue(test_parent.pets.all() == [pet1, pet2]) # pet2 was > not touched, > # but pet1 > should remain > # since only > # pet3 was > removed > # child1 > should be also removed since > # > relationship is unbalanced, i.e. > # user can't > have access to a child if it > # does not > have access to all of the child's pets > > > > import sys > > if __name__ == '__main__': > # # run tests > unittest.main() > > Checking the logs I see that in the second test > > 2018-05-18 20:54:58,686:pets2.py:284 DEBUG ************A` - add child1 > to test_parent*************** > 2018-05-18 20:54:58,692:base.py:682 INFO BEGIN (implicit) > 2018-05-18 20:54:58,694:base.py:1151 INFO SELECT children.id AS > children_id, children.name AS children_name > FROM children > WHERE children.id = ? > 2018-05-18 20:54:58,695:base.py:1154 INFO (1,) > 2018-05-18 20:54:58,696:result.py:681 DEBUG Col ('children_id', > 'children_name') > 2018-05-18 20:54:58,696:result.py:1106 DEBUG Row (1, 'FakeChild1') > 2018-05-18 20:54:58,698:base.py:1151 INFO SELECT parents.id AS > parents_id, parents.name AS parents_name > FROM parents > WHERE parents.id = ? > 2018-05-18 20:54:58,698:base.py:1154 INFO (1,) > 2018-05-18 20:54:58,699:result.py:681 DEBUG Col ('parents_id', > 'parents_name') > 2018-05-18 20:54:58,699:result.py:1106 DEBUG Row (1, 'test_parent') > 2018-05-18 20:54:58,700:pets2.py:102 DEBUG **********1. adding the pets > of <Child (name='FakeChild1')> to <Parent > (name='test_parent')>*************** > 2018-05-18 20:54:58,703:base.py:1151 INFO INSERT INTO > parents_children_relationship (parent_id, child_id) VALUES (?, ?) > 2018-05-18 20:54:58,703:base.py:1154 INFO (1, 1) > 2018-05-18 20:54:58,705:base.py:1151 INFO SELECT pets.id AS pets_id, > pets.name AS pets_name, pets.child_id AS pets_child_id > FROM pets > WHERE ? = pets.child_id > 2018-05-18 20:54:58,705:base.py:1154 INFO (1,) > 2018-05-18 20:54:58,705:result.py:681 DEBUG Col ('pets_id', 'pets_name', > 'pets_child_id') > 2018-05-18 20:54:58,706:result.py:1106 DEBUG Row (1, 'FakePet1', 1) > 2018-05-18 20:54:58,706:result.py:1106 DEBUG Row (3, 'FakePet3', 1) > 2018-05-18 20:54:58,707:base.py:1151 INFO INSERT INTO > parents_pets_relationship VALUES (?, ?) > 2018-05-18 20:54:58,707:base.py:1154 INFO ((1, 1), (1, 3)) > 2018-05-18 20:54:58,707:pets2.py:113 DEBUG **********1. > done!*************** > 2018-05-18 20:54:58,709:base.py:1151 INFO INSERT INTO > parents_children_relationship (parent_id, child_id) VALUES (?, ?) > 2018-05-18 20:54:58,709:base.py:1154 INFO (1, 1) > 2018-05-18 20:54:58,710:base.py:702 INFO ROLLBACK > > I really don't understand why both are different cases, and in particular > why one fails and the other does not. > > Thanks again for your time! > > > Am Freitag, 18. Mai 2018 17:13:23 UTC-4 schrieb Mike Bayer: >> >> On Fri, May 18, 2018 at 4:44 PM, Diego Quintana <daqui...@gmail.com> >> wrote: >> > So I'm back to this, and I wonder about something you said: >> >> >> >> >> >> the main complication here is that those "dynamic" relationships >> >> require that a query runs for everything, which means everything has >> >> to be in the database, which means it flushes the session very >> >> aggressively (and also disabling the flush, another thing I tried, >> >> means it doesn't read the contents of the collections accurately), and >> >> all of that makes an already tricky operation nearly impossible >> >> without it barreling into itself. >> >> >> >> If you are committed to using the "dynamic" relationships, you can >> >> always rely on emitting SQL to read from those association tables, and >> >> there's a completely unorthodox way to do this which would be way more >> >> efficient in most cases, and is extremely simple, just emit the DELETE >> >> statements: >> > >> > >> > So, I don't know actually if I have to be committed to one approach over >> > the >> > other. I was trying to leverage the orm layer for consistency, but if >> > there >> > is another approach to achieve the same thing I would like to know. >> > Also, >> > you mentioned a test you've added, do you mind to share it? >> > >> > BTW, the code bit you changed makes it work, thanks!. There are a lot of >> > things I don't understand. But first, why does I need to use >> > `session_object`? to expose the `execute` method? What is that I am >> > "bypassing" by using this over the orm? >> >> object_session() gives you the Session that has loaded / persisted >> that object for you, assuming that Session is still around. This is >> important because when you deal with the object you are really dealing >> with a proxy for a row in the database, local to a specific >> transaction. Other transactions might see a totally different row. >> So you want to stick to that one transaction when you emit SQL. >> >> not sure if you watched my video on this, I try to go into it pretty >> deep: http://www.sqlalchemy.org/library.html#thesqlalchemysessionindepth >> >> >> > >> > >> > Am Freitag, 4. Mai 2018 11:40:34 UTC-3 schrieb Diego Quintana: >> >> >> >> At the moment I've moved to other features, but I should be back to >> >> this >> >> somewhere in the near future. I will let you know the results. >> >> >> >> I really appreciate your time, thanks again. >> >> >> >> Best, >> >> >> >> Am Donnerstag, 3. Mai 2018 10:10:47 UTC-3 schrieb Mike Bayer: >> >>> >> >>> On Thu, May 3, 2018 at 7:39 AM, Diego Quintana <daqui...@gmail.com> >> >>> wrote: >> >>> > Thanks again for your reply >> >>> > >> >>> >> at the core is that when you remove a child from the parent in the >> >>> >> _remove_pets event, you want to prevent the _remove_children() >> >>> >> event >> >>> >> from actually happening, I think. >> >>> > >> >>> > >> >>> > Yes, since it is a different usage case or flavour. I was trying to >> >>> > pass >> >>> > kwargs to the event listener directly, but it does not work. >> >>> > >> >>> >> If I remove a pet from a parent, then we remove the child from the >> >>> >> parent, and *only* that pet. we dont remove other pets that might >> >>> >> be >> >>> >> associated with that child. >> >>> > >> >>> > >> >>> > Correct, this would be case A and the idea behind it is that, if a >> >>> > User >> >>> > has >> >>> > *access* to a Child, it should also have access to all of the >> >>> > Child's >> >>> > pets. >> >>> > If for some reason Parent does not have access to all of them, means >> >>> > it >> >>> > does >> >>> > not have access to the Child either. >> >>> > >> >>> > Removing a pet that has a Child that is also present in >> >>> > user.children >> >>> > should >> >>> > trigger this, leaving the parent with, as you say, only pets minus >> >>> > the >> >>> > one >> >>> > that was removed. It would also remove the child from the >> >>> > parent/children >> >>> > relationship in the association table. >> >>> > >> >>> >> if I remove a child from the parent, then we remove *all* pets >> >>> >> associated with the child from that parent. >> >>> > >> >>> > >> >>> > Correct, and this would be case B >> >>> > >> >>> >> This seems like it's a contradiction. I have parent p1, not >> >>> >> referring >> >>> >> to child c1, but it refers to pet p1 which *does* refer to child >> >>> >> c1, >> >>> >> and that is valid. There's basically two flavors of "remove >> >>> >> child >> >>> >> from parent", is that right? >> >>> > >> >>> > >> >>> > Yes, both relationships -parent/pet and parent/child- are not seeing >> >>> > each >> >>> > other, and the only binding is >> >>> > their relationship between child and pets. >> >>> > >> >>> > Thus, the two flavors are depending on where is that listener being >> >>> > called >> >>> > from. For the case A, this is called inside the if statement in >> >>> > _remove_pets. Outside of this it should be always case B. >> >>> > >> >>> >> I tried to work on an implementation here which would also have to >> >>> >> be >> >>> >> extremely clever but I realized I don't actually understand what >> >>> >> this >> >>> >> is supposed to do. if "remove child from parent" has two different >> >>> >> flavors then there needs to be all kinds of trickery to protect the >> >>> >> events from each other. >> >>> > >> >>> > >> >>> > I understand that it requires a lot of fiddling. I was trying to >> >>> > pass >> >>> > kwargs >> >>> > to the listener directly, and parse them inside the other listener, >> >>> > but >> >>> > the >> >>> > other listener is not receiving them. I was seeing this is on >> >>> > purpose >> >>> > here , >> >>> > since accepting kwargs would pollute the API. Perhaps a custom event >> >>> > implementation? >> >>> >> >>> I slowly realized it looked like you hoped that flag would pass >> >>> through but there's too many layers of indirection for it to work that >> >>> way. the main complication here is that those "dynamic" relationships >> >>> require that a query runs for everything, which means everything has >> >>> to be in the database, which means it flushes the session very >> >>> aggressively (and also disabling the flush, another thing I tried, >> >>> means it doesn't read the contents of the collections accurately), and >> >>> all of that makes an already tricky operation nearly impossible >> >>> without it barreling into itself. >> >>> >> >>> If you are committed to using the "dynamic" relationships, you can >> >>> always rely on emitting SQL to read from those association tables, and >> >>> there's a completely unorthodox way to do this which would be way more >> >>> efficient in most cases, and is extremely simple, just emit the DELETE >> >>> statements: >> >>> >> >>> from sqlalchemy.orm import object_session >> >>> >> >>> @sa.event.listens_for(Parent.children, 'remove') >> >>> def _remove_children(parent, child, initiator, *args, **kwargs): >> >>> object_session(parent).execute( >> >>> "delete from parents_pets_relationship where " >> >>> "parent_id=:parent_id and pet_id=:pet_id", >> >>> [ >> >>> {"parent_id": parent.id, "pet_id": pet.id} >> >>> for pet in child.pets >> >>> ] >> >>> ) >> >>> >> >>> >> >>> @sa.event.listens_for(Parent.pets, 'remove') >> >>> def _remove_pets(parent, pet, initiator, *args, **kwargs): >> >>> object_session(parent).execute( >> >>> "delete from parents_children_relationship where " >> >>> "parent_id=:parent_id and child_id=:child_id", >> >>> {"parent_id": parent.id, "child_id": pet.child.id} >> >>> ) >> >>> >> >>> I added a second test for the other case and this works as far as I've >> >>> gotten it. since we are only dealing with these standalone >> >>> association tables the above is pretty simple, and now you only emit >> >>> one query (albeit a DML query, not a SELECT) rather than lots of >> >>> SELECT statements. The above is at least very simple to work >> >>> with since the ORM has no involvement past getting you those events. >> >>> >> >>> if the above does what you need then it's probably worth doing that >> >>> way. >> >>> >> >>> >> >>> >> >>> > >> >>> > Thanks again for your time >> >>> > >> >>> > >> >>> > >> >>> > Diego Alexandro Quintana Valenzuela >> >>> > Ingeniero Civil Electricista >> >>> > Universidad de la Frontera - Chile >> >>> > +56 9 7965 3455 >> >>> > >> >>> > IEEE PES & CIS Member >> >>> > diego.q...@ieee.org >> >>> > LinkedIn >> >>> > https://www.linkedin.com/in/diego-quintana-valenzuela/ >> >>> > >> >>> > 2018-05-02 18:46 GMT-03:00 Mike Bayer <mik...@zzzcomputing.com>: >> >>> >> >> >>> >> On Wed, May 2, 2018 at 12:22 PM, Diego Quintana >> >>> >> <daqui...@gmail.com> >> >>> >> wrote: >> >>> >> > Hello, thanks again for your help. I'm not sure I understand what >> >>> >> > you >> >>> >> > said >> >>> >> > totally, and I believe this is the most simple MCVE I can >> >>> >> > provide. >> >>> >> > >> >>> >> > My local tests use postgresql, but I'm setting an in-memory >> >>> >> > sqlite3 >> >>> >> > engine >> >>> >> > here. I'm not fond of the differences between two backends, but >> >>> >> > the >> >>> >> > tests >> >>> >> > run without problems. >> >>> >> >> >>> >> So this is great, and shows the problem. but what you are trying >> >>> >> to >> >>> >> do here is deeply complicated. I was going to just type out >> >>> >> everything I did to figure this out but this was way too long a >> >>> >> process. >> >>> >> >> >>> >> at the core is that when you remove a child from the parent in the >> >>> >> _remove_pets event, you want to prevent the _remove_children() >> >>> >> event >> >>> >> from actually happening, I think. >> >>> >> >> >>> >> If I remove a pet from a parent, then we remove the child from the >> >>> >> parent, and *only* that pet. we dont remove other pets that might >> >>> >> be >> >>> >> associated with that child. >> >>> >> >> >>> >> if I remove a child from the parent, then we remove *all* pets >> >>> >> associated with the child from that parent. >> >>> >> >> >>> >> This seems like it's a contradiction. I have parent p1, not >> >>> >> referring >> >>> >> to child c1, but it refers to pet p1 which *does* refer to child >> >>> >> c1, >> >>> >> and that is valid. There's basically two flavors of "remove >> >>> >> child >> >>> >> from parent", is that right? >> >>> >> >> >>> >> I tried to work on an implementation here which would also have to >> >>> >> be >> >>> >> extremely clever but I realized I don't actually understand what >> >>> >> this >> >>> >> is supposed to do. if "remove child from parent" has two different >> >>> >> flavors then there needs to be all kinds of trickery to protect the >> >>> >> events from each other. >> >>> >> >> >>> >> >> >>> >> >> >>> >> >> >>> >> >> >>> >> >> >>> >> >> >>> >> >> >>> >> >> >>> >> >> >>> >> > import sqlalchemy as sa >> >>> >> > from sqlalchemy.ext.declarative import declarative_base >> >>> >> > >> >>> >> > Base = declarative_base() >> >>> >> > >> >>> >> > # many to many relationship between parents and children >> >>> >> > parents_children_relationship = >> >>> >> > sa.Table('parents_children_relationship', >> >>> >> > Base.metadata, >> >>> >> > sa.Column('parent_id', sa.Integer, >> >>> >> > sa.ForeignKey('parents.id')), >> >>> >> > sa.Column('child_id', sa.Integer, >> >>> >> > sa.ForeignKey('children.id')), >> >>> >> > sa.UniqueConstraint('parent_id', 'child_id')) >> >>> >> > >> >>> >> > # many to many relationship between User and Pet >> >>> >> > parents_pets_relationship = sa.Table('parents_pets_relationship', >> >>> >> > Base.metadata, >> >>> >> > sa.Column('parent_id', sa.Integer, >> >>> >> > sa.ForeignKey('parents.id')), >> >>> >> > sa.Column('pet_id', sa.Integer, sa.ForeignKey('pets.id')), >> >>> >> > sa.UniqueConstraint('parent_id', 'pet_id')) >> >>> >> > >> >>> >> > class Parent(Base): >> >>> >> > __tablename__ = 'parents' >> >>> >> > >> >>> >> > id = sa.Column(sa.Integer, primary_key=True) >> >>> >> > name = sa.Column(sa.String(64)) >> >>> >> > >> >>> >> > # many to many relationship between parent and children >> >>> >> > # my case allows for a children to have many parents. Don't >> >>> >> > ask. >> >>> >> > children = sa.orm.relationship('Child', >> >>> >> > >> >>> >> > secondary=parents_children_relationship, >> >>> >> > backref=sa.orm.backref('parents', >> >>> >> > lazy='dynamic'), >> >>> >> > lazy='dynamic') >> >>> >> > >> >>> >> > # many to many relationship between parents and pets >> >>> >> > pets = sa.orm.relationship('Pet', >> >>> >> > secondary=parents_pets_relationship, >> >>> >> > backref=sa.orm.backref('parents', >> >>> >> > lazy='dynamic'), # >> >>> >> > lazy='dynamic') >> >>> >> > >> >>> >> > >> >>> >> > def __repr__(self): >> >>> >> > return '<Parent (name=%r)>' % (self.name) >> >>> >> > >> >>> >> > class Child(Base): >> >>> >> > __tablename__ = 'children' >> >>> >> > id = sa.Column(sa.Integer, primary_key=True) >> >>> >> > name = sa.Column(sa.String(64)) >> >>> >> > # parents = <backref relationship with User model> >> >>> >> > >> >>> >> > # one to many relationship with pets >> >>> >> > pets = sa.orm.relationship('Pet', backref='child', >> >>> >> > lazy='dynamic') >> >>> >> > >> >>> >> > def __repr__(self): >> >>> >> > return '<Child (name=%r)>' % (self.name) >> >>> >> > >> >>> >> > class Pet(Base): >> >>> >> > __tablename__ = 'pets' >> >>> >> > id = sa.Column(sa.Integer, primary_key=True) >> >>> >> > name = sa.Column(sa.String(64)) >> >>> >> > # child = backref relationship with cities >> >>> >> > child_id = sa.Column(sa.Integer, >> >>> >> > sa.ForeignKey('children.id'), >> >>> >> > nullable=True) >> >>> >> > # parents = <relationship backref from User> >> >>> >> > >> >>> >> > def __repr__(self): >> >>> >> > return '<Pet (name=%r)>' % (self.name) >> >>> >> > >> >>> >> > >> >>> >> > >> >>> >> > @sa.event.listens_for(Parent.children, 'append') >> >>> >> > def _append_children(parent, child, initiator): >> >>> >> > """ >> >>> >> > If a new child is appended to the parent, this listener >> >>> >> > will also add the pets bound to the child being bound to the >> >>> >> > parent. >> >>> >> > """ >> >>> >> > # appends also the pets bound to the child that the >> >>> >> > # parent is being appended to >> >>> >> > parent.pets.extend(child.pets.all()) >> >>> >> > >> >>> >> > @sa.event.listens_for(Parent.children, 'remove') >> >>> >> > def _remove_children(parent, child, initiator, *args, **kwargs): >> >>> >> > """ >> >>> >> > If a child is removed from the parent, this listener >> >>> >> > will also remove only remove_single_pet --> <Pet> >> >>> >> > """ >> >>> >> > >> >>> >> > remove_single_pet = kwargs.get('remove_single_pet', None) >> >>> >> > >> >>> >> > if remove_single_pet is not None: >> >>> >> > parent.pets.remove(remove_single_pet) >> >>> >> > else: # removes every pet >> >>> >> > for pet in child.pets: >> >>> >> > parent.pets.remove(pet) >> >>> >> > >> >>> >> > @sa.event.listens_for(Parent.pets, 'remove') >> >>> >> > def _remove_pets(parent, pet, initiator, *args, **kwargs): >> >>> >> > """ >> >>> >> > If a pet is removed from the parent, and the parent also is >> >>> >> > related >> >>> >> > to the child that has access to that pet, then >> >>> >> > >> >>> >> > * removes relationship with the child, and >> >>> >> > * keeps relationship with the remaining pets, except the one >> >>> >> > that >> >>> >> > was >> >>> >> > removed >> >>> >> > """ >> >>> >> > >> >>> >> > if pet.child in parent.children.all(): >> >>> >> > remove_single_pet = pet >> >>> >> > _remove_children(parent, pet.child, initiator, >> >>> >> > remove_single_pet) >> >>> >> > >> >>> >> > >> >>> >> > #### test ### >> >>> >> > >> >>> >> > import unittest >> >>> >> > from sqlalchemy import create_engine >> >>> >> > from sqlalchemy.orm import sessionmaker >> >>> >> > >> >>> >> > class BasicTestModelCase(unittest.TestCase): >> >>> >> > >> >>> >> > def setUp(self): >> >>> >> > e = create_engine("sqlite://", echo=True) >> >>> >> > Base.metadata.create_all(e) >> >>> >> > >> >>> >> > Session = sessionmaker(bind=e) >> >>> >> > self.session = Session() >> >>> >> > >> >>> >> > >> >>> >> > def tearDown(self): >> >>> >> > # Base.metadata.drop_all() >> >>> >> > pass >> >>> >> > >> >>> >> > def test_child_pet_relationship_on_parents_combined(self): >> >>> >> > """ >> >>> >> > Test that a parent can be hold children and pets that >> >>> >> > don't >> >>> >> > belong necessary to the child, given the behaviour tested >> >>> >> > in >> >>> >> > the >> >>> >> > previous test. >> >>> >> > """ >> >>> >> > >> >>> >> > # create new parent >> >>> >> > test_parent = Parent(name='test_parent') >> >>> >> > >> >>> >> > child1 = Child(id=1, >> >>> >> > name='FakeChild1') >> >>> >> > >> >>> >> > child2 = Child(id=2, >> >>> >> > name='FakeChild2') >> >>> >> > >> >>> >> > pet1 = Pet(id=1, >> >>> >> > name='FakePet1', >> >>> >> > child_id=1) >> >>> >> > >> >>> >> > pet2 = Pet(id=2, >> >>> >> > name='FakePet2', >> >>> >> > child_id=2) >> >>> >> > >> >>> >> > pet3 = Pet(id=3, >> >>> >> > name='FakePet3', >> >>> >> > child_id=1) >> >>> >> > >> >>> >> > self.session.add(test_parent) >> >>> >> > self.session.add(child1) >> >>> >> > self.session.add(child2) >> >>> >> > self.session.add(pet1) >> >>> >> > self.session.add(pet2) >> >>> >> > self.session.add(pet3) >> >>> >> > self.session.commit() >> >>> >> > >> >>> >> > # add parent to the child >> >>> >> > child1.parents.append(test_parent) >> >>> >> > self.session.add(child1) >> >>> >> > self.session.commit() >> >>> >> > >> >>> >> > # add parent to the child >> >>> >> > pet2.parents.append(test_parent) >> >>> >> > >> >>> >> > # persist changes in the db >> >>> >> > self.session.add(pet2) >> >>> >> > self.session.commit() >> >>> >> > >> >>> >> > print(test_parent.pets.all()) >> >>> >> > print(child2.pets.all()) >> >>> >> > >> >>> >> > # check that previous relationships are intact >> >>> >> > self.assertTrue(child1.pets.all() == [pet1, pet3]) >> >>> >> > self.assertTrue(child2.pets.all() == [pet2]) >> >>> >> > >> >>> >> > # resultant elements should be only child1, its pets and >> >>> >> > the >> >>> >> > single >> >>> >> > Pet >> >>> >> > self.assertTrue(test_parent.children.all() == [child1]) >> >>> >> > self.assertTrue(test_parent.pets.all() == [pet1, pet2, >> >>> >> > pet3]) >> >>> >> > >> >>> >> > # remove child from parent >> >>> >> > pet3.parents.remove(test_parent) ## ERROR here >> >>> >> > >> >>> >> > # resultant elements should be remaining pets, and no >> >>> >> > child >> >>> >> > self.assertTrue(test_parent.children.all() == []) >> >>> >> > self.assertTrue(test_parent.pets.all() == [pet1, pet2]) # >> >>> >> > pet2 >> >>> >> > was >> >>> >> > not touched, >> >>> >> > # >> >>> >> > but >> >>> >> > pet1 >> >>> >> > should remain since only >> >>> >> > # >> >>> >> > pet3 >> >>> >> > was >> >>> >> > removed >> >>> >> > # >> >>> >> > child1 >> >>> >> > should be also removed since >> >>> >> > # >> >>> >> > relationship is unbalanced, i.e. >> >>> >> > # >> >>> >> > user >> >>> >> > can't >> >>> >> > have access to a child if it >> >>> >> > # >> >>> >> > does >> >>> >> > not >> >>> >> > have access to all of the child's pets >> >>> >> > >> >>> >> > if __name__ == '__main__': >> >>> >> > # run tests >> >>> >> > unittest.main() >> >>> >> > >> >>> >> > Am Mittwoch, 2. Mai 2018 11:40:14 UTC-3 schrieb Mike Bayer: >> >>> >> >> >> >>> >> >> On Wed, May 2, 2018 at 10:14 AM, Diego Quintana >> >>> >> >> <daqui...@gmail.com> >> >>> >> >> wrote: >> >>> >> >> > This worked. >> >>> >> >> > >> >>> >> >> > I'm trying to achieve some rather tricky behaviour, where >> >>> >> >> > >> >>> >> >> > Adding a children to some parent will also add the child's >> >>> >> >> > pets >> >>> >> >> > to >> >>> >> >> > the >> >>> >> >> > parent >> >>> >> >> > Removing a children from some parent will also remove every >> >>> >> >> > current >> >>> >> >> > relationship that the Parent has with such pet >> >>> >> >> > If upon removal of a pet from a Parent, there is a Pet.child >> >>> >> >> > that >> >>> >> >> > is >> >>> >> >> > also in >> >>> >> >> > Parent.children, >> >>> >> >> > >> >>> >> >> > remove that Child from Parent, but keep existing relationships >> >>> >> >> > in >> >>> >> >> > Parent.pets except the pet that is being removed >> >>> >> >> > else only remove the pet from the parent >> >>> >> >> > >> >>> >> >> > >> >>> >> >> > Some code I'm using for this is >> >>> >> >> > >> >>> >> >> > @db.event.listens_for(Parent.children, 'append') >> >>> >> >> > def _append_children(parent, child, initiator): >> >>> >> >> > """ >> >>> >> >> > If a new child is appended to the parent, this listener >> >>> >> >> > will also add the pets bound to the child being bound to >> >>> >> >> > the >> >>> >> >> > parent. >> >>> >> >> > """ >> >>> >> >> > # appends also the pets bound to the child that the >> >>> >> >> > # parent is being appended to >> >>> >> >> > parent.pets.extend(child.pets.all()) >> >>> >> >> > >> >>> >> >> > @db.event.listens_for(Parent.children, 'remove') >> >>> >> >> > def _remove_children(parent, child, initiator, *args, >> >>> >> >> > **kwargs): >> >>> >> >> > """ >> >>> >> >> > If a child is removed from the parent, this listener >> >>> >> >> > will also remove only remove_single_pet --> <Pet> >> >>> >> >> > """ >> >>> >> >> > >> >>> >> >> > remove_single_pet = kwargs.get('remove_single_pet', None) >> >>> >> >> > >> >>> >> >> > if remove_single_pet is not None: >> >>> >> >> > parent.pets.remove(remove_single_pet) >> >>> >> >> > else: # removes every pet >> >>> >> >> > for pet in child.pets: >> >>> >> >> > parent.pets.remove(pet) >> >>> >> >> > >> >>> >> >> > >> >>> >> >> > @db.event.listens_for(Parent.pets, 'remove') >> >>> >> >> > def _remove_pets(parent, pet, initiator, *args, **kwargs): >> >>> >> >> > """ >> >>> >> >> > If a pet is removed from the parent, and the parent also >> >>> >> >> > is >> >>> >> >> > related >> >>> >> >> > to the child that has access to that pet, then >> >>> >> >> > >> >>> >> >> > * removes relationship with the child, and >> >>> >> >> > * keeps relationship with the remaining pets, except the >> >>> >> >> > one >> >>> >> >> > that >> >>> >> >> > was >> >>> >> >> > removed >> >>> >> >> > """ >> >>> >> >> > >> >>> >> >> > if pet.child in parent.children.all(): >> >>> >> >> > remove_single_pet = pet >> >>> >> >> > _remove_children(parent, pet.child, initiator, >> >>> >> >> > remove_single_pet) >> >>> >> >> > >> >>> >> >> > >> >>> >> >> > #### test.py >> >>> >> >> > >> >>> >> >> > def test_child_pet_relationship_on_parents(self): >> >>> >> >> > >> >>> >> >> > >> >>> >> >> > # create new parent >> >>> >> >> > test_parent = Parent(name='test_parent') >> >>> >> >> > >> >>> >> >> > # commit parent to the database >> >>> >> >> > db.session.add(test_parent) >> >>> >> >> > db.session.commit() >> >>> >> >> > >> >>> >> >> > child1 = Child(id=1, >> >>> >> >> > name='FakeChild1') >> >>> >> >> > >> >>> >> >> > child2 = Child(id=2, >> >>> >> >> > name='FakeChild2') >> >>> >> >> > >> >>> >> >> > pet1 = Pet(id=1, >> >>> >> >> > name='FakePet1', >> >>> >> >> > child_id=1) >> >>> >> >> > >> >>> >> >> > pet2 = Pet(id=2, >> >>> >> >> > name='FakePet2', >> >>> >> >> > child_id=2) >> >>> >> >> > >> >>> >> >> > pet3 = Pet(id=3, >> >>> >> >> > name='FakePet3', >> >>> >> >> > child_id=1) >> >>> >> >> > >> >>> >> >> > db.session.add(child1) >> >>> >> >> > db.session.add(child2) >> >>> >> >> > db.session.add(pet1) >> >>> >> >> > db.session.add(pet2) >> >>> >> >> > db.session.add(pet3) >> >>> >> >> > >> >>> >> >> > db.session.commit() >> >>> >> >> > >> >>> >> >> > # add parent to the child >> >>> >> >> > child1.parents.append(test_parent) >> >>> >> >> > # add parent to the child >> >>> >> >> > pet2.parents.append(test_parent) >> >>> >> >> > >> >>> >> >> > # persist changes in the db >> >>> >> >> > db.session.add(child1) >> >>> >> >> > db.session.add(pet2) >> >>> >> >> > db.session.commit() >> >>> >> >> > >> >>> >> >> > # check that previous relationships are intact >> >>> >> >> > self.assertTrue(child1.pets.all() == [pet1, pet3]) >> >>> >> >> > self.assertTrue(child2.pets.all() == [pet2]) >> >>> >> >> > >> >>> >> >> > # resultant elements should be only child1, its pets >> >>> >> >> > and >> >>> >> >> > the >> >>> >> >> > single >> >>> >> >> > Pet >> >>> >> >> > self.assertTrue(test_parent.children.all() == >> >>> >> >> > [child1]) >> >>> >> >> > self.assertTrue(test_parent.pets.all() == [pet1, pet2, >> >>> >> >> > pet3]) >> >>> >> >> > >> >>> >> >> > # remove child from parent >> >>> >> >> > pet3.parents.remove(test_parent) >> >>> >> >> > >> >>> >> >> > # resultant elements should be remaining pets, and no >> >>> >> >> > child >> >>> >> >> > self.assertTrue(test_parent.children.all() == []) >> >>> >> >> > self.assertTrue(test_parent.pets.all() == [pet1, >> >>> >> >> > pet2]) # >> >>> >> >> > pet2 >> >>> >> >> > was >> >>> >> >> > not touched, >> >>> >> >> > >> >>> >> >> > # >> >>> >> >> > but >> >>> >> >> > pet1 >> >>> >> >> > should remain since only >> >>> >> >> > >> >>> >> >> > # >> >>> >> >> > pet3 >> >>> >> >> > was >> >>> >> >> > removed >> >>> >> >> > >> >>> >> >> > # >> >>> >> >> > child1 >> >>> >> >> > should be also removed since >> >>> >> >> > >> >>> >> >> > # >> >>> >> >> > relationship is unbalanced, i.e. >> >>> >> >> > >> >>> >> >> > # >> >>> >> >> > user >> >>> >> >> > can't >> >>> >> >> > have access to a child if it >> >>> >> >> > >> >>> >> >> > # >> >>> >> >> > does >> >>> >> >> > not >> >>> >> >> > have access to all of the child's pets >> >>> >> >> > >> >>> >> >> > >> >>> >> >> > >> >>> >> >> > I'm having errors >> >>> >> >> > >> >>> >> >> > sqlalchemy.orm.exc.StaleDataError: DELETE statement on table >> >>> >> >> > 'parent_pets_relationship' expected to delete 1 row(s); Only 0 >> >>> >> >> > were >> >>> >> >> > matched. >> >>> >> >> >> >>> >> >> it looks like you have an explicit association mapping on the >> >>> >> >> secondary table as described in the green box in the section >> >>> >> >> >> >>> >> >> >> >>> >> >> >> >>> >> >> >> >>> >> >> https://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#association-object, >> >>> >> >> but I dont' have your complete mappings so I can't say for sure, >> >>> >> >> you'd >> >>> >> >> need to provide a complete MCVE. >> >>> >> >> >> >>> >> >> >> >>> >> >> >> >>> >> >> >> >>> >> >> > >> >>> >> >> > and the logger says nothing much. I suspect I'm falling into >> >>> >> >> > some >> >>> >> >> > weird >> >>> >> >> > recursion, calling the listener from another listener, where >> >>> >> >> > the >> >>> >> >> > second >> >>> >> >> > call >> >>> >> >> > can't find something that was already deleted. >> >>> >> >> > >> >>> >> >> > This might be a long shot, but I'm hoping this pattern might >> >>> >> >> > be >> >>> >> >> > solved >> >>> >> >> > already. >> >>> >> >> > >> >>> >> >> > >> >>> >> >> > Am Donnerstag, 26. April 2018 13:00:45 UTC-3 schrieb Mike >> >>> >> >> > Bayer: >> >>> >> >> >> >> >>> >> >> >> On Thu, Apr 26, 2018 at 11:04 AM, Diego Quintana >> >>> >> >> >> <daqui...@gmail.com> >> >>> >> >> >> wrote: >> >>> >> >> >> > Hello. >> >>> >> >> >> > >> >>> >> >> >> > Say I have three tables in a declarative fashion, `Parent`, >> >>> >> >> >> > `Child`, >> >>> >> >> >> > and >> >>> >> >> >> > `Pet`, in such way that >> >>> >> >> >> > >> >>> >> >> >> > * `Parent` has a many-to-many relationship with both >> >>> >> >> >> > `Child` >> >>> >> >> >> > and >> >>> >> >> >> > `Pet`, >> >>> >> >> >> > meaning that a Parent can own a Child and its pets, and >> >>> >> >> >> > also a >> >>> >> >> >> > Pet >> >>> >> >> >> > without >> >>> >> >> >> > its Child. >> >>> >> >> >> > * `Child` has a one-to-many relationship with `Pet` >> >>> >> >> >> > >> >>> >> >> >> > The code for them is (using Flask-SQLAlchemy, although I >> >>> >> >> >> > believe >> >>> >> >> >> > the >> >>> >> >> >> > solution lives in the realm of SQLAlchemy rather than in >> >>> >> >> >> > Flask). >> >>> >> >> >> > >> >>> >> >> >> > class Parent(db.Model): >> >>> >> >> >> > __tablename__ = 'parents' >> >>> >> >> >> > >> >>> >> >> >> > id = db.Column(db.Integer, primary_key=True) >> >>> >> >> >> > name = db.Column(db.String(64)) >> >>> >> >> >> > >> >>> >> >> >> > # many to many relationship between parent and >> >>> >> >> >> > children >> >>> >> >> >> > # my case allows for a children to have many >> >>> >> >> >> > parents. >> >>> >> >> >> > Don't >> >>> >> >> >> > ask. >> >>> >> >> >> > children = db.relationship('Child', >> >>> >> >> >> > >> >>> >> >> >> > secondary=parents_children_relationship, >> >>> >> >> >> > >> >>> >> >> >> > backref=db.backref('parents', >> >>> >> >> >> > lazy='dynamic'), >> >>> >> >> >> > lazy='dynamic') >> >>> >> >> >> > >> >>> >> >> >> > # many to many relationship between parents and >> >>> >> >> >> > pets >> >>> >> >> >> > pets = db.relationship('Pet', >> >>> >> >> >> > >> >>> >> >> >> > secondary=users_pets_relationship, >> >>> >> >> >> > >> >>> >> >> >> > backref=db.backref('parents', >> >>> >> >> >> > lazy='dynamic'), # >> >>> >> >> >> > lazy='dynamic') >> >>> >> >> >> > >> >>> >> >> >> > # many to many relationship between parents and >> >>> >> >> >> > children >> >>> >> >> >> > parents_children_relationship = >> >>> >> >> >> > db.Table('parents_children_relationship', >> >>> >> >> >> > db.Column('parent_id', db.Integer, >> >>> >> >> >> > db.ForeignKey('parents.id')), >> >>> >> >> >> > db.Column('child_id', db.Integer, >> >>> >> >> >> > db.ForeignKey('children.id')), >> >>> >> >> >> > UniqueConstraint('parent_id', 'child_id')) >> >>> >> >> >> > >> >>> >> >> >> > # many to many relationship between User and Pet >> >>> >> >> >> > users_pets_relationship = >> >>> >> >> >> > db.Table('users_pets_relationship', >> >>> >> >> >> > db.Column('parent_id', db.Integer, >> >>> >> >> >> > db.ForeignKey('parents.id')), >> >>> >> >> >> > db.Column('pet_id', db.Integer, >> >>> >> >> >> > db.ForeignKey('pets.id')), >> >>> >> >> >> > UniqueConstraint('parent_id', 'pet_id')) >> >>> >> >> >> > >> >>> >> >> >> > class Child(db.Model): >> >>> >> >> >> > __tablename__ = 'children' >> >>> >> >> >> > id = db.Column(db.Integer, primary_key=True) >> >>> >> >> >> > name = db.Column(db.String(64)) >> >>> >> >> >> > # parents = <backref relationship with User model> >> >>> >> >> >> > >> >>> >> >> >> > # one to many relationship with pets >> >>> >> >> >> > pets = db.relationship('Pet', backref='child', >> >>> >> >> >> > lazy='dynamic') >> >>> >> >> >> > >> >>> >> >> >> > >> >>> >> >> >> > class Pet(db.Model): >> >>> >> >> >> > __tablename__ = 'pets' >> >>> >> >> >> > id = db.Column(db.Integer, primary_key=True) >> >>> >> >> >> > name = db.Column(db.String(64)) >> >>> >> >> >> > # child = backref relationship with cities >> >>> >> >> >> > child_id = db.Column(db.Integer, >> >>> >> >> >> > db.ForeignKey('children.id'), >> >>> >> >> >> > nullable=True) >> >>> >> >> >> > # parents = <relationship backref from User> >> >>> >> >> >> > >> >>> >> >> >> > >> >>> >> >> >> > >> >>> >> >> >> > I would like to do something like this >> >>> >> >> >> > >> >>> >> >> >> > parent_a = Parent() >> >>> >> >> >> > child_a = Child() >> >>> >> >> >> > pet_a = Pet() >> >>> >> >> >> > >> >>> >> >> >> > >> >>> >> >> >> > >> >>> >> >> >> > I can then do this >> >>> >> >> >> > >> >>> >> >> >> > parent_a.children.append(child_a) >> >>> >> >> >> > # commit/persist data >> >>> >> >> >> > parent_a.children.all() # [child_a] >> >>> >> >> >> > >> >>> >> >> >> > >> >>> >> >> >> > >> >>> >> >> >> > I would like to achieve something like this >> >>> >> >> >> > >> >>> >> >> >> > child_a.pets.append(pet_a) >> >>> >> >> >> > parent_a.children.append(child_a) >> >>> >> >> >> > # commit/persist data >> >>> >> >> >> > parent_a.children.all() # [child_a] >> >>> >> >> >> > parent_a.pets.all() # [pet_a], because pet_a gets >> >>> >> >> >> > # automatically added to parent >> >>> >> >> >> > using >> >>> >> >> >> > some >> >>> >> >> >> > sorcery >> >>> >> >> >> > # like for child in >> >>> >> >> >> > parent_a.children.all(): >> >>> >> >> >> > # >> >>> >> >> >> > parent.pets.append(child.pets.all()) >> >>> >> >> >> > # or something like that. >> >>> >> >> >> > >> >>> >> >> >> > I can achieve this with a method in the Parent object like >> >>> >> >> >> > add_child_and_its_pets(), but I would like to override the >> >>> >> >> >> > way >> >>> >> >> >> > relationship >> >>> >> >> >> > works, so I don't need to override other modules that may >> >>> >> >> >> > benefit >> >>> >> >> >> > from >> >>> >> >> >> > this >> >>> >> >> >> > behaviour, like Flask-Admin for instance. >> >>> >> >> >> > >> >>> >> >> >> > >> >>> >> >> >> > Basically how should I override the backref.append method >> >>> >> >> >> > or >> >>> >> >> >> > the >> >>> >> >> >> > relationship.append method to also append other objects >> >>> >> >> >> > from >> >>> >> >> >> > other >> >>> >> >> >> > relationships at call time i.e. on the python side? How >> >>> >> >> >> > should >> >>> >> >> >> > I >> >>> >> >> >> > override >> >>> >> >> >> > the remove methods as well? >> >>> >> >> >> >> >>> >> >> >> this seems like coordination of two separate relationships, >> >>> >> >> >> so >> >>> >> >> >> use >> >>> >> >> >> attribute events for that, >> >>> >> >> >> >> >>> >> >> >> >> >>> >> >> >> >> >>> >> >> >> >> >>> >> >> >> >> >>> >> >> >> http://docs.sqlalchemy.org/en/latest/orm/events.html?highlight=attributeevent#sqlalchemy.orm.events.AttributeEvents >> >>> >> >> >> : >> >>> >> >> >> >> >>> >> >> >> @event.listens_for(Parent.children, "append") >> >>> >> >> >> def _append_pets(parent, child, initiator): >> >>> >> >> >> parent.pets.extend(child.pets) # or whatever it is you >> >>> >> >> >> need >> >>> >> >> >> >> >>> >> >> >> you would need to look at the "append" and "remove" events. >> >>> >> >> >> >> >>> >> >> >> >> >>> >> >> >> >> >>> >> >> >> > >> >>> >> >> >> > >> >>> >> >> >> > I have also posted this question in Stack Overflow, in case >> >>> >> >> >> > it >> >>> >> >> >> > means >> >>> >> >> >> > something. >> >>> >> >> >> > >> >>> >> >> >> > >> >>> >> >> >> > Best! >> >>> >> >> >> > >> >>> >> >> >> > >> >>> >> >> >> > >> >>> >> >> >> > -- >> >>> >> >> >> > 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+...@googlegroups.com. >> >>> >> >> >> > To post to this group, send email to >> >>> >> >> >> > sqlal...@googlegroups.com. >> >>> >> >> >> > Visit this group at >> >>> >> >> >> > https://groups.google.com/group/sqlalchemy. >> >>> >> >> >> > For more options, visit https://groups.google.com/d/optout. >> >>> >> >> > >> >>> >> >> > -- >> >>> >> >> > 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+...@googlegroups.com. >> >>> >> >> > To post to this group, send email to >> >>> >> >> > sqlal...@googlegroups.com. >> >>> >> >> > Visit this group at >> >>> >> >> > https://groups.google.com/group/sqlalchemy. >> >>> >> >> > For more options, visit https://groups.google.com/d/optout. >> >>> >> > >> >>> >> > -- >> >>> >> > 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+...@googlegroups.com. >> >>> >> > To post to this group, send email to sqlal...@googlegroups.com. >> >>> >> > Visit this group at https://groups.google.com/group/sqlalchemy. >> >>> >> > For more options, visit https://groups.google.com/d/optout. >> >>> >> >> >>> >> -- >> >>> >> 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 a topic in >> >>> >> the >> >>> >> Google Groups "sqlalchemy" group. >> >>> >> To unsubscribe from this topic, visit >> >>> >> >> >>> >> https://groups.google.com/d/topic/sqlalchemy/jgKgv5zQT7E/unsubscribe. >> >>> >> To unsubscribe from this group and all its topics, send an email to >> >>> >> sqlalchemy+...@googlegroups.com. >> >>> >> To post to this group, send email to sqlal...@googlegroups.com. >> >>> >> Visit this group at https://groups.google.com/group/sqlalchemy. >> >>> >> For more options, visit https://groups.google.com/d/optout. >> >>> > >> >>> > >> >>> > -- >> >>> > 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+...@googlegroups.com. >> >>> > To post to this group, send email to sqlal...@googlegroups.com. >> >>> > Visit this group at https://groups.google.com/group/sqlalchemy. >> >>> > For more options, visit https://groups.google.com/d/optout. >> > >> > -- >> > 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+...@googlegroups.com. >> > To post to this group, send email to sqlal...@googlegroups.com. >> > Visit this group at https://groups.google.com/group/sqlalchemy. >> > For more options, visit https://groups.google.com/d/optout. > > -- > 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. > To post to this group, send email to sqlalchemy@googlegroups.com. > Visit this group at https://groups.google.com/group/sqlalchemy. > For more options, visit https://groups.google.com/d/optout. -- 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. To post to this group, send email to sqlalchemy@googlegroups.com. Visit this group at https://groups.google.com/group/sqlalchemy. For more options, visit https://groups.google.com/d/optout.