SQLAlchemy count of adjacency list members as hybrid property - sqlalchemy

I've defined a 1-level adjacency list relationship (children cannot nest) in SQLA 1.4:
class User(Base):
id = db.Column(db.Integer, primary_key=True)
parent_id = db.Column(db.Integer, ForeignKey("user.id"))
children = relationship("User", backref=backref("parent", remote_side=[id]))
But I'm struggling to define a hybrid or column property that will allow me to query things like (User.id, User.child_count) and apply filter criteria to parent and child.

Related

Flask SQLAlchemy many-to-many relationship issue

I'm having an issue with my many-to-many relationship in Flask SQLAlchemy. I have a general understanding of how this works, but I can't seem to be able to wrap my head around my example.
I'm making a tennis score keeping app and I currently have two models, Player and Score and and intermediary table Match (players can challenge each other and keep track of the matches they played). The issue is that my intermediary table has three columns that point to the same column in the Player class (player id) and I cant figure out how to make the relationship column. My code so far is as follows:
match = db.Table("match",
db.Model.metadata,
db.Column("id", db.Integer, primary_key=True),
db.Column("player1", db.Integer, db.ForeignKey("players.id")),
db.Column("player2", db.Integer, db.ForeignKey("players.id")),
db.Column("score", db.Integer, db.ForeignKey("scores.id")),
db.Column("winner", db.Integer, db.ForeignKey("players.id"))
)
class Player(db.Model):
__tablename__ = "players"
id = db.Column(db.Integer, primary_key=True)
fullname = db.Column(db.String)
# scores = db.relationship("Score", secondary=match)
class Score(db.Model):
__tablename__ = "scores"
id = db.Column(db.Integer, primary_key=True)
score = db.Column(db.String)
date = db.Column(db.String)
# players = db.relationship("Player", secondary=match)
I know I'm supposed to show the relationship in both the Player and Score class to the match table (the commented out part), but if I try to create it, it gives me the following error:
"AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Player.scores - there are multiple foreign key paths linking the tables via secondary table 'match'. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference from the secondary table to each of the parent and child tables."
If I create it without the columns 'scores' and 'players', it creates it just fine, but then I can't query the intermediary table or add rows to it. (Or if that's possible, I don't really know how)
Can someone please explain what I'm doing wrong? Overall I understand how a many-to-many relationship is supposed to work, but I just can't wrap my head around what I'm supposed to do here. screams in confusion
So I found the answer to my question and I'm just gonna leave it here, in case it helps someone else struggling with the same issue...
So I was trying to use a secondary table, but that's only meant to include foreign keys, however my relationship table has other data (id, score, winner...) which turns it into an Association Object based many-to-many relationship. And that one requires mapping the relationship from Parent to the Association object instead of directly onto the child.
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)
extra_data = Column(String(50))
parent = db.relationship("Child", back_populates="children")
child = db.relationship("Parent", back_populates="parents")
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Association", back_populates="parent")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
parents = db.relationship("Association", back_populates="child")

SQLAlchemy delete many to many with Association Object

So I have myself a many to many relationship which requires extra data in the association table. As a result, I have used an Association Object.
I can delete the Association Object itself from the DB just fine, but there doesn't seem to be any cascade taking place. I have looked at the documentation and followed it as best I could (I'm somewhat of a beginner here), but the situation as explained there does not seem to apply to my situation entirely (and I can't quite figure out the way to get it to work right).
So I have three classes. Student, CMClass and StudentClass (this being the Association Object):
class Student(Base):
__tablename__ = 'Students'
id = Column('ID', Integer, primary_key=True, autoincrement=True)
name = Column('NAME', String(50), unique=True)
CMClasses = relationship("StudentClass", back_populates="Student", secondary="StudentClasses", cascade="all, delete")
class CMClass(Base):
__tablename__ = "Classes"
id = Column('ID', Integer, primary_key=True, autoincrement=True)
name = Column('NAME', String(50), unique=True)
classDate = Column('CLASS_DATE', DateTime, nullable=True)
Students = relationship("StudentClass", back_populates="CMClass", secondary="StudentClasses")
class StudentClass(Base):
__tablename__ = 'StudentClasses'
student_id = Column(Integer, ForeignKey('Students.ID', ondelete="CASCADE"), primary_key=True)
CMClass_id = Column(Integer, ForeignKey('Classes.ID', ondelete="CASCADE"), primary_key=True)
attended = Column(Boolean)
Student = relationship("Student", back_populates="CMClasses")
CMClass = relationship("CMClass", back_populates="Students")
It all seems to work rather well honestly, except for the cascade delete. I've played around with the 'relationship' part of the two sub classes (Student and CMClass). I think that's where the issue lies, but honestly I'm just not sure how to set it up. The examples just use an association table, which seems to work a little differently from an association object.

What is the correct way to create object with relationship in SQLAlchemy

I've two models, Parent and Child over a SQLite db.
class Parent(Base):
__tablename__ = 'PARENTS'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False, sqlite_on_conflict_unique='IGNORE')
children = relationship("Child", back_populates="parent")
class Child(Base):
__tablename__ = 'CHILDREN'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
parent_id = Column(Integer, ForeignKey('PARENTS.id'), nullable=False)
parent = relationship("Parent", back_populates="children")
I need to create children like the following:
session.add(Child(name="...",parent=Parent(name="...")),
that is I want to create a child having its name and the name of the parent. I would like to know which is the best way to do this. Can I instruct SA to go and select Parents for me retrieving the parent.id from parent.name? Can I leverage some sort of caching mechanism for not having the parent table queried for each adding child (parent changes seldom). I don't need to create a new parent when I create a new child, but I want it to be linked to an already existing parent. Moreover I need to have long lists of children to be created fastly (e.g. for seeding the db).
Thanks.

SQLAlchemy one-to-many relationship clarification

After reading the SQLAlchemy documentation, it's still unclear to me how one-to-many relationships are actually supposed to be specified. I'll break down the documentation and explain why I'm confused (http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#one-to-many):
A one to many relationship places a foreign key on the child table
referencing the parent.
It looks like I am to place some Column attribute on the model that will be on the "many" side of the relationship.
relationship() is then specified on the parent, as referencing a collection of items represented by the child:
This means that there is some attribute on the parent that specifies the model participating in the "many" side of the relationship.
This would make total sense to me if it weren't for that fact that there could be a situation where I want to define two one-to-many relationships with the same participants on both sides of the relationship.
How does SQLAlchemy know that the ForeignKey column on the "many" side of the relationship corresponds to the relationship attribute placed on the "one" side?
A one-to-many relationship is set up like this:
class Group(Base):
id = Column(Integer, primary_key=True)
users = relationship(lambda: User)
class User(Base):
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey(Group.id))
SQLAlchemy infers that you meant to use parent_id as the join condition for users based on the fact that it's the only foreign key linking the two tables.
In the case where you have circular relationships:
class Group(Base):
id = Column(Integer, primary_key=True)
owner_id = Column(Integer, ForeignKey("users.id"))
users = relationship(lambda: User)
class User(Base):
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey(Group.id))
owned_groups = relationship(Group)
If you try this, it won't work because SQLAlchemy complains that it cannot infer what foreign key to use for each relationship. Instead, you have to tell it explicitly what to use:
class Group(Base):
id = Column(Integer, primary_key=True)
owner_id = Column(Integer, ForeignKey("users.id"))
users = relationship(lambda: User, foreign_keys=lambda: User.parent_id)
class User(Base):
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey(Group.id))
owned_groups = relationship(Group, foreign_keys=Group.owner_id)
A more complete example with backrefs:
class Group(Base):
id = Column(Integer, primary_key=True)
owner_id = Column(Integer, ForeignKey("users.id"))
users = relationship(lambda: User, foreign_keys=lambda: User.parent_id, back_populates="parent")
owner = relationship(lambda: User, foreign_keys=owner_id, back_populates="owned_groups")
class User(Base):
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey(Group.id))
owned_groups = relationship(Group, foreign_keys=Group.owner_id, back_populates="owner")
parent = relationship(Group, foreign_keys=parent_id, back_populates="users")

SQLAlchemy - How to map ManyToMany using autoloaded association tables

I am autoloading an MSSQL db. There are a few ManyToMany assoc tables. I'm not sure how to map each side. Here's a typical example of how they look in the db:
Table: tbUsersToGroups
PK: ID_UserToGroup
FK: User_ID
FK: Group_ID
So I can successfully autoload that assoc table and the Users and Groups tables per below, but everything I've tried to map the sides has failed.
class UserToGroup(Base):
__tablename__ = 'tbUsersToGroups'
__table_args__ = {'autoload':True,'extend_existing':True,'schema':'dbo'}
and
class User(Base):
__tablename__ = 'tbUsers'
__table_args__ = {'autoload':True,'schema':'dbo'}
and
class Group(Base):
__tablename__ = 'tbGoups'
__table_args__ = {'autoload':True,'schema':'dbo'}
Any help would be great.
You have mapped the association table to a class. It's very unusual and probably going to cause you some pain to combine an association object with a many-to-many relationship. If the association table doesn't have any other columns of interest, you can drop the mapping
and use a many-to-many relationship:
Edit: I missed the fact that you're doing per-table reflection, rather than full database reflection; For a many-to-many, you have to tell sqlalchemy about the table, but without mapping it to a class:
user_to_groups_table = sqlalchemy.Table('tbUsersToGroups', Base.metadata,
autoload=True,
extend_existing=True
schema='dbo')
class User(Base):
__tablename__ = 'tbUsers'
__table_args__ = {'autoload':True,'schema':'dbo'}
class Group(Base):
__tablename__ = 'tbGoups'
__table_args__ = {'autoload':True,'schema':'dbo'}
users = relationship(User, secondary=user_to_groups_table, backref="groups")
If there are columns in the association table that you want to have an object-oriented access to, you should use two One-To-Many relationships to relate the three classes; Optionally, you can also use an association proxy to get a convenient many-to-many property for when you only need to use those extra columns occasionally (and they have defaults):
from sqlalchemy.ext.associationproxy import association_proxy
class UserToGroup(Base):
__tablename__ = 'tbUsersToGroups'
__table_args__ = {'autoload':True,'extend_existing':True,'schema':'dbo'}
class User(Base):
__tablename__ = 'tbUsers'
__table_args__ = {'autoload':True,'schema':'dbo'}
usergroups = relationship(UserToGroup, backref="user")
groups = association_proxy("usergroups", "group")
class Group(Base):
__tablename__ = 'tbGoups'
__table_args__ = {'autoload':True,'schema':'dbo'}
usergroups = relationship(UserToGroup, backref="group")
users = association_proxy("usergroups", "user")