FastAPI可以和任何数据库和任意样式的库配合使用,这里看一下使用SQLAlchemy的示例。下面的示例很容易的调整为PostgreSQL
,MySQL
,SQLite
,Oracle
等。当前示例中我们使用SQLite
ORM对象关系映射
FastAPI可以与任何数据库在任何样式的库中一起与数据库进行通信。一种常见的模式就是“ORM
”:对象关系映射。所谓对象关系映射就是,ORM具有在代码和数据库表(“关系型”)中的对象中间转换(“映射”)的工具。即使用ORM,在SQL数据库创建一个代表映射的类,该类的每个数据代表数据表中的一个列,具有名称和类型。约定俗成:类使用大写如Pet,代表SQL为表的pets,该累的每个实例对象标识数据库中的一行数据。比如一个对象orion_cat(Pet的一个实例)可以有一个属性orion_cat.type, 对标数据库中的type列。并且该属性的值可以是其它,例如"cat"
文件结构
假设有个fastapi_sqlalchemy的项目,其下有一个包为sql_app,结构如下所示:
fastapi_sqlalchemy
├── sql_app
│ ├── __init__.py
│ ├── crud.py
│ ├── database.py
│ ├── main.py
│ ├── models.py
│ ├── requirements.txt
│ └── schemas.py
└── sql_app.db
既然是sql_app包,肯定少不了__init__.py
,这里的初始化函数为空。其中依赖requirement.txt中为
fastapi==0.111.0
pydantic==2.7.4
SQLAlchemy==2.0.30
📢📢:这里使用的SQLAlchemy 为2.0以上版本,如果使用v1,则有些语法不同。
创建SQLAlchemy部件
首先看一下sql_app/database.py
创建SQLAlchemy引擎和初始化Base类
# @File : database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
# 来允许SQLite这样
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# 用于建立数据库SQLAlchemy模型models
Base = declarative_base()
创建一个SQLAlchemy 引擎,其中connect_args={“check_same_thread”: False} 仅用于SQLite,其他数据库不需要 。
默认情况下,SQLite 只允许一个线程与其通信,假设有多个线程的话,也只将处理一个独立的请求。这是为了防止意外地为不同的事物(不同的请求)共享相同的连接。但是在 FastAPI 中,使用普通函数(def)时,多个线程可以为同一个请求与数据库交互,所以我们需要使用connect_args={“check_same_thread”: False}来让SQLite允许这样。
此外,我们将确保每个请求都在依赖项中获得自己的数据库连接会话,因此不需要该默认机制。
连接到一个SQLite数据库,该文件在当前目录下的sql_app.db中
创建SessionLocal
的时候,每个 SessionLocal 的类的实例都是一个数据库会话,当然该类本身还不是数据库会话。但是一旦我们创建了一个SessionLocal类的实例,这个实例将是实际的数据库会话。我们将它命名为SessionLocal是为了将它与我们从 SQLAlchemy 导入的Session区别开来。稍后我们将使用Session(从 SQLAlchemy 导入的那个)。要创建SessionLocal类,请使用函数sessionmaker
Base = declarative_base()
,在后面的会继承这个Base类,来创建每个数据库模型或者类(ORM模型)
创建数据库模型models
在文件sql_app/models.py
中,使用上一步创建的Base类派生子类来创建SQLAlchemy模型。
📢📢:
- SQLAlchemy中的“模型”指的的是和数据库交互的类和实例
- Pydantic中的“模型”指的是不同的东西,即数据验证、转换以及文档类和实例
# @File : models.py
from sql_app.database import Base
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True) # primary_key 为True标识主键
email = Column(String, unique=True, index=True) # unique 如果为True表示这列不允许出现重复的值,index=True为这列创建索引,提升查询效率
hashed_password = Column(String)
is_active = Column(Boolean, default=True) # default=True 为这列设置默认值
items = relationship("Item", back_populates="owner")
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True)
title = Column(String, index=True)
description = Column(String, index=True)
owner_id = Column(Integer, ForeignKey("users.id"))
owner = relationship("User", back_populates="items")
- 这个__tablename__属性是用来告诉 SQLAlchemy 要在数据库中为每个模型使用的数据库表的名称
- 使用Column来表示 SQLAlchemy 中的默认值。传递一个 SQLAlchemy “类型”,如Integer、String和Boolean,它定义了数据库中的类型,作为参数,以及其他限定。这些属性中的每一个都代表其相应数据库表中的一列
- 关系或者说内联外联的关系,使用
relationship
,当访问 user 中的属性items时,如 中my_user.items,它将有一个ItemSQLAlchemy 模型列表(来自items表),这些模型具有指向users表中此记录的外键。当您访问my_user.items时,SQLAlchemy 实际上会从items表中的获取一批记录并在此处填充进去。同样,当访问 Item中的属性owner时,它将包含表中的UserSQLAlchemy 模型users。使用owner_id属性/列及其外键来了解要从users表中获取哪条记录
创建Pydantic模型
接下来看看sql_app/shchemas.py
📢📢:为了避免 SQLAlchemy模型和 Pydantic模型之间的混淆,我们将有models.py(SQLAlchemy 模型的文件)和schemas.py( Pydantic 模型的文件)。这些 Pydantic 模型或多或少地定义了一个“schema”(一个有效的数据形状)。因此,这将帮助我们在使用两者时避免混淆。
创建初始化Pydantic模式/模型
创建一个ItemBase
和UserBase
Pydantic模型(或者我们说“schema
”),他们拥有创建或读取数据时具有的共同属性。然后创建一个继承自他们的ItemCreate
和UserCreate
,并添加创建时所需的其他数据(或属性)。因此在创建时也应当有一个password属性。但是为了安全起见,password不会出现在其他同类 Pydantic模型中,例如通过API读取一个用户数据时,它不应当包含在内。
# @File : schemas.py
from pydantic import BaseModel
class ItemBase(BaseModel):
title: str
description: str | None = None
class UserBase(BaseModel):
email: str
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
# orm_mode = True
from_attributes = True
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: list[Item] = []
class Config:
# orm_mode = True
# 解决pydantic/_internal/_config.py:334: UserWarning: Valid config keys have changed in V2:
# * 'orm_mode' has been renamed to 'from_attributes'
from_attributes = True
先介绍下Config类的作用
class Config:
# orm_mode = True
from_attributes = True
如果是Pydantic 是v1版本,则使用orm=True
, 本机环境安装的是v2+版本。这个类是一个Pydantic配置项。orm_mode或者from_attributes 会告诉Pydantic模型读取数据,而不仅仅是字典,而是一个ORM模型(或者说一个具有属性的对象)。这样,而不是仅仅试图从dict上 id 中获取值,如下所示:
id = data["id"]
它还会尝试从属性中获取它,如:
id = data.id
有了这个,Pydantic模型与 ORM 兼容,您只需在路径操作response_model的参数中声明它即可。将能够返回一个数据库模型,它将从中读取数据
📢📢:SQLAlchemy 风格和 Pydantic 风格¶
请注意,SQLAlchemy模型使用 =来定义属性,并将类型作为参数传递给Column,例如:
name = Column(String)
虽然 Pydantic模型使用: 声明类型,但新的类型注释语法/类型提示是:
name: str
请牢记这一点,这样您在使用:还是=时就不会感到困惑
CRUD工具
现在让我们看看文件sql_app/crud.py
。在这个文件中,我们将编写可重用的函数用来与数据库中的数据进行交互。
CRUD分别为:增加(Create)、查询(Read)、更改(Update)、删除(Delete),即增删改查。
…虽然在这个例子中我们只是新增和查询。
读取数据¶
从 sqlalchemy.orm
中导入Session
,这将允许您声明db参数的类型,并在您的函数中进行更好的类型检查和完成。导入之前的models(SQLAlchemy 模型)和schemas
(Pydantic模型/模式)。
创建一些工具函数来完成:
- 通过 ID 和电子邮件查询单个用户。
- 查询多个用户。
- 查询多个项目
# @File : crud.py
from sqlalchemy.orm import Session
from sql_app import models, schemas
def get_user(db: Session, user_id: int):
return db.query(models.User).filter(models.User.id == user_id).first()
def get_user_by_email(db: Session, email: str):
return db.query(models.User).filter(models.User.email == email).first()
def get_users(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.User).offset(skip).limit(limit).all()
def create_user(db: Session, user: schemas.UserCreate):
fake_hashed_password = user.password + "notreallyhashed"
db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
def get_items(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Item).offset(skip).limit(limit).all()
def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int):
db_item = models.Item(**item.dict(), owner_id=user_id)
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
📢📢:Tip
这里不是将每个关键字参数传递给Item并从Pydantic模型中读取每个参数,而是先生成一个字典,其中包含Pydantic模型的数据:
item.dict()
然后我们将dict的键值对 作为关键字参数传递给 SQLAlchemy Item:
Item(**item.dict())
然后我们传递 Pydantic模型未提供的额外关键字参数owner_id:
Item(**item.dict(), owner_id=user_id)
因此创建数据的步骤即为:
现在创建工具函数来创建数据。
- 使用您的数据创建一个 SQLAlchemy 模型实例。
- 使用add来将该实例对象添加到数据库会话。
- 使用commit来将更改提交到数据库(以便保存它们)。
- 使用refresh来刷新您的实例对象(以便它包含来自数据库的任何新数据,例如生成的 ID)
FastAPI应用程序
现在在sql_app/main.py
文件中 让我们集成和使用我们之前创建的所有其他部分。
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from sql_app import schemas, crud, models
from sql_app.database import SessionLocal, engine
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
@app.get("/users/", response_model=list[schemas.User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
@app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post("/users/{user_id}/items/", response_model=schemas.Item)
def create_item_for_user(
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
):
return crud.create_user_item(db=db, item=item, user_id=user_id)
@app.get("/items/", response_model=list[schemas.Item])
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
items = crud.get_items(db, skip=skip, limit=limit)
return items
现在使用我们在sql_app/database.py
文件中创建的SessionLocal
来创建依赖项。我们需要每个请求有一个独立的数据库会话/连接(SessionLocal),在整个请求中使用相同的会话,然后在请求完成后关闭它。然后将为下一个请求创建一个新会话。为此,我们将创建一个包含yield的依赖项,正如前面关于Dependencies with yield
的部分中所解释的那样。我们的依赖项将创建一个新的 SQLAlchemy SessionLocal
,它将在单个请求中使用,然后在请求完成后关闭它。
📢📢:参数db实际上是 type SessionLocal,但是这个类(用 创建sessionmaker())是 SQLAlchemy 的“代理” Session,所以,编辑器并不真正知道提供了哪些方法。
但是通过将类型声明为Session,编辑器现在可以知道可用的方法(.add()、.query()、.commit()等)并且可以提供更好的支持(比如完成)。类型声明不影响实际对象。
运行
# 执行命令
% uvicorn sql_app.main:app --reload
打开浏览器:http://127.0.0.1:8000/docs