When do I need to use sqlalchemy back_populates? - python

When I try SQLAlchemy Relation Example following this guide: Basic Relationship Patterns
I have this code
#!/usr/bin/env python
# encoding: utf-8
from sqlalchemy import create_engine
from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)
session = Session()
Base = declarative_base(bind=engine)
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship("Parent")
Base.metadata.create_all()
p = Parent()
session.add(p)
session.commit()
c = Child(parent_id=p.id)
session.add(c)
session.commit()
print "children: {}".format(p.children[0].id)
print "parent: {}".format(c.parent.id)
It works well, but in the guide, it says the model should be:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
**children = relationship("Child", back_populates="parent")**
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
**parent = relationship("Parent", back_populates="children")**
Why don't I need back_populates or backref in my example? When should I use one or the other?

If you use backref you don't need to declare the relationship on the second table.
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child", backref="parent")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
If you're not using backref, and defining the relationship's separately, then if you don't use back_populates, sqlalchemy won't know to connect the relationships, so that modifying one also modifies the other.
So, in your example, where you've defined the relationship's separately, but didn't provide a back_populates argument, modifying one field wouldn't automatically update the other in your transaction.
>>> parent = Parent()
>>> child = Child()
>>> child.parent = parent
>>> print(parent.children)
[]
See how it didn't automatically fill out the children field?
Now, if you supply a back_populates argument, sqlalchemy will connect the fields.
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child", back_populates="parent")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship("Parent", back_populates="children")
So now we get
>>> parent = Parent()
>>> child = Child()
>>> child.parent = parent
>>> print(parent.children)
[Child(...)]
Sqlalchemy knows these two fields are related now, and will update each as the other is updated. It's worth noting that using backref will do this, too. Using back_populates is nice if you want to define the relationships on every class, so it's easy to see all the fields just be glancing at the model class, instead of having to look at other classes that define fields via backref.

Related

Defining relationship on association table with filtering

This example develops on the Parent/Child association table on the SQLAlchemy docs (https://docs.sqlalchemy.org/en/13/orm/basic_relationships.html#association-object)
How can I define the 'son' or 'daughter' attribute on the Parent class to filter the association table to the appropriate child type table?
I understand how the 'children' relationship works, but I would like to add extra conditionals to the relationship. I suspect this involves the use of secondary in the definition of relationship, but this is at the edge of my understanding of relationships in SQLAlchemy.
from sqlalchemy import Table, Column, Integer, ForeignKey, String
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Association(Base):
__tablename__ = 'association'
left_id = Column(Integer, ForeignKey('left.id'), primary_key=True)
right_id = Column(Integer, ForeignKey('right.id'), primary_key=True)
child = relationship("Child", back_populates="parents")
parent = relationship("Parent", back_populates="children")
child_type = Column(Integer, ForeignKey('child_type'))
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Association", back_populates="parent")
# I would also like to define 'sons' and 'daughters' here
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
parents = relationship("Association", back_populates="child")
class ChildType(Base):
__tablename__ = 'child_type'
id = Column(Integer, primary_key=True)
name = Column(String(50))
son_type = ChildType(name='son')
daughter_type = ChildType(name='daughter')
dad = Parent()
son = Child()
dad_son = Association(child_type=son_type)
dad_son.child = son
dad.children.append(dad_son)
daughter = Child()
dad_daughter = Association(child_type=daughter_type)
dad_daughter.child = daughter
dad.children.append(dad_daughter)
Filtering the association table in the secondary to only include rows of the type that you are interested in for each relationship seems to work:
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Association", back_populates="parent")
# I would also like to define 'sons' and 'daughters' here
sons = relationship(
'Child',
secondary="join(Association, ChildType, "
"and_(Association.child_type_id==ChildType.id, "
"ChildType.name=='son'))",
viewonly=True
)
daughters = relationship(
'Child',
secondary="join(Association, ChildType, "
"and_(Association.child_type_id==ChildType.id, "
"ChildType.name=='daughter'))",
viewonly=True
)
... but adding objects via those relationships won't work as they don't know how to construct the association object with the child_type, hence the viewonly. Also, I changed Association.child_type to child_type_id = Column(Integer, ForeignKey('child_type.id')) which is why that's different in the example.
The Child/ChildType thing stands out as a possible application of the 'Mapping Class Inheritance Hierarchies' pattern, if you aren't locked into this schema, that might be something to explore.

Parent/child one-to-many distinguish Null vs. zero children

I have a parent child relationship as follows. I'd like to be able to distinguish between null children (e.g. information not known yet) vs. zero children. This is the approach I'm currently taking. It works, but seems a bit cumbersome. Is there a better way to go about this?
from sqlalchemy import Column, Integer, ForeignKey, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
engine = create_engine('sqlite:///')
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship('Child', uselist=False)
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, ForeignKey('parent.id'), primary_key=True)
child_items = relationship('ChildItem')
class ChildItem(Base):
__tablename__ = 'childitems'
id = Column(Integer, ForeignKey('child.id'), primary_key=True)
Base.metadata.create_all(engine)
p = Parent()
assert(p.children is None) # Would like to be able to do something like this.
c = Child()
c.child_items.append(ChildItem())
p.children = c
assert(p.children is not None)

One to Many Relationship sqlalchemy id's

Following this tutorial, for the one to many relationships, I have this simple code:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child", back_populates="parent")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship("Parent", back_populates="children")
Now the problem is populating parent_id, whenever I set the children as a list, and commit the results to a database as follows:
# assume I have a session
children = Child(), Child(), Child()
p = Parent(children=children)
session.add(p)
session.commit()
If I check the database at this point, parent_id is not populated. I guess this makes sense, because I haven't explicitly defined parent_id anywhere, but is there a way to get the children to get the parents id?
Parent accept children as a list so:
children = [Child(), Child(), Child()]
...
p = Parent.query.first()
for child in p.children:
print(child.parent_id)

Sqlalchemy: Querying on m2m relations for polymorphic classes

I've got two classes that are connected through a many-to-many relationship: Parent and Tag.
Base = declarative_base()
association_table = Table('associations', Base.metadata,
Column('parent_id', Integer, ForeignKey('parent.id')),
Column('tag_id', Integer, ForeignKey('tag.id')),
)
class Tag(Base):
__tablename__ = 'tags'
id = Column(Integer, Sequence('tag_id_seq'), primary_key=True)
name = Column(String)
class Parent(Base):
__tablename__ = 'parents'
id = Column(Integer, Sequence('parent_id_seq'), primary_key=True)
tags = relationship('Tag', secondary=association_table, backref='parents')
If I want to query for all the Tag objects that have one or more relationships to a Parent, I'd do:
session.query(Tag).filter(Tag.parents.any()).all()
However, this Parent class is parent to child classes, Alice and Bob:
class Alice(Parent):
__tablename__ = 'alices'
__mapper_args__ = {'polymorphic_identity': 'alice'}
alice_id = Column(Integer, ForeignKey('parents.id'), primary_key=True)
class Bob(Parent):
__tablename__ = 'bobs'
__mapper_args__ = {'polymorphic_identity': 'bob'}
bob_id = Column(Integer, ForeignKey('parents.id'), primary_key=True)
Now I'd like to be able to retrieve all the Tag objects that have one or more relations to an Alice object. The previous query, session.query(Tag).filter(Tag.parents.any()).all(), would not do as it doesn't discriminate between Alice or Bob objects - it doesn't even know about their existence.
I've messed around with Query's for a while with no success so I assume that if it can be done, it must have something to do with some extra lines of code in the Table classes like those shown above. While the documentation holds info about polymorphic classes and many-to-many relations and Mike Bayer himself offered someone this answer to a seemingly related question which looks interesting but which I'm far from understanding, I'm kind of stuck.
The code samples may disgust the Python interpreter, but does hopefully get my point across. Candy for those who can help me on my way!
While writing a little MWE I actually found a solution, which is actually almost the same as what I had tried already. budder gave me new hope for the approach though, thanks :)
from sqlalchemy import create_engine, ForeignKey, Column, String, Integer, Sequence, Table
from sqlalchemy.orm import sessionmaker, relationship, backref
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
association_table = Table('associations', Base.metadata,
Column('parent_id', Integer, ForeignKey('parents.id')),
Column('tag_id', Integer, ForeignKey('tags.id')),
)
class Tag(Base):
__tablename__ = 'tags'
id = Column(Integer, Sequence('tag_id_seq'), primary_key=True)
name = Column(String)
class Parent(Base):
__tablename__ = 'parents'
id = Column(Integer, Sequence('parent_id_seq'), primary_key=True)
tags = relationship('Tag', secondary=association_table, backref='parents')
class Alice(Parent):
__tablename__ = 'alices'
__mapper_args__ = {'polymorphic_identity': 'alice'}
alice_id = Column(Integer, ForeignKey('parents.id'), primary_key=True)
class Bob(Parent):
__tablename__ = 'bobs'
__mapper_args__ = {'polymorphic_identity': 'bob'}
bob_id = Column(Integer, ForeignKey('parents.id'), primary_key=True)
engine = create_engine("sqlite://")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
tag_a = Tag(name='a')
tag_b = Tag(name='b')
tag_c = Tag(name='c')
session.add(tag_a)
session.add(tag_b)
session.add(tag_c)
session.commit()
session.add(Alice(tags=[tag_a]))
session.add(Bob(tags=[tag_b]))
session.commit()
for tag in session.query(Tag).\
filter(Tag.parents.any(Parent.id == Alice.alice_id)).\
all():
print(tag.name)
If there's a good alternative approach, I'd still be interested. I can imagine sqlalchemy offering a more direct and elegant approach so that one could do, for example:
for tag in session.query(Tag).\
filter(Tag.alices.any()).\
all():
print(tag.name)
If you write your object classes a certain way you can use the blunt force method...Just search for them...Kind of like a crude query method:
all_tag_objects = session.query(Tag).all() ## All tag objects in your database
tags = []
for tag in all_tag_objects:
for parent in tag.parents:
if parent.alices != []: ## If there are alice objects in the tag parents alice reltionship instance variable...Then we append the tag because it meets our criteria.
flagged_tags.append(tag)
Sounds like you found a better way though, but I guess the ultimate test would be to actually do a speed test.

Mock out database layer in SQLAlchemy

I am working on writing some unit tests around an application which uses SQLAlchemy. I would like to abstract/mock away the database layer and just test the object.
For example classes:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship("Parent", backref=backref('children', order_by=id))
What I would like to achieve while unit testing is something like follows:
parent = Mock()
child = Child(parent)
assertTrue(someMethod(child))
Any ideas on how to accomplish something along those lines?

Categories