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

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)

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 ORM - 3-some object relationships

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.

SQLAlchemy can't join two tables with two foreign keys between them

The code below does not work due to this line owner_id = Column(Integer, ForeignKey('employees.employee_id')) in Manager class. SQLAlchemy generates error message:
AmbiguousForeignKeysError: Can't determine join between 'employees' and > 'managers'; tables have more than one foreign key constraint relationship > between them. Please specify the 'onclause' of this join explicitly.
Please help to fix that!
The idea is that every Manager is an Employee and works for some Owner. There might be zero, one or more Managers working for an Owner.
from sqlalchemy import (Table, Column, Integer, String, create_engine,
MetaData, ForeignKey)
from sqlalchemy.orm import mapper, create_session
from sqlalchemy.ext.declarative import declarative_base
e = create_engine('sqlite:////tmp/foo.db', echo=True)
Base = declarative_base(bind=e)
class Employee(Base):
__tablename__ = 'employees'
employee_id = Column(Integer, primary_key=True)
name = Column(String(50))
type = Column(String(30), nullable=False)
__mapper_args__ = {'polymorphic_on': type}
def __init__(self, name):
self.name = name
class Manager(Employee):
__tablename__ = 'managers'
__mapper_args__ = {'polymorphic_identity': 'manager'}
employee_id = Column(Integer, ForeignKey('employees.employee_id'),
primary_key=True)
manager_data = Column(String(50))
owner_id = Column(Integer, ForeignKey('employees.employee_id'))
def __init__(self, name, manager_data):
super(Manager, self).__init__(name)
self.manager_data = manager_data
class Owner(Manager):
__tablename__ = 'owners'
__mapper_args__ = {'polymorphic_identity': 'owner'}
employee_id = Column(Integer, ForeignKey('managers.employee_id'),
primary_key=True)
owner_secret = Column(String(50))
def __init__(self, name, manager_data, owner_secret):
super(Owner, self).__init__(name, manager_data)
self.owner_secret = owner_secret
Base.metadata.drop_all()
Base.metadata.create_all()
s = create_session(bind=e, autoflush=True, autocommit=False)
o = Owner('nosklo', 'mgr001', 'ownerpwd')
s.add(o)
s.commit()
SQLAlchemy is confused about how to join Manager to Employee because you have multiple foreign keys between the two tables, employee_id and owner_id. In this case, you need to specify the inherit_condition to the mapper explicitly:
class Manager(Employee):
__tablename__ = 'managers'
employee_id = Column(Integer, ForeignKey('employees.employee_id'),
primary_key=True)
manager_data = Column(String(50))
owner_id = Column(Integer, ForeignKey('employees.employee_id'))
__mapper_args__ = {'polymorphic_identity': 'manager', 'inherit_condition': employee_id == Employee.employee_id}
You are both extending parent model as well as defining relationship to the same parent model.
simply use one way, you could simply created relations and have all your models extend base class
class Employee(Base):
__tablename__ = 'employees'
employee_id = Column(Integer, primary_key=True)
...
class Owner(Base):
__tablename__ = 'owners'
__mapper_args__ = {'polymorphic_identity': 'owner'}
owner_id = Column(Integer, primary_key=True)
...
...
class Manager(Base):
__tablename__ = 'managers'
__mapper_args__ = {'polymorphic_identity': 'manager'}
manager_id = Column(Integer, primary_key=True)
employee_id = Column(Integer, ForeignKey('employees.employee_id'), primary_key=True)
owner_id = Column(Integer, ForeignKey('owners.owner_id'))
...
...
add the rest of relevant field where indicated as ....

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 to insert symbol(,) to result in SQLAlchemy level?

#mapping class
class Billing(Base):
__tablename__ = 'billing'
id = Column(Integer, primary_key=True)
billingdate= Column(DateTime, nullable=False)
amt = Column(Integer, nullable=False)
rate = Column(Integer, nullable=False)
fk_cpid = Column(Integer, ForeignKey('company.cpid'))
#run
query = self.mssql_session.query(Billing.billingdate).all()
result
$83749283 => $83,749,283
how to insert symbol(,) at Billing.billingdate in ONLY SQLAlchemy level?
Replace, SubString?
from sqlalchemy.ext.hybrid import hybrid_property
class Billing(Base):
__tablename__ = 'billing'
id = Column(Integer, primary_key=True)
billingdate= Column(DateTime, nullable=False)
_amt = Column(Integer, nullable=False)
rate = Column(Integer, nullable=False)
fk_cpid = Column(Integer, ForeignKey('company.cpid'))
#hybrid_property
def amt(self):
return '${:,}'.format(self._amt)
Hopes, this code can help you.

Categories