Flask-SQLAIchemy扩展的配置与使用
在Flask中,所有扩展插件基本上都是Flask-Xxx进行命名,Flask-SQLAIchemy就是一个为Flask应用增加SQLAIchemy支持的扩展,致力于简化在Flask中SQLAIchemy的使用,而SQLAIchemy是目前Python中最强大的ORM框架,功能全面,使用简单。
文档资料:
- Flask-SQLAlchemy 文档:https://flask-sqlalchemy.palletsprojects.com/en/3.1.x/
- SQLAlchemy 文档:https://www.sqlalchemy.org/
ORM 是什么
ORM 其实是 对象映射关系(Object-Relational Mapping),即将数据库中的表与面向对象编程中的类关联起来,它把数据库中的表映射为类,表中的行映射为类的实例,表中的列映射为类的属性。这样一来,就可以通过对类的操作来进行数据库的增删改查,而不必直接操作数据库,让程序员更加专注于业务逻辑,减少了与数据库交互的复杂性。
例如有一张基础表为 account,字段信息如下:
sql
CREATE TABLE "account" (
"id" uuid NOT NULL,
"name" varchar(255) NOT NULL,
"email" varchar(255) NOT NULL,
"avatar" varchar(255) NOT NULL,
"password" varchar(255) NOT NULL,
"updated_at" timestamp(6) NOT NULL,
"created_at" timestamp(6) NOT NULL,
);
将其映射到 Python 的 Class 为:
python
import uuid
from datetime import datetime
from sqlalchemy import Column, String, DateTime, UUID, PrimaryKeyConstraint, Index
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Account(db.Model):
__tablename__ = "account"
__table_args__ = (
PrimaryKeyConstraint("id", name="pk_account_id"),
Index("idx_account_email", "email"),
)
id = Column(UUID, default=uuid.uuid4, nullable=False)
name = Column(String(255), default="", nullable=False)
email = Column(String(255), default="", nullable=False)
avatar = Column(String(255), default="", nullable=False)
password = Column(String(255), default="", nullable=False)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, nullable=False)
created_at = Column(DateTime, default=datetime.now, nullable=False)
这样,在 Python 中,只需要对映射类进行特定方法的调用,即可实现数据库的增删改查,伪代码示例如下:
python
# 类映射伪代码
account = Account(id="xxx-xxx-xxx-xxx")
account.add() # 增:映射到SQL中的 create table xxx (xxx, xxx, xxx);
account.delete() # 删:映射到SQL中的 delete from xxx where xxx;
account.update() # 改:映射到SQL中的 update table set xxx=xxx, xxx=xxx where xxx;
account.get() # 查:映射到SQL中的 select * from xxx where xxx;
通过 ORM 可以在代码中少写甚至不写 SQL,去实现数据库的增删改查等复杂业务,同时确保 SQL 安全,不被注入。从上面可以看出 ORM 的优缺点其实非常明显:
- 优点
- 有语法提示,省去自己拼写 SQL,保证 SQL 语法的正确性;
- ORM 提供方言功能(dialect,可以转换为多种数据库语法),减少学习成本与迁移数据库的成本;
- 面向对象,可读性强,开发效率高;
- 防止 SQL 注入攻击;
- 搭配数据库迁移,更新数据库方便;
- 缺点
- 需要语法转换,效率比原生 SQL 低;
- 复杂的查询往往语法比较复杂(可以使用原生 SQL 代替);
Flask-SQLAlchemy 环境安装
使用 pip 可以直接安装 Flask-SQLAlchemy,命令如下:
powershell
pip install flask-sqlalchemy
期间如果提示 ModuleNotFoundError: No module named 'psycopg2',是因为使用 Postgres 作为数据库,但缺少驱动引擎导致的,安装对应的驱动引擎即可,命令如下:
bash
pip install psycopg2
也可以一次性安装两个依赖:
bash
pip install flask-sqlalchemy psycopg2
和其他的 Flask 扩展一样,几乎绝大部分扩展都需要进行初始化与配置,Flask-SQLAlchemy 也一样。Flask-SQLAlchemy 的相关配置封装到了 Flask 的配置项中,可以通过 app.config 配置,或使用配置加载方案(如 config.from_object)进行设置。
SQLALCHEMY 的主要配置如下:
| 配置项 | 说明 | 示例 |
|---|---|---|
SQLALCHEMY_DATABASE_URI |
设置数据库的连接地址 | postgresql://root:123456@127.0.0.1:5432/test31 |
SQLALCHEMY_BINDS |
访问多个数据库时,用于设置数据库的连接地址 | {"other_db": "sqlite:///other.db"} |
SQLALCHEMY_ECHO |
是否打印底层执行的 SQL 语句 | True |
SQLALCHEMY_RECORD_QUERIES |
是否记录执行的查询语句,用于慢查询分析,调试模式下自动启动 | False |
SQLALCHEMY_TRACK_MODIFICATIONS |
是否追踪数据库变化(触发钩子函数),会消耗额外的内存 | False |
SQLALCHEMY_ENGINE_OPTIONS |
设置针对 sqlalchemy 本体的配置项 | SQLALCHEMY_POOL_SIZE: 连接池最大数量 SQLALCHEMY_POOL_RECYCLE: 单个连接最长生命周期 |
要从 Flask 中将配置信息加载到 Flask-SQLAlchemy,可以使用 db.init(flask_app) 即可。
应用ORM模型的创建与增删改查
创建模型
要想在项目中使用 ORM,必须创建对应的类,并继承 db.Model,即可实现与对应数据库进行关联。在类中还可以通过 __tablename__ 属性来设置表名,通过 __table_args__ 来为表添加相应的主键、index 索引、unique 索引等约束。
其中关于 ORM 模型的字段类型、索引约束、主键等均可以从 sqlalchemy 包中导入,从这个包导入的类支持所有的数据库,在切换数据库时不需要进行调整,除此之外还可以导入特定数据库特有的结构,例如 Postgres 中特有的 JSONB 数据类型,特有的类型在切换数据库时,需要修改代码进行调整。
一些常见的 SQLAlchemy 数据类型
python
from sqlalchemy import (
Column, # 用户定义字段
UUID, # UUID 类型,例如:123e4567-e89b-12d3-a456-426655440000
String, # 字符串,对应数据库的 varchar
Text, # 长文本类型,对应数据库的 text
DateTime, # 时间类型,对应数据库的 timestamp
Integer, # 整型,对应数据库的 int
Decimal, # 小数型,对应数据库的 decimal
PrimaryKeyConstraint, # 创建主键约束
Index, # 创建索引
UniqueConstraint, # 创建唯一值约束
)
from sqlalchemy.dialects.postgresql import JSONB # Postgres 特有的 JSONB 数据类型,使用二进制存储 JSON,性能更强
完整实例:
python
import uuid
from datetime import datetime
from sqlalchemy import (
Column,
UUID,
String,
DateTime,
PrimaryKeyConstraint,
Index,
)
from sqlalchemy.dialects.postgresql import JSONB
from internal.extension.database_extension import db
class App(db.Model):
"""AI应用模型"""
__tablename__ = "app"
__table_args__ = (
PrimaryKeyConstraint("id", name="pk_app_id"),
Index("idx_app_account_id", "account_id"),
)
id = Column(UUID, default=uuid.uuid4, nullable=False)
account_id = Column(UUID, nullable=False)
name = Column(String(255), default="", nullable=False)
icon = Column(String(255), default="", nullable=False)
config = Column(JSONB, default={}, nullable=False)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, nullable=False)
created_at = Column(DateTime, default=datetime.now, nullable=False)
创建完模型后,可以通过调用 db.create_all() 在对应的数据库中创建相应的模型表,例如:
python
from flask import Flask
from internal.model import App
class Http(Flask):
def __init__(self):
# ... 其他初始化代码
with self.app_context():
# 确保模型已被导入,这样 db.create_all() 才会识别它
_ = App()
# 创建所有定义的模型表
db.create_all()
...
对于这类规律性 + 重复性的代码,可以考虑使用 ChatGPT 来生成,创建 ORM 模型提示词:
对于这类规律性+重复性的代码,可以考虑使用 ChatGPT 来生成,创建 ORM 模型提示词:
角色
你是一个拥有10年经验的资深Python工程师,精通Flask,Flask-SQLAlchemy,Postgres,以及其他Python开发工具,能够为用户提出的需求或者提供的代码段生成指定的完整代码。
技能说明
- 如果需要实现Flask-SQLAlchemy的ORM类,集成
db.Model时,从from internal.extension.database_extension import db这里导入db; - 创建ORM模型时,表名
__tablename__及类名全部都是单数; - 所有的字段都要添加
nullable=False代表字段不允许为空; - UUID类型的字段添加默认值
default=uuid.uuid4,String类型的字段长度均设置为String(255),默认值设置为default=""; - 所有模型都有
updated_at和created_at字段,类型均是DateTime,其中updated_at包含default和onupdate,而created_at仅包含default,值全部都是datetime.now; - 请给ORM模型添加上
__table_args__属性,涵盖PrimaryKeyConstraint为主键,所有模型都以id为主键,主键的类型为UUID,如果用户声明其他约束,例如UniqueConstraint、Index等时,请按照需求进行添加; - 属性的类型全部从
sqlalchemy包中导入,例如:from sqlalchemy import (Column, UUID, String, DateTime, PrimaryKeyConstraint, UniqueConstraint); uuid.uuid4从import uuid中导入,datetime.now从from datetime import datetime导入;- 对于
description等字段,通过字面意思,可以看出是描述,一般内容比较长,可以使用Text类型; - 用户如果表名了某个字段类型为
json,则统一设置成JSONB,并从from sqlalchemy.dialects.postgresql import JSONB导入,这是Postgres特有的; - 其他的规范请根据你的知识库进行操作,项目使用的数据库是Postgres;
操作实例
bash
import uuid
from datetime import datetime
from sqlalchemy import (
Column,
UUID,
String,
DateTime,
PrimaryKeyConstraint,
Index,
)
from sqlalchemy.dialects.postgresql import JSONB
from internal.extension.database_extension import db
class App(db.Model):
"""AI应用模型"""
__tablename__ = "app"
__table_args__ = (
PrimaryKeyConstraint("id", name="pk_app_id"),
Index("idx_app_account_id", "account_id"),
)
id = Column(UUID, default=uuid.uuid4, nullable=False)
account_id = Column(UUID, nullable=False)
name = Column(String(255), default="", nullable=False)
icon = Column(String(255), default="", nullable=False)
config = Column(JSONB, default={}, nullable=False)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, nullable=False)
created_at = Column(DateTime, default=datetime.now, nullable=False)
注意事项
- 只处理与生成Python相关的提问,对于其他非相关行业问题,请婉拒回答。
- 只使用用户使用的语言进行回答,不使用其他语言。
- 确保回答的针对性和专业性。
用户的需求是:
新增数据
在 Flask-SQLAlchemy 中有两种主要的方式来执行数据库查询:
- 使用 db.session.query(Model).xxx这种方法使用 SQLAlchemy 原生查询语法来操作数据库,可以使用 .query 来访问一个表的查询,然后使用 .xxx 来指定你要执行的操作,比如 .filter()、.order_by() 等。这种方法更加灵活,可以用于复杂的查询需求。
- 也可以使用 ORM 模型的 query 属性每个继承 db.Model 的模型都有一个 query 属性,可以通过这个属性来执行查询,例如 App.query.filter() 等。
示例:
python
# 使用 db.session.query.xxx 的方式
from yourapp import db
from yourapp.models import User
# 查询所有用户,并按照ID升序排列
users = db.session.query(User).order_by(User.id).all()
# 查询用户名为'john'的用户
user = db.session.query(User).filter_by(username='john').first()
python
# 使用 ORM 模型的方式
from yourapp.models import User
# 查询所有用户,并按照ID升序排列
users = User.query.order_by(User.id).all()
# 查询用户名为'john'的用户
user = User.query.filter_by(username='john').first()
在 LLMOps 项目中,我们约定无论是单表、多表、简单亦或者复杂的操作,均使用 db.session.query.xxx 的方式进行操作,确保业务逻辑的统一性。
如果想要往表里添加数据,只需依次调用 add(model) 和 .commit() 即可,示例:
python
def demo():
# 实例化 Students 模型对象
user = Students(name='yy', fullname='yoyo')
# 添加到会话,并用commit提交数据
db.session.add(user)
db.session.commit()
return {
"code": 0,
"msg": "create success!"
}
查询数据
在 Flask-SQLAlchemy 中执行数据查询的方式非常多,示例:
python
Model.query.all() # 获取表内的所有数据
Model.query.first() # 获取第一个查询结果
Model.query.get(id) # 根据主键 id 进行查询
Model.query.filter().all() # 筛选查询,相当于 SQL 中的 WHERE 语句
Model.query.filter_by().all() # 筛选查询,相当于 SQL 中的 WHERE 语句
Model.query.filter().one_or_none() # 筛选查询,相当于 SQL 中的 WHERE + LIMIT 1,只获取一条数据
修改和删除数据
在 Flask-SQLAlchemy 中,无论是更新还是删除,只要使用了 db.session 进行操作都需要调用 commit() 才会将数据同步到实际数据库中。
python
# 1.通过调用 update 方法进行更新(支持批量)
Students.query.filter_by(name='yy').update({"fullname": "xx"})
# 2.通过修改模型实例属性进行更新
student = Student.query.first()
student.name = "imooc"
db.session.commit()
python
# 1.通过调用 delete 方法删除数据(支持批量)
Student.query.filter_by(name="imooc").delete()
# 2.通过 db.session.delete 进行删除
student = Student.query.first()
db.session.delete(student)
db.session.commit()
重写SQLAlchemy核心类实现自动提交
commit与rollback
对于新增、修改、删除的操作,每次都需要写 db.session.commit() 进行提交,并且在业务逻辑中,如果触发了异常,还需要调用 db.session.rollback() 进行回滚,例如:
python
try:
student = Student.query.first()
student.name = "imooc"
db.session.commit()
except Exception as e:
db.session.rollback()
对于重复性的操作,一般的方式是提供一个工具方法或工具类,所以可以重写 SQLAlchemy 核心类,创建 auto_commit 并使用 contextmanager 装饰该函数。
python
from contextlib import contextmanager
from flask_sqlalchemy import SQLAlchemy as _SQLAlchemy
class SQLAlchemy(_SQLAlchemy):
"""重写SQLAlchemy核心,实现数据自动提交"""
@contextmanager
def auto_commit(self):
try:
yield
self.session.commit()
except Exception as e:
self.session.rollback()
raise e
@contextmanager 装饰器的原理其实非常简单,它将生成器函数中 yield 之前的部分视为上下文的进入操作,yield 之后的部分视为离开操作。因此,我们可以在 yield 之前对数据进行操作(新增、修改、删除),在 yield 之后执行 self.session.commit() 提交更改。
有了该方法后,对于数据库操作,我们可以这样简化代码:
python
with db.auto_commit():
student = Student.query.first()
student.name = "imooc"
Flask-Migrate扩展介绍与使用
我们使用 db.create_all() 将 ORM 模型映射到数据库的表中,但是如果新增了 ORM 模型,或者更新了 ORM 模型,使用 db.create_all() 都不会重新创建表或是更新表。
需要先使用 db.drop_all() 删除数据库中的所有表之后再调用 db.create_all() 才能重新创建,但是这样的话,原来表中的数据就都被删除了,这肯定是不行的,于是就有了数据库迁移。
Flask-Migrate 初始化
在开发过程中,随着需求的变化,有可能需要添加或修改表的一些字段,但是原表中的数据不能删除,此时就需要创建新表,并将旧表中的数据迁移至新表中,Flask-Migrate 这个扩展就可以在不破坏数据的情况下更新数据库表的结构,并完成数据从旧表到新表的迁移。
安装命令:
bash
pip install flask-migrate
安装后进行初始化即可:
python
from flask_migrate import Migrate
migrate = Migrate()
migrate.init_app(app, db, directory="internal/migration")
Flask-Migrate 常见命令
初始化迁移环境
在开始迁移数据之前,需要先使用 init 命令初始化创建一个迁移环境:
bash
# 当 flask 的应用入口在项目根目录,且文件名为 app.py,并且实例变量名为 app 时
flask db init
# LLM Ops 项目运行命令
flask --app app.server.app db init
运行命令后,会在 directory 指定的位置创建一个迁移环境,默认位置为 ./migrations。
生成迁移脚本
使用如下命令自动生成迁移脚本:
bash
flask --app app.server.app db migrate -m "create_table"
其中 -m "create_table" 是可选的,用于为这次迁移添加一个简单的注释。
在生成的迁移脚本中,会包含两个函数:
- upgrade():把迁移的改动应用到数据中
- downgrade():将改动撤销
注意下,在生产环境中一般不使用 Flask-Migrate 迁移,一般人工生成相应的 SQL 执行迁移,如果要使用迁移,一定要仔细检查生成的迁移文件是否符合预期,确认后才可以使用。
更新及回滚数据库
生成迁移脚本后,可以使用 upgrade 命令来更新数据库:
python
flask --app app.server.app db upgrade
每一次更新 ORM 模型,都需要执行 migrate 命令生成迁移文件,然后才可以使用 upgrade 命令将更新同步到数据库中。
如果想要回滚数据库,可以使用 downgrade 命令:
python
flask --app app.server.app db downgrade
每执行一次命令会向上回滚一个版本,如果想一次性回滚到最原始的版本(即删除所有数据库表),可以使用如下命令:
python
flask --app app.server.app db downgrade base
如果想回滚到特定的版本,可以在 downgrade 后带上特定的版本号:
python
flask --app app.server.app db downgrade <版本号>