FastAPI不需要您使用SQL(关系)数据库。
但是您可以使用所需的任何关系数据库。
在这里,我们将看到一个使用SQLAlchemy的示例。
您可以轻松地使其适应SQLAlchemy支持的任何数据库,例如:
在此示例中,我们将使用SQLite,因为它使用单个文件并且Python具有集成的支持。因此,您可以复制此示例并按原样运行它。
稍后,对于您的生产应用程序,您可能想要使用PostgreSQL之类的数据库服务器。
Tip
有一个使用FastAPI和PostgreSQL的官方项目生成器,它们都基于Docker,包括前端和更多工具: https://github.com/tiangolo/full-stack-fastapi-postgresql
Note
注意,大多数代码是SQLAlchemy您将在任何框架中使用的标准代码。
该FastAPI具体的代码是一如既往的小。
FastAPI可与任何数据库和任何样式的库一起使用,以与数据库进行通信。
一种常见的模式是使用“ ORM”:“对象关系映射”库。
ORM具有在代码和数据库表中的对象(“ 关系 ”)之间进行转换(“ 映射 ”)的工具。
使用ORM,通常可以创建一个表示SQL数据库中的表的类,该类的每个属性表示一个具有名称和类型的列。
例如,一个类Pet可以表示一个SQL表pets。
该类的每个实例对象都代表数据库中的一行。
例如,对象orion_cat(的实例Pet)可以具有orion_cat.type该列的属性type。该属性的值可以是例如"cat"。
这些ORM还具有在表或实体之间建立连接或关系的工具。
这样,您还可以拥有一个属性orion_cat.owner,并且所有者将包含该宠物所有者的数据,该数据取自表owner。
因此,orion_cat.owner.name可能是该宠物主人的名字(来自表中的name列owners)。
它的值可能像"Arquilian"。
当您尝试从宠物对象访问信息时,ORM会做所有工作来从相应的表所有者那里获取信息。
常见的ORM例如:Django-ORM(Django框架的一部分),SQLAlchemy ORM(SQLAlchemy的一部分,独立于框架)和Peewee(独立于框架),等等。
在这里,我们将看到如何使用SQLAlchemy ORM。
以类似的方式,您可以使用任何其他ORM。
Tip
在文档中有一篇使用Peewee的等效文章。
对于这些示例,假设您有一个名为的目录my_super_project,其中包含一个名为的子目录
sql_app,其结构如下:
.└── sql_app
├── init.py
├── crud.py
├── database.py
├── main.py
├── models.py
└── schemas.py
该文件__init__.py只是一个空文件,但是它告诉Python sql_app所有模块(Python文件)都是一个包。
现在,让我们看看每个文件/模块的功能。
让我们参考文件 sql_app/database.py.
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
...
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
在此示例中,我们正在“连接”到SQLite数据库(使用SQLite数据库打开文件)。
该文件将位于文件中的同一目录中test.db。
这就是为什么最后一部分是./test.db。
如果您使用的是PostgreSQL数据库,则只需取消注释以下行:
SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
…并使其适应您的数据库数据和凭据(等效于MySQL,MariaDB或其他任何数据库)。
Tip
如果要使用其他数据库,这是必须修改的行。
第一步是创建一个SQLAlchemy“引擎”。
稍后我们将engine在其他地方使用它。
engine = create_engine( SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} )
注意
参数:
connect_args={"check_same_thread":False}
…仅适用于SQLite。其他数据库不需要它。
默认情况下,假定每个线程将处理一个独立的请求,SQLite将仅允许一个线程与其通信。
这是为了防止为不同的事物(针对不同的请求)意外共享同一连接。
但是在FastAPI中,使用普通函数(def),可以针对同一请求使用多个线程与数据库进行交互,因此我们需要使SQLite知道它应该允许使用connect_args={“check_same_thread”: False}。
另外,我们将确保每个请求都以依赖关系获取其自己的数据库连接会话,因此不需要该默认机制。
SessionLocal该类的每个实例将是一个数据库会话。该类本身还不是数据库会话。
但是,一旦我们创建了SessionLocal该类的实例,该实例将成为实际的数据库会话。
我们SessionLocal将其命名为有别于Session我们从SQLAlchemy导入的名称。
稍后我们将使用Session(从SQLAlchemy导入的一种)。
要创建SessionLocal类,请使用函数sessionmaker:
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
现在,我们将使用declarative_base()返回类的函数。
稍后,我们将从该类继承以创建每个数据库模型或类(ORM模型):
Base = declarative_base()
现在来看文件sql_app/models.py。
from sqlalchemy
import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from .database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
items = relationship("Item", back_populates="owner")
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True)
description = Column(String, index=True)
owner_id = Column(Integer, ForeignKey("users.id"))
owner = relationship("User", back_populates="items")
我们将使用Base之前创建的此类创建SQLAlchemy模型。
Tip
SQLAlchemy使用术语“ 模型 ”来指代与数据库交互的这些类和实例。
但是Pydantic还使用术语“ 模型 ”来指代不同的东西,即数据验证,转换以及文档类和实例。
创建从其继承的类。
这些类是SQLAlchemy模型。
from .database
import Base
class User(Base):
__tablename__ = "users"
...
class Item(Base):
__tablename__ = "items"
...
该__tablename__属性告诉SQLAlchemy这些模型中的每个模型要在数据库中使用的表的名称。
创建模型属性/列
现在创建所有模型(类)属性。
这些属性中的每一个都代表其相应数据库表中的一列。
我们使用ColumnSQLAlchemy中的默认值。
而我们通过SQLAlchemy的类“类型”,如Integer,String和Boolean,它定义了数据库的类型,作为参数。
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
...
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True)
description = Column(String, index=True)
owner_id = Column(Integer, ForeignKey("users.id"))
...
现在创建关系。
为此,我们使用relationshipSQLAlchemy ORM提供的方法。
这或多或少将成为“魔术”属性,其中将包含与该表相关的其他表中的值。
...
from sqlalchemy.orm import relationship
...
items = relationship("Item", back_populates="owner")
...
...
owner = relationship("User", back_populates="items")
当访问中的属性items时User(如中的)my_user.items,它将具有一个ItemSQLAlchemy模型列表(来自items表),该模型具有指向users表中该记录的外键。
当您访问时my_user.items,SQLAlchemy实际上会从items表中的数据库中获取项目并在此处填充它们。
并且当访问中的属性owner时Item,它将包含表中的UserSQLAlchemy模型users。它将使用owner_id属性/列及其外键来知道要从users表中获取哪个记录。
现在让我们检查文件sql_app/schemas.py。
from typing import List
from pydantic import BaseModel
class ItemBase(BaseModel):
title: str
description: str = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
orm_mode = True
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: List[Item] = []
class Config:
orm_mode = True
Tip
为了避免SQLAlchemy 模型和Pydantic 模型之间的混淆,我们将使用models.py带有SQLAlchemy模型的文件,以及schemas.py带有Pydantic模型的文件。
这些Pydantic模型或多或少定义了“模式”(有效数据形状)。
因此,这将有助于我们避免在同时使用两者时产生混淆。
创建一个ItemBase和UserBasePydantic 模型(或称“方案”)以在创建或读取数据时具有共同的属性。
并创建一个ItemCreate和并UserCreate从它们继承(这样它们将具有相同的属性),以及创建所需的所有其他数据(属性)。
因此,用户password在创建它时也会有一个。
但是为了安全password起见,例如不会在其他Pydantic 模型中使用,因此在读取用户时不会从API发送该消息。
...
from pydantic import BaseModel
class ItemBase(BaseModel):
title: str
description: str = None
class ItemCreate(ItemBase):
pass
...
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
...
请注意,SQLAlchemy 模型使用定义属性=,并将类型作为参数传递给Column,例如:
name=Column(String)
而Pydantic 模型使用声明类型:,新的类型注释语法/类型提示:
name:str
有它在心中,所以在使用的时候,你就不会感到困惑 = and :
创建用于读取/返回的Pydantic 模型 /模式
现在创建Pydantic 模型(方案),该模型在读取数据以及从API返回数据时将使用。
例如,在创建项目之前,我们不知道分配给它的ID是什么,但是在读取它(从API返回)时,我们已经知道它的ID。
同样,当读取用户时,我们现在可以声明items将包含属于该用户的项目。
不仅是这些商品的ID,还包括我们在Pydantic 模型中定义的用于读取商品的所有数据:Item。
...
class Item(ItemBase):
id: int
owner_id: int
...
class User(UserBase):
id: int
is_active: bool
items: List[Item] = []
Tip
请注意,在读取用户(从API返回)时将使用User的Pydantic 模型不包含password。
现在,在Pydantic 模型中阅读Item和User添加一个内部Config类。
此类Config用于为Pydantic提供配置。
在Config类中,设置属性orm_mode = True。
...
class Item(ItemBase):
...
class Config:
orm_mode = True
...
class User(UserBase):
...
class Config:
orm_mode = True
Tip
请注意,它使用分配了一个值=,例如:
orm_mode = True
它以前没有:用于类型声明。
这是在设置配置值,而不是声明类型。
即使orm_modePydantic 模型不是dictORM模型(或具有属性的任何其他任意对象),Pydantic 也会告诉Pydantic 模型读取数据。
这样,而不是仅尝试从中获取id值dict,如:
id=data["id"]
它还将尝试从属性获取它,如:
id=data.id
这样,Pydantic 模型与ORM兼容,您可以response_model在path操作的参数中声明它。
您将能够返回数据库模型,并且它将从中读取数据。
SQLAlchemy和许多其他默认情况下是“延迟加载”。
例如,这意味着除非您尝试访问将包含该数据的属性,否则它们不会从数据库中获取关系数据。
例如,访问属性items:
current_user.items
将使SQLAlchemy转到items表中并获取该用户的项目,但不能早于此。
没有orm_mode,如果您从路径操作返回了SQLAlchemy模型,则该模型将不包含关系数据。
即使您在Pydantic模型中声明了这些关系。
但是在ORM模式下,由于Pydantic本身会尝试从属性访问其所需的数据(而不是假设dict),因此您可以声明要返回的特定数据,并且即使从ORM中也可以获取它。
现在,我们来看文件sql_app/crud.py。
from sqlalchemy.orm import Session
from . 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
在此文件中,我们将具有可重用的功能来与数据库中的数据进行交互。
CRUD来源于:Create, Read, Update, and Delete.
…尽管在此示例中,我们仅创建和阅读。
Session从导入sqlalchemy.orm,这将允许您声明db参数的类型,并在函数中进行更好的类型检查和完成。
导入models(SQLAlchemy模型)和schemas(Pydantic 模型 /模式)。
创建实用程序函数以:
from sqlalchemy.orm import Session
from . 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 get_items(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Item).offset(skip).limit(limit).all()
Tip
通过创建仅用于与数据库交互(获取用户或项目)的函数,而与路径操作函数无关,您可以更轻松地在多个部分中重用它们,并为其添加单元测试。
现在创建实用程序函数来创建数据。
这些步骤是:
...
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 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
SQLAlchemy模型User包含hashed_password,其中应包含密码的安全散列版本。
但是由于API客户端提供的是原始密码,因此您需要提取原始密码并在应用程序中生成哈希密码。
然后将hashed_password参数传递给保存的值。
Warning
此示例不安全,密码不进行哈希处理。
在现实生活中的应用程序中,您将需要对密码进行哈希处理,并且永远不要以明文形式保存它们。
有关更多详细信息,请返回教程中的“安全性”部分。
在这里,我们仅关注数据库的工具和机制。
Tip
我们没有将每个关键字参数传递给ItemPydantic 模型并从Pydantic 模型中读取每个参数,而是dict使用Pydantic 模型的数据生成一个,该参数具有:
item.dict()
然后我们将dict的键/值对作为关键字参数传递给SQLAlchemy Item,并带有:
Item(**item.dict())
A然后,我们传递owner_idPydantic 模型未提供的extra关键字参数,并带有:
Item(**item.dict(), owner_id=user_id)
现在,在文件中,sql_app/main.py我们集成并使用之前创建的所有其他部分。
from typing import List
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from . import crud, models, schemas
from .database import SessionLocal, engine
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Dependency
def get_db():
try:
db = SessionLocal()
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
以一种非常简单的方式创建数据库表:
models.Base.metadata.create_all(bind=engine)
通常,您可能会使用Alembic初始化数据库(创建表等)。
而且您还将Alembic用于“迁移”(这是它的主要工作)。
“迁移”是每当您更改SQLAlchemy模型的结构,添加新属性等以在数据库中复制这些更改,添加新列,新表等时所需的一组步骤。
信息
为此,您需要使用Python 3.7或更高版本,或者在Python 3.6中,安装“ backports”:
bash pip install async-exit-stack async-generator
这将安装async-exit-stack和async-generator。
您还可以将替代方法与最后说明的“中间件”一起使用。
现在,使用SessionLocal我们在sql_app/databases.py文件中创建的类来创建依赖项。
我们需要SessionLocal每个请求有一个独立的数据库会话/连接(),在所有请求中使用相同的会话,然后在请求完成后将其关闭。
然后将为下一个请求创建一个新会话。
为此,我们将使用创建新的依赖关系yield,如之前在带有依赖项yield的部分中所述。
我们的依赖关系将创建一个新的SQLAlchemy SessionLocal,它将在单个请求中使用,然后在请求完成后将其关闭。
... def get_db():
try:
db = SessionLocal()
yield db
finally:
db.close()
...
信息
我们将的创建SessionLocal()和请求处理放在一个try块中。
然后我们将其关闭finally。
这样,我们确保在请求后数据库会话始终关闭。即使在处理请求时出现异常。
然后,在路径操作函数中使用依赖项时,我们使用Session直接从SQLAlchemy导入的类型对其进行声明。
然后,这将为我们提供更好的路径操作功能内的编辑器支持,因为编辑器将知道db参数的类型Session:
...
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
...
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
...
def read_user(user_id: int, db: Session = Depends(get_db)):
...
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
...
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
...
技术细节
参数db实际上是type SessionLocal,但是此类(用创建sessionmaker())是SQLAlchemy的“代理” Session,因此,编辑器实际上并不知道提供了什么方法。
但是,作为申报类型Session,编辑器现在可以知道可用的方法(.add(),.query(),.commit()等),并能提供更好的支持(如完成)。类型声明不影响实际对象。
现在,最后是标准的FastAPI 路径操作代码。
...
@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
我们正在使用yield,在依赖项中的每个请求之前创建数据库会话,然后在之后关闭它。
然后,我们可以在path操作函数中创建所需的依赖关系,以直接获取该会话。
这样,我们可以crud.get_user直接从path操作函数内部调用并使用该会话。
Tip
请注意,您返回的值是SQLAlchemy模型或SQLAlchemy模型的列表。
但是,由于所有路径操作都response_model使用Pydantic 模型 /模式orm_mode,因此Pydantic模型中声明的数据将通过所有常规操作进行过滤和验证,并从中提取并返回给客户端。
Tip
另请注意,其中包含response_models具有标准Python类型,例如List[schemas.Item]。
但是作为内容/的该参数List是一个Pydantic 模型与orm_mode,该数据将被检索并返回到客户端为常,没有任何问题。
在这里,我们在路径操作函数内部以及在依赖项中使用SQLAlchemy代码,然后它将与外部数据库进行通信。
这可能需要一些“等待”。
但是由于SQLAlchemy不具有await直接使用的兼容性,因此类似于:
user=awaitdb.query(User).first()
…相反,我们使用的是:
user=db.query(User).first()
然后,我们应将路径操作函数和相关性声明为不带async def,而仅以普通形式声明def为:
...
def read_user(user_id: int, db: Session = Depends(get_db)):
...
如果你是好奇,并有深厚的技术功底,你可以检查如何非常技术细节async defVS def是在处理异步文档。
迁移¶
因为我们直接使用SQLAlchemy,并且不需要任何插件即可与FastAPI一起使用,所以我们可以直接将数据库迁移与Alembic集成。
并且,由于与SQLAlchemy和SQLAlchemy模型相关的代码位于单独的独立文件中,因此您甚至可以使用Alembic执行迁移,而无需安装FastAPI,Pydantic或其他任何工具。
以同样的方式,您将能够在代码的其他部分中使用与FastAPI不相关的相同SQLAlchemy模型和实用程序。
例如,在使用Celery,RQ或ARQ的后台任务工作者中。
请记住,您应该有一个名为的目录my_super_project,其中包含一个名为的子目录sql_app。
sql_app 应该具有以下文件:
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
#SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from .database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
items = relationship("Item", back_populates="owner")
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True)
description = Column(String, index=True)
owner_id = Column(Integer, ForeignKey("users.id"))
owner = relationship("User", back_populates="items")
from typing import List
from pydantic import BaseModel
class ItemBase(BaseModel):
title: str
description: str = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
orm_mode = True
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: List[Item] = []
class Config:
orm_mode = True
from sqlalchemy.orm import Session
from . 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
from typing import List
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from . import crud, models, schemas
from .database import SessionLocal, engine
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Dependency
def get_db():
try:
db = SessionLocal()
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
Check it
您可以复制此代码并按原样使用。
信息
实际上,此处显示的代码是测试的一部分。就像这些文档中的大多数代码一样。
然后,您可以使用Uvicorn运行它:
uvicorn sql_app.main:app --reload
然后,您可以在 http://127.0.0.1:8000/docs打开浏览器。
您将能够与FastAPI应用程序进行交互,从真实的数据库中读取数据:
如果要独立于FastAPI来直接浏览SQLite数据库(文件)以调试其内容,添加表,列,记录,修改数据等,则可以使用DB Browser for SQLite。
它看起来像这样:
您还可以使用在线SQLite浏览器,例如SQLite Viewer或ExtendsClass。
中间件替代DB会话¶
如果您不能使用依赖项yield-例如,如果您没有使用Python 3.7,并且无法安装上述针对Python 3.6的“反向端口”,则可以在类似的“中间件”中设置会话办法。
“中间件”基本上是始终针对每个请求执行的功能,其中某些代码在端点功能之前执行,而某些代码在端点功能之后执行。
我们将添加的中间件(只是一个函数)将为SessionLocal每个请求创建一个新的SQLAlchemy ,将其添加到请求中,然后在请求完成后将其关闭。
...
@app.middleware("http")
async def db_session_middleware(request: Request, call_next):
response = Response("Internal server error", status_code=500)
try:
request.state.db = SessionLocal()
response = await call_next(request)
finally:
request.state.db.close()
return response
...
信息
我们将的创建SessionLocal()和请求处理放在一个try块中。
然后我们将其关闭finally。
这样,我们确保在请求后数据库会话始终关闭。即使在处理请求时出现异常。.
request.state是每个Starlette Request对象的属性。它可以存储附加到请求本身的任意对象,例如本例中的数据库会话。
在这种情况下,对于我们来说,这有助于确保所有请求都使用一个数据库会话,然后在中间件中关闭。
在此处添加中间件与依赖项的功能类似,yield但有一些区别:
它需要更多代码,并且稍微复杂一些。
中间件必须是一种async功能。
每个请求都运行一个中间件。
tip
yield当依赖关系足以满足用例时,最好使用依赖关系。
信息
与的依赖关系yield最近已添加到FastAPI中。
本教程的先前版本仅包含带有中间件的示例,并且可能有多个应用程序使用中间件进行数据库会话管理。