SQLAlchemy - Relationship across four tables? - python

Here is my setup:
class User(Base):
__tablename__ = 'users'
user_id = Column(Integer, primary_key=True)
class Object(Base):
__tablename__ = 'objects'
object_id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('User.user_id'))
class ObjectHistory(Base):
__tablename__ = 'object_histories'
history_id = Column(Integer, primary_key=True)
object_id = Column(Integer, primary_key=True)
class Action(Base):
__tablename__ = 'actions'
action_id = Column(Integer, primary_key=True)
class ActionDetail(Base):
__tablename__ = 'action_details'
detail_id = Column(Integer, primary_key=True)
history_id = Column(Integer, ForeignKey('ObjectHistory.id')
object = relationship('Object', secondary='object_histories',
primaryjoin='ActionDetail.history_id==ObjectHistory.history_id',
secondaryjoin='Object.object_id==ObjectHistory.object_id',
backref='action_details')
user = relationship('User', ????, backref='action_details')
How can I create the user relationship on ActionDetail? Is it possible? I've tried something similar to the object relationship above it, but to no avail.

Related

SQLAlchemy relationship through 2 many-to-many tables

I have the following models:
class A(Base):
__tablename__ = 'A'
a_id = Column(Integer, primary_key=True)
class B(Base):
__tablename__ = 'B'
b_id = Column(Integer, primary_key=True)
class C(Base):
__tablename__ = 'C'
c_id = Column(Integer, primary_key=True)
class AB(Base):
__tablename__ = 'A_B'
a_id = Column(Integer, ForeignKey(A.a_id), primary_key=True)
b_id = Column(Integer, ForeignKey(B.b_id), primary_key=True)
class BC(Base):
__tablename__ = 'B_C'
b_id = Column(Integer, ForeignKey(B.b_id), primary_key=True)
c_id = Column(Integer, ForeignKey(C.c_id), primary_key=True)
What I want is to add a relationship between A and C using the many-to-many tables b_id as joiner column. I know I can do it using seconday and secondaryjoin when there is a relationship through one many-to-many table, but I have no idea how to do that with 2 many-to-many tables.
I want something like this:
class A(Base):
a_id = Column(Integer, primary_key=True)
c_colletion = relationship(?)
You can use a custom secondary:
ab = AB.__table__
bc = BC.__table__
ac = select([ab.c.a_id, bc.c.c_id]).select_from(ab.join(bc, ab.c.b_id == bc.c.b_id))
A.c_collection = relationship("C", secondary=ac,
primaryjoin=A.a_id == ac.c.a_id,
secondaryjoin=ac.c.c_id == C.c_id)

Factory Boy model with one-to-many and many-to-many fields

I'm testing my SQLAlchemy models with pytest and Factory Boy, but I find their documentation lacking in terms of relationships. I have my schema set up so there are users who can belong to multiple groups (groups can hold multiple users) and they can have multiple tokens, but a token only belongs to a single user:
_user_groups_table = Table(
'user_groups', Base.metadata,
Column('user_id', INTEGER(unsigned=True), ForeignKey('user.id')),
Column('group_id', INTEGER(unsigned=True), ForeignKey('user_group.id'))
)
class UserGroup(Base):
__tablename__ = 'user_group'
id = Column(INTEGER(unsigned=True), Sequence('user_group_id_seq'), primary_key=True, autoincrement=True)
name = Column(String(255), unique=True, nullable=False)
class User(Base):
__tablename__ = 'user'
id = Column(INTEGER(unsigned=True), Sequence('user_id_seq'), primary_key=True, autoincrement=True)
name = Column(String(255), unique=True, nullable=False)
groups = relationship('UserGroup', secondary=_user_groups_table)
auth_tokens = relationship('Token', cascade='delete')
class Token(Base):
__tablename__ = 'token'
id = Column(INTEGER(unsigned=True), Sequence('token_id_seq'), primary_key=True, autoincrement=True)
user_id = Column(INTEGER(unsigned=True), ForeignKey('user.id'), nullable=False)
value = Column(String(511), unique=True, nullable=False)
I've been trying different things, including a #factory.post_generation method that adds groups & tokens to the user instance, but when I put a user in a fixture and use it in my test functions, these fields never show up. Do you have any recommendations on how to model this schema with Factory Boy?
I am writing this for sqlalchemy part I dont know about the Factory boy. You already have a table which let you n to n relationship between users and groups(UserGroup table) and user-tokens for example:
id user_id group_id id user_id value
1 5 3 1 5 adb45
2 5 4 2 5 xyz01
3 5 5
4 1 9
5 1 3
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(255), unique=True, nullable=False)
tokens = relationship("Token", backref="user")
class Group(Base):
__tablename__ = "groups"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(255), unique=True, nullable=False)
class UserGroup(Base):
__tablename__ = "usergroups"
id = Column(Integer, primary_key=True, autoincrement=True)
group_id = Column(Integer, ForeignKey("Group.id"))
user_id = Column(Integer, ForeignKey("User.id"))
class Token(Base):
__tablename__ = "tokens"
id = Column(Integer, primary_key=True, autoincrement=True)
value = Column(String(511), unique=True, nullable=False)
user_id = Column(Integer, ForeignKey("User.id"))
and the sqlalchemy documentation is good.

Can't create records in SQLAlchemy due to One or more mappers failed to initialize

I have a model file (simplified)
Repos have single user, and users can have multiple repos.
class Repo(Base):
__tablename__ = "repos"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(512), unique=True)
user_id = Column(Integer, ForeignKey('users.id'))
user = db.relationship("User", back_populates="users")
class User(Base):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(200), unique=True)
repos = db.relationship("User", back_populates="repos")
When I try to insert a record I get the error:
One or more mappers failed to initialize - can't proceed with initialization of other mappers. Original exception was: Mapper 'Mapper|User|users' has no property 'users'
How can I fix my model?
back_populates for user relationship should be repos, and for repos relationship it should be user
class Repo(Base):
__tablename__ = "repos"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(512), unique=True)
user_id = Column(Integer, ForeignKey('users.id'))
user = db.relationship("User", back_populates="repos")
class User(Base):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(200), unique=True)
repos = db.relationship("Repo", back_populates="user")

In sqlalchemy how to access anonymous field in subquery

First, I used Sqlalchemy's polymorphic architecture.
ChildA and ChildB extends Child.
ChildA has name column.
ChildB has age column.
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship(Parent, backref='children')
class ChildA(Child):
__tablename__ = 'child_a'
id = Column(Integer, primary_key=True)
name = Column(String(50))
class ChildB(Child):
__tablename__ = 'child_b'
id = Column(Integer, primary_key=True)
age = Column(Integer)
parent = DBSession.query(Parent).first()
subquery = parent.children.join(ChildA).subquery()
So I want to access ChildA.name column from subquery.
Something like subquery.c.ChildA.name == 'Tom'
If I understood properly what you are trying to do, you don't really need a subquery, it could be simply something like
In [13]:
f = session.query(Parent, ChildA).join(ChildA).first()
print(f.ChildA.name)
Pedro
For the use of subqueries, I would recommend you take a look to sqlalchemy tutorial.
On the other hand, I wasn't able to use the classes as you've defined them, I had to add a ForeignKey like this
class ChildA(Child):
__tablename__ = 'child_a'
id = Column(Integer, ForeignKey('child.id'), primary_key=True)
name = Column(String(50))
class ChildB(Child):
__tablename__ = 'child_b'
id = Column(Integer, ForeignKey('child.id'), primary_key=True)
age = Column(Integer)
I have no doubt that it works for you, this probably depends on the setup.
And finally, I would like to recommend you to use a column for the type of child. With this, it will be easier to recognize the children you are using. Something like this,
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship(Parent, backref='children')
type = Column(String(20))
__mapper_args__ = {
'polymorphic_identity':'child',
'polymorphic_on':type
}
class ChildA(Child):
__tablename__ = 'child_a'
id = Column(Integer, ForeignKey('child.id'), primary_key=True)
name = Column(String(50))
__mapper_args__ = {
'polymorphic_identity':'child_a',
}
class ChildB(Child):
__tablename__ = 'child_b'
id = Column(Integer, ForeignKey('child.id'), primary_key=True)
age = Column(Integer)
__mapper_args__ = {
'polymorphic_identity':'child_b',
}
Please take a look to sqlalchemy docs for details.
Hope it helps.

Sqlalchemy foreign key to a subset of polymorphic classes

I am using sqlalchemy to create a structure that resembles a graph, so that there are several types of nodes and links joining them. The nodes are defined like this:
class Node(Base):
__tablename__ = 'node'
id = Column(Integer, primary_key=True)
type = Column(Unicode)
__mapper_args__ = {'polymorphic_on': type}
class InputNode(Node):
__tablename__ = 'inputnode'
id = Column(Integer, ForeignKey('node.id'), primary_key=True)
__mapper_args__ = {'polymorphic_identity': 'input'}
class ThruNode(Node):
__tablename__ = 'thrunode'
id = Column(Integer, ForeignKey('node.id'), primary_key=True)
__mapper_args__ = {'polymorphic_identity': 'thru'}
class OutputNode(Node):
__tablename__ = 'outputnode'
id = Column(Integer, ForeignKey('node.id'), primary_key=True)
__mapper_args__ = {'polymorphic_identity': 'output'}
Now I want to create a Link table which would looks something like this:
class Link(Base):
__tablename__ = 'link'
input = Column(Integer, ForeignKey('node.id', where='type IN ("input", "thru")'))
output = Column(Integer, ForeignKey('node.id', where='type IN ("thru", "output")'))
The bit I'm struggling with is how to do the where part of it, since as I've written it is not valid in sqlalchemy. I had though of using a CheckConstraint or a ForeignKeyConstraint, but I can't see how either of them could actually be used to do this.
I haven't tryed it nor am I an expert of this, but shouldn't this work?
class Link(Base):
__tablename__ = 'link'
input = Column(Integer, ForeignKey('thrunode.id'))
output = Column(Integer, ForeignKey('outputnode.id'))
First I had another idea that maybe you could have used different names for the ids and than use those, kind of like:
instead of:
class InputNode(Node):
__tablename__ = 'inputnode'
id = Column(Integer, ForeignKey('node.id'), primary_key=True)
__mapper_args__ = {'polymorphic_identity': 'input'}
this:
class ThruNode(Node):
[...]
thrunode_id = Column(Integer, ForeignKey('node.id'), primary_key=True)
[...]
and then:
class Link(Base):
__tablename__ = 'link'
input = Column(Integer, ForeignKey('node.thrunode_id'))
output = Column(Integer, ForeignKey('node.outputnode_id'))
I got the idea from here sqlalchemy docs: declarative.html#joined-table-inheritance

Categories

Resources