Generic database functions for serveral tables with sqlalchemy - python

I have a base table and serveral tables for professors, students, ... und use for this the single table inheritance from sqlalchemy.
from sqlalchemy import create_engine
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
base = declarative_base()
class TableBase(base):
__tablename__ = 'base_table'
id = Column(Integer, primary_key=True)
name = Column(String)
email = Column(String)
class Student(TableBase):
__tablename__ = 'student_table'
id = Column(Integer, ForeignKey(TableBase.id), primary_key=True)
__mapper_args__ = {
'polymorphic_identity': 'student_table'
}
class Professor(TableBase):
__tablename__ = 'professor_table'
id = Column(Integer, ForeignKey(TableBase.id), primary_key=True)
__mapper_args__ = {
'polymorphic_identity': 'professor_table'
}
class Database:
def __init__(self):
db_string = "postgresql+psycopg2://user:secret#localhost:5432/<database>"
engine = create_engine(db_string)
Session = sessionmaker(engine)
self.session = Session()
base.metadata.create_all(engine)
def add_entry(self, data, table):
table_entry = self.create_table_entry(data, table)
self.session.add(table_entry)
self.session.commit()
def add_entries(self, data_list, table):
entries = []
for data in data_list:
table_entry = self.create_table_entry(data, table.__class__.__name__)
entries.append(table_entry)
self.session.add_all(entries)
self.session.commit()
def create_table_entry(self, data, table):
table_entry = table(
id=data["id"],
subject=data["subject"],
email=data["email"]
)
return table_entry
def main():
student_table = Student()
professor_table = Professor()
db = Database()
data = {"id": 42, "name": "John", "email": ""}
db.create_table_entry(data, student_table)
db.create_table_entry(data, professor_table)
if __name__ == "__main__":
main()
But I don't know how I could create the create_table_entry function with using different object classes.
What are the best practice way for this?

def create_table_entry(data, table):
return table(**data)
Then do you even need separate function for this?

you can use sqlalchemy insert command like this:
from sqlalchemy import insert
def add_row(data, table):
self.session.execute(insert(table), data)
self.session.commit()

Related

SQLAlchemy - Self-referential many-to-many relationship in custom declarative base class

I am building a Python application that uses SQLAlchemy, and I am trying to implement a many-to-many relationship between a custom declarative base class and itself (self-referential). But I cannot get it to work. I am attaching the code down below, as well as the error traceback, in case anyone can help :) All the entities of the model already extended from this base class, and the application was working so far, in case that helps.
Thanks!!
Code (non-functional):
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from sqlalchemy import MetaData, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy import String
permissions = Table(
'permissions', MetaData(),
Column('origin_id', String, ForeignKey('bases.id'), primary_key=True),
Column('target_id', String, ForeignKey('bases.id'), primary_key=True)
)
class Base:
__tablename__ = 'bases'
__table_args__ = {
'mysql_engine': 'InnoDB'
}
id = Column(String, primary_key=True)
targets = relationship(
'Base',
secondary='permissions',
primaryjoin='Base.id == permissions.c.origin_id',
secondaryjoin='Base.id == permissions.c.target_id',
backref='origins'
# Reference:
# https://docs.sqlalchemy.org/en/14/orm/join_conditions.html#self-referential-many-to-many
)
Base = declarative_base(cls=Base)
Traceback:
class ContactMethod(Base):
File "/usr/local/lib/python3.8/site-packages/SQLAlchemy-1.4.23-py3.8-linux-x86_64.egg/sqlalchemy/orm/decl_api.py", line 72, in __init__
_as_declarative(reg, cls, dict_)
File "/usr/local/lib/python3.8/site-packages/SQLAlchemy-1.4.23-py3.8-linux-x86_64.egg/sqlalchemy/orm/decl_base.py", line 126, in _as_declarative
return _MapperConfig.setup_mapping(registry, cls, dict_, None, {})
File "/usr/local/lib/python3.8/site-packages/SQLAlchemy-1.4.23-py3.8-linux-x86_64.egg/sqlalchemy/orm/decl_base.py", line 177, in setup_mapping
return cfg_cls(registry, cls_, dict_, table, mapper_kw)
File "/usr/local/lib/python3.8/site-packages/SQLAlchemy-1.4.23-py3.8-linux-x86_64.egg/sqlalchemy/orm/decl_base.py", line 299, in __init__
self._scan_attributes()
File "/usr/local/lib/python3.8/site-packages/SQLAlchemy-1.4.23-py3.8-linux-x86_64.egg/sqlalchemy/orm/decl_base.py", line 511, in _scan_attributes
raise exc.InvalidRequestError(
sqlalchemy.exc.InvalidRequestError: Mapper properties (i.e. deferred,column_property(), relationship(), etc.) must be declared as #declared_attr callables on declarative mixin classes. For dataclass field() objects, use a lambda:
See a working self-containing example:
## Imports
from sqlalchemy import Column, ForeignKey, Integer, String, Table, create_engine
from sqlalchemy.orm import Session, as_declarative, declared_attr, relationship
## Configuration
engine = create_engine("sqlite:///:memory:", echo=True)
#as_declarative()
class Base(object):
#declared_attr
def __tablename__(cls):
return cls.__name__.lower()
permissions = Table(
"permissions",
Base.metadata,
Column("origin_id", String, ForeignKey("model_base.id"), primary_key=True),
Column("target_id", String, ForeignKey("model_base.id"), primary_key=True),
)
## Model definitions
class ModelBase(Base):
__tablename__ = "model_base"
id = Column(Integer, primary_key=True)
type = Column(String, nullable=False)
__mapper_args__ = {
"polymorphic_on": type,
"polymorphic_identity": None,
}
targets = relationship(
"ModelBase",
secondary="permissions",
primaryjoin="model_base.c.id == permissions.c.origin_id",
secondaryjoin="model_base.c.id == permissions.c.target_id",
backref="origins",
# lazy="raise",
)
class ClassA(ModelBase):
__tablename__ = "class_a"
__mapper_args__ = {"polymorphic_identity": "class_a"}
id = Column(ForeignKey("model_base.id"), primary_key=True)
name = Column(String)
class ClassB(ModelBase):
__tablename__ = "class_b"
__mapper_args__ = {"polymorphic_identity": "class_b"}
id = Column(ForeignKey("model_base.id"), primary_key=True)
name = Column(String)
value = Column(Integer)
## Tests
def _main():
with Session(engine) as session:
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
print("=" * 80)
# data
a1, a2 = [ClassA(name="a1"), ClassA(name="a2")]
b1, b2 = [ClassB(name="b1"), ClassB(name="b2", value=3)]
session.add_all([a1, a2, b1, b2])
session.flush()
a1.targets.append(a2)
a1.targets.append(b1)
a1.targets.append(b2)
print(b2.targets)
print(b2.origins)
session.commit()
session.expunge_all()
print("=" * 80)
a1 = session.query(ClassA).first()
print(a1)
print(a1.origins)
print(a1.targets)
session.commit()
if __name__ == "__main__":
_main()
Here’s an example of what the error output recommends to remediate:
from sqlalchemy.ext.declarative import declared_attr
class Base:
...
#declared_attr
def targets(cls):
return relationship(
'Base',
secondary='permissions',
primaryjoin='Base.id == permissions.c.origin_id',
secondaryjoin='Base.id == permissions.c.target_id',
backref='origins'
)
...
Sidenote: you can use the as_declarative mixin on your base class as a shortcut.
References
Augmenting the base: https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/mixins.html#augmenting-the-base
declared_attr: https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/api.html#sqlalchemy.ext.declarative.declared_attr
as_declarative: https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/api.html#sqlalchemy.ext.declarative.as_declarative

Why am I getting "InvalidRequestError"?

I'm trying to implement some foreign keys between tables and query using joins as per this ORM tutorial:
from sqlalchemy import (Column, ForeignKey, Integer, create_engine)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
Base = declarative_base()
class DataAccessLayer():
def __init__(self):
conn_string = "mysql+mysqlconnector://root:root#localhost/"
self.engine = create_engine(conn_string)
def create_session(self):
Base.metadata.create_all(self.engine)
Session = sessionmaker()
Session.configure(bind=self.engine)
self.session = Session()
class Bet(Base):
__tablename__ = "bet"
__table_args__ = ({"schema": "belgarath", "extend_existing": True})
id_ = Column(Integer, primary_key=True)
match_id = Column(Integer, ForeignKey("belgarath.match_.id_"))
match_ = relationship("Match", back_populates="belgarath.bet")
class Match(Base):
__tablename__ = "match_"
__table_args__ = ({"schema": "belgarath", "extend_existing": True})
id_ = Column(Integer, primary_key=True)
tournament_id = Column(Integer)= Column(Integer)
dal = DataAccessLayer()
dal.create_session()
bets = dal.session.query(Bet)
bets.join(Match)
for bet in bets:
print(bet.id_, bet.tournament_id = Column(Integer))
However I'm getting the following error on the bets = dal.session.query(Bet) line:
Exception has occurred: InvalidRequestError
Mapper 'mapped class Match->match_' has no property 'belgarath.bet'
Where am I going wrong? Do I need some kind of reciprocal relationship in Match?

SQLAlchemy Polymorphism: Parent table does not exist when creating a child record

When trying to commit a record the following exception arises:
OperationalError (sqlite3.OperationalError) no such
table: basedocument [SQL: INSERT INTO basedocument (common_field,
doc_type) VALUES (?, ?)] [parameters: ('humanidade', 'user')]
basedocument is the parent table in a polimorphic association, and user is the child table.
Shouldn't the parent table be created when I create a record on a child table?
Here's the code:
from sqlalchemy import create_engine, ForeignKey, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.orm import scoped_session, sessionmaker
engine = create_engine("sqlite:///temp.db")
sessao = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()
class BaseDocument(Base):
__tablename__ = "basedocument"
id = Column(Integer, primary_key=True)
common_field = Column(String)
doc_type = Column(String(20))
__mapper_args__ = {
"polymorphic_identity": "basedocument",
"polymorphic_on": doc_type,
}
class User(BaseDocument):
__tablename__ = "user"
id = Column(Integer, ForeignKey("basedocument.id"), primary_key=True)
name = Column(String)
fullname = Column(String)
nickname = Column(String)
__mapper_args__ = {
"polymorphic_identity": "user",
}
u1 = User(
name="Dumont",
fullname="Santos Dumont",
nickname="voador",
common_field="humanidade",
)
sessao.add(u1)
sessao.commit()
print("\nObject: ", u1)
Well, I just noticed that I forgot to use
Base.metadata.create_all(engine)
after the classes definitions. This is what really creates the tables.
This answer is valid for many questions around the "No such table" error.
The right code:
from sqlalchemy import create_engine, ForeignKey, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.orm import scoped_session, sessionmaker
engine = create_engine("sqlite:///temp.db")
sessao = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()
class BaseDocument(Base):
__tablename__ = "basedocument"
id = Column(Integer, primary_key=True)
common_field = Column(String)
doc_type = Column(String(20))
__mapper_args__ = {
"polymorphic_identity": "basedocument",
"polymorphic_on": doc_type,
}
class User(BaseDocument):
__tablename__ = "user"
id = Column(Integer, ForeignKey("basedocument.id"), primary_key=True)
name = Column(String)
fullname = Column(String)
nickname = Column(String)
__mapper_args__ = {
"polymorphic_identity": "user",
}
Base.metadata.create_all(engine)
u1 = User(
name="Dumont",
fullname="Santos Dumont",
nickname="voador",
common_field="humanidade",
)
sessao.add(u1)
sessao.commit()
print("\nObject: ", u1)

Extract table names from SQLAlchemy exist query

How do i extract the table name from SQLAlchemy exist statement
assume we have the following code
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
class Person(Base):
__tablename__ = 'person'
id = Column(Integer, primary_key=True)
name = Column(String(250), nullable=False)
engine = create_engine('mysql://...')
Session = sessionmaker(bind=engine)
conn = engine.connect()
session = Session(bind=conn)
query_exists = session.query(Person).exists()
how can i extract the table name from the query_exists?
from sqlalchemy.sql.visitors import ClauseVisitor
from sqlalchemy import Table
def extract_tables(sql_stmt):
tables = []
visitor = ClauseVisitor()
cluase_iter = visitor.iterate(elem)
for e in cluase_iter:
if isinstance(e, Table):
tables.append(e)
if isinstance(e, (ValuesBase, UpdateBase)):
tables.append(e.table)
return set(tables)

SQLAlchemy and Normalization

I have a question about sqlalchemy and DB normalization.
I have a table called Accounts, and 2 kinds of persons, Natural_Persons and Legal_Persons.
I need to relate an account to just one person at a time.
For example, the account ID 4 is related with the Natural_Person ID 5.
But... How can I know when I query that information, if the ID 5 in the account record is from a Natural Person or a Legal one?
The simplest solution (for me at the moment) is to add a new field to the Accounts table called person_type, and to use, for example, a char to differentiate them.
So now I have a record in the accounts table with the following data:
account_id = 4
person_id = 5
person_type = N
But now I want to use the DB with sqlalchemy.
If I load an account record using a Account class instance then if I access the "person" attribute it should check the person_type field and create an instance of NaturalPerson class, or LegalPerson class according to the case!
Something like:
acc = Account(4)
acc.person
"""
if person_type == "L", person returns a LegalPerson instance
but otherwise ...
"""
Table inheritance is what you are looking for:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy import create_engine, Column, Integer, ForeignKey, String
Base = declarative_base()
class Account(Base):
__tablename__ = 'account'
id = Column(Integer, primary_key=True)
person_id = Column(Integer, ForeignKey('person.id'))
person = relationship("Person")
class Person(Base):
__tablename__ = 'person'
id = Column(Integer, primary_key=True)
name = Column(String(50))
type = Column(String(20))
__mapper_args__ = {
'polymorphic_on':type,
'polymorphic_identity':'base'
}
class NaturalPerson(Person):
__mapper_args__ = {
'polymorphic_identity':'natural'
}
class LegalPerson(Person):
__mapper_args__ = {
'polymorphic_identity':'legal'
}
engine = create_engine('sqlite:///:memory:', echo=True)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
a = Account()
np = NaturalPerson()
a.person = np
session.add(a)
a = session.query(Account).first()
print type(a.person)

Categories