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?
Related
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.
Here are my models (ignoring imports):
class Parent(Base):
__tablename__ = 'parents'
id = Column(Integer, primary_key=True)
name = Column(String)
children = relationship('Child', backref='parent', lazy='dynamic')
class Child(Base):
__tablename__ = 'children'
id = Column(Integer, primary_key=True)
name = Column(String)
parent_id = Column(Integer, ForeignKey('parents.id'))
Next I create a parent and a child, and relate them:
dbsession = session()
child = Child(name='bar')
dbsession.add(child)
parent = Parent(name='foo')
parent.children.append(child)
dbsession.add(parent)
dbsession.commit()
And all that works fine (so ignore any errors I may have made copying it to here). Now I'm trying to break the relationship, while keeping both the parent and the child in the database, and I'm coming up empty.
I appreciate any help.
I'm not sure exactly what you mean by break a relationship or why but I think this might work:
child = dbsession.query(Child).filter(Child.name=='bar').one()
child.parent_id = None
dbsession.add(child)
dbsession.commit()
This post gives more info about blanking a foreign key: Can a foreign key be NULL and/or duplicate?
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.
I have a model Zone, a model Entity and a model Transit. Transit is minimally defined as:
class Entity(db.Model):
__tablename__ = 'entities'
id = db.Column(db.Integer, primary_key=True)
contained_by = db.Column(db.Integer, db.ForeignKey('entities.id'))
contains = db.relationship('Entity', backref='container')
discriminator = db.Column('type', db.String(50))
__mapper_args__ = {'polymorphic_on': discriminator}
class Zone(Entity):
__mapper_args__ = {'polymorphic_identity': 'zones'}
routes = db.relationship('Transit')
(stuff goes here)
class Transit(db.Model):
__tablename__ = "transits"
start = db.Column(db.Integer, db.ForeignKey('zones.id'))
end = db.Column(db.Integer, db.ForeignKey('zones.id'))
Zone also has a couple of bits about distance and how defensible it is, but that is irrelevant for this.
First off, due to the fact that Zone is subclassed from Entity using single-table inheritance can I reference zones.id?
Secondly, will the Zone.routes property merge Transit.start and Transit.end?
no, you need to use the table name, which in your case (Single-table inheritance) is entities
no, these will not be merged. You can create two relationships, and have a (hybrid) property which would combine both, but this will only be for reading purposes, as when you would like to modify this property (for example, add Transits), you would still need to specify both sides (start and end).
I am not sure I understand the question here
update-1: as requested in comment, Concrete-Table inheritance code below:
class Zone(Entity):
__mapper_args__ = {'polymorphic_identity': 'zones'}
__tablename__ = "zones"
id = Column(Integer, ForeignKey('entities.id'), primary_key=True)
#property
def transits(self):
return self.transits_from_here + self.transits_to_here
class Transit(Base):
__tablename__ = "transits"
id = Column(Integer, primary_key=True)
start = Column(Integer, ForeignKey('zones.id'))
end = Column(Integer, ForeignKey('zones.id'))
s_zone = relationship(Zone, primaryjoin=(start==Zone.id), backref="transits_from_here")
e_zone = relationship(Zone, primaryjoin=(end==Zone.id), backref="transits_to_here")
I want to delete the parent row if the associated rows in child tables have been removed.
class Child(Base):
__tablename__ = "children"
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey("parents.id", ondelete='CASCADE'))
class Parent(Base):
__tablename__ = "parents"
id = Column(Integer, primary_key=True)
child = relationship(Child, backref="parent", passive_deletes=True)
If I remove the child
child_obj = session.query(Child).first()
session.delete(child_obj)
session.commit()
It does delete the child obj but parent remains as it is. I want to remove the parent as well using cascading.
You can read this thread:
Linking Relationships with Backref
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String)
addresses = relationship("Address", backref="user")
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
email = Column(String)
user_id = Column(Integer, ForeignKey('user.id'))
And you can define it in your child class:
parent = relationship(Parent, backref=backref("children", cascade="all,delete"))
You could do this for simple* cases by creating a listener that intercepts deletions of Child instances and deletes the parent if there are no other children.
import sqlalchemy as sa
#sa.event.listens_for(sessionmaker, 'persistent_to_deleted')
def intercept_persistent_to_deleted(session, object_):
# We are only interested in instances of Child.
if not isinstance(object_, Child):
return
p = object_.parent
# Handle null parents.
if p is None:
return
cs = session.query(Child).filter(Child.parent == p).count()
if cs == 0:
session.delete(p)
* I would recommend thorough testing if your code is doing things like deleting children and then creating new children with the deleted children's parents in the same session. The listener works in this case:
c = session.query(Child).first()
p = c.parent
session.delete(c)
c1 = Child(parent=p)
session.add(c1)
session.commit()
but hasn't been tested on anything more complicated.
The behavior you're referring to is kinda simple. But once the application gets larger, you'd need more sophisticated methods. I, personally, implement a static method remove for every class. It takes the id and any other parameter necessary to know the delete scheme.
The answer is already given... But that's - not efficient - approach just to give you an example.
class Child(Base):
__tablename__ = "children"
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey("parents.id", ondelete='CASCADE'))
#staticmethod
def remove(_id_):
child = Child.query().get(_id_)
Parent.remove(child.parent_id)
session.delete(child)
class Parent(Base):
__tablename__ = "parents"
id = Column(Integer, primary_key=True)
child = relationship(Child, backref="parent", passive_deletes=True)
#staticmethod
def remove(_id_):
parent = parent.query().get(_id_)
session.delete(parent)
If you, for example, added a Column named is_old - in the Child class - that's a binary integer 0 or 1 and wanted to delete the parent of the Child that has is_old == 1, It'll be very easy task.