什么是SQLModel
- 一个用于与SQL数据库交互的库
- 基于类型注解
- 与FastAPI兼容性非常高,因为这俩库是一人写的
安装
pip install sqlmodel
完了
创建模型与表
创建模型
假设你需要创建一个名为hero
的表,包含以下字段:
- id
- name
- secret_name
- age
那么你可以这么写:
from sqlmodel import Field, SQLModel, create_engine
# create_engine将会在之后的代码中用到
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
secret_name: str
age: int | None = None
假设你有好好学基础Python课程,知道一些基础语法,但你仍然可能对上述代码有一些小小的困惑,它可能包含了一些你不清楚的语法,这里稍微做一些解释:
name: str
:这里的:
是类型标注的一部分,整体而言,这里就是在告诉Python(以及你的IDE)变量name
是str
类型的。关于类型标注,你还可以参考Python 类型提示简介 – FastAPIid: int | None
:这里除了:
还出现了|
,这与 C++ 的|
差别不大,可以理解为或。就是说,变量id可能是int
也可能是None
table=True
代表了这个类将会被当作表处理。如果不设置这项,那SQLModel不会将此类和数据库中的表对应,之后你也不能创建hero
表了
在上述代码中,我们将变量id
设为int
或是None
,因为id
是hero
的自增主键,在创建时不需要人为指定,因此在创建时其值可能是None
,SQLModel
在实际往表中添加记录时会帮你处理这个问题。当然,你也需要使用Field()
来指定这是个主键
你可能看出来了,这里的name
和secret_name
必须在创建hero
实例时指定,而age
不需要
创建数据库中的表
要使用简单方便的sqlite数据库,你可以用以下方式创建数据库引擎:
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
当然,你也可以换成MySQL数据库,那么你需要这么写:
mysql_url = f"mysql+pymysql://{user}:{password}@{host}/{db_name}?charset={charset}"
# db_name是特定的数据库的名字
# 常见的charset可能形如"utf8mb4"
engine = create_engine(mysql_url, echo=True)
无论使用什么数据库,现在我们都获得了一个连接数据库的引擎engine
。由于我们将echo
设为了True
,SQLModel会在执行任何SQL语句时都输出对应语句。当然,在生产环境时你不需要他,只需要把他删掉即可,默认值为False
最后,你终于可以创建这个表了:
SQLModel.metadata.create_all(engine)
这句语句执行完毕后,数据库内就会出现hero
表了
噢,我猜聪明的你肯定在想,为什么我没有往这个
create_all
里传任何表类作为参数,但是他还是成功创建了这个表?事实上,SQLModel会记录你所创建的所有继承于
SQLModel
类的子类,然后他就能知道创建哪些表这很重要,因为有时候你会将定义模型和创建表放在两个文件里。这时,你就需要在创建表前先
import
入对应模型,然后再使用create_all
函数
往表内插入数据
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
hero_2 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
# 数据来自SQLModel文档,略有删改
session = Session(engine)
session.add(hero_1)
session.add(hero_2)
session.commit()
session.close()
hero_1
和hero_2
都是Hero
类的实例,很好理解session
是与数据库的对话,用于处理这组关于数据库的操作- 通过
session.add()
,你可以往数据库里添加数据 session.commit()
提交了这些数据,直到此时,数据库内的数据才会真的有所更改- 最后,使用
session.close()
关闭会话
当然,你也可以使用with
语句块来自动进行session
的关闭:
with Session(engine) as session:
session.add(hero_1)
session.add(hero_2)
session.commit()
在
session.commit()
后,如果你直接再次访问hero_1
,其不会包含数据,因为他需要刷新这会在你调用了任何
hero_1
的字段(例如hero_1.id
)过后被隐式自动刷新。你也可以使用session.refresh(hero_1)
来刷新,使其包含了最新的数据
删改查
查
在之前的模型基础上:
from sqlmodel import Session, select
with Session(engine) as session:
statement = select(Hero)
results = session.exec(statement)
for hero in results:
print(hero)
在这里:
select()
创建了一个statement
,这个statement
需要放到session.exec()
里执行- 这里返回的
results
是一个可迭代对象,而非一个列表。如果你直接需要一个列表,可以使用`session.exec().all() - 如果你只需要一个对象,可以使用
results.first()
如果你需要某些特定的记录,你可以这么做:
from sqlmodel import Session, select, where
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Deadpond")
results = session.exec(statement)
for hero in results:
print(hero)
在这里:
where()
接受的是一个表达式,而非单纯的传参- 如果你单纯只需要通过
id
查找一个对象,可以使用session.get(Hero, hero_id)
改
简单的更改对象,然后保存即可
with Session(engine) as session:
hero = session.get(Hero, hero_id)
hero.age = 16
session.add(hero)
session.commit()
# 如果你需要在此之后继续访问这个hero对象,你就需要...
session.refresh(hero)
# 然后你就可以...
print(hero)
删
简单的使用session.delete()
即可
with Session(engine) as session:
hero = session.get(Hero, hero_id)
session.delete(hero)
session.commit()