How do I add more than one inheritance relationship in SQLAlchemy? - sqlalchemy

So, I have a users table, and employees table, and a tenants table.
I'm using joined table inheritance.
class User(Base):
__tablename__ = 'usr_users'
usr_user_id = Column(Integer, primary_key=True)
usr_first_name = Column(Unicode(50))
usr_last_name = Column(Unicode(50))
tenant = relationship("Tenant", uselist=False, backref="User")
usr_type = Column(String(24))
__mapper_args__ = {
'polymorphic_identity':'user',
'polymorphic_on': usr_type
}
class Tenant(User):
"""
Application's user model.
"""
__tablename__ = 'ten_tenants'
ten_tenant_id = Column(Integer, ForeignKey('usr_users.usr_user_id'), primary_key=True)
__mapper_args__ = {
'polymorphic_identity': 'tenant'
}
class Employee(User):
__tablename__ = 'emp_employees'
emp_employee_id = Column(Integer, ForeignKey('usr_users.usr_user_id'), primary_key=True)
__mapper_args__ = {
'polymorphic_identity': 'employee'
}
I've got everything working when a user becomes an employee.
user = Employee()
session.add(user)
An entry in the user table, and a value in the "type" column of "employee".
But what if I have a user that is both a employee and a tenant?
What syntax do I use to pull the user, and then add a Tenant relationship so that the resulting user has both a employee relationship and a tenant relationship?

Related

SQLAlchemy Check if Many-To-Many relationship exists

In my database there is a Many-To-Many relationship between users and projects. A user can be member of multiple projects and a project can have multiple members.
I want know if a given user is member of a given project.
I currently solve this by querying for both the project and the user and by then checking if the user is contained in the projects member list. Here is my (working) code:
def is_project_member(db: Session, project_id: int, user_id: int):
project = db.query(dms.Project).filter(dms.Project.id == project_id).first()
user = db.query(dms.User).filter(dms.User.id == user_id).first()
return user in project.members
Can this be done more efficiently? Might it possible to directly query the relationship table?
Here my database models:
project_user_table = Table(
"project_user_association",
Base.metadata,
Column("project_id", ForeignKey("projects.id")),
Column("user_id", ForeignKey("users.id"))
)
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
projects = relationship("Project",
secondary=project_user_table, back_populates="members")
class Project(Base):
__tablename__ = "projects"
id = Column(Integer, primary_key=True, index=True)
members = relationship("User", secondary=project_user_table,
back_populates="projects")

Import data from a joined table as a current (readable) column in SQLAlchemy?

I have this schema:
class Company(db.Model):
__tablename__ = 'companies'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(250), nullable=True, default=None)
domain = db.Column(db.String(250), nullable=True, default=None)
organization_id = db.Column(db.Integer, db.ForeignKey('organizations.id'), nullable=False)
class Contact(db.Model):
__tablename__ = 'contacts'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(250), nullable=True, default=None)
email = db.Column(db.String(250), nullable=False)
company_id = db.Column(db.Integer, db.ForeignKey('companies.id'), nullable=True, default=None)
company = relationship('Company')
organization_id = db.Column({Import Company.organization_id as eager})
The last line is of course garbage, but it's to show the idea:
I'd like to have the value "organization_id" available in Contact, even though it's not present in the table "contacts", but since it's present in "companies", is there a way to ask SQLAlchemy to load the value from "companies" via a JOIN, and affect it to "contacts" as a read-only value?
That way, when I search for a contact, for instance :
contact = Contact.query.filter(Contact.email = 'test#test.com').first()
print(contact.organization_id) # => 1
Thank you.
You can use the hybrid_property decorator to define an attribute on your class:
class Contact(db.Model):
...
#hybrid_property
def organization_id(self):
return self.company.organization_id if self.company else None
Using contact.organization_id will load the company using the foreign key relationship.

Many-to-One relationships in SQLAlchemy

I'm having trouble with Many-to-One relationships between my SQLAlchemy models. The relationship between ChangeOrder (many) and Contract (one) is fine, but the one between LineItem (many) and ChangeOrder (one) isn't working.
I've tried both approaches suggested in the basic relationships docs and both fail for the LineItem to ChangeOrder relationship.
# using back_populates
class Contract(Base):
__tablename__ = "contract"
id = Column(Integer, primary_key=True)
change_orders = relationship("ChangeOrder", back_populates="contract")
class ChangeOrder(Base):
__tablename__ = "changeorder"
id = Column(Integer, primary_key=True)
line_items = relationship("LineItem", back_populates="change_order")
contract_id = Column(Integer, ForeignKey("contract.id"))
contract = relationship("Contract", back_populates="change_orders")
class LineItem(Base):
__tablename__ = "lineitem"
id = Column(Integer, primary_key=True)
change_order_id = Column(Integer, ForeignKey("changeorder.id"))
change_order = relationship("ChangeOrder", back_populates="line_items")
def test_insert_change_order(db_session, item):
c = Contract()
db_session.add(c)
db_session.commit()
co = ChangeOrder(contract_id=c.id)
db_session.add(co)
db_session.commit()
row = db_session.query(Contract).get(c.id)
assert len(row.change_orders) == 1 # this Many-to-One works
li = LineItem(change_order_id=co.id)
db_session.add(li)
db_session.commit()
row = db_session.query(ChangeOrder).get(co.id)
assert len(row.line_items) == 1 # this Many-to-One does not
I also tried the backref approach, but it has the same problem.
# using backref
class LineItem(Base):
__tablename__ = "lineitem"
id = Column(Integer, primary_key=True)
change_order_id = Column(Integer, ForeignKey("changeorder.id"))
change_order = relationship("ChangeOrder", backref="line_items")
class ChangeOrder(Base):
__tablename__ = "changeorder"
id = Column(Integer, primary_key=True)
contract_id = Column(Integer, ForeignKey("contract.id"))
contract = relationship("Contract", backref="change_orders")
class Contract(Base):
__tablename__ = "contract"
id = Column(Integer, primary_key=True)
conftest.py
import pytest
from flask_sqlalchemy import SQLAlchemy
from frontend.app import app
#pytest.fixture
def testapp():
db = SQLAlchemy()
app.config["SQLALCHEMY_ECHO"] = True
with app.app_context():
db.create_all()
yield app
db.session.remove()
db.drop_all()
#pytest.fixture(scope="session")
def database():
db = SQLAlchemy()
with app.app_context():
db.create_all()
yield db
#pytest.fixture(scope="session")
def _db(database):
return database

How to query a relationship on multiple polymorphic-inheritance tables?

Let's say you have the following simplified example schema, which uses SQLAlchemy joined table polymorphic inheritance. Engineer and Analyst models have a Role relationship. The Intern model does not.
class Role(db.Model):
__tablename__ = 'role'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(16), index=True)
class EmployeeBase(db.Model):
__tablename__ = 'employee_base'
id = db.Column(db.Integer, primary_key=True)
some_attr = db.Column(db.String(16))
another_attr = db.Column(db.String(16))
type = db.Column(db.String(50), index=True)
__mapper_args__ = {
'polymorphic_identity': 'employee',
'polymorphic_on': type
}
class Engineer(EmployeeBase):
__tablename__ = 'engineer'
id = db.Column(db.Integer, db.ForeignKey('employee_base.id'), primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey('role.id'), index=True)
role = db.relationship('Role', backref='engineers')
__mapper_args__ = {
'polymorphic_identity': 'engineer',
}
class Analyst(EmployeeBase):
__tablename__ = 'analyst'
id = db.Column(db.Integer, db.ForeignKey('employee_base.id'), primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey('role.id'), index=True)
role = db.relationship('Role', backref='analysts')
__mapper_args__ = {
'polymorphic_identity': 'analyst',
}
class Intern(EmployeeBase):
__tablename__ = 'intern'
id = db.Column(db.Integer, db.ForeignKey('employee_base.id'), primary_key=True)
term_ends = db.Column(db.DateTime, index=True, nullable=False)
__mapper_args__ = {
'polymorphic_identity': 'intern',
}
If I want to find Employees with a Role name having "petroleum" somewhere in the name, how would I do that?
I've tried many, many approaches. The closest I've come is this, which only returns Analyst matches:
employee_role_join = with_polymorphic(EmployeeBase,
[Engineer, Analyst])
results = db.session.query(employee_role_join).join(Role).filter(Role.name.ilike('%petroleum%'))
If I try to do something like this, I get an AttributeError, because I'm searching on an attribute of the joined Role table:
employee_role_join = with_polymorphic(EmployeeBase,
[Engineer, Analyst])
results = db.session.query(employee_role_join).filter(or_(
Engineer.role.name.ilike('%petroleum%'),
Analyst.role.name.ilike('%petroleum%')))
You can try specifying the join ON clause explicitly since the issue with your first query seems to be that Role is joining only on the analyst.role_id column:
employee_role_join = with_polymorphic(EmployeeBase, [Engineer, Analyst])
results = session.query(employee_role_join).join(Role).filter(Role.name.ilike('%petroleum%'))
print(str(results))
SELECT employee_base.id AS employee_base_id,
employee_base.some_attr AS employee_base_some_attr,
employee_base.another_attr AS employee_base_another_attr,
employee_base.type AS employee_base_type,
engineer.id AS engineer_id,
engineer.role_id AS engineer_role_id,
analyst.id AS analyst_id,
analyst.role_id AS analyst_role_id
FROM employee_base
LEFT OUTER JOIN engineer ON employee_base.id = engineer.id
LEFT OUTER JOIN analyst ON employee_base.id = analyst.id
JOIN role ON role.id = analyst.role_id
WHERE lower(role.name) LIKE lower(?)
employee_role_join is an AliasedClass that exposes both Analyst and Engineer, which we can then use to create a join-ON clause like so:
results = session.query(employee_role_join)\
.join(Role, or_( \
employee_role_join.Engineer.role_id==Role.id, \
employee_role_join.Analyst.role_id==Role.id \
))\
.filter(Role.name.ilike('%petroleum%'))
which changes the resulting SQL to JOIN role ON engineer.role_id = role.id OR analyst.role_id = role.id
Define the role_id on EmployeeBase. Even though Intern doesn't have the relationship back to the role table, the field can be null for that case.
I changed EmployeeBase to this:
class EmployeeBase(db.Model):
__tablename__ = 'employee_base'
id = db.Column(db.Integer, primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey('role.id'), index=True)
given_name = db.Column(db.String(16))
surname = db.Column(db.String(16))
type = db.Column(db.String(50), index=True)
__mapper_args__ = {
'polymorphic_identity': 'employee',
'polymorphic_on': type
}
And removed the role_id column definition from all other employee models.
db.create_all()
petrolium_engineer = Role(name='Petrolium Engineer')
geotech_engineer = Role(name='Geotech Engineer')
analyst_petrolium = Role(name='Analyst of Petrolium')
db.session.add(petrolium_engineer)
db.session.add(geotech_engineer)
db.session.add(analyst_petrolium)
db.session.add(
Intern(given_name='Joe', surname='Blogs', term_ends=datetime.now())
)
db.session.add(
Engineer(given_name='Mark', surname='Fume', role=petrolium_engineer)
)
db.session.add(
Engineer(given_name='Steve', surname='Rocks', role=geotech_engineer)
)
db.session.add(
Analyst(given_name='Cindy', surname='Booker', role=analyst_petrolium)
)
db.session.commit()
petrolium_roles = db.session.query(EmployeeBase).join(Role).\
filter(Role.name.contains('Petrolium')).all()
for emp in petrolium_roles:
print(f'{emp.given_name} {emp.surname} is {emp.role.name}')
# Mark Fume is Petrolium Engineer
# Cindy Booker is Analyst of Petrolium

sqlalchemy polymorphic_identity in list

I have the following setup and was hoping I can have polymorphic inheritance where both id_2 and id_3 discriminator values would map the same Child model. In sqlalchemy, is it possible to designate polymorphic_identity with a list of possible values?
class Parent(ScoutModel):
__tablename__ = 'parent'
identifiers = ('id_1', 'id_2', 'id_2')
id = Column(Integer, primary_key=True)
discriminator = Column(String(4), nullable=False, default=identifiers[0])
__mapper_args__ = {
'polymorphic_on': discriminator,
}
class Child(Company):
__tablename__ = 'child'
id = Column(ForeignKey('parent.id'), primary_key=True)
__mapper_args__ = {
'polymorphic_identity': '???',
}
Is there another approach that could accomplish this perhaps?
Thank you,
Fydo