This is the first time I've used ORM, so I'm not sure the best way to handle this. I have a one-to-many relationship where each Parent can have many Children:
class Parent(Base): __tablename__ = 'Parent' name = Column(String(50)) gid = Column(String(16), primary_key = True) lastUpdate = Column(DateTime) def __init__(self,name, gid): self.name = name self.gid = gid self.lastUpdate = datetime.datetime.now() class Child(Base): __tablename__ = 'Child' id = Column(Integer, primary_key = True) loc = Column(String(50)) status = Column(String(50)) parent_gid = Column(String(16), ForeignKey('Parent.gid')) parent = relationship("Parent", backref=backref('children'))
Now, updates are coming in over the network. When an update comes in, I want to UPDATE the appropriate Parent row (updating lastUpdate column) and INSERT new children rows into the database. I don't know how to do that with ORM. Here is my failed attempt:
engine = create_engine('sqlite+pysqlite:///file.db', module=dbapi2) Base.metadata.create_all(engine) session = sessionmaker(bind=engine)() def addChildren(parent): p = session.query(Parent).filter(Parent.gid == p1.gid).all() if len(p) == 0: session.add(p1) session.commit() else: updateChildren = parent.children[:] parent.chlidren = [] for c in updateChildren: c.parent_gid = parent.gid session.add_all(updateChildren) session.commit() if __name__ == '__main__': #first update from the 'network' p1 = Parent(name='team1', gid='t1') p1.children = [Child(loc='x', status='a'), Child(loc='y', status='b')] addChildren(p1) import time time.sleep(1) #here comes another network update p1 = Parent(name='team1', gid='t1') p1.children = [Child(loc='z', status='a'), Child(loc='k', status='b')] #this fails addChildren(p1)
I initially tried to do a merge, but that caused the old children to be disassociated with the parent (the foreign IDs were set to null). What is the best way to approach this with ORM? Thanks
EDIT
I guess it doesn't really make sense to create entirely new objects when updates come in over the network. I should just query the session for the appropriate parent, then create new children if necessary and merge? E.g.
def addChildren(pname, pid, cloc, cstat): p = session.query(Parent).filter(Parent.gid == pid).all() if len(p) == 0: p = Parent(pname, pid) p.children = [Child(loc=cloc, status=cstat)] session.add(p) session.commit() else: p = p[0] p.children.append(Child(loc=cloc, status=cstat)) session.merge(p) session.commit()
The comments class attribute defines a One-to-Many relationship between the Post model and the Comment model. You use the db. relationship() method, passing it the name of the comments model ( Comment in this case). You use the backref parameter to add a back reference that behaves like a column to the Comment model.
Many to Many relationship between two tables is achieved by adding an association table such that it has two foreign keys - one from each table's primary key.
In Flask-SQLAlchemy, the backref parameter in relationship method allows you to declare a new property under a specified class as seen in the example in their docs: class Person(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50)) addresses = db.relationship('Address', backref='person ...
first() applies a limit of one within the generated SQL, so that only one primary entity row is generated on the server side (note this may consist of multiple result rows if join-loaded collections are present). Calling Query. first() results in an execution of the underlying query.
You are right - you should not create the same parent twice. In terms of adding children, ... well, you really need only to add them and you do not care about the existing ones... So your edited code should do the job just fine. You can make it shorter and more readable though:
def addChildren(pname, pid, cloc, cstat): p = session.query(Parent).get(pid) # will give you either Parent or None if not(p): p = Parent(pname, pid) session.add(p) p.children.append(Child(loc=cloc, status=cstat)) session.commit()
The disadvantage of this way is that for existing Parent the whole collection of Children will be loaded into memory before a new Child is added and later saved to the database. If this is the case (many and increasing number of children for each parent), then the lazy='noload'
might be useful:
parent = relationship("Parent", backref=backref('children', lazy='noload'))
This might dramatically improve the speed of inserts, but in this case the access to p.children
will never load the existing objects from the database. In such scenarios it is enough to define another relationship. In these situations I prefer to use Building Query-Enabled Properties, so you end up with one property only for adding objects, and the other only for quering persisted results, which often are used by different parts of the system.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With