started migrating ORM code from DB reflection to declarative mapping

This commit is contained in:
eclipse 2025-07-17 23:55:34 +02:00
parent 4517d0f557
commit 4b951ebf7d
7 changed files with 315 additions and 99 deletions

View File

@ -1,10 +1,18 @@
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from the_works.models import Base
db = SQLAlchemy() # Instantiate the SQLAlchemy around the database schema's declarative mapping
db = SQLAlchemy(model_class=Base)
def init_db(app): def init_db(app):
# initialize the Flask app with the flask_sqlalchemy extension
db.init_app(app) db.init_app(app)
# create nonexistent tables in DB
with app.app_context(): with app.app_context():
db.create_all()
"""
# check if database is empty and if so, populate it with fresh tables # check if database is empty and if so, populate it with fresh tables
#TODO: does it make sense to try and create all tables by default? Existing tables wouldn't be overwritten but what about DB constraints ? #TODO: does it make sense to try and create all tables by default? Existing tables wouldn't be overwritten but what about DB constraints ?
if not len(db.metadata.tables): if not len(db.metadata.tables):
@ -23,3 +31,4 @@ def init_db(app):
# generate declarative table objects by reflecting the DB # generate declarative table objects by reflecting the DB
db.reflect() db.reflect()
"""

View File

@ -1,93 +1,89 @@
from the_works.database import db # based on output of `sqlacodegen --generator declarative sqlite:///path/to/the_works.sqlite`
from sqlalchemy.orm import relationship
from sqlalchemy.ext.associationproxy import association_proxy from typing import List, Optional
from sqlalchemy import Column, ForeignKey, Integer, LargeBinary, Table, Text, types
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from flask import url_for from flask import url_for
import sys import sys
# add method to sqlalchemy.orm.decl_api.Model
def _asdict(self):
d = {}
for col in self.__table__.c:
if type(col.type) == db.types.BLOB:
d[col.key] = "Blob (NULL)" if self.__getattribute__(col.key) == None else f"Blob ({sys.getsizeof(self.__getattribute__(col.key))} Bytes)"
else:
value = str(self.__getattribute__(col.key))
d[col.key] = value[:50] + '...' if len(value) > 50 else value
return d
db.Model._asdict = _asdict
# override repr() method from sqlalchemy.orm.decl_api.Model class Base(DeclarativeBase):
def __repr__(self): def _asdict(self):
return f"{type(self).__name__}({str(self._asdict())})" d = {}
db.Model.__repr__ = __repr__ for col in self.__table__.c:
if type(col.type) == types.BLOB:
d[col.key] = "Blob (NULL)" if self.__getattribute__(col.key) == None else f"Blob ({sys.getsizeof(self.__getattribute__(col.key))} Bytes)"
else:
value = str(self.__getattribute__(col.key))
d[col.key] = value[:50] + '...' if len(value) > 50 else value
return d
class Text(db.Model): def __repr__(self):
__table__ = db.Model.metadata.tables['Text'] return f"{type(self).__name__}({str(self._asdict())})"
reihe = relationship("Reihe", back_populates="text")
textform = relationship("Textform", back_populates="text")
sprache = relationship("Sprache", back_populates="text")
veroeffentlichung = relationship("Veroeffentlichung", back_populates="text")
text_genre = relationship("Text_Genre", back_populates="text", cascade="save-update, merge, delete, delete-orphan")
genres = association_proxy("text_genre", "Genre")
class Werk(db.Model):
__table__ = db.Model.metadata.tables['Werk']
reihe = relationship("Reihe", back_populates="werk")
verlag = relationship("Verlag", back_populates="werk")
werksform = relationship("Werksform", back_populates="werk")
veroeffentlichung = relationship("Veroeffentlichung", back_populates="werk")
werk_genre = relationship("Werk_Genre", back_populates="werk", cascade="save-update, merge, delete, delete-orphan")
# titelbild = relationship("Titelbild", back_populates="werk", cascade="all, delete-orphan", single_parent=True)
titelbild = relationship("Titelbild", back_populates="werk")
genres = association_proxy("werk_genre", "Genre")
werk_herausgeber = relationship("Werk_Herausgeber", back_populates="werk", cascade="save-update, merge, delete, delete-orphan")
herausgeber = association_proxy("werk_herausgeber", "Herausgeber")
class Veroeffentlichung(db.Model): class Genre(Base):
__table__ = db.Model.metadata.tables['Veroeffentlichung'] __tablename__ = 'Genre'
text = relationship("Text", back_populates="veroeffentlichung")
werk = relationship("Werk", back_populates="veroeffentlichung")
pseudonym = relationship("Pseudonym", back_populates="veroeffentlichung")
class Reihe(db.Model): ID: Mapped[int] = mapped_column(Integer, primary_key=True)
__table__ = db.Model.metadata.tables['Reihe'] Genre: Mapped[str]
text = relationship("Text", back_populates="reihe")
werk = relationship("Werk", back_populates="reihe")
verlag = relationship("Verlag", back_populates="reihe")
class Verlag(db.Model): text: Mapped[List['Text_']] = relationship('Text_', secondary='Text_Genre', back_populates='genre')
__table__ = db.Model.metadata.tables['Verlag'] werk: Mapped[List['Werk']] = relationship('Werk', secondary='Werk_Genre', back_populates='genre')
werk = relationship("Werk", back_populates="verlag")
reihe = relationship("Reihe", back_populates="verlag")
class Sprache(db.Model):
__table__ = db.Model.metadata.tables['Sprache']
text = relationship("Text", back_populates="sprache")
class Textform(db.Model): class Herausgeber(Base):
__table__ = db.Model.metadata.tables['Textform'] __tablename__ = 'Herausgeber'
text = relationship("Text", back_populates="textform")
class Werksform(db.Model): ID: Mapped[int] = mapped_column(Integer, primary_key=True)
__table__ = db.Model.metadata.tables['Werksform'] Name: Mapped[str]
werk = relationship("Werk", back_populates="werksform")
class Genre(db.Model): werk: Mapped[List['Werk']] = relationship('Werk', secondary='Werk_Herausgeber', back_populates='herausgeber')
__table__ = db.Model.metadata.tables['Genre']
text_genre = relationship("Text_Genre", back_populates="genre")
werk_genre = relationship("Werk_Genre", back_populates="genre")
class Pseudonym(db.Model):
__table__ = db.Model.metadata.tables['Pseudonym']
veroeffentlichung = relationship("Veroeffentlichung", back_populates="pseudonym")
class Herausgeber(db.Model): class Pseudonym(Base):
__table__ = db.Model.metadata.tables['Herausgeber'] __tablename__ = 'Pseudonym'
werk_herausgeber = relationship("Werk_Herausgeber", back_populates="herausgeber")
class Titelbild(db.Model): ID: Mapped[int] = mapped_column(Integer, primary_key=True)
__table__ = db.Model.metadata.tables['Titelbild'] Pseudonym: Mapped[str]
werk = relationship("Werk", back_populates="titelbild")
veroeffentlichung: Mapped[List['Veroeffentlichung']] = relationship('Veroeffentlichung', back_populates='pseudonym')
class Sprache(Base):
__tablename__ = 'Sprache'
ID: Mapped[int] = mapped_column(Integer, primary_key=True)
Sprache: Mapped[str]
text: Mapped[List['Text_']] = relationship('Text_', back_populates='sprache')
class Textform(Base):
__tablename__ = 'Textform'
ID: Mapped[int] = mapped_column(Integer, primary_key=True)
Textform: Mapped[str]
text: Mapped[List['Text_']] = relationship('Text_', back_populates='textform')
class Titelbild(Base):
__tablename__ = 'Titelbild'
ID: Mapped[int] = mapped_column(Integer, primary_key=True)
Mimetype: Mapped[str]
Dateiname: Mapped[str]
Dateigroesse: Mapped[int]
Breite: Mapped[int]
Hoehe: Mapped[int]
Bild: Mapped[bytes]
Thumbnail: Mapped[bytes]
sha256: Mapped[str] = mapped_column(Text, unique=True)
werk: Mapped[List['Werk']] = relationship('Werk', back_populates='titelbild')
def _asdict_with_urls(self): def _asdict_with_urls(self):
tb = self._asdict() tb = self._asdict()
@ -95,27 +91,117 @@ class Titelbild(db.Model):
tb["Thumbnail"] = url_for("titelbild.thumbnail", id=self.ID) tb["Thumbnail"] = url_for("titelbild.thumbnail", id=self.ID)
return tb return tb
class Text_Genre(db.Model):
__table__ = db.Model.metadata.tables['Text_Genre']
text = relationship("Text", back_populates="text_genre")
genre = relationship("Genre", back_populates="text_genre")
def __init__(self, genre: int): class Verlag(Base):
self.Genre = genre __tablename__ = 'Verlag'
class Werk_Genre(db.Model): ID: Mapped[int] = mapped_column(Integer, primary_key=True)
__table__ = db.Model.metadata.tables['Werk_Genre'] Verlag: Mapped[str]
werk = relationship("Werk", back_populates="werk_genre")
genre = relationship("Genre", back_populates="werk_genre")
def __init__(self, genre: int): reihe: Mapped[List['Reihe']] = relationship('Reihe', back_populates='verlag')
self.Genre = genre werk: Mapped[List['Werk']] = relationship('Werk', back_populates='verlag')
class Werk_Herausgeber(db.Model):
__table__ = db.Model.metadata.tables['Werk_Herausgeber']
werk = relationship("Werk", back_populates="werk_herausgeber")
herausgeber = relationship("Herausgeber", back_populates="werk_herausgeber")
def __init__(self, hrsg: int): class Werksform(Base):
self.Herausgeber = hrsg __tablename__ = 'Werksform'
ID: Mapped[int] = mapped_column(Integer, primary_key=True)
Werksform: Mapped[str]
werk: Mapped[List['Werk']] = relationship('Werk', back_populates='werksform')
class Reihe(Base):
__tablename__ = 'Reihe'
ID: Mapped[int] = mapped_column(Integer, primary_key=True)
Titel: Mapped[str]
Verlag: Mapped[Optional[str]] = mapped_column('Verlag', ForeignKey('Verlag.ID'))
verlag: Mapped[Optional['Verlag']] = relationship('Verlag', back_populates='reihe')
text: Mapped[List['Text_']] = relationship('Text_', back_populates='reihe')
werk: Mapped[List['Werk']] = relationship('Werk', back_populates='reihe')
class Text_(Base):
__tablename__ = 'Text'
ID: Mapped[int] = mapped_column(Integer, primary_key=True)
Titel: Mapped[str]
Untertitel: Mapped[Optional[str]]
Reihe: Mapped[Optional[int]] = mapped_column('Reihe', ForeignKey('Reihe.ID'))
Textform: Mapped[Optional[int]] = mapped_column('Textform', ForeignKey('Textform.ID'))
Sprache: Mapped[Optional[int]] = mapped_column('Sprache', ForeignKey('Sprache.ID'))
genre: Mapped[List['Genre']] = relationship('Genre', secondary='Text_Genre', back_populates='text')
reihe: Mapped[Optional['Reihe']] = relationship('Reihe', back_populates='text')
sprache: Mapped[Optional['Sprache']] = relationship('Sprache', back_populates='text')
textform: Mapped[Optional['Textform']] = relationship('Textform', back_populates='text')
veroeffentlichung: Mapped[List['Veroeffentlichung']] = relationship('Veroeffentlichung', back_populates='text')
class Werk(Base):
__tablename__ = 'Werk'
ID: Mapped[int] = mapped_column(Integer, primary_key=True)
Titel: Mapped[str]
Untertitel: Mapped[Optional[str]]
Werksform: Mapped[Optional[int]] = mapped_column('Werksform', ForeignKey('Werksform.ID'))
Verlag: Mapped[Optional[int]] = mapped_column('Verlag', ForeignKey('Verlag.ID'))
Reihe: Mapped[Optional[int]] = mapped_column('Reihe', ForeignKey('Reihe.ID'))
Reihennummer: Mapped[Optional[str]]
Erscheinungsdatum: Mapped[Optional[str]]
ISBN_13: Mapped[Optional[str]]
ISBN_10: Mapped[Optional[str]]
ISSN: Mapped[Optional[str]]
Preis: Mapped[Optional[str]]
Titelbild: Mapped[Optional[int]] = mapped_column('Titelbild', ForeignKey('Titelbild.ID'))
Klappentext: Mapped[Optional[str]]
Anmerkungen: Mapped[Optional[str]]
genre: Mapped[List['Genre']] = relationship('Genre', secondary='Werk_Genre', back_populates='werk')
herausgeber: Mapped[List['Herausgeber']] = relationship('Herausgeber', secondary='Werk_Herausgeber', back_populates='werk')
reihe: Mapped[Optional['Reihe']] = relationship('Reihe', back_populates='werk')
titelbild: Mapped[Optional['Titelbild']] = relationship('Titelbild', back_populates='werk')
verlag: Mapped[Optional['Verlag']] = relationship('Verlag', back_populates='werk')
werksform: Mapped[Optional['Werksform']] = relationship('Werksform', back_populates='werk')
veroeffentlichung: Mapped[List['Veroeffentlichung']] = relationship('Veroeffentlichung', back_populates='werk')
t_Text_Genre = Table(
'Text_Genre',
Base.metadata,
Column('Text', ForeignKey('Text.ID'), primary_key=True),
Column('Genre', ForeignKey('Genre.ID'), primary_key=True)
)
class Veroeffentlichung(Base):
__tablename__ = 'Veroeffentlichung'
ID: Mapped[int] = mapped_column(Integer, primary_key=True)
Text: Mapped[int] = mapped_column('Text', ForeignKey('Text.ID'))
Werk: Mapped[int] = mapped_column('Werk', ForeignKey('Werk.ID'))
Pseudonym: Mapped[int] = mapped_column('Pseudonym', ForeignKey('Pseudonym.ID'))
AltTitel: Mapped[Optional[str]]
AltUntertitel: Mapped[Optional[str]]
pseudonym: Mapped['Pseudonym'] = relationship('Pseudonym', back_populates='veroeffentlichung')
text: Mapped['Text_'] = relationship('Text_', back_populates='veroeffentlichung')
werk: Mapped['Werk'] = relationship('Werk', back_populates='veroeffentlichung')
t_Werk_Genre = Table(
'Werk_Genre',
Base.metadata,
Column('Werk', ForeignKey('Werk.ID'), primary_key=True),
Column('Genre', ForeignKey('Genre.ID'), primary_key=True)
)
t_Werk_Herausgeber = Table(
'Werk_Herausgeber',
Base.metadata,
Column('Herausgeber', ForeignKey('Herausgeber.ID'), primary_key=True),
Column('Werk', ForeignKey('Werk.ID'), primary_key=True)
)

121
the_works/old_models.py Normal file
View File

@ -0,0 +1,121 @@
from the_works.database import db
from sqlalchemy.orm import relationship
from sqlalchemy.ext.associationproxy import association_proxy
from flask import url_for
import sys
# add method to sqlalchemy.orm.decl_api.Model
def _asdict(self):
d = {}
for col in self.__table__.c:
if type(col.type) == db.types.BLOB:
d[col.key] = "Blob (NULL)" if self.__getattribute__(col.key) == None else f"Blob ({sys.getsizeof(self.__getattribute__(col.key))} Bytes)"
else:
value = str(self.__getattribute__(col.key))
d[col.key] = value[:50] + '...' if len(value) > 50 else value
return d
db.Model._asdict = _asdict
# override repr() method from sqlalchemy.orm.decl_api.Model
def __repr__(self):
return f"{type(self).__name__}({str(self._asdict())})"
db.Model.__repr__ = __repr__
class Text(db.Model):
__table__ = db.Model.metadata.tables['Text']
reihe = relationship("Reihe", back_populates="text")
textform = relationship("Textform", back_populates="text")
sprache = relationship("Sprache", back_populates="text")
veroeffentlichung = relationship("Veroeffentlichung", back_populates="text")
text_genre = relationship("Text_Genre", back_populates="text", cascade="save-update, merge, delete, delete-orphan")
genres = association_proxy("text_genre", "Genre")
class Werk(db.Model):
__table__ = db.Model.metadata.tables['Werk']
reihe = relationship("Reihe", back_populates="werk")
verlag = relationship("Verlag", back_populates="werk")
werksform = relationship("Werksform", back_populates="werk")
veroeffentlichung = relationship("Veroeffentlichung", back_populates="werk")
werk_genre = relationship("Werk_Genre", back_populates="werk", cascade="save-update, merge, delete, delete-orphan")
# titelbild = relationship("Titelbild", back_populates="werk", cascade="all, delete-orphan", single_parent=True)
titelbild = relationship("Titelbild", back_populates="werk")
genres = association_proxy("werk_genre", "Genre")
werk_herausgeber = relationship("Werk_Herausgeber", back_populates="werk", cascade="save-update, merge, delete, delete-orphan")
herausgeber = association_proxy("werk_herausgeber", "Herausgeber")
class Veroeffentlichung(db.Model):
__table__ = db.Model.metadata.tables['Veroeffentlichung']
text = relationship("Text", back_populates="veroeffentlichung")
werk = relationship("Werk", back_populates="veroeffentlichung")
pseudonym = relationship("Pseudonym", back_populates="veroeffentlichung")
class Reihe(db.Model):
__table__ = db.Model.metadata.tables['Reihe']
text = relationship("Text", back_populates="reihe")
werk = relationship("Werk", back_populates="reihe")
verlag = relationship("Verlag", back_populates="reihe")
class Verlag(db.Model):
__table__ = db.Model.metadata.tables['Verlag']
werk = relationship("Werk", back_populates="verlag")
reihe = relationship("Reihe", back_populates="verlag")
class Sprache(db.Model):
__table__ = db.Model.metadata.tables['Sprache']
text = relationship("Text", back_populates="sprache")
class Textform(db.Model):
__table__ = db.Model.metadata.tables['Textform']
text = relationship("Text", back_populates="textform")
class Werksform(db.Model):
__table__ = db.Model.metadata.tables['Werksform']
werk = relationship("Werk", back_populates="werksform")
class Genre(db.Model):
__table__ = db.Model.metadata.tables['Genre']
text_genre = relationship("Text_Genre", back_populates="genre")
werk_genre = relationship("Werk_Genre", back_populates="genre")
class Pseudonym(db.Model):
__table__ = db.Model.metadata.tables['Pseudonym']
veroeffentlichung = relationship("Veroeffentlichung", back_populates="pseudonym")
class Herausgeber(db.Model):
__table__ = db.Model.metadata.tables['Herausgeber']
werk_herausgeber = relationship("Werk_Herausgeber", back_populates="herausgeber")
class Titelbild(db.Model):
__table__ = db.Model.metadata.tables['Titelbild']
werk = relationship("Werk", back_populates="titelbild")
def _asdict_with_urls(self):
tb = self._asdict()
tb["Bild"] = url_for("titelbild.image", id=self.ID)
tb["Thumbnail"] = url_for("titelbild.thumbnail", id=self.ID)
return tb
class Text_Genre(db.Model):
__table__ = db.Model.metadata.tables['Text_Genre']
text = relationship("Text", back_populates="text_genre")
genre = relationship("Genre", back_populates="text_genre")
def __init__(self, genre: int):
self.Genre = genre
class Werk_Genre(db.Model):
__table__ = db.Model.metadata.tables['Werk_Genre']
werk = relationship("Werk", back_populates="werk_genre")
genre = relationship("Genre", back_populates="werk_genre")
def __init__(self, genre: int):
self.Genre = genre
class Werk_Herausgeber(db.Model):
__table__ = db.Model.metadata.tables['Werk_Herausgeber']
werk = relationship("Werk", back_populates="werk_herausgeber")
herausgeber = relationship("Herausgeber", back_populates="werk_herausgeber")
def __init__(self, hrsg: int):
self.Herausgeber = hrsg

View File

@ -6,12 +6,12 @@ import inspect
bp = Blueprint("home", __name__) bp = Blueprint("home", __name__)
# prepare list of DB table classes to be searched by search_all() # prepare list of ORM classes to be searched by search_all()
tables = [] tables = []
for name, obj in inspect.getmembers(the_works.models): for name, obj in inspect.getmembers(the_works.models):
if "_" not in name and inspect.isclass(obj): if inspect.isclass(obj) and issubclass(obj, the_works.models.Base) and obj.__name__ != "Base":
tables.append(obj) tables.append(obj)
#print(tables) #DEBUG print(f"tables is {tables}") #DEBUG
@bp.route("/") @bp.route("/")
def startpage(): def startpage():

View File

@ -1,7 +1,7 @@
from flask import Blueprint, render_template, request, redirect, flash, url_for from flask import Blueprint, render_template, request, redirect, flash, url_for
from sqlalchemy import select, insert, update, delete from sqlalchemy import select, insert, update, delete
from the_works.database import db from the_works.database import db
from the_works.models import Text, Reihe, Sprache, Textform, Text_Genre, Genre from the_works.models import Text_ as Text, Reihe, Sprache, Textform, t_Text_Genre, Genre
bp = Blueprint("text", __name__) bp = Blueprint("text", __name__)

View File

@ -1,7 +1,7 @@
from flask import Blueprint, render_template, request, redirect, flash, url_for from flask import Blueprint, render_template, request, redirect, flash, url_for
from sqlalchemy import select, insert, update, delete from sqlalchemy import select, insert, update, delete
from the_works.database import db from the_works.database import db
from the_works.models import Veroeffentlichung, Text, Werk, Werksform, Pseudonym from the_works.models import Veroeffentlichung, Text_ as Text, Werk, Werksform, Pseudonym
bp = Blueprint("veroeffentlichung", __name__) bp = Blueprint("veroeffentlichung", __name__)

View File

@ -1,7 +1,7 @@
from flask import Blueprint, render_template, request, redirect, flash, url_for from flask import Blueprint, render_template, request, redirect, flash, url_for
from sqlalchemy import select, insert, update, delete from sqlalchemy import select, insert, update, delete
from the_works.database import db from the_works.database import db
from the_works.models import Werk, Reihe, Verlag, Werksform, Werk_Genre, Genre, Werk_Herausgeber, Herausgeber, Titelbild from the_works.models import Werk, Reihe, Verlag, Werksform, t_Werk_Genre, Genre, t_Werk_Herausgeber, Herausgeber, Titelbild
from the_works.views import titelbild as tb from the_works.views import titelbild as tb
bp = Blueprint("werk", __name__) bp = Blueprint("werk", __name__)