SQLAlchemy ORM - 3-some object relationships - python

I'm using SQLAlchemy with Postgres.
Code example from https://auth0.com/blog/sqlalchemy-orm-tutorial-for-python-developers/
I have 3 objects:
Actor
Stuntman
ContactDetails
In the original code example, the relationships are as follow:
Actor:ContactDetails = 1:0..*
Actor:Stuntman = 1:1
See Code:
class Actor(Base):
__tablename__ = 'actors'
id = Column(Integer, primary_key=True)
name = Column(String)
birthday = Column(Date)
class Stuntman(Base):
__tablename__ = 'stuntmen'
id = Column(Integer, primary_key=True)
name = Column(String)
active = Column(Boolean)
actor_id = Column(Integer, ForeignKey('actors.id'))
actor = relationship("Actor", backref=backref("stuntman", uselist=False))
class ContactDetails(Base):
__tablename__ = 'contact_details'
id = Column(Integer, primary_key=True)
phone_number = Column(String)
address = Column(String)
actor_id = Column(Integer, ForeignKey('actors.id'))
actor = relationship("Actor")
I'm struggling to extend this model to following relationships:
Actor:ContactDetails = 1:0..*
Actor:Stuntman = 1:1
Stuntman:ContactDetails = 1:0..*
Stuntmen work hard too and deserve ContactDetails. Who can help please?!

In my opinion best way to achieve what you need is using association table. This one should work:
actors_to_contact_details = Table('actors_to_contact_details', Base.metadata,
Column('actor_id', Integer, ForeignKey('actors.id')),
Column('contact_detail_id', Integer, ForeignKey('contact_details.id'))
)
stuntmen_to_contact_details = Table('stuntmen_to_contact_details', Base.metadata,
Column('stuntman_id', Integer, ForeignKey('stuntmen.id')),
Column('contact_detail_id', Integer, ForeignKey('contact_details.id'))
)
class Actor(Base):
__tablename__ = 'actors'
id = Column(Integer, primary_key=True)
name = Column(String)
birthday = Column(Date)
contact_details = relationship("ContactDetails", secondary=actors_to_contact_details)
class Stuntman(Base):
__tablename__ = 'stuntmen'
id = Column(Integer, primary_key=True)
name = Column(String)
active = Column(Boolean)
actor_id = Column(Integer, ForeignKey('actors.id'))
actor = relationship("Actor", backref=backref("stuntman", uselist=False))
contact_details = relationship("ContactDetails", backref="stuntman", secondary=stuntmen_to_contact_details)
class ContactDetails(Base):
__tablename__ = 'contact_details'
id = Column(Integer, primary_key=True)
phone_number = Column(String)
address = Column(String)
if you want, you can put unique=True in association table like this
actors_to_contact_details = Table('actors_to_contact_details', Base.metadata,
Column('actor_id', Integer, ForeignKey('actors.id')),
Column('contact_detail_id', Integer, ForeignKey('contact_details.id'), unique=True)
)
stuntmen_to_contact_details = Table('stuntmen_to_contact_details', Base.metadata,
Column('stuntman_id', Integer, ForeignKey('stuntmen.id')),
Column('contact_detail_id', Integer, ForeignKey('contact_details.id'), unique=True)
)
Other option would be to put another nullable foreign key on Stuntman table, but I would go with solution presented above.

Related

sqlalchemy filter records by relationship

i cant get a simple query example of how to get record data based on the relationship,
models.py
from sqlalchemy import ForeignKey, Column, Integer, String, Boolean
from sqlalchemy.orm import relationship, declarative_base
Base = declarative_base()
class Category(Base):
__tablename__ = "category"
id = Column(Integer, primary_key=True)
wp_id = Column(Integer, nullable=False)
name = Column(String)
child_categories = relationship("Keyword", back_populates="parent_category", cascade="all, delete-orphan")
def __repr__(self):
return f"<wp id: {self.wp_id}, category name: {self.name}>"
class Keyword(Base):
__tablename__ = "keyword"
id = Column(Integer, primary_key=True)
wp_id = Column(Integer, unique=True)
name = Column(String)
questions = relationship("Question", back_populates="keyword", cascade="all, delete-orphan")
is_processed = Column(Boolean, nullable=True)
is_posted = Column(Boolean, nullable=True)
category_id = Column(String, ForeignKey('category.id'), nullable=True)
parent_category = relationship("Category", back_populates="child_categories")
def __repr__(self):
return f"<id: {self.wp_id}, keyword name: {self.name}>"
class Question(Base):
__tablename__ = "question"
id = Column(Integer, primary_key=True)
question = Column(String)
answer = Column(String)
keyword_id = Column(Integer, ForeignKey('keyword.id'), nullable=False)
keyword = relationship("Keyword", back_populates='questions')
def __repr__(self):
return f"<question: {self.question}>"
and i want all the keywords that are child of category with wp_id == 23
alot of the material in internet are either old, or are solutions for flask sqlalchemy, and there seems many ways to do one thing, thats why im a bit confused,
i tried the below:
wp_category_id = 23
def get_category_keywords_details(wp_category_id):
global engine
with Session(engine) as session:
keyword_details = session.execute(select(Keyword).where(Keyword.parent_category.wp_id ==
wp_category_id)).all()
which gave error:
AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with Keyword.parent_category has an attribute 'wp_id'

SqlAlchemy Error on Creating multiple foreign key to one table

I am new using sqlAlchemy and having problem creating new tables, specially when it comes around 2 foreign keys pointing to 1 table:
class Offers(db.Model):
__tablename__ = 'offers'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
contact_ign = db.Column(db.String(100))
conversion_rate = db.Column(db.Float)
stock = db.Column(db.Integer)
create_date = db.Column(db.DateTime(timezone=True), default=func.now())
currency_pair = db.relationship('CurrencyPairs', backref='pair', lazy='dynamic')
class CurrencyPairs(db.Model):
__tablename__ = 'currency_pairs'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
league = db.Column(db.String(100))
pair_id = db.Column(db.Integer, db.ForeignKey('offers.id'))
want = db.relationship('Currency', backref='want', lazy='dynamic')
have = db.relationship('Currency', backref='have', lazy='dynamic')
class Currency(db.Model):
__tablename__ = 'currency'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=False)
poe_trade = db.Column(db.Integer, nullable=False)
poe_official = db.Column(db.String(10), nullable=False)
tier = db.Column(db.Integer, nullable=False)
want_id = db.Column(db.Integer, db.ForeignKey('currency_pairs.id'))
have_id = db.Column(db.Integer, db.ForeignKey('currency_pairs.id'))
The error I am getting is:
sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'Mapper|CurrencyPairs|currency_pairs'. Original exception was: Could not determine join condition b
etween parent/child tables on relationship CurrencyPairs.want - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key refe
rence to the parent table
I have try different things but I get same result.
What am I doing wrong?
Thanks In advance.
I know this is an old question, but I had the same problem. I hope to help others with the answer.
This issue is addressed in the sqlalchemy documentation.
https://docs.sqlalchemy.org/en/13/orm/join_conditions.html#handling-multiple-join-paths
class Offers(db.Model):
__tablename__ = 'offers'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
contact_ign = db.Column(db.String(100))
conversion_rate = db.Column(db.Float)
stock = db.Column(db.Integer)
create_date = db.Column(db.DateTime(timezone=True), default=func.now())
currency_pair = db.relationship('CurrencyPairs', backref='pair', lazy='dynamic')
class CurrencyPairs(db.Model):
__tablename__ = 'currency_pairs'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
league = db.Column(db.String(100))
pair_id = db.Column(db.Integer, db.ForeignKey('offers.id'))
want_currency = relationship("Currency", foreign_keys='[Currency.want_id]', back_populates="want_currency_pairs")
have_currency = relationship("Currency", foreign_keys='[Currency.have_id]', back_populates="have_currency_pairs")
class Currency(db.Model):
__tablename__ = 'currency'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=False)
poe_trade = db.Column(db.Integer, nullable=False)
poe_official = db.Column(db.String(10), nullable=False)
tier = db.Column(db.Integer, nullable=False)
want_currency_pairs = relationship(CurrencyPairs, foreign_keys="[Currency.want_id]", back_populates="want_currency")
have_currency_pairs = relationship(CurrencyPairs, foreign_keys="[Currency.have_id]", back_populates="have_currency")
The way you wrote the code, sqlalchemy can't really understand which relationship to choose, because you have 2 of the same relationship. So you have to describe to sqlalchemy that there are 2 relationships to the same table.

SQLAlchemy, Foreign Key Refering for various tables

How can i do a foreign key refer to id to multiple tables?
class HistoryActivation(Base):
__tablename__ = 'active_control'
id = Column(Integer, primary_key=True)
id_entity = Column(Integer) #This one refer to any entity (id) that has a relation with this class.
date = Column(DATE)
user = Column(String(120))
active = Column(BOOLEAN)
class Person(Base):
__tablename__ = 'persons'
id = Column(Integer, primary_key=True)
name = Column(String(50))
history_activation = relationship("HistoryActivation",
primaryjoin="HistoryActivation.id_entidade == Person.id")
class Company(Base):
__tablename__ = 'companies'
id = Column(Integer, primary_key=True)
name = Column(String(50))
history_activation = relationship("HistoryActivation",
primaryjoin="HistoryActivation.id_entidade == Company.id")
class Whatever(Base): ...
So, id_entity refer to id to various tables, something like:
id_entity = Column(Integer, ForeignKey('Person.id or Company.id or Whatever.id').
Is possible to tell ForeignKey can refer more than one relation?

SQLAlchemy association confusion

I have a student database in SQLAlchemy with the following tables.
class Marks(Base):
__table_args__ = {'extend_existing': True}
__tablename__ = 'marks'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
mark = Column(Integer, nullable=False, unique=False)
subject_fk = Column(Integer, ForeignKey('subjects.id'))
subject = relationship('Subjects', foreign_keys=[locale_fk],uselist=False,
backref='marks', innerjoin=False, post_update=False)
class Subjects(Base):
__table_args__ = {'extend_existing': True}
__tablename__ = 'subjects'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
subject = Column(String(10, convert_unicode=True), nullable=False, unique=True)
class StudentIds(Base):
__table_args__ = {'extend_existing': True}
__tablename__ = 'student_ids'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
student_id = Column(String(50, convert_unicode=True), nullable=False,unique=False)
junctionid = relationship("Junction", cascade='save-update',
backref='student_ids', lazy='select', post_update=True)
mk=relationship("Marks",secondary=lambda:junction_table)
marks = association_proxy('mk', 'mark')
class Junction(Base):
__table_args__ = {'extend_existing': True}
__tablename__ = "junction_table"
student_id_fk = Column(Integer, ForeignKey('student_ids.id'), primary_key=True)
mark_fk = Column(Integer, ForeignKey('marks.id'), primary_key=True)
mark = relationship('Marks', cascade='save-update', backref='mark_id_mapper', innerjoin=True)
I have data populated in Student Ids, Marks and Subjects table.. Now, I need to populate the junction table with the student_id and the mark foreign keys(Marks obtained in each subject are inserted in the marks table).. How do I get this done, I mean I can create an association proxy inside Student id table that maps with the Mark table and populate it there?
I went through this documentation: http://docs.sqlalchemy.org/en/rel_0_9/orm/extensions/associationproxy.html and I kind of understand that i need this proxy between student_id and marks..so that I can query student_id.mark to obtain marks. But how do i get that in my Junction table?
Can someone please guide me?
I suggest it is one to many relationship (one student - many marks, http://docs.sqlalchemy.org/en/rel_0_9/orm/basic_relationships.html#one-to-many). So you might want to add student_id to your Marks model and relationship with Marks to StudentIds model instead of creating Junction model.
class StudentIds(Base):
__table_args__ = {'extend_existing': True}
__tablename__ = 'student_ids'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
student_id = Column(String(50, convert_unicode=True), nullable=False,unique=False)
junctionid = relationship("Junction", cascade='save-update',
backref='student_ids', lazy='select', post_update=True)
marks = relationship("Marks", backref="student")
class Marks(Base):
__table_args__ = {'extend_existing': True}
__tablename__ = 'marks'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
mark = Column(Integer, nullable=False, unique=False)
subject_fk = Column(Integer, ForeignKey('subjects.id'))
subject = relationship('Subjects', foreign_keys=[subject_fk],uselist=False,
backref='marks', innerjoin=False, post_update=False)
student_id = Column(Integer, ForeignKey('student_ids.id'))
student = relationship('StudentIds', foreign_keys=[student_id])
class Subjects(Base):
__table_args__ = {'extend_existing': True}
__tablename__ = 'subjects'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
subject = Column(String(10, convert_unicode=True), nullable=False, unique=True)
If you want to create many to many relationship, then you might want to look into creating association table (http://docs.sqlalchemy.org/en/rel_0_9/orm/basic_relationships.html#many-to-many)
association_table = Table('association', Base.metadata,
Column('students_id', Integer, ForeignKey('student_ids.id')),
Column('marks_id', Integer, ForeignKey('marks.id'))
)
class Marks(Base):
__table_args__ = {'extend_existing': True}
__tablename__ = 'marks'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
mark = Column(Integer, nullable=False, unique=False)
subject_fk = Column(Integer, ForeignKey('subjects.id'))
subject = relationship('Subjects', foreign_keys=[subject_fk],uselist=False, backref='marks', innerjoin=False, post_update=False)
class Subjects(Base):
__table_args__ = {'extend_existing': True}
__tablename__ = 'subjects'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
subject = Column(String(10, convert_unicode=True), nullable=False, unique=True)
class StudentIds(Base):
__table_args__ = {'extend_existing': True}
__tablename__ = 'student_ids'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
student_id = Column(String(50, convert_unicode=True), nullable=False,unique=False)
junctionid = relationship("Junction", cascade='save-update',
backref='student_ids', lazy='select', post_update=True)
marks = relationship("Marks", secondary=association_table)
Anyways you don't need Junction model for creating relation between students and their marks.

How can I create a many to many relationship using SQLAlchemy?

How can I create a many to many relationship using the models below:
class Association(Base):
a_id = Column(BigInteger, ForeignKey('a.id'), index=True)
b_id = Column(String, ForeignKey('b.some_other_id'))
class A(Base):
id = Column(BigInteger, primary_key=True)
class B(Base):
id = Column(BigInteger, primary_key=True)
some_other_id = Column(String(100), index=True, unique=True)
Adapting from SQLAlchemy documentation: You do not need to have the association model at all, if you do not need to have extra attributes for the association:
association_table = Table('association', Base.metadata,
Column('a_id', Integer, ForeignKey('a.id')),
Column('b_id', Integer, ForeignKey('b.some_id'))
)
class A(Base):
__tablename__ = 'a'
id = Column(Integer, primary_key=True)
b_s = relationship("B",
secondary=association_table,
backref="a_s")
class B(Base):
__tablename__ = 'b'
some_id = Column(Integer, primary_key=True)
Then b.a_s is a collection of A and a.b_s is a collection of B; changes therein will be reflected in the database on session.flush().
If you do want to have extra properties for the association between each A and B, then you can use the Association object pattern:
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))
child = relationship("Child")
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Association")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
Use the relationship function of sqlalchemy.orm and make sure to declare a primary key in your join table.
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, ForeignKey, BigInteger, String
from sqlalchemy.orm import relationship
Base=declarative_base()
class Association(Base):
__tablename__='association'
a_id = Column(BigInteger, ForeignKey('a.id'), index=True, primary_key=True)
b_id = Column(String, ForeignKey('b.some_other_id'), primary_key=True)
class A(Base):
__tablename__='a'
id = Column(BigInteger, primary_key=True)
b_ids = relationship('B', secondary=Association)
class B(Base):
__tablename__='b'
id = Column(BigInteger, primary_key=True)
some_other_id = Column(String(100), index=True, unique=True)
a_ids = relationship('A', secondary=Association)

Categories