SqlAlchemy : how to use relationship() together with Array of ForeignKey Column? - python

I am wondering if I could use sqlalchemy relationship() with Array of ForeignKey,
like the below examples :
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.types import ARRAY
from sqlalchemy.orm import relationship
from database import Base
class Topic(Base):
__tablename__ = 'topics'
id = Column(Integer, primary_key=True, index=True)
topic_name = Column(String)
class Room(Base):
__tablename__ = 'rooms'
id = Column(Integer, primary_key=True, index=True)
room_name = Column(String)
body = Column(String)
topics_id = Column(ARRAY(Integer, ForeignKey("topics.id")))
topics = relationship("Topic", foreign_keys='topics_id', uselist=True) #Single-direction
The relationship is that a Room has many of topics in it, but a Topic does not need to belong to any room.
Since the Room model already has a topics_id field that store array of foreign_keys of Topics model,
so I tried use this topics_id, passing into relationship()
Plus I do not want the Topic to has a link to Room model
but it give me this error
sqlalchemy.exc.InvalidRequestError: When initializing mapper mapped class Room->rooms, expression 'topics_id' failed to locate a name ("name 'topics_id' is not defined"). If this is a class name, consider adding this relationship() to the <class 'models.Room'> class after both dependent classes have been defined.

You can create a many-to-many relation for a schema like this. In the many-to-many relation, your topics can be a part of many rooms and every room can have multiple topics.
You can read about it here on the sqlalchemy official documentation
https://docs.sqlalchemy.org/en/14/orm/basic_relationships.html#many-to-many

Related

SQLAlchemy backref and backpopulates conflicts with relationship and mapper mapped has no property

In FastAPI application I am using sqlalchemy library. I have tried to establish a many to many relationship as described below:
PostCity = Table('PostCity',
Base.metadata,
Column('id', Integer, primary_key=True),
Column('post_id', Integer, ForeignKey('post.id')),
Column('city_id', Integer, ForeignKey('city.id')))
class DbPost(Base):
__tablename__ = 'post'
id = Column(Integer, primary_key=True, index=True)
image_url = Column(String)
cities = relationship('DbCity', secondary=PostCity, backref='post')
class DbCity(Base):
__tablename__ = 'city'
id = Column(Integer, primary_key=True, index=True)
name = Column(String)
posts = relationship('DbPost', secondary=PostCity, backref='city')
During add and commit commands it throws an error:
SAWarning: relationship 'DbCity.posts' will copy column city.id to column PostCity.city_id, which conflicts with relationship(s): 'Dntention, consider if these relationships should be linked with back_populates, or if viewonly=True should be applied to one or more if they are read-only. For the less common case that foreign key constraints are partially overlapping, the orm.foreign() annotation can be used to isolate the columns that should be written towards. To silence this warning, add the parameter 'overlaps="cities,post"' to the 'DbCity.posts' relationship. (Background on this error at: https://sqlalche.me/e/14/qzyx) city = DbCity(name=city_name)
This answer suggests to use back_populates= , however, when I am replacing backref with back_populates in both DbPost and DbCity classes it gives another error:
sqlalchemy.exc.InvalidRequestError: Mapper 'mapped class DbCity->city' has no property 'post'
What is the reason of this and how could I fix it ?
The warning
SAWarning: relationship 'DbCity.posts' will copy column post.id to column PostCity.post_id, which conflicts with relationship(s): 'DbCity.post' (copies post.id to PostCity.post_id), 'DbPost.cities' (copies post.id to PostCity.post_id).
is emitted because the two relationships are duplicating each other. The backref on each creates a relationship on the other. You only need to configure the relationship on one of the models, the backref will automatically configure the other model.

Flask-AppBuilder Many-to-Many relationship within the same Table

I have many to many relationships between the same table and want to define them in Flask-AppBuilder. For example, a parent-child relationship between humans, where both instances belong to the same table "Human". However when I do this, I get the error:
Error
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition
between parent/child tables on relationship Human.child - there are no
foreign keys linking these tables. Ensure that referencing columns
are associated with a ForeignKey or ForeignKeyConstraint, or specify a
'primaryjoin' expression.
Example models.py
from flask_appbuilder import Model
from sqlalchemy import Column, String, ForeignKey
from sqlalchemy.orm import relationship
class HumanHasChild(Model):
parent_id = Column(String(200), ForeignKey("Human.id"), primary_key=True)
child_id = Column(String(200), ForeignKey("Human.id"), primary_key=True)
class Human(Model):
id = Column(String(200), primary_key=True)
child = relationship("HumanHasChild", foreign_keys="HumanHasChild.child_id")
Contrary to the error message, I do specify the foreign key three times. However I tried adding an explicit join condition as well:
class Human(Model):
id = Column(String(200), primary_key=True)
child = relationship("HumanHasChild", foreign_keys="HumanHasChild.child_id",primaryjoin="Human.id==HumanHasChild.parent_id")
However even then I get an error:
sqlalchemy.exc.ArgumentError: Could not locate any relevant foreign
key columns for primary join condition 'human.id =
human_has_child.parent_id' on relationship Human.child. Ensure that
referencing columns are associated with a ForeignKey or
ForeignKeyConstraint, or are annotated in the join condition with the
foreign() annotation.
How can I get many-to-many relationships within the same table to work?
Following SQLAlchemy Many-to-Many Relationship on a Single Table, the answer is:
from flask_appbuilder import Model
from sqlalchemy import Column, String, ForeignKey, Table
from sqlalchemy.orm import relationship
HumanHasChild = Table("HumanHashChild",Model.metadata,
Column("parent_id",String(200), ForeignKey("Human.id"), primary_key=True),
Column("child_id",String(200), ForeignKey("Human.id"), primary_key=True)
)
class Human(Model):
id = Column(String(200), primary_key=True)
child = relationship("Human",
secondary=HumanHasChild,
foreign_keys = [HumanHasChild.c.parent_id,HumanHasChild.c.child_id],
primaryjoin=id==HumanHasChild.c.parent_id,
secondaryjoin=id==HumanHasChild.c.child_id,
backref="children")

sqlalchemy.exc.AmbiguousForeignKeysError after Inheritance

I'm using sqlacodegen for reflecting a bunch of tables from my database.
And i'm getting the following error:
sqlalchemy.exc.AmbiguousForeignKeysError: Can't determine join between 'Employee' and 'Sales'; tables have more than one foreign key constraint relationship between them. Please specify the 'onclause' of this join explicitly.
Here's a simplified version of my tables.
I read in the documentation that I should use the foreign_keys parameter to resolve ambiguity between foreign key targets. Although, I think this problem is because of the inheritance. Could someone help me understand what is going on.
# coding: utf-8
from sqlalchemy import Column, ForeignKey, Integer
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class Employee(Base):
__tablename__ = 'Employee'
EmployeeId = Column(Integer, primary_key=True)
class Sales(Employee):
__tablename__ = 'Sales'
EmployeeID = Column(ForeignKey('Employee.EmployeeId'), primary_key=True)
OldemployeeID = Column(ForeignKey('Employee.EmployeeId'))
employee = relationship('Employee', foreign_keys=[EmployeeID])
old_employee = relationship("Employee", foreign_keys=[OldemployeeID])
When your tables have multiple possible paths to inherit between them (Sales.EmployeeID or Sales.OldEmployeeID), SqlAlchemy doesn't know which one to use and you'll need to tell it the path explicitly, by using inherit_condition. For instance to inherit by EmployeeID:
class Sales(Employee):
...
__mapper_args__ = { "inherit_condition": EmployeeID == Employee.EmployeeId }
For the sake of example, you could also inherit by OldEmployeeID, by entering OldEmployeeID == Employee.EmployeeId - this would mean that both your Sales primary key and the Employee primary key are allowed to be different.
Just use backref and use Integer on both EmployeeID and OldemployeeID. Otherwise you will get an another error.
class Sales(Employee):
__tablename__ = 'Sales'
EmployeeID = Column(Integer, ForeignKey('Employee.EmployeeId'), primary_key=True)
OldemployeeID = Column(Integer, ForeignKey('Employee.EmployeeId'))
employee = relationship('Employee', foreign_keys=[EmployeeID], backref='Employee')
old_employee = relationship("Employee", foreign_keys=[OldemployeeID], backref='Employee')

How to create a field with a list of foreign keys in SQLAlchemy?

I am trying to store a list of models within the field of another model. Here is a trivial example below, where I have an existing model, Actor, and I want to create a new model, Movie, with the field Movie.list_of_actors:
import uuid
from sqlalchemy import Boolean, Column, Integer, String, DateTime
from sqlalchemy.schema import ForeignKey
rom sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class Actor(Base):
__tablename__ = 'actors'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(String)
nickname = Column(String)
academy_awards = Column(Integer)
# This is my new model:
class Movie(Base):
__tablename__ = 'movies'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
title = Column(String)
# How do I make this a list of foreign keys???
list_of_actors = Column(UUID(as_uuid=True), ForeignKey('actors.id'))
I understand that this can be done with a many-to-many relationship, but is there a more simple solution? Note that I don't need to look up which Movie's an Actor is in - I just want to create a new Movie model and access the list of my Actor's. And ideally, I would prefer not to add any new fields to my Actor model.
I've gone through the tutorials using the relationships API, which outlines the various one-to-many/many-to-many combinations using back_propagates and backref here: http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html But I can't seem to implement my list of foreign keys without creating a full-blown many-to-many implementation.
But if a many-to-many implementation is the only way to proceed, is there a way to implement it without having to create an "association table"? The "association table" is described here: http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#many-to-many ? Either way, an example would be very helpful!
Also, if it matters, I am using Postgres 9.5. I see from this post there might be support for arrays in Postgres, so any thoughts on that could be helpful.
Update
It looks like the only reasonable approach here is to create an association table, as shown in the selected answer below. I tried using ARRAY from SQLAlchemy's Postgres Dialect but it doesn't seem to support Foreign Keys. In my example above, I used the following column:
list_of_actors = Column('actors', postgresql.ARRAY(ForeignKey('actors.id')))
but it gives me an error. It seems like support for Postgres ARRAY with Foreign Keys is in progress, but still isn't quite there. Here is the most up to date source of information that I found: http://blog.2ndquadrant.com/postgresql-9-3-development-array-element-foreign-keys/
If you want many actors to be associated to a movie, and many movies be associated to an actor, you want a many-to-many. This means you need an association table. Otherwise, you could chuck away normalisation and use a NoSQL database.
An association table solution might resemble:
class Actor(Base):
__tablename__ = 'actors'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(String)
nickname = Column(String)
academy_awards = Column(Integer)
class Movie(Base):
__tablename__ = 'movies'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
title = Column(String)
actors = relationship('ActorMovie', uselist=True, backref='movies')
class ActorMovie(Base):
__tablename__ = 'actor_movies'
actor_id = Column(UUID(as_uuid=True), ForeignKey('actors.id'))
movie_id = Column(UUID(as_uuid=True), ForeignKey('movies.id'))
If you don't want ActorMovie to be an object inheriting from Base, you could use sqlachlemy.schema.Table.

How to declared one-to-many if there are 2 fields for a same foreign key

I'm new to python(sqlalchemy), and I'm learning to build web site with pylons and sqlalchemy.
I have a problem when I declare the relationship between models. I've tried it several hours, but failed. But I think it should be a basic question.
I have two classes: User and Article, user can create articles, and modified the other people's article(like wiki).
So a user has created-articles and edited-articles.
class Article(Base):
__tablename__ = 'articles'
id = Column(Integer, primary_key=True)
title = ...
user_id = Column(Integer, ForeignKey('users.id'))
editor_id = Column(Integer, ForeignKey('users.id'))
# relations
user = relationship('User', backref='articles') # -> has error
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String(20))
def __init__(self):
pass
But there is an error displayed:
InvalidRequestError: One or more mappers failed to compile. Exception was probably suppressed within a hasattr() call. Message was: Could not determine join condition between parent/child tables on relationship Article.user. Specify a 'primaryjoin' expression. If this is a many-to-many relationship, 'secondaryjoin' is needed as well.
I tried to add primaryjoin to the line('has error'), but don't know what it should be. I tried some codes, but none works.
Thank you in advance!
Ah, thats obvious one.
Article class has two references to User, user_id and editor_id, so SQLA does not know which one of them to use for your relation. Just use explicit primaryjoin:
user = relation('User', backref='articles', primaryjoin="Article.user_id==User.id")

Categories