Given the following table:
class Table(Base):
__tablename__ = "table"
id = Column(Integer, primary_key=True)
name = Column(String(50))
What is the best way of testing result from below:
result = session.query(Table)
Should I convert to a dictionary and compare against a dictionary fixture?
Related
I have following models defined:
class Attribute(Base):
__tablename__ = "attributes"
id = Column(BigInteger, primary_key=True, index=True)
data_id = Column(BigInteger, ForeignKey("data.art_no"))
name = Column(VARCHAR(500), index=True)
data = relationship("Data", back_populates="attributes")
class Data(Base):
__tablename__ = "data"
art_no = Column(BigInteger, primary_key=True, index=True)
multiplier = Column(Float)
attributes = relationship("Attribute", back_populates="data", cascade="all, delete, delete-orphan")
If I query for a Data object, I get this for attributes:
[<app.db.models.Attribute object at 0x10d755d30>]
But I want to get:
['attribute name X']
What I want to get is, that the attributes field should be an array of the Attribute.name fields of the join'ed attributes.
My current query is:
db.query(models.Data).all()
How do I need to modify my query so the attributes field of Data contains not Attribute objects but just the strings name of `Attributes?
I hope you understand the question well ;)
db.query(models.Data).all() returns an array of Data objects. So you can define a custom property on the Data class to extract names from attributes relationship:
class Attribute(Base):
__tablename__ = "attributes"
id = Column(BigInteger, primary_key=True, index=True)
data_id = Column(BigInteger, ForeignKey("data.art_no"))
name = Column(VARCHAR(500), index=True)
data = relationship("Data", back_populates="attributes_rel")
class Data(Base):
__tablename__ = "data"
art_no = Column(BigInteger, primary_key=True, index=True)
multiplier = Column(Float)
attributes_rel = relationship("Attribute", back_populates="data", cascade="all, delete, delete-orphan")
#property
def attributes(self):
return [attribute.name for attribute in self.attributes_rel]
Note that by default sqlalchemy will fetch attributes_rel for each Data object separately upon access. This might result in N+1 selects problem. To avoid that you should specify relationship loading technique
Also take a look at with_entities and hybrid attributes
I have an existing database an want to build an SQLAlchemy wrapper to use the DB in Python. Lookup tables like the following are commonly used in the DB:
class Industry(Base):
__tablename__ = 'industry'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=True)
class IndustrySector(Base):
__tablename__ = 'industry_sector'
id = Column(Integer, primary_key=True)
industry_id = Column(Integer, ForeignKey('industry.id'), nullable=False)
name = Column(String, nullable=True)
What I would like to do is to create a new instance of IndustrySector using the name of the industry rather than the (technical) key of the industry, i.e.,
new_industry_sector = IndustrySector(industry_id = 'Manufacturing', name = 'Textile')
instead of
manu_industry_id = session.query(Industry.id).filter(Industry.name=='Manufacturing').first().id
new_industry_sector = IndustrySector(name = 'Textile', industry_id = new_industry_id)
Obviously, above example can't work because I am filtering on the ID rather than the name. But I don't know how to get the name of the foreign-keyed table into this. Of course I could simply add a #classmethod that handles the lookup, but if there exists any built-in functionality I'd much rather use that.
Any help / pointers are appreciated
You could create a constructor which will replace the string with the id value whenever an object is created.
class IndustrySector(Base):
__tablename__ = 'industry_sector'
id = Column(Integer, primary_key=True)
industry_id = Column(Integer, ForeignKey('industry.id'), nullable=False)
name = Column(String, nullable=True)
def __init__(self, industry_id, name):
self.name = name
self.industry_id = fetch_id(industry_id)
def fetch_id(industry_id):
# fetch and return the id
SQLAlchemy newbie here.
I'm trying to define a model subclass that represents a subset of table data. Specifically, I want the subclass to map the most recent row for a given ID.
For example, suppose I have the following model:
class AddressHistory(Base):
__table__ = 'address_table'
date = Column(Date, index=True, nullable=False)
id = Column(BigInteger, primary_key=True)
street = Column(String(2000))
city = Column(String(2000))
state = Column(String(2000))
zip = Column(Integer)
What I want to do is define a subclass of this model which represents the most recent address record for a given id:
class MostRecentAddress(Address):
“””
Represents a row in AddressHistory with the most recent date for a given id.
”””
Is there some sort of subquery I can pass to the mapper_args ? Or is there a way to define the table as a select statement?
You're looking for single table inheritance.
https://docs.sqlalchemy.org/en/13/orm/inheritance.html#single-table-inheritance
Your code sample is very nearly exactly how to go about doing this. You just need to add the mapper.
class Person(Base):
__tablename__ = 'people'
id = Column(Integer, primary_key=True)
discriminator = Column('type', String(50))
__mapper_args__ = {'polymorphic_on': discriminator}
class Engineer(Person):
__mapper_args__ = {'polymorphic_identity': 'engineer'}
primary_language = Column(String(50))
I have two models, related with many-to-many, one of them is hierarchical model:
#hierarchical model
class Tag(Base):
__tablename__ = "tags"
id = Column(Integer, primary_key=True)
name = Column(String)
Tag.parent_id = Column(Integer, ForeignKey(Tag.id, ondelete='CASCADE'))
Tag.childs = relationship(Tag, backref=backref('parent', remote_side=[Tag.id]),
cascade="all, delete")
class Subject(Base):
__tablename__ = "subjects"
id = Column(Integer, primary_key=True, doc="ID")
name = Column(String)
tags = relationship(Tag, secondary="tags_subjects", backref="subjects")
#many-to-many relations model
class TagsSubjects(Base):
__tablename__ = "tags_subjects"
id = Column(Integer, primary_key=True)
tag_id = Column(Integer, ForeignKey("tags.id"))
subject_id = Column(Integer, ForeignKey("subjects.id"))
So, I'll try to explain what I want to do... I want to make one (or several) query, for search all Subject's objects,
that have 'name' field value like 'foo' OR that has related tags having names with values like 'foo'
OR that has related tags, that has one or more parents (or above by hierarchy) tag with 'name' value like 'foo'
I've tried to do somethis like this:
>>> subjects = session.query(Subject).filter(or_(
Subject.name.ilike('%{0}%'.format('foo')),
Subject.tags.any(
Tag.name.ilike('%{0}%'.format('foo')))
)).order_by(Subject.name).all()
But it isn't correct and "flat" query, without hierarchical feature :(
How to do this by SQLAlchemy's API?
Thanks!
P.S. I'm using SQLite backend
I have read the SQLAlchemy documentation and tutorial about building many-to-many relation but I could not figure out how to do it properly when the association table contains more than the 2 foreign keys.
I have a table of items and every item has many details. Details can be the same on many items, so there is a many-to-many relation between items and details
I have the following:
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
My association table is (It's defined before the other 2 in the code):
class ItemDetail(Base):
__tablename__ = 'ItemDetail'
id = Column(Integer, primary_key=True)
itemId = Column(Integer, ForeignKey('Item.id'))
detailId = Column(Integer, ForeignKey('Detail.id'))
endDate = Column(Date)
In the documentation, it's said that I need to use the "association object". I could not figure out how to use it properly, since it's mixed declarative with mapper forms and the examples seem not to be complete. I added the line:
details = relation(ItemDetail)
as a member of Item class and the line:
itemDetail = relation('Detail')
as a member of the association table, as described in the documentation.
when I do item = session.query(Item).first(), the item.details is not a list of Detail objects, but a list of ItemDetail objects.
How can I get details properly in Item objects, i.e., item.details should be a list of Detail objects?
From the comments I see you've found the answer. But the SQLAlchemy documentation is quite overwhelming for a 'new user' and I was struggling with the same question. So for future reference:
ItemDetail = Table('ItemDetail',
Column('id', Integer, primary_key=True),
Column('itemId', Integer, ForeignKey('Item.id')),
Column('detailId', Integer, ForeignKey('Detail.id')),
Column('endDate', Date))
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
details = relationship('Detail', secondary=ItemDetail, backref='Item')
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
items = relationship('Item', secondary=ItemDetail, backref='Detail')
Like Miguel, I'm also using a Declarative approach for my junction table. However, I kept running into errors like
sqlalchemy.exc.ArgumentError: secondary argument <class 'main.ProjectUser'> passed to to relationship() User.projects must be a Table object or other FROM clause; can't send a mapped class directly as rows in 'secondary' are persisted independently of a class that is mapped to that same table.
With some fiddling, I was able to come up with the following. (Note my classes are different than OP's but the concept is the same.)
Example
Here's a full working example
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import declarative_base, relationship, Session
# Make the engine
engine = create_engine("sqlite+pysqlite:///:memory:", future=True, echo=False)
# Make the DeclarativeMeta
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
projects = relationship('Project', secondary='project_users', back_populates='users')
class Project(Base):
__tablename__ = "projects"
id = Column(Integer, primary_key=True)
name = Column(String)
users = relationship('User', secondary='project_users', back_populates='projects')
class ProjectUser(Base):
__tablename__ = "project_users"
id = Column(Integer, primary_key=True)
notes = Column(String, nullable=True)
user_id = Column(Integer, ForeignKey('users.id'))
project_id = Column(Integer, ForeignKey('projects.id'))
# Create the tables in the database
Base.metadata.create_all(engine)
# Test it
with Session(bind=engine) as session:
# add users
usr1 = User(name="bob")
session.add(usr1)
usr2 = User(name="alice")
session.add(usr2)
session.commit()
# add projects
prj1 = Project(name="Project 1")
session.add(prj1)
prj2 = Project(name="Project 2")
session.add(prj2)
session.commit()
# map users to projects
prj1.users = [usr1, usr2]
prj2.users = [usr2]
session.commit()
with Session(bind=engine) as session:
print(session.query(User).where(User.id == 1).one().projects)
print(session.query(Project).where(Project.id == 1).one().users)
Notes
reference the table name in the secondary argument like secondary='project_users' as opposed to secondary=ProjectUser
use back_populates instead of backref
I made a detailed writeup about this here.
Previous Answer worked for me, but I used a Class base approach for the table ItemDetail. This is the Sample code:
class ItemDetail(Base):
__tablename__ = 'ItemDetail'
id = Column(Integer, primary_key=True, index=True)
itemId = Column(Integer, ForeignKey('Item.id'))
detailId = Column(Integer, ForeignKey('Detail.id'))
endDate = Column(Date)
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
details = relationship('Detail', secondary=ItemDetail.__table__, backref='Item')
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
items = relationship('Item', secondary=ItemDetail.__table__, backref='Detail')