​SQL (关系型) 数据库-fastapi集成

SQL (关系型) 数据库 - FastAPI

FastAPI不需要你使用SQL(关系型)数据库。

但是您可以使用任何您想要的关系型数据库。

在这里,让我们看一个使用着SQLAlchemy的示例。

您可以很容易地将SQLAlchemy支持任何数据库,像:

  • PostgreSQL
  • MySQL
  • SQLite
  • Oracle
  • Microsoft SQL Server,等等其它数据库

在此示例中,我们将使用SQLite,因为它使用单个文件并且 在Python中具有集成支持。因此,您可以复制此示例并按原样来运行它。

稍后,对于您的产品级别的应用程序,您可能会要使用像PostgreSQL这样的数据库服务器。

Tip

这儿有一个FastAPIPostgreSQL的官方项目生成器,全部基于Docker,包括前端和更多工具:GitHub - tiangolo/full-stack-fastapi-postgresql: Full stack, modern web application generator. Using FastAPI, PostgreSQL as database, Docker, automatic HTTPS and more.

Note

请注意,大部分代码是SQLAlchemy的标准代码,您可以用于任何框架。FastAPI特定的代码和往常一样少。

ORMs(对象关系映射)¶

FastAPI可与任何数据库在任何样式的库中一起与 数据库进行通信。

一种常见的模式是使用“ORM”:对象关系映射。

ORM 具有在代码和数据库表(“关系型”)中的对象之间转换(“映射*”)的工具。

使用 ORM,您通常会在 SQL 数据库中创建一个代表映射的类,该类的每个属性代表一个列,具有名称和类型。

例如,一个类Pet可以表示一个 SQL 表pets

该类的每个实例对象都代表数据库中的一行数据。

又例如,一个对象orion_catPet的一个实例)可以有一个属性orion_cat.type, 对标数据库中的type列。并且该属性的值可以是其它,例如"cat"

这些 ORM 还具有在表或实体之间建立关系的工具(比如创建多表关系)。

这样,您还可以拥有一个属性orion_cat.owner,它包含该宠物所有者的数据,这些数据取自另外一个表。

因此,orion_cat.owner.name可能是该宠物主人的姓名(来自表owners中的列name)。

它可能有一个像"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 文件)都是一个包。

现在让我们看看每个文件/模块的作用。

创建 SQLAlchemy 部件¶

让我们涉及到文件sql_app/database.py

导入 SQLAlchemy 部件¶

 

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" engine = create_engine( SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} ) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base()

为 SQLAlchemy 定义数据库 URL地址¶

 

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" engine = create_engine( SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} ) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base()

在这个例子中,我们正在“连接”到一个 SQLite 数据库(用 SQLite 数据库打开一个文件)。

该文件将位于文件中的同一目录中sql_app.db

这就是为什么最后一部分是./sql_app.db.

如果您使用的是PostgreSQL数据库,则只需取消注释该行:

 

SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"

...并根据您的数据库数据和相关凭据(也适用于 MySQL、MariaDB 或任何其他)对其进行调整。

Tip

如果您想使用不同的数据库,这是就是您必须修改的地方。

创建 SQLAlchemy 引擎¶

第一步,创建一个 SQLAlchemy的“引擎”。

我们稍后会将这个engine在其他地方使用。

 

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" engine = create_engine( SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} ) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base()

注意¶

参数:

 

connect_args={"check_same_thread": False}

...仅用于SQLite,在其他数据库不需要它。

技术细节

默认情况下,SQLite 只允许一个线程与其通信,假设有多个线程的话,也只将处理一个独立的请求。

这是为了防止意外地为不同的事物(不同的请求)共享相同的连接。

但是在 FastAPI 中,普遍使用def函数,多个线程可以为同一个请求与数据库交互,所以我们需要使用connect_args={"check_same_thread": False}来让SQLite允许这样。

此外,我们将确保每个请求都在依赖项中获得自己的数据库连接会话,因此不需要该默认机制。

创建一个SessionLocal类¶

每个实例SessionLocal都会是一个数据库会话。当然该类本身还不是数据库会话。

但是一旦我们创建了一个SessionLocal类的实例,这个实例将是实际的数据库会话。

我们命名它是SessionLocal为了将它与我们从 SQLAlchemy 导入的Session区别开来。

稍后我们将使用Session(从 SQLAlchemy 导入的那个)。

要创建SessionLocal类,请使用函数sessionmaker

 

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" engine = create_engine( SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} ) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base()

创建一个Base类¶

现在我们将使用declarative_base()返回一个类。

稍后我们将用这个类继承,来创建每个数据库模型或类(ORM 模型):

 

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" engine = create_engine( SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} ) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base()

创建数据库模型¶

现在让我们看看文件sql_app/models.py

Base类来创建 SQLAlchemy 模型¶

我们将使用我们之前创建的Base类来创建 SQLAlchemy 模型。

Tip

SQLAlchemy 使用的“模型”这个术语 来指代与数据库交互的这些类和实例。

而 Pydantic 也使用“模型”这个术语 来指代不同的东西,即数据验证、转换以及文档类和实例。

database(来自上面的database.py文件)导入Base

创建从它继承的类。

这些类就是 SQLAlchemy 模型。

 

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")

这个__tablename__属性是用来告诉 SQLAlchemy 要在数据库中为每个模型使用的数据库表的名称。

创建模型属性/列¶

现在创建所有模型(类)属性。

这些属性中的每一个都代表其相应数据库表中的一列。

我们使用Column来表示 SQLAlchemy 中的默认值。

我们传递一个 SQLAlchemy “类型”,如IntegerStringBoolean,它定义了数据库中的类型,作为参数。

 

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")

创建关系¶

现在创建关系。

为此,我们使用SQLAlchemy ORM提供的relationship

这将或多或少会成为一种“神奇”属性,其中表示该表与其他相关的表中的值。

 

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")

当访问 user 中的属性items时,如 中my_user.items,它将有一个ItemSQLAlchemy 模型列表(来自items表),这些模型具有指向users表中此记录的外键。

当您访问my_user.items时,SQLAlchemy 实际上会从items表中的获取一批记录并在此处填充进去。

同样,当访问 Item中的属性owner时,它将包含表中的UserSQLAlchemy 模型users。使用owner_id属性/列及其外键来了解要从users表中获取哪条记录。

创建 Pydantic 模型¶

现在让我们查看一下文件sql_app/schemas.py

Tip

为了避免 SQLAlchemy模型和 Pydantic模型之间的混淆,我们将有models.py(SQLAlchemy 模型的文件)和schemas.py( Pydantic 模型的文件)。

这些 Pydantic 模型或多或少地定义了一个“schema”(一个有效的数据形状)。

因此,这将帮助我们在使用两者时避免混淆。

创建初始 Pydantic模型/模式¶

创建一个ItemBaseUserBasePydantic模型(或者我们说“schema”)以及在创建或读取数据时具有共同的属性。

ItemCreate为 创建一个UserCreate继承自它们的所有属性(因此它们将具有相同的属性),以及创建所需的任何其他数据(属性)。

因此在创建时也应当有一个password属性。

但是为了安全起见,password不会出现在其他同类 Pydantic模型中,例如用户请求时不应该从 API 返回响应中包含它。

Python 3.10+Python 3.9+Python 3.8+

 

from pydantic import BaseModel class ItemBase(BaseModel): title: str description: str | None = 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

SQLAlchemy 风格和 Pydantic 风格¶

请注意,SQLAlchemy模型使用 =来定义属性,并将类型作为参数传递给Column,例如:

 

name = Column(String)

虽然 Pydantic模型使用: 声明类型,但新的类型注释语法/类型提示是:

 

name: str

请牢记这一点,这样您在使用:还是=时就不会感到困惑。

创建用于读取/返回的Pydantic模型/模式

现在创建当从 API 返回数据时、将在读取数据时使用的Pydantic模型(schemas)。

例如,在创建一个项目之前,我们不知道分配给它的 ID 是什么,但是在读取它时(从 API 返回时)我们已经知道它的 ID。

同样,当读取用户时,我们现在可以声明items,将包含属于该用户的项目。

不仅是这些项目的 ID,还有我们在 Pydantic模型中定义的用于读取项目的所有数据:Item.

Python 3.10+Python 3.9+Python 3.8+

 

from pydantic import BaseModel class ItemBase(BaseModel): title: str description: str | None = 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

请注意,读取用户(从 API 返回)时将使用不包括passwordUser Pydantic模型

使用 Pydantic 的orm_mode

现在,在用于查询的 Pydantic模型ItemUser,添加一个内部Config类。

此类Config用于为 Pydantic 提供配置。

Config类中,设置属性orm_mode = True

Python 3.10+Python 3.9+Python 3.8+

 

from pydantic import BaseModel class ItemBase(BaseModel): title: str description: str | None = 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

请注意,它使用=分配一个值,例如:

orm_mode = True

它不使用之前的:来类型声明。

这是设置配置值,而不是声明类型。

Pydanticorm_mode将告诉 Pydantic模型读取数据,即它不是一个dict,而是一个 ORM 模型(或任何其他具有属性的任意对象)。

这样,而不是仅仅试图从dict上 id 中获取值,如下所示:

 

id = data["id"]

尝试从属性中获取它,如:

 

id = data.id

有了这个,Pydantic模型与 ORM 兼容,您只需在路径操作response_model的参数中声明它即可。

您将能够返回一个数据库模型,它将从中读取数据。

ORM 模式的技术细节¶

SQLAlchemy 和许多其他默认情况下是“延迟加载”。

这意味着,例如,除非您尝试访问包含该数据的属性,否则它们不会从数据库中获取关系数据。

例如,访问属性items

 

current_user.items

将使 SQLAlchemy 转到items表并获取该用户的项目,在调用.items之前不会去查询数据库。

没有orm_mode,如果您从路径操作返回一个 SQLAlchemy 模型,它不会包含关系数据。

即使您在 Pydantic 模型中声明了这些关系,也没有用处。

但是在 ORM 模式下,由于 Pydantic 本身会尝试从属性访问它需要的数据(而不是假设为 dict),你可以声明你想要返回的特定数据,它甚至可以从 ORM 中获取它。

CRUD工具¶

现在让我们看看文件sql_app/crud.py

在这个文件中,我们将编写可重用的函数用来与数据库中的数据进行交互。

CRUD分别为:增加查询更改删除,即增删改查。

...虽然在这个例子中我们只是新增和查询。

读取数据¶

从 sqlalchemy.orm中导入Session,这将允许您声明db参数的类型,并在您的函数中进行更好的类型检查和完成。

导入之前的models(SQLAlchemy 模型)和schemas(Pydantic模型/模式)。

创建一些实用函数来完成:

  • 通过 ID 和电子邮件查询单个用户。
  • 查询多个用户。
  • 查询多个项目。
 

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

Tip

通过创建仅专用于与数据库交互(获取用户或项目)的函数,独立于路径操作函数,您可以更轻松地在多个部分中重用它们,并为它们添加单元测试。

创建数据¶

现在创建实用程序函数来创建数据。

它的步骤是:

  • 使用您的数据创建一个 SQLAlchemy 模型实例。
  • 使用add来将该实例对象添加到您的数据库。
  • 使用commit来对数据库的事务提交(以便保存它们)。
  • 使用refresh来刷新您的数据库实例(以便它包含来自数据库的任何新数据,例如生成的 ID)。
 

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

Tip

SQLAlchemy 模型User包含一个hashed_password,它应该是一个包含散列的安全密码。

但由于 API 客户端提供的是原始密码,因此您需要将其提取并在应用程序中生成散列密码。

然后将hashed_password参数与要保存的值一起传递。

Warning

此示例不安全,密码未经过哈希处理。

在现实生活中的应用程序中,您需要对密码进行哈希处理,并且永远不要以明文形式保存它们。

有关更多详细信息,请返回教程中的安全部分。

在这里,我们只关注数据库的工具和机制。

Tip

这里不是将每个关键字参数传递给Item并从Pydantic模型中读取每个参数,而是先生成一个字典,其中包含Pydantic模型的数据:

item.dict()

然后我们将dict的键值对 作为关键字参数传递给 SQLAlchemy Item

Item(**item.dict())

然后我们传递 Pydantic模型未提供的额外关键字参数owner_id

Item(**item.dict(), owner_id=user_id)

FastAPI应用程序¶

现在在sql_app/main.py文件中 让我们集成和使用我们之前创建的所有其他部分。

创建数据库表¶

以非常简单的方式创建数据库表:

Python 3.9+Python 3.8+

 

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(): 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

Alembic 注意¶

通常你可能会使用 Alembic,来进行格式化数据库(创建表等)。

而且您还可以将 Alembic 用于“迁移”(这是它的主要工作)。

“迁移”是每当您更改 SQLAlchemy 模型的结构、添加新属性等以在数据库中复制这些更改、添加新列、新表等时所需的一组步骤。

您可以在Project Generation - Template的模板中找到一个 FastAPI 项目中的 Alembic 示例。具体在alembic代码目录中。

创建依赖项¶

现在使用我们在sql_app/database.py文件中创建的SessionLocal来创建依赖项。

我们需要每个请求有一个独立的数据库会话/连接(SessionLocal),在所有请求中使用相同的会话,然后在请求完成后关闭它。

然后将为下一个请求创建一个新会话。

为此,我们将创建一个新的依赖项yield,正如前面关于Dependencies withyield的部分中所解释的那样。

我们的依赖项将创建一个新的 SQLAlchemy SessionLocal,它将在单个请求中使用,然后在请求完成后关闭它。

Python 3.9+Python 3.8+

 

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(): 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

Info

我们将SessionLocal()请求的创建和处理放在一个try块中。

然后我们在finally块中关闭它。

通过这种方式,我们确保数据库会话在请求后始终关闭。即使在处理请求时出现异常。

但是您不能从退出代码中引发另一个异常(在yield之后)。可以查阅 Dependencies with yield and HTTPException

然后,当在路径操作函数中使用依赖项时,我们使用Session,直接从 SQLAlchemy 导入的类型声明它。

这将为我们在路径操作函数中提供更好的编辑器支持,因为编辑器将知道db参数的类型Session

Python 3.9+Python 3.8+

 

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(): 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

技术细节

参数db实际上是 type SessionLocal,但是这个类(用 创建sessionmaker())是 SQLAlchemy 的“代理” Session,所以,编辑器并不真正知道提供了哪些方法。

但是通过将类型声明为Session,编辑器现在可以知道可用的方法(.add()、.query()、.commit()等)并且可以提供更好的支持(比如完成)。类型声明不影响实际对象。

创建您的FastAPI 路径操作

现在,到了最后,编写标准的FastAPI 路径操作代码。

Python 3.9+Python 3.8+

 

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(): 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

我们在依赖项中的每个请求之前利用yield创建数据库会话,然后关闭它。

所以我们就可以在路径操作函数中创建需要的依赖,就能直接获取会话。

这样,我们就可以直接从路径操作函数内部调用crud.get_user并使用该会话,来进行对数据库操作。

Tip

请注意,您返回的值是 SQLAlchemy 模型或 SQLAlchemy 模型列表。

但是由于所有路径操作的response_model都使用 Pydantic模型/使用orm_mode模式,因此您的 Pydantic 模型中声明的数据将从它们中提取并返回给客户端,并进行所有正常的过滤和验证。

Tip

另请注意,response_models应当是标准 Python 类型,例如List[schemas.Item].

但是由于它的内容/参数List是一个 使用orm_mode模式的Pydantic模型,所以数据将被正常检索并返回给客户端,所以没有问题。

关于 def 对比 async def

在这里,我们在路径操作函数和依赖项中都使用着 SQLAlchemy 模型,它将与外部数据库进行通信。

这会需要一些“等待时间”。

但是由于 SQLAlchemy 不具有await直接使用的兼容性,因此类似于:

 

user = await db.query(User).first()

...相反,我们可以使用:

 

user = db.query(User).first()

然后我们应该声明路径操作函数和不带 的依赖关系async def,只需使用普通的def,如下:

 

@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) ...

Info

如果您需要异步连接到关系数据库,请参阅Async SQL (Relational) Databases

Very Technical Details

如果您很好奇并且拥有深厚的技术知识,您可以在Async文档中查看有关如何处理 async defdef差别的技术细节。

迁移¶

因为我们直接使用 SQLAlchemy,并且我们不需要任何类型的插件来使用FastAPI,所以我们可以直接将数据库迁移至Alembic进行集成。

由于与 SQLAlchemy 和 SQLAlchemy 模型相关的代码位于单独的独立文件中,您甚至可以使用 Alembic 执行迁移,而无需安装 FastAPI、Pydantic 或其他任何东西。

同样,您将能够在与FastAPI无关的代码的其他部分中使用相同的 SQLAlchemy 模型和实用程序。

例如,在具有Celery、RQ或ARQ的后台任务工作者中。

审查所有文件¶

最后回顾整个案例,您应该有一个名为的目录my_super_project,其中包含一个名为sql_app

sql_app中应该有以下文件:

  • sql_app/__init__.py:这是一个空文件。

  • 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:///./sql_app.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()

  • 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")

  • sql_app/schemas.py:

Python 3.10+Python 3.9+Python 3.8+

 

from pydantic import BaseModel class ItemBase(BaseModel): title: str description: str | None = 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

  • 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

  • sql_app/main.py:

Python 3.9+Python 3.8+

 

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(): 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

执行项目¶

您可以复制这些代码并按原样使用它。

Info

事实上,这里的代码只是大多数测试代码的一部分。

你可以用 Uvicorn 运行它:

uvicorn sql_app.main:app --reload
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

restart ↻

打开浏览器进入 http://127.0.0.1:8000/docs。

您将能够与您的FastAPI应用程序交互,从真实数据库中读取数据:

直接与数据库交互¶

如果您想独立于 FastAPI 直接浏览 SQLite 数据库(文件)以调试其内容、添加表、列、记录、修改数据等,您可以使用SQLite 的 DB Browser

它看起来像这样:

您还可以使用SQLite Viewer或ExtendsClass等在线 SQLite 浏览器。

中间件替代数据库会话¶

如果你不能使用依赖项yield——例如,如果你没有使用Python 3.7并且不能安装上面提到的Python 3.6的“backports” ——你可以在类似的“中间件”中设置会话方法。

“中间件”基本功能是一个为每个请求执行的函数在请求之前进行执行相应的代码,以及在请求执行之后执行相应的代码。

创建中间件¶

我们将添加中间件(只是一个函数)将为每个请求创建一个新的 SQLAlchemySessionLocal,将其添加到请求中,然后在请求完成后关闭它。

Python 3.9+Python 3.8+

 

from fastapi import Depends, FastAPI, HTTPException, Request, Response from sqlalchemy.orm import Session from . import crud, models, schemas from .database import SessionLocal, engine models.Base.metadata.create_all(bind=engine) app = FastAPI() @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 # Dependency def get_db(request: Request): return request.state.db @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

Info

我们将SessionLocal()请求的创建和处理放在一个try块中。

然后我们在finally块中关闭它。

通过这种方式,我们确保数据库会话在请求后始终关闭,即使在处理请求时出现异常也会关闭。

关于request.state

request.state是每个Request对象的属性。它用于存储附加到请求本身的任意对象,例如本例中的数据库会话。您可以在Starlette 的关于Requeststate的文档中了解更多信息。

对于这种情况下,它帮助我们确保在所有请求中使用单个数据库会话,然后关闭(在中间件中)。

使用yield依赖项与使用中间件的区别¶

在此处添加中间件yield的依赖项的作用效果类似,但也有一些区别:

  • 中间件需要更多的代码并且更复杂一些。
  • 中间件必须是一个async函数。
    • 如果其中有代码必须“等待”网络,它可能会在那里“阻止”您的应用程序并稍微降低性能。
    • 尽管这里的SQLAlchemy工作方式可能不是很成问题。
    • 但是,如果您向等待大量I/O的中间件添加更多代码,则可能会出现问题。
  • 每个请求都会运行一个中间件。
    • 将为每个请求创建一个连接。
    • 即使处理该请求的路径操作不需要数据库。

Tip

tyield当依赖项 足以满足用例时,使用tyield依赖项方法会更好。

Info

yield的依赖项是最近刚加入FastAPI中的。

所以本教程的先前版本只有带有中间件的示例,并且可能有多个应用程序使用中间件进行数据库会话管理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/251144.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

云原生之深入解析Linkerd Service Mesh的功能和使用

一、简介 Linkerd 是 Kubernetes 的一个完全开源的服务网格实现,它通过为你提供运行时调试、可观测性、可靠性和安全性,使运行服务更轻松、更安全,所有这些都不需要对代码进行任何更改。Linkerd 通过在每个服务实例旁边安装一组超轻、透明的…

Python常见面试知识总结(一):迭代器、拷贝、线程及底层结构

前言: Hello大家好,我是Dream。 今天来总结一下Python和C语言中常见的面试知识,欢迎大家一起前来探讨学习~ 【一】Python中迭代器的概念? 可迭代对象是迭代器、生成器和装饰器的基础。简单来说,可以使用for来循环遍历…

时序预测 | Python实现CNN电力需求预测

时序预测 | Python实现CNN电力需求预测 目录 时序预测 | Python实现CNN电力需求预测预测效果基本描述程序设计参考资料预测效果 基本描述 该数据集因其每小时的用电量数据以及 TSO 对消耗和定价的相应预测而值得注意,从而可以将预期预测与当前最先进的行业预测进行比较。使用该…

前端框架的虚拟DOM(Virtual DOM)

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

《PySpark大数据分析实战》-11.Spark on YARN模式安装Hadoop

📋 博主简介 💖 作者简介:大家好,我是wux_labs。😜 热衷于各种主流技术,热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员(PCTA)、TiDB数据库专家(PCTP…

Vue学习计划-Vue2--VueCLi(七)nextTick、、浏览器本地缓存、脚手架配置代理

1. nextTick 语法: this.$nextTick(回调函数)作用:在下一次DOM更新结束后执行其指定的回调什么时候用: 当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行 **举个栗子:…

B+树与索引

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO 联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬 对于60%的程序员而言&a…

【 某景点舆情分析:Python、Echarts、Flask、文本处理技术的应用】

某景点舆情分析:Python、Echarts、Flask、文本处理技术的应用 前言技术栈数据获取与准备景点数据统计分析评论数据处理与分析词频统计分词与文本处理情感分析 数据可视化Web应用搭建结语 前言 随着旅游行业的蓬勃发展,越来越多的人通过网络平台获取关于…

SQL 入门指南:从零开始学习 SQL

当今时代,数据已经成为了我们生活中不可或缺的一部分。无论是企业的经营决策,还是个人的日常消费习惯,都需要通过对数据的收集、分析和应用来实现更好的结果。 而关系型数据库系统,作为最常见的数据存储和管理方式,SQ…

HNU-数据库系统-实验1-数据定义/数据操纵

数据库系统 课程实验1数据定义/数据操纵 计科210X 甘晴void 202108010XXX 目录 文章目录 数据库系统 课程实验1<br>数据定义/数据操纵实验目的实验样例实验环境实验内容1.1 数据库定义1&#xff09;实验内容与要求2&#xff09;实验重难点3&#xff09;实验基础知识①模…

设计模式之结构型设计模式(二):工厂模式 抽象工厂模式 建造者模式

工厂模式 Factory 1、什么是工厂模式 工厂模式旨在提供一种统一的接口来创建对象&#xff0c;而将具体的对象实例化的过程延迟到子类或者具体实现中。有助于降低客户端代码与被创建对象之间的耦合度&#xff0c;提高代码的灵活性和可维护性。 定义了一个创建对象的接口&…

node.js mongoose简述

目录 官方文档 mongoose Schema Model Query document 关系 官方文档 Mongoose v8.0.3: Getting Started mongoose Mongoose 是一个 Node.js 环境下 MongoDB 的对象建模工具。它提供了一种在应用程序中与 MongoDB 数据库进行交互的方式&#xff0c;使得开发者能够使用…

数据可视化?这些平台能处

图表在各行各业都起到举重若轻的作用&#xff0c;无论是项目汇报、业绩分析&#xff0c;亦或是数据挖掘、统计分析&#xff0c;良好的可视化可以为我们的阐述起到画龙点睛的效果。在一篇文章中&#xff0c;如果只有密密麻麻的文字堆积&#xff0c;无论是谁恐怕都无法长期保持注…

mysql !=索引为什么失效

可以先看这个&#xff1a;Mysql查询条件为大于时&#xff0c;不走索引失效场景-CSDN博客 如下建立了联合索引&#xff0c; 注意查询phone是用不对劲了索引&#xff0c;key_len82 改为 !时&#xff0c; key key_len没内容&#xff0c;说明没有走索引&#xff0c;为什么没有走&…

DIY电脑装机机箱风扇安装方法

作为第一次自己diy一台电脑主机的我&#xff0c;在经历了众多的坑中今天来说一下如何安装机箱风扇的问题 一、风扇的数量 1、i3 xx50显卡 就用一个cpu散热风扇即可 2、i5 xx60 一个cpu散热风扇 一个风扇即可 3、i7 xx70 一个cpu散热 4个风扇即可 4、i9 xx80 就需要7个以…

Ubuntu安装蓝牙模块pybluez以及问题解决方案【完美解决】

文章目录 简介问题及解决办法总结 简介 近期因工程需要在Ubuntu中使用蓝牙远程一些设备。安装Bluetooth的Python第三方软件包pybluez时遇到很多问题&#xff0c;一番折腾后完美解决。此篇博客进行了梳理和总结&#xff0c;供大家参考。 问题及解决办法 pip install pybluez安…

数据迁移测试经验分享

以下为作者观点&#xff1a; 数据迁移&#xff0c;是在保证新旧系统业务连续性的前提下&#xff0c;将数据从旧数据库迁移到新数据库的过程&#xff0c;测试前通过迁移策略和方案了解新旧系统数据如何重构与关联&#xff0c;测试过程需确保数据迁移的正确性&#xff0c;主要体…

Lambda 表达式与C++并发编程

Lambda 表达式&#xff08;lambda expression&#xff09;是一个匿名函数&#xff0c;Lambda表达式基于数学中的λ演算得名&#xff0c;直接对应于其中的lambda抽象&#xff08;lambda abstraction&#xff09;&#xff0c;是一个匿名函数&#xff0c;即没有函数名的函数。 // …

Easy Excel生成复杂下Excel模板(下拉框)给用户下载

引言 文件的下载是一个非常常见的功能&#xff0c;也有一些非常好的框架可以使用&#xff0c;这里我们就介绍一种比较常见的场景&#xff0c;下载Excel模版&#xff0c;导入功能通常会配有一个模版下载的功能&#xff0c;根据下载的模版&#xff0c;填充数据然后再上传。 需求…

CAN 五: CAN编程实践

1、CAN基本驱动步骤 (1)CAN参数初始化 工作模式、波特率等函数&#xff1a;HAL_CAN_Init (2)使能CAN时钟和初始化相关引脚 GPIO模式设为复用功能模式函数&#xff1a;HAL_CAN_MspInit(CAN的初始化回调函数) (3)设置过滤器 过滤器的配置函数&#xff1a;HAL_CAN_ConfigFil…
最新文章