FastAPISQL (Relational) Databases

SQL (Relational) Databases

FastAPI不需要您使用SQL(关系)数据库。
但是您可以使用所需的任何关系数据库。
在这里,我们将看到一个使用SQLAlchemy的示例。
您可以轻松地使其适应SQLAlchemy支持的任何数据库,例如:

  • PostgreSQL
  • MySQL
  • SQLite
  • Oracle
  • Microsoft SQL Server, etc.

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

稍后,对于您的生产应用程序,您可能想要使用PostgreSQL之类的数据库服务器。

Tip
有一个使用FastAPI和PostgreSQL的官方项目生成器,它们都基于Docker,包括前端和更多工具: https://github.com/tiangolo/full-stack-fastapi-postgresql

Note
注意,大多数代码是SQLAlchemy您将在任何框架中使用的标准代码。
该FastAPI具体的代码是一如既往的小。

ORMs

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文件)都是一个包。
现在,让我们看看每个文件/模块的功能。

创建SQLAlchemy部分

让我们参考文件 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()

导入SQLAlchemy部分

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
...

为SQLAlchemy创建数据库

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

第一步是创建一个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该类的实例,该实例将成为实际的数据库会话。
我们SessionLocal将其命名为有别于Session我们从SQLAlchemy导入的名称。
稍后我们将使用Session(从SQLAlchemy导入的一种)。
要创建SessionLocal类,请使用函数sessionmaker:

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 

创建一个Base类

现在,我们将使用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模型

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

Tip
SQLAlchemy使用术语“ 模型 ”来指代与数据库交互的这些类和实例。
但是Pydantic还使用术语“ 模型 ”来指代不同的东西,即数据验证,转换以及文档类和实例。

Import Base from database (the file database.py from above).

创建从其继承的类。
这些类是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表中获取哪个记录。

创建Pydantic模型

现在让我们检查文件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模型或多或少定义了“模式”(有效数据形状)。
因此,这将有助于我们避免在同时使用两者时产生混淆。

创建初始的Pydantic models/schemas 模型/模式

创建一个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 style and Pydantic style

请注意,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的orm_mode

现在,在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操作的参数中声明它。
您将能够返回数据库模型,并且它将从中读取数据。

有关ORM模式的技术细节

SQLAlchemy和许多其他默认情况下是“延迟加载”。
例如,这意味着除非您尝试访问将包含该数据的属性,否则它们不会从数据库中获取关系数据。
例如,访问属性items:

current_user.items 

将使SQLAlchemy转到items表中并获取该用户的项目,但不能早于此。
没有orm_mode,如果您从路径操作返回了SQLAlchemy模型,则该模型将不包含关系数据。
即使您在Pydantic模型中声明了这些关系。
但是在ORM模式下,由于Pydantic本身会尝试从属性访问其所需的数据(而不是假设dict),因此您可以声明要返回的特定数据,并且即使从ORM中也可以获取它。

CRUD utils

现在,我们来看文件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.
…尽管在此示例中,我们仅创建和阅读。

Read data¶

Session从导入sqlalchemy.orm,这将允许您声明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 get_items(db: Session, skip: int = 0, limit: int = 100):
 
    return db.query(models.Item).offset(skip).limit(limit).all()
 

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

创建数据

现在创建实用程序函数来创建数据。
这些步骤是:

  • 使用您的数据创建一个SQLAlchemy模型实例。
  • add 该实例对象到您的数据库会话。
  • commit 对数据库的更改(以便将其保存)。
  • refresh 您的实例(以便它包含数据库中的任何新数据,例如生成的ID)。
...
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) 

主FastAPI应用

现在,在文件中,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 Note

通常,您可能会使用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 路径操作

现在,最后是标准的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,该数据将被检索并返回到客户端为常,没有任何问题。

About def vs async def¶

在这里,我们在路径操作函数内部以及在依赖项中使用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是在处理异步文档。

Migrations

迁移¶
因为我们直接使用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:///./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()
  • 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:
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
  • 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:
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应用程序进行交互,从真实的数据库中读取数据:
FastAPISQL (Relational) Databases_第1张图片

直接与数据库进行交互

如果要独立于FastAPI来直接浏览SQLite数据库(文件)以调试其内容,添加表,列,记录,修改数据等,则可以使用DB Browser for SQLite。
它看起来像这样:
FastAPISQL (Relational) Databases_第2张图片
您还可以使用在线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。
这样,我们确保在请求后数据库会话始终关闭。即使在处理请求时出现异常。.

About request.state¶

request.state是每个Starlette Request对象的属性。它可以存储附加到请求本身的任意对象,例如本例中的数据库会话。

在这种情况下,对于我们来说,这有助于确保所有请求都使用一个数据库会话,然后在中间件中关闭。

与yield或中间件的依赖关系

在此处添加中间件与依赖项的功能类似,yield但有一些区别:

  • 它需要更多代码,并且稍微复杂一些。

  • 中间件必须是一种async功能。

    • 如果其中包含必须“等待”网络的代码,则可能会“阻塞”您的应用程序,从而导致性能降低。
    • 尽管这里的工作方式可能不是很成问题SQLAlchemy。
    • 但是,如果您向具有大量I / O等待的中间件添加更多代码,则可能会出现问题。
  • 每个请求都运行一个中间件。

    • 因此,将为每个请求创建一个连接。
    • 即使处理该请求的路径操作不需要DB。

tip
yield当依赖关系足以满足用例时,最好使用依赖关系。

信息
与的依赖关系yield最近已添加到FastAPI中。
本教程的先前版本仅包含带有中间件的示例,并且可能有多个应用程序使用中间件进行数据库会话管理。

你可能感兴趣的:(#,WebFramework,python)