高级自定义

通过向 SQLAlchemy 构造函数传递参数,可以自定义扩展管理的各种对象。

模型类

SQLAlchemy 模型全部继承自声明基类。在 Flask-SQLAlchemy 中,它显示为 db.Model,所有模型都对其进行扩展。可以通过子类化默认值并向 model_class 传递自定义类来对其进行自定义。

以下示例为每个模型提供一个整数主键,或为联接表继承提供一个外键。

注意

并非所有内容的整数主键都是最好的数据库设计(这取决于项目的具体要求),这只是一个示例。

from sqlalchemy import Integer, String, ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, declared_attr

class Base(DeclarativeBase):
    @declared_attr.cascading
    @classmethod
    def id(cls):
        for base in cls.__mro__[1:-1]:
            if getattr(base, "__table__", None) is not None:
                    return mapped_column(ForeignKey(base.id), primary_key=True)
            else:
                return mapped_column(Integer, primary_key=True)

db = SQLAlchemy(app, model_class=Base)

class User(db.Model):
    name: Mapped[str]

class Employee(User):
    title: Mapped[str]

抽象模型和 Mixin

如果仅在某些模型(而非所有模型)上需要行为,请使用抽象模型基类仅自定义这些模型。例如,如果某些模型应跟踪其创建或更新的时间。

from datetime import datetime, timezone
from sqlalchemy.orm import Mapped, mapped_column

class TimestampModel(db.Model):
    __abstract__ = True
    created: Mapped[datetime] = mapped_column(default=lambda: datetime.now(timezone.utc))
    updated: Mapped[datetime] = mapped_column(default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))

class Author(db.Model):
    id: Mapped[int] = mapped_column(primary_key=True)
    username: Mapped[str] = mapped_column(unique=True)

class Post(TimestampModel):
    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str]

还可以使用 mixin 类来完成此操作,分别从 db.Model 继承。

class TimestampMixin:
    created: Mapped[datetime] = mapped_column(default=lambda: datetime.now(timezone.utc))
    updated: Mapped[datetime] = mapped_column(default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))

class Post(TimestampMixin, db.Model):
    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str]

禁用表名生成

某些项目更喜欢手动设置每个模型的 __tablename__,而不是依赖 Flask-SQLAlchemy 的检测和生成。实现此目的的简单方法是设置每个 __tablename__,而不修改基类。但是,可以通过在 SQLAlchemy 构造函数中设置 disable_autonaming=True 来禁用表名生成。

class Base(sa_orm.DeclarativeBase):
    pass

db = SQLAlchemy(app, model_class=Base, disable_autonaming=True)

会话类

Flask-SQLAlchemy 的 Session 类根据与模型或表关联的绑定键选择要查询的引擎。但是,还有其他策略(例如水平分片),可以使用不同的会话类来实现。 class_ 是扩展的 session_options 参数的键,用于更改会话类。

Flask-SQLAlchemy 总是会将扩展实例作为 db 参数传递给会话,所以它必须接受该参数才能继续工作。这可用于访问 db.engines

from sqlalchemy.ext.horizontal_shard import ShardedSession
from flask_sqlalchemy.session import Session

class CustomSession(ShardedSession, Session):
    ...

db = SQLAlchemy(session_options={"class_": CustomSession})

查询类

警告

查询接口在 SQLAlchemy 中被认为是过时的。这包括 session.queryModel.querydb.Querylazy="dynamic" 关系。最好使用 session.execute(select(...))

可以自定义会话、模型和关系使用的查询接口。这可用于添加额外的查询方法。例如,你可以添加一个 get_or 方法,该方法获取一行或返回一个默认值。

from flask_sqlalchemy.query import Query

class GetOrQuery(Query):
    def get_or(self, ident, default=None):
        out = self.get(ident)

        if out is None:
            return default

        return out

db = SQLAlchemy(query_class=GetOrQuery)

user = User.query.get_or(user_id, anonymous_user)

传递 query_class 参数将自定义 db.Querydb.session.queryModel.querydb.relationship(lazy="dynamic") 关系。也可以在每个对象的基础上自定义这些关系。

要自定义特定模型的 query 属性,请在模型类上设置 query_class 属性。

class User(db.Model):
    query_class = GetOrQuery

要自定义特定的动态关系,请将 query_class 参数传递给该关系。

db.relationship(User, lazy="dynamic", query_class=GetOrQuery)

要仅自定义 session.query,请将 query_cls 键传递给构造函数的 session_options 参数。

db = SQLAlchemy(session_options={"query_cls": GetOrQuery})