SqlAlchemy relationship many-to-many to an other many-to-many - python

I have the following mapped class, which is an association coming from 2 other classes.
class InstanceCustomer(Base):
__tablename__ = 'Instances_Customers_Association'
cust_id = Column(Integer, ForeignKey('Customers.id'), primary_key=True)
inst_id = Column(Integer, ForeignKey('Instances.id'), primary_key=True)
customer = relationship(Customer, backref=backref('customer'))
instance = relationship(Instance, backref=backref('instance'))
def __init__(self, cust_id=None, inst_id=None):
self.cust_id = cust_id
self.inst_id = inst_id
def __repr__(self):
return "<InstanceCustomer(cust_id='%s', inst_id='%s')>" % (self.cust_id, self.inst_id)
I would like to associate it to the class Person. So as 1 InstanceCustomer can have many Person and 1 Person can have many Instance Customer, I will need an other association between them, how can i do that? Is the primary-key/Foreign-key a problem as well?
Here is the class Person
class Person(Base):
__tablename__ = 'person'
id = Column( Integer, primary_key=True)

Is a N:N relationship, you need a cross relationship table. A example:
Class A(Base):
id = Column( Integer, primary_key=True)
Class B(Base):
id = Column( Integer, primary_key=True)
As = relationship(
'A',
secondary=AnB,
backref=backref('Bs')
)
AnB = Table(
"table_a_to_b",
Base.metadata,
Column(
'a_id',
Integer,
ForeignKey('A.id')
),
Column(
'b_id',
Integer,
ForeignKey('B.id')
)
)
Sqlalchemy doc for reference.

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 ....

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)

SQLAlchemy - Array of multiple types without inheritance

I have an existing set of sqlalchemy models like so
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String)
first_name = Column(String)
last_name = Column(String)
email = Column(String)
user_group = Table('user_group', Base.metadata,
Column('user_id', Integer, ForeignKey('users.id')),
Column('group_id', Integer, ForeignKey('groups.id'))
)
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
name = Column(String)
members = relationship("User", secondary=user_group)
and I would like to create a third model which can have a 1 to many relationship with either a User or a Group type. So I want to end up with something that can be used like this
class Contact(Base):
__tablename__ = 'contacts'
id = Column(Integer, primary_key=True)
type_id = Column(Integer) # the id of the group or user row which it refers to.
type = Column(String) # 'group' or 'user'
class Thing(Base):
__tablename__ = 'things'
relationship('Contact') # Array of Contacts
where
thing = Thing()
thing.contacts[0].type # returns 'group' or 'user'
thing.contacts[0].contact # returns the actual User or Group object
I know this can be done with inheritance but is there any alternative? I'd rather not have to subclass my basic User and Group models.
Thanks in advance!
Read Generic Associations section of the documentation.
Your description matches the case of the Generic ForeignKey

Categories