Eager loading of one particular attribute of a related object (sqlalchemy) - python-3.x

I declared the following models in sqlalchemy (python). My loading strategy is to use deferred as a default, and to modify my query when I need to avoid lazy loading.
What I'm trying to achieve is to get all legal_provision records, and have the related case_law items loaded at the same time, overruling the lazy loading (deferred) for the url column, but not for the other columns of case_law.
I don't seem to be able to find a convenient solution for this. Any help much appreciated!
ORM:
from sqlalchemy import Integer, String, Column, Table, ForeignKey
from sqlalchemy.orm import deferred, relationship, declarative_base
Base = declarative_base()
case_law_legal_provisions = Table(
'case_law_legal_provisions',
Column('case_law_id', ForeignKey('case_law.id'), primary_key=True),
Column('legal_provision_id', ForeignKey('legal_provision.id'), primary_key=True)
)
class LegalProvision(Base):
__tablename__ = 'legal_provision'
id = Column(Integer, primary_key=True)
reference = deferred(Column(String, unique=True))
case_law = relationship('CaseLaw', secondary='case_law_legal_provisions', backpopulates='legal_provisions')
class CaseLaw(Base):
__tablename__ = 'case_law'
id = Column(Integer, primary_key=True)
url = deferred(Column(String))
contents = deferred(Column(String))
legal_provisions = relationship('LegalProvision', secondary='case_law_legal_provisions', backpopulates='case_law')

Related

SQLAlchemy how to use a "fully module-qualified path"?

I'm contributing to a project that is using sqlalchemy. This project has a model.py file where they define all their classes, for example Foobar(BASE). Now, I've created another module mymodel.py where I need to extend some of those classes. So for example, in mymodule.py I have Foobar(model.Foobar) which I use to extend the parent class with new properties and functions. The problem is that when using either of those classes, I get this error from sqlalchemy: sqlalchemy.exc.InvalidRequestError: Multiple classes found for path "Foobar" in the registry of this declarative base. Please use a fully module-qualified path..
My question then is, how can I fix this error without renaming my classes? Since they are defined in a different module, how do I use a "fully module-qualified path"?
Faced the same issue, and also struggled to understand what the error is referring to by "fully module-qualified path" or where should I make the change.
After some trial and error, the key insight for me was that it has to do with SQLAlchemy relationships. Furthermore, in my case it also had to do with having the same table name in different schemas (I was using Postgres).
Simplified scenario that triggered the error:
# database/models/family_1.py
from database import MyOneAndOnlyBase
...
class Parent(MyOneAndOnlyBase):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
children = relationship("Child")
__table_args__ = {"schema": "family_1"}
class Child(MyOneAndOnlyBase):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
__table_args__ = {"schema": "family_1"}
# database/models/family_2.py
from database import MyOneAndOnlyBase
...
class Parent(MyOneAndOnlyBase):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
children = relationship("Child")
__table_args__ = {"schema": "family_2"}
class Child(MyOneAndOnlyBase):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
__table_args__ = {"schema": "family_2"}
And the way to fix it using a fully module-qualified path:
# database/models/family_1.py
from database import MyOneAndOnlyBase
...
class Parent(MyOneAndOnlyBase):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
children = relationship("database.models.family_1.Child")
__table_args__ = {"schema": "family_1"}
class Child(MyOneAndOnlyBase):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
__table_args__ = {"schema": "family_1"}
# database/models/family_2.py
from database import MyOneAndOnlyBase
...
class Parent(MyOneAndOnlyBase):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
children = relationship("database.models.family_2.Child")
__table_args__ = {"schema": "family_2"}
class Child(MyOneAndOnlyBase):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
__table_args__ = {"schema": "family_2"}
As stated by SQLAlchemy there a two classes found named Foobar. One is model.Foobar and the second one is mymodel.Foobar. You need to use the fully module-qualified path which is mymodel.Foobar to reference the new class.

Change SQLAlchemy __tablename__

I am using SQLAlchemy to handle requests from an API endpoint; my database tables (I have hundreds) are differentiated via a unique string (e.g. test_table_123)...
In the code below, __tablename__ is static. If possible, I would like that to change based on the specific table I would like to retrieve, as it would be tedious to write several hundred unique classes.
from config import db, ma # SQLAlchemy is init'd and tied to Flask in this config module
class specific_table(db.Model):
__tablename__ = 'test_table_123'
var1 = db.Column(db.Integer, primary_key=True)
var2 = db.Column(db.String, index=True)
var3 = db.Column(db.String)
class whole_table_schema(ma.ModelSchema):
class Meta:
model = specific_table
sqla_session = db.session
def single_table(table_name):
# collect the data from the unique table
my_data = specific_table().query.order_by(specific_table.level_0).all()
Thank you very much for your time in advance.
You can use reflect feature of SQLAlchemy.
engine = db.engine
metadata = MetaData()
metadata.reflect(bind=engine)
and finally
db.session.query(metadata.tables[table_name])
If you want smoother experience with querying, as previous solution cannot offer one, you might declare and map your tables: tables = {table_name: create_table(table_name) for table_name in table_names}, where create_table constructs models with different __tablename__. Instead of creating all tables at once, you can create them on demand.

Creating relationships with ORM Objects before inserting into a Database

Right now I have double the classes for the same data. The first are the Bill and the Expense class, used locally to exchange data within the program. I then I have Bill_Table and Expense_Table, used to exchange data between the program and database. This makes my program needlessly complicated, when I just want one of each.
Bill has a member variable that is a list of Expenses, like so:
class Bill:
vendor = None # type: str
expenses = None # type: list[Expense]
# plenty more variables here
def __init__(self, vendor=None,):
self.vendor = vendor
self.expenses = list()
class Expense:
account = None # type: str
amount = None # type: int
# etc...
My Bill_Table and Expense_Table are set up pretty much identical. I use some functions to convert a Bill into a Bill_table, or Expense into an Expense_Table, or visa versa.
from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Bill_Table(Base):
__tablename__ = 'bills'
id = Column(Integer, primary_key=True)
expenses = relationship("Expense_Table")
# etc...
class Expense_Table(Base):
__tablename__ = 'expenses'
id = Column(Integer, primary_key=True)
bill_id = Column(Integer, ForeignKey('bills.id'))
# etc...
How would I map some Expense_Table objects to a Bill_Table object, without connecting to a database? So I could have the same functionality, but also when I insert a Bill_Table into the database, it will also import it's Expense_Table objects with it too?

Flask ImportError: cannot import name [duplicate]

I am trying to make a circular one-to-one relationship (not sure what the correct term is) with SQLAlchemy that looks the following:
class Parent(Base):
__tablename__ = 'parents'
id = db.Column(Integer, primary_key=True)
child_id = db.Column(db.Integer,db.ForeignKey("children.id", use_alter=True))
child = db.relationship("Child",
uselist=False,
foreign_keys=[child_id],
post_update=True)
class Child(Base):
__tablename__ = 'children'
id = db.Column(db.Integer, primary_key=True)
parent_id = db.Column(db.Integer, db.ForeignKey("parents.id"))
user = db.relationship("Parent",
uselist=False,
foreign_keys=[parent_id])
Everything works as expected until I try to db.drop_all() and I get an error that the sqlalchemy.sql.schema.ForeignKeyConstraint name is None. Am I doing something wrong when trying to make this circular one-to-one relationship? I would really like to be able to query just the single column to get the id of the other one, hence the circular reference.
From the SQLAlchemy Docs:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
child = relationship("Child", uselist=False, back_populates="parent")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship("Parent", back_populates="child")
Then you can Parent.child or Child.parent all day long

How to use order_by in SQLAlchemy when using relationship() with secondary-parameter (many-to-many)?

I create a many-to-many relationship between a Person (as an author) and a Reference (as a scientific publication).
ReferenceAuthor = sa.Table('ReferenceAuthor', _Base.metadata,
sa.Column('ReferenceID', sa.Integer, sa.ForeignKey('Reference.ID'), primary_key=True),
sa.Column('PersonID', sa.Integer, sa.ForeignKey('Person.ID'), primary_key=True),
sa.Column('Index', sa.Integer)
)
class Reference(_Base):
__tablename__ = 'Reference'
_id = sa.Column('ID', sa.Integer, primary_key=True)
_authors = sao.relationship('Person', secondary=ReferenceAuthor)
class Person(_Base):
__tablename__ = 'Person'
_id = sa.Column('ID', sa.Integer, primary_key=True)
I would like to have the objects in Reference().authors ordered by ReferenceAuthor.Index.
The three tables here come from a foreign sqlite3-database. I am not able to change the structure of that tables. I have to deal with them like they are.
I could finde some examples about order_by in relationship() but nothing combined with the use of secondary=.
I tried an association_proxy and read the docs about that.
class ReferenceAuthor(_Base):
__tablename__ = 'ReferenceAuthor'
_reference_id = sa.Column('ReferenceID', sa.Integer, sa.ForeignKey('Reference.ID'), primary_key=True)
_person_id = sa.Column('PersonID', sa.Integer, sa.ForeignKey('Person.ID'), primary_key=True)
_index = sa.Column('Index', sa.Integer)
_person = sao.relationship('Person')
class Reference(_Base):
__tablename__ = 'Reference'
_id = sa.Column('ID', sa.Integer, primary_key=True)
_authors = saa.association_proxy('ReferenceAuthor', 'Person')
When I read the docs I think I understand the concept behind it. But with my code it doesn't work and I don't understand why. The error is
sqlalchemy.exc.InvalidRequestError: Mapper 'Mapper|Reference|Reference' has no property 'ReferenceAuthor'.
Solution 1:
Depending on the nice comment-answer of #univerio:
class ReferenceAuthor(_Base):
__tablename__ = 'ReferenceAuthor'
_reference_id = sa.Column('ReferenceID', sa.Integer, sa.ForeignKey('Reference.ID'), primary_key=True)
_person_id = sa.Column('PersonID', sa.Integer, sa.ForeignKey('Person.ID'), primary_key=True)
_index = sa.Column('Index', sa.Integer)
_person = sao.relationship('Person')
class Reference(_Base):
__tablename__ = 'Reference'
_id = sa.Column('ID', sa.Integer, primary_key=True)
_reference_authors = sao.relationship('ReferenceAuthor',
order_by=ReferenceAuthor._index)
_authors = saa.association_proxy('_reference_authors', '_person')
Solution 2:
Got this hint from the sqla-mailinglist. Depending on the first code in my initial question:
_authors = sao.relationship('Person',
secondary=ReferenceAuthor,
order_by=ReferenceAuthor.c.Index)
This looks quite simpler.
Side question: What is the difference between that two solutions?

Resources