-
-
Couldn't load subscription status.
- Fork 900
Description
I've been playing with mapped_column and MappedAsDataclass (new stuff in SQLAlchemy 2.x):
Declarative Table with mapped_column()
Declarative Dataclass Mapping
Example:
mkdir sqlaplayground
cd sqlaplayground
pyenv local 3.11
python -m venv .venv
source .venv/bin/activate
pip install -U pip wheel setuptools
cat requirements.txt
# sqlalchemy >= 2.0.0b3
# flask-sqlalchemy @ git+https://github.com/pallets-eco/flask-sqlalchemy@3.0.x
# flask >= 2.0.0
pip install -r requirements.txtfrom __future__ import annotations
from typing import Optional
from sqlalchemy import ForeignKey, String, create_engine, orm
from sqlalchemy.orm import (
DeclarativeBase, Mapped, MappedAsDataclass, mapped_column, sessionmaker
)
CONFIG = {
"SQLALCHEMY_DATABASE_URI": "sqlite+pysqlite://",
}
Engine = create_engine(CONFIG["SQLALCHEMY_DATABASE_URI"], future=True)
Session = sessionmaker(bind=Engine, future=True)
session = Session()
class BaseModel(MappedAsDataclass, DeclarativeBase):
"""subclasses will be converted to dataclasses"""
pass
class Book(BaseModel):
__tablename__ = "books"
id: Mapped[Optional[int]] = mapped_column(
init=False, primary_key=True, autoincrement=True
)
title: Mapped[Optional[str]] = mapped_column(String(length=64), default=None)
author_id: Mapped[Optional[int]] = mapped_column(
ForeignKey("authors.id"), nullable=False, index=True, default=None
)
author: Mapped[Optional[Author]] = orm.relationship(
"Author", uselist=False, back_populates="books", default=None
)
class Author(BaseModel):
__tablename__ = "authors"
id: Mapped[Optional[int]] = mapped_column(
init=False, primary_key=True, autoincrement=True
)
name: Mapped[Optional[str]] = mapped_column(String(length=64), default=None)
books: Mapped[list[Book]] = orm.relationship(
"Book", uselist=True, back_populates="author", default_factory=list
)
BaseModel.metadata.create_all(Engine)
book = Book(title="42", author=Author(name="Name"))
session.add(book)
session.commit()This rather verbose declaration of models gives us some nice things:
- more precise
mypyandPyRightstatic typechecking. - dataclass-like
__init__
repr(book)
# "Book(id=1, title='42', author_id=1, author=Author(id=1, name='Name', books=[...]))"
?book.__init__
# book.__init__(title=None, author_id=None, author=None)First try at using my BaseModel with Flask-SQLAlchemy gets me into trouble with metaclass inheritance:
import flask
import flask_sqlalchemy
app = flask.Flask(__name__)
app.config.from_mapping(CONFIG)
db = flask_sqlalchemy.SQLAlchemy(model_class=BaseModel)
# TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict)
# subclass of the metaclasses of all its baseswhich is fair enough...
I did try few things from Advanced Customization but so far came empty handed.
Can Flask-SQLAlchemy support this "new" declarative models?
Should it?
Maybe avoid this problem by somehow replacing metaclass magic with __init_subclass__ from
PEP 487 - Simpler customisation of class creation