fastAPI
FastAPI
FastAPi是一个用于构建api的现代,快速(高性能)的web框架,使用python并基于标准的python类型提示
特点
- 高性能: 基于Starlette和Pydantic,利用异步(asynchronous)编程,提供出色的性能。最快的python web框架之一
- 自动文档生成: 自动生成交互式API文档,支持Swagger UI和ReDoc,让API的理解和测试更加直观。
- 类型注解支持: 利用Python的类型提示,提供更严格的输入验证和更好的代码提示。
- 异步支持: 支持异步请求处理,使得处理IO密集型任务更加高效。
适用场景
- 构建API后端: 用于构建RESTful API,支持前后端分离的Web应用。
- 微服务架构: 可以作为微服务的后端框架,支持快速开发和部署。
- 数据处理API: 适用于处理数据,接收和返回JSON数据。
- 实时通信: 支持WebSocket,适用于实时通信场景。
为什么选择FastAPI
- Pythonic: 使用Python的自然语法和类型提示,降低学习曲线。
- 性能优越: 利用异步编程和底层的Starlette框架,提供卓越的性能。
- 文档友好: 自动生成交互式文档,减少文档维护的工作量。
- 生态系统: 基于Python生态系统,可以方便地集成各种库和工具。
安装FastAPI
FastAPI 依赖 Python 3.8 及更高版本。
安装 FastAPI 很简单,这里我们使用 pip 命令来安装。
pip install fastapi
另外我们还需要一个 ASGI 服务器,生产环境可以使用 Uvicorn 或者 Hypercorn:
pip install "uvicorn[standard]"
运行第一个FastAPI应用
创建一个名为 main.py 的文件,添加以下代码:
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
在命令行中运行以下命令以启动应用:
uvicorn main:app --reload
现在,打开浏览器并访问 http://127.0.0.1:8000/docs,你应该能够看到 FastAPI 自动生成的交互式文档,并在根路径 ("/") 返回的 JSON 响应。
代码解析:
from fastapi import FastAPI
: 这行代码从< code>fastapi 模块中导入了FastAPI
类。FastAPI
类是 FastAPI 框架的核心,用于创建 FastAPI 应用程序实例。app = FastAPI()
:这行代码创建了一个 FastAPI 应用实例。与 Flask 不同,FastAPI 不需要传递__name__
参数,因为它默认使用当前模块。@app.get("/")
: 这是一个装饰器,用于告诉 FastAPI 哪个 URL 应该触发下面的函数,并且指定了 HTTP 方法为 GET。在这个例子中,它指定了根 URL(即网站的主页)。def read_root():
: 这是定义了一个名为read_root
的函数,它将被调用当用户使用 GET 方法访问根 URL 时。return {"Hello": "World"}
: 这行代码是read_root
函数的返回值。当用户使用 GET 方法访问根 URL 时,这个 JSON 对象将被发送回用户的浏览器或 API 客户端。
第一个FastAPI应用
创建一个名为 main.py 的文件,添加以下代码:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
在命令行中运行以下命令以启动应用:
uvicorn main:app --reload
接下来我们来丰富下代码功能,并做具体说明。
以下的 FastAPI 应用,使用了两个路由操作(/ 和 /items/{item_id}):
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
1、创建 FastAPI 实例:
app = FastAPI()
在这一步,创建了一个 FastAPI 应用的实例,它将用于定义和管理应用的各个组件,包括路由。
FastAPI 是 FastAPI 框架的主要类。
2、定义根路径 / 的路由操作:
@app.get("/")
def read_root():
return {"Hello": "World"}
这个路由操作使用了 @app.get("/") 装饰器,表示当用户通过 HTTP GET 请求访问根路径时,将执行 read_root 函数。函数返回一个包含 {"Hello": "World"} 的字典,这个字典会被 FastAPI 自动转换为 JSON 格式并返回给用户。
3、定义带路径参数和查询参数的路由操作:
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
这个路由操作使用了 @app.get("/items/{item_id}") 装饰器,表示当用户通过 HTTP GET 请求访问 /items/{item_id} 路径时,将执行 read_item 函数。
函数接受两个参数:
- item_id --是路径参数,指定为整数类型。
- q -- 是查询参数,指定为字符串类型或空(None)。
函数返回一个字典,包含传入的 item_id 和 q 参数。
q 参数通过 Union[str, None] 表示可以是字符串类型或空,这样就允许在请求中不提供 q 参数。
使用浏览器访问 http://127.0.0.1:8000/items/5?q=runoob,你将会看到如下 JSON 响应:
{"item_id": 5, "q": "runoob"}
FastAPI 交互式 API 文档
FastAPI 提供了内置的交互式 API 文档,使开发者能够轻松了解和测试 API 的各个端点。
这个文档是自动生成的,基于 OpenAPI 规范,支持 Swagger UI 和 ReDoc 两种交互式界面。
通过 FastAPI 的交互式 API 文档,开发者能够更轻松地理解和使用 API,提高开发效率
在运行 FastAPI 应用时,Uvicorn 同时启动了交互式 API 文档服务。
默认情况下,你可以通过访问 http://127.0.0.1:8000/docs 来打开 Swagger UI 风格的文档:
Swagger UI 提供了一个直观的用户界面,用于浏览 API 的各个端点、查看请求和响应的结构,并支持直接在文档中进行 API 请求测试。通过 Swagger UI,你可以轻松理解每个路由操作的输入参数、输出格式和请求示例。
或者通过 http://127.0.0.1:8000/redoc 来打开 ReDoc 风格的文档。
ReDoc 是另一种交互式文档界面,具有清晰简洁的外观。它使得开发者能够以可读性强的方式查看 API 的描述、请求和响应。与 Swagger UI 不同,ReDoc 的设计强调文档的可视化和用户体验。
交互式文档的优势
- 实时更新: 交互式文档会实时更新,反映出应用代码的最新更改。
- 自动验证: 输入参数的类型和格式会得到自动验证,降低了错误的可能性。
- 便于测试: 可以直接在文档中进行 API 请求测试,避免使用其他工具。
升级实例
接下来我们修改 main.py 文件来从 PUT 请求中接收请求体。
我们借助 Pydantic 来使用标准的 Python 类型声明请求体。
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_offer: Union[bool, None] = None
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):
return {"item_name": item.name, "item_id": item_id}
修改完代码后,我们不需要重启服务器,因为服务器将会自动重载,因为在前面的章节中 uvicorn 命令添加了 --reload 选项。
这时候我们再访问 http://127.0.0.1:8000/docs,交互式 API 文档将会自动更新,并加入新的请求体:
python类型提示符
示例:
def get_full_name(first_name: str, last_name: str):
full_name = first_name.title() + " " + last_name.title()
return full_name
print(get_full_name("john", "doe"))
-
简单类型
不只是
str
,你能够声明所有的标准python类型- int
- float
- bool
- bytes
def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes): return item_a, item_b, item_c, item_d, item_d, item_e
-
嵌套类型
有些容器数据结构可以包含其他的值,比如:
dict
、list
、set
、tuple
。它们内部的值也会拥有自己的类型你可以使用python的
typing
标准库来申明这些类型以及子类型-
列表
定义一个由
str
组成的list
变量从
typing
模块导入List
(注意是大写L):from typing import List def process_items(items: List[str]): # 表示:变量"items"是一个list,并且这个列表里的每一个元素都是str for item in items: print(item)
-
元组和集合
声明
tuple
和set
的方法也是一样的:from typing import Set, Tuple def process_items(items_t: Tuple[int, int, str], items_s: Set[bytes]): # 变量 items_t 是一个 tuple,其中的前两个元素都是 int 类型, 最后一个元素是 str 类型。 # 变量 items_s 是一个 set,其中的每个元素都是 bytes 类型 return items_t, items_s
-
字典
定义dict时,需要传入两个子类型,用逗号进行分隔
- 第一个子类型声明
dict
的所有键 - 第二个子类型声明
dict
的所有值
from typing import Dict def process_items(prices: Dict[str, float]): ''' 变量 prices 是一个 dict: 这个 dict 的所有键为 str 类型(可以看作是字典内每个元素的名称)。 这个 dict 的所有值为 float 类型(可以看作是字典内每个元素的价格)。 ''' for item_name, item_price in prices.items(): print(item_name) print(item_price)
- 第一个子类型声明
-
-
类作为类型
也可以将类声明为变量的类型
假设有一个名为
Person
的类,拥有name属性:class Person: def __init__(self, name: str): self.name = name def get_person_name(one_person: Person): return one_person.name
FastAPI 基本路由
在 FastAPI 中,基本路由是定义 API 端点的关键。每个路由都映射到应用程序中的一个函数,用于处理特定的 HTTP 请求,并返回相应的响应。
FastAPI 请求和响应
在 FastAPI 中,请求(Request)和响应(Response)是与客户端交互的核心。
FastAPI 提供了强大的工具来解析请求数据,并根据需要生成规范的响应。
接下来我们来详细看下 FastAPI 的请求和响应。
请求数据
查询参数
以下实例中我们定义了一个 /items/ 路由,接受两个查询参数 skip 和 limit,它们的类型均为整数,默认值分别为 0 和 10。
传递 GET 请求的参数 http://127.0.0.1:8000/items/?skip=1&limit=5
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/")
def read_item(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}
在命令行中运行以下命令以启动应用:
uvicorn main:app --reload
查询参数和字符串校验
FastAPI允许你为参数声明额外的信息和校验
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/")
async def read_items(q: str | None = None):
# 查询参数q的;类型为str,默认值为None,所有是可选的
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
额外的校验
路径参数
我们可以把参数设置在路径上,这样 URL 看起来更美观一些。
传递 GET 请求的参数 http://127.0.0.1:8000/items/5/?q=runoob
以下实例我们定义了一个带有路径参数 item_id 和查询参数 q 的路由。
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}
注意:
有时,路径操作中的路径是写死的
比如要使用
/user/me
获取当前用户的数据然后还要使用
/user/{user_id}
, 通过用户id获取指定用户的数据由于路径操作是按顺序依次运行的,因此,一定要在
/user/{user_id}
之前声明/user/me
否则,
/users/{user_id}
将匹配/users/me
,FastAPI 会认为正在接收值为"me"
的user_id
参数。
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/me")
async def read_user_me():
return {"user_id": "the current user"}
@app.get("/users/{user_id}")
async def read_user(user_id: str):
return {"user_id": user_id}
预设值
路径操作使用python的Enum
类型接收预设的路径参数
创建Enum类型
-
导入
Enum
并创建继承自str
和Enum
的子类。 -
通过从
str
继承,API 文档就能把值的类型定义为字符串,并且能正确渲染。 -
然后,创建包含固定值的类属性,这些固定值是可用的有效值:
from enum import Enum from fastapi import FastAPI class ModelName(str, Enum): alexnet = "alexnet" resnet = "resnet" lenet = "lenet" app = FastAPI() @app.get("/models/{model_name}") async def get_model(model_name: ModelName): if model_name is ModelName.alexnet: return {"model_name": model_name, "message": "Deep Learning FTW!"} if model_name.value == "lenet": return {"model_name": model_name, "message": "LeCNN all the images"} return {"model_name": model_name, "message": "Have some residuals"}
响应数据
返回JSON数据
路由处理函数返回一个字典,该字典将被 FastAPI 自动转换为 JSON 格式,并作为响应发送给客户端:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/")
def read_item(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}
返回Pydantic模型
路由处理函数返回一个 Pydantic 模型实例,FastAPI 将自动将其转换为 JSON 格式,并作为响应发送给客户端:
from pydantic import BaseModel
from fastapi import FastAPI
app = FastAPI()
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
@app.post("/items/")
def create_item(item: Item):
return item
POST 请求,返回的数据格式如下所示:
{
"name": "runoob",
"description": "菜鸟教程 POST 测试",
"price": 12,
"tax": 1
}
请求头和 Cookie
使用 Header 和 Cookie 类型注解获取请求头和 Cookie 数据。
from fastapi import Header, Cookie
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/")
def read_item(user_agent: str = Header(None), session_token: str = Cookie(None)):
return {"User-Agent": user_agent, "Session-Token": session_token}
以上代码在浏览器访问 http://127.0.0.1:8000/items/42/ 页面显示如下,可以看到我们自定义的响应头:
FastAPI Pydantic 模型
Pydantic 是一个用于数据验证和序列化的 Python 模型库。
它在 FastAPI 中广泛使用,用于定义请求体、响应体和其他数据模型,提供了强大的类型检查和自动文档生成功能。
以下是关于 Pydantic 模型的详细介绍:
1. 定义 Pydantic 模型
使用 Pydantic 定义一个模型非常简单,只需创建一个继承自 pydantic.BaseModel 的类,并在其中定义字段。字段的类型可以是任何有效的 Python 类型,也可以是 Pydantic 内置的类型。
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
以上代码中中,我们定义了一个名为 Item 的 Pydantic 模型,包含了四个字段:name、description、price 和 tax,name 和 price 是必需的字段,而 description 和 tax 是可选的字段,其默认值为 None。
2. 使用 Pydantic 模型
2.1 请求体验证
在 FastAPI 中,可以将 Pydantic 模型用作请求体(Request Body),以自动验证和解析客户端发送的数据。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
@app.post("/items/")
def create_item(item: Item):
return item
2.2 查询参数验证
Pydantic 模型还可以用于验证查询参数、路径参数等。
from fastapi import FastAPI, Query
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
@app.get("/items/")
def read_item(item: Item, q: str = Query(..., max_length=10)):
return {"item": item, "q": q}
以上代码中,read_item 路由处理函数接受一个 Item 模型的实例作为查询参数,以及一个名为 q 的字符串查询参数。通过使用 Query 函数,我们还可以为查询参数指定更多的验证规则,如最大长度限制。
3. 自动文档生成
使用 Pydantic 模型的一个重要优势是,它能够自动为 FastAPI 生成交互式 API 文档。文档会包括模型的字段、类型、验证规则等信息,让开发者和 API 使用者能够清晰地了解如何正确使用 API。
打开 http://127.0.0.1:8000/docs,API 文档显示如下:
4. 数据转换和序列化
Pydantic 模型不仅提供了验证功能,还可以用于将数据转换为特定类型(例如 JSON)或反向序列化。在 FastAPI 中,这种能力是自动的,你无需手动处理。
通过使用 Pydantic 模型,你可以更轻松地定义和验证数据,使得代码更清晰、更健壮,并通过自动生成的文档提供更好的 API 交互体验。
接下来我们可以打开 http://127.0.0.1:8000/docs 来进行 POST 测试:
填写请求参数:
FastAPI 路径操作依赖项
FastAPI 提供了简单易用,但功能强大的依赖注入系统,这个依赖系统设计的简单易用,可以让开发人员轻松地把组件集成至 FastAPI。
FastAPI 提供了路径操作依赖项(Path Operation Dependencies)的机制,允许你在路由处理函数执行之前或之后运行一些额外的逻辑。
依赖项就是一个函数,且可以使用与路径操作函数相同的参数。
路径操作依赖项提供了一种灵活的方式来组织代码、验证输入、进行身份验证等。
接下来我们会具体介绍 FastAPI 路径操作依赖项的相关知识点。
1、依赖项(Dependencies)
依赖项是在路由操作函数执行前或后运行的可复用的函数或对象。
它们被用于执行一些通用的逻辑,如验证、身份验证、数据库连接等。在 FastAPI 中,依赖项通常用于两个方面:
- 预处理(Before)依赖项: 在路由操作函数执行前运行,用于预处理输入数据,验证请求等。
- 后处理(After)依赖项: 在路由操作函数执行后运行,用于执行一些后处理逻辑,如日志记录、清理等。
1.1 依赖注入
依赖注入是将依赖项注入到路由操作函数中的过程。
在 FastAPI 中,通过在路由操作函数参数中声明依赖项来实现依赖注入。
FastAPI 将负责解析依赖项的参数,并确保在执行路由操作函数之前将其传递给函数。
1.2 依赖项的使用
定义依赖项:
from fastapi import Depends, FastAPI
app = FastAPI()
\# 依赖项函数
def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
在这个例子中,common_parameters 是一个依赖项函数,用于预处理查询参数。
在路由中使用依赖项:
from fastapi import Depends
\# 路由操作函数
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
在这个例子中,read_items 路由操作函数中的参数 commons 使用了 Depends(common_parameters),表示 common_parameters 是一个依赖项。FastAPI 将在执行路由操作函数之前运行 common_parameters 函数,并将其返回的结果传递给 read_items 函数。
2、路径操作依赖项的基本使用
2.1 预处理(Before)
以下实例中,common_parameters 是一个依赖项函数,它接受查询参数 q、skip 和 limit,并返回一个包含这些参数的字典。
在路由操作函数 read_items 中,通过传入 Depends(common_parameters),我们使用了这个依赖项函数,实现了在路由执行前预处理输入数据的功能。
from fastapi import Depends, FastAPI, HTTPException
app = FastAPI()
# 依赖项函数
def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
# 路由操作函数
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
2.2 后处理(After)
以下例子中,after_request 是一个后处理函数,用于在路由执行后执行一些逻辑。
在路由操作函数 read_items_after 中,通过传入 Depends(after_request),我们使用了这个后处理依赖项,实现了在路由执行后进行额外操作的功能。
from fastapi import Depends, FastAPI, HTTPException
app = FastAPI()
# 依赖项函数
def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
# 路由操作函数
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
# 后处理函数
async def after_request():
# 这里可以执行一些后处理逻辑,比如记录日志
pass
# 后处理依赖项
@app.get("/items/", response_model=dict)
async def read_items_after(request: dict = Depends(after_request)):
return {"message": "Items returned successfully"}
3、多个依赖项的组合
以下例子中,common_parameters 和 verify_token 是两个不同的依赖项函数,verify_token 依赖于 common_parameters,这种组合依赖项的方式允许我们在路由执行前先验证一些参数,然后在进行身份验证。
from fastapi import Depends, FastAPI, HTTPException
app = FastAPI()
# 依赖项函数1
def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
# 依赖项函数2
def verify_token(token: str = Depends(common_parameters)):
if token is None:
raise HTTPException(status_code=400, detail="Token required")
return token
# 路由操作函数
@app.get("/items/")
async def read_items(token: dict = Depends(verify_token)):
return token
4、异步依赖项
依赖项函数和后处理函数可以是异步的,允许在它们内部执行异步操作。
以下例子中,get_token 是一个异步的依赖项函数,模拟了一个异步操作。
在路由操作函数 read_items 中,我们使用了这个异步依赖项函数。
from fastapi import Depends, FastAPI, HTTPException
from typing import Optional
import asyncio
app = FastAPI()
# 异步依赖项函数
async def get_token():
# 模拟异步操作
await asyncio.sleep(2)
return "fake-token"
# 异步路由操作函数
@app.get("/items/")
async def read_items(token: Optional[str] = Depends(get_token)):
return {"token": token}
通过使用路径操作依赖项,你可以在路由执行前或后执行额外的逻辑,从而实现更灵活、可组合的代码组织方式。
FastAPI 表单数据
在 FastAPI 中,接收表单数据是一种常见的操作,通常用于处理用户通过 HTML 表单提交的数据。
FastAPI 提供了 Form 类型,可以用于声明和验证表单数据。
1、声明表单数据模型
接下来我们设计一个接收一个登陆的表单数据,要使用表单,需预先安装 python-multipart:
pip install python-multipart。
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login/")
async def login(username: str = Form(), password: str = Form()):
return {"username": username}
接下来我们可以进入 API 文档 http://127.0.0.1:8000/docs 进行测验:
使用 Pydantic 模型来声明表单数据模型。
在模型中,使用 Field 类型声明每个表单字段,并添加必要的验证规则。
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str = Field(..., title="Item Name", max_length=100)
description: str = Field(None, title="Item Description", max_length=255)
price: float = Field(..., title="Item Price", gt=0)
以上例子中,Item 是一个 Pydantic 模型,用于表示表单数据。
模型中的字段 name、description 和 price 分别对应表单中的不同输入项,并设置了相应的验证规则。
除了可以在 API 文档中测验,另外我们也可以自己创建 html 来测试:
<form action="http://localhost:8000/items/" method="post">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required>
<br>
<label for="description">Description:</label>
<textarea id="description" name="description"></textarea>
<br>
<label for="price">Price:</label>
<input type="number" id="price" name="price" required min="0">
<br>
<button type="submit">Submit</button>
</form>
2、在路由中接收表单数据
在路由操作函数中,可以使用 Form 类型来接收表单数据。
Form 类型的参数可以与 Pydantic 模型的字段一一对应,以实现表单数据的验证和转换。
from fastapi import FastAPI, Form
app = FastAPI()
# 路由操作函数
@app.post("/items/")
async def create_item(
name: str = Form(...),
description: str = Form(None),
price: float = Form(..., gt=0),
):
return {"name": name, "description": description, "price": price}
以上例子中,create_item 路由操作函数接收了三个表单字段:name、description 和 price,这些字段与 Item 模型的相应字段一致,FastAPI 将自动根据验证规则验证表单数据。
接下来我们可以进入 API 文档 http://127.0.0.1:8000/docs 进行测验:
3、处理文件上传
如果表单包含文件上传,可以使用 UploadFile 类型处理。
以下是一个处理文件上传的实例:
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
# 路由操作函数
@app.post("/files/")
async def create_file(file: UploadFile = File(...)):
return {"filename": file.filename}
在这个例子中,create_file 路由操作函数接收了一个 UploadFile 类型的文件参数。
FastAPI 将负责处理文件上传,并将文件的相关信息包装在 UploadFile 对象中,可以轻松地获取文件名、内容类型等信息。
通过上述方式,FastAPI 提供了一种简单而强大的方法来接收和处理表单数据,同时保持了代码的清晰性和可维护性。
FastAPI开发项目的目录结构
使用fastAPI开发项目时,良好的目录结构可以帮助我们更好的组织底阿妈,提高可维护性和扩展性。同样,对基类的封装,也可以进一步减少开发代码,提供便利,并减少出错率。
my_fastapi_project/
├── app/
│ ├── __init__.py
│ ├── main.py # 入口文件
│ ├── core/
│ │ ├── __init__.py
│ │ ├── config.py # 配置文件
│ │ ├── security.py # 安全相关
│ │ └── ... # 其他核心功能
│ ├── api/
│ │ ├── __init__.py
│ │ ├── v1/
│ │ │ ├── __init__.py
│ │ │ ├── endpoints/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── users.py # 用户相关接口
│ │ │ │ ├── items.py # 其他接口
│ │ │ │ └── ...
│ │ │ └── ... # 其他版本的API
│ ├── models/
│ │ ├── __init__.py
│ │ ├── user.py # 用户模型
│ │ ├── item.py # 其他模型
│ │ └── ...
│ ├── schemas/
│ │ ├── __init__.py
│ │ ├── user.py # 用户数据模型
│ │ ├── item.py # 其他数据模型
│ │ └── ...
│ ├── crud/
│ │ ├── __init__.py
│ │ ├── user.py # 用户CRUD操作
│ │ ├── item.py # 其他CRUD操作
│ │ └── ...
│ ├── db/
│ │ ├── __init__.py
│ │ ├── base.py # 数据库基础设置
│ │ ├── session.py # 数据库会话
│ │ └── ...
│ ├── tests/
│ │ ├── __init__.py
│ │ ├── test_main.py # 测试主文件
│ │ ├── test_users.py # 用户相关测试
│ │ └── ...
│ └── utils/
│ ├── __init__.py
│ ├── utils.py # 工具函数
│ └── ...
├── .env # 环境变量文件
├── alembic/ # 数据库迁移工具目录
│ ├── env.py
│ ├── script.py.mako
│ └── versions/
│ └── ...
├── alembic.ini # Alembic 配置文件
├── requirements.txt # 项目依赖
├── Dockerfile # Docker 配置文件
└── README.md # 项目说明文件
目录结构说明
- app/: 项目的主目录,包含所有应用相关代码。
- main.py: 项目的入口文件,启动FastAPI应用。
- core/: 核心功能,如配置、安全等。
- api/: API路由和视图,分版本管理。
- models/: 数据库模型。
- schemas/: 数据模型,用于请求和响应的验证。
- crud/: 数据库操作(CRUD:创建、读取、更新、删除)。
- db/: 数据库相关设置和会话管理。
- tests/: 测试代码。
- utils/: 工具函数和公用模块。
- .env: 环境变量文件,用于存储敏感信息,如数据库连接字符串。
- alembic/: 数据库迁移工具Alembic的配置目录。
- requirements.txt: 项目依赖列表。
- Dockerfile: Docker配置文件,用于容器化部署。
- README.md: 项目说明文件。
这个结构可以根据需求进行调整,但保持清晰和模块化是良好的实践。
python项目总的__init__.py
,有意义吗?
在python项目中,__init__.py
文件的主要作用是将目录标识为一个python包。它使得目录中的模块可以被导入和使用,在一下情况下__init__.py
可以不仅仅是一个空文件,还可以包含一些初始化代码。
__init__.py
的意义:
-
将目录标识为包:
任何包含
__init__.py
的目录都会被python解释器认为是一个包,这样就可使用导入语法,如import mypackage.module
-
初始化代码:
可以在
__init__.py
中包含一些初始化代码,如导入包内的子模块,设置包级别的变量或函数,配置日志等记录等。例如:# mypackage/__init__.py from .submodule1 import func1 from .submodule2 import func2 __all__ = ["func1", "func2"]
-
简化导入
通过在
__init__.py
中导入子模块,可以简化包的导入路径,使得用户可以直接从包中导入函数或类,而不必知道具体的模块结构# mypackage/__init__.py from .submodule import MyClass # Now you can do from mypackage import MyClass
对于Python 3.3及以上版本,__init__.py
文件不是强制性的,即使没有 __init__.py
文件,Python解释器也可以识别包。然而,添加 __init__.py
文件仍然是一个良好的习惯,可以避免某些情况下的意外行为,并且明确表示该目录是一个包。
Fast API项目的开发处理过程
在FastAPI 项目中,,CRUD操作通常在一个专门的 crud
模块中实现。这个模块会调用SQLAlchemy模型对象来进行数据库操作。
-
定义模型(model/user.py)
from sqlalchemy import Column, Integer, String from app.db.base_class import Base class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) email = Column(String, unique=True, index=True, nullable=False) hashed_password = Column(String, nullable=False) full_name = Column(String, index=True)
-
创建数据库会话(db/session.py)
from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker DATABASE_URL = "sqlite:///./test.db" # 使用SQLite数据库作为示例 engine = create_engine(DATABASE_URL) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
-
定义CRUD操作
from sqlalchemy.orm import Session from app.models.user import User from app.schemas.user import UserCreate, UserUpdate def get_user(db: Session, user_id: int): return db.query(User).filter(User.id == user_id).first() def get_user_by_email(db: Session, email: str): return db.query(User).filter(User.email == email).first() def get_users(db: Session, skip: int = 0, limit: int = 10): return db.query(User).offset(skip).limit(limit).all() def create_user(db: Session, user: UserCreate): db_user = User( email=user.email, hashed_password=user.hashed_password, # 在实际应用中应该对密码进行哈希处理 full_name=user.full_name ) db.add(db_user) db.commit() db.refresh(db_user) return db_user def update_user(db: Session, user_id: int, user: UserUpdate): db_user = get_user(db, user_id) if db_user: db_user.email = user.email db_user.full_name = user.full_name db.commit() db.refresh(db_user) return db_user def delete_user(db: Session, user_id: int): db_user = get_user(db, user_id) if db_user: db.delete(db_user) db.commit() return db_user
-
定义数据模型(schemas/user.py)
from pydantic import BaseModel class UserBase(BaseModel): email: str full_name: str = None class UserCreate(UserBase): hashed_password: str class UserUpdate(UserBase): pass class User(UserBase): id: int class Config: orm_mode = True
-
在API端点中使用CRUD操作(api/v1/endpoints/users.py)
from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from app import crud, models, schemas from app.db.session import SessionLocal router = APIRouter() def get_db(): db = SessionLocal() try: yield db finally: db.close() @router.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) @router.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 @router.put("/users/{user_id}", response_model=schemas.User) def update_user(user_id: int, user: schemas.UserUpdate, db: Session = Depends(get_db)): db_user = crud.update_user(db=db, user_id=user_id, user=user) if db_user is None: raise HTTPException(status_code=404, detail="User not found") return db_user @router.delete("/users/{user_id}", response_model=schemas.User) def delete_user(user_id: int, db: Session = Depends(get_db)): db_user = crud.delete_user(db=db, user_id=user_id) if db_user is None: raise HTTPException(status_code=404, detail="User not found") return db_user @router.get("/users/", response_model=List[schemas.User]) def read_users(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)): users = crud.get_users(db, skip=skip, limit=limit) return users
-
注册路由(main.py)
from fastapi import FastAPI from app.api.v1.endpoints import users app = FastAPI() app.include_router(users.router, prefix="/api/v1", tags=["users"]) if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)
-
初始化数据库(db/base.py)
from app.db.session import engine from app.models import user user.Base.metadata.create_all(bind=engine)
-
运行应用
在根目录下运行
uvicorn app.main:app --reload
实际FastAPI项目对基类的封装
可以通过创建一个通用的CURD基类来封装常规的CRUD操作,然后让特定的CRUD继承这个基类。这样可以减少重复代码,提高代码的可维护性和可复用性。
-
创建通用CRUD基类(crud/base.py)
from typing import Generic, Type, TypeVar, Optional, List from pydantic import BaseModel from sqlalchemy.orm import Session from app.db.base_class import Base ModelType = TypeVar("ModelType", bound=Base) CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel) UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel) class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): def __init__(self, model: Type[ModelType]): self.model = model def get(self, db: Session, id: int) -> Optional[ModelType]: return db.query(self.model).filter(self.model.id == id).first() def get_multi(self, db: Session, skip: int = 0, limit: int = 100) -> List[ModelType]: return db.query(self.model).offset(skip).limit(limit).all() def create(self, db: Session, obj_in: CreateSchemaType) -> ModelType: obj_in_data = obj_in.dict() db_obj = self.model(**obj_in_data) # type: ignore db.add(db_obj) db.commit() db.refresh(db_obj) return db_obj def update(self, db: Session, db_obj: ModelType, obj_in: UpdateSchemaType) -> ModelType: obj_data = db_obj.dict() update_data = obj_in.dict(skip_defaults=True) for field in obj_data: if field in update_data: setattr(db_obj, field, update_data[field]) db.commit() db.refresh(db_obj) return db_obj def remove(self, db: Session, id: int) -> ModelType: obj = db.query(self.model).get(id) db.delete(obj) db.commit() return obj
-
定义用户CRUD操作(crud/user.py)
from typing import Any from sqlalchemy.orm import Session from app.crud.base import CRUDBase from app.models.user import User from app.schemas.user import UserCreate, UserUpdate class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]): def get_by_email(self, db: Session, email: str) -> Any: return db.query(self.model).filter(self.model.email == email).first() user = CRUDUser(User)
-
定义数据类型(schemas/user.py)
from pydantic import BaseModel class UserBase(BaseModel): email: str full_name: str = None class UserCreate(UserBase): hashed_password: str class UserUpdate(UserBase): pass class User(UserBase): id: int class Config: orm_mode = True
-
在API端点中使用CRUD操作(api/v1/endpoints/users.py)
from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from typing import List from app import crud, schemas from app.db.session import SessionLocal from app.models.user import User router = APIRouter() def get_db(): db = SessionLocal() try: yield db finally: db.close() @router.post("/users/", response_model=schemas.User) def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): db_user = crud.user.get_by_email(db, email=user.email) if db_user: raise HTTPException(status_code=400, detail="Email already registered") return crud.user.create(db=db, obj_in=user)
-
在API端点中使用CRUD操作(api/v1/endpoints/users.py)
from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from typing import List from app import crud, schemas from app.db.session import SessionLocal from app.models.user import User router = APIRouter() def get_db(): db = SessionLocal() try: yield db finally: db.close() @router.post("/users/", response_model=schemas.User) def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): db_user = crud.user.get_by_email(db, email=user.email) if db_user: raise HTTPException(status_code=400, detail="Email already registered") return crud.user.create(db=db, obj_in=user) @router.get("/users/{user_id}", response_model=schemas.User) def read_user(user_id: int, db: Session = Depends(get_db)): db_user = crud.user.get(db, id=user_id) if db_user is None: raise HTTPException(status_code=404, detail="User not found") return db_user @router.put("/users/{user_id}", response_model=schemas.User) def update_user(user_id: int, user: schemas.UserUpdate, db: Session = Depends(get_db)): db_user = crud.user.get(db=db, id=user_id) if db_user is None: raise HTTPException(status_code=404, detail="User not found") return crud.user.update(db=db, db_obj=db_user, obj_in=user) @router.delete("/users/{user_id}", response_model=schemas.User) def delete_user(user_id: int, db: Session = Depends(get_db)): db_user = crud.user.get(db=db, id=user_id) if db_user is None: raise HTTPException(status_code=404, detail="User not found") return crud.user.remove(db=db, id=user_id) @router.get("/users/", response_model=List[schemas.User]) def read_users(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)): users = crud.user.get_multi(db, skip=skip, limit=limit) return users
其他的就是类似前面的做法了。
通过这种方式,你可以在通用的CRUD基类中封装常规的CRUD操作,而特定的CRUD类(如 CRUDUser
)只需要继承这个基类并添加特定的操作方法。这样不仅减少了重复代码,也提高了代码的可维护性和可复用性。
如果你希望可以通过定义一个通用的API基类来封装常规的CRUD操作方法,然后在具体的端点文件中继承这个基类。这样可以进一步减少重复代码,提高代码的可维护性和可复用性。
创建通用API基类 (api/deps.py
)
from typing import Type, TypeVar, Generic, List
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from pydantic import BaseModel
from app.crud.base import CRUDBase
from app.db.session import SessionLocal
ModelType = TypeVar("ModelType", bound=BaseModel)
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)
class CRUDRouter(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
def __init__(self, crud: CRUDBase[ModelType, CreateSchemaType, UpdateSchemaType]):
self.crud = crud
self.router = APIRouter()
self.router.post("/", response_model=ModelType)(self.create_item)
self.router.get("/{item_id}", response_model=ModelType)(self.read_item)
self.router.put("/{item_id}", response_model=ModelType)(self.update_item)
self.router.delete("/{item_id}", response_model=ModelType)(self.delete_item)
self.router.get("/", response_model=List[ModelType])(self.read_items)
def get_db(self):
db = SessionLocal()
try:
yield db
finally:
db.close()
async def create_item(self, item_in: CreateSchemaType, db: Session = Depends(self.get_db)):
db_item = self.crud.create(db=db, obj_in=item_in)
return db_item
async def read_item(self, item_id: int, db: Session = Depends(self.get_db)):
db_item = self.crud.get(db=db, id=item_id)
if not db_item:
raise HTTPException(status_code=404, detail="Item not found")
return db_item
async def update_item(self, item_id: int, item_in: UpdateSchemaType, db: Session = Depends(self.get_db)):
db_item = self.crud.get(db=db, id=item_id)
if not db_item:
raise HTTPException(status_code=404, detail="Item not found")
return self.crud.update(db=db, db_obj=db_item, obj_in=item_in)
async def delete_item(self, item_id: int, db: Session = Depends(self.get_db)):
db_item = self.crud.get(db=db, id=item_id)
if not db_item:
raise HTTPException(status_code=404, detail="Item not found")
return self.crud.remove(db=db, id=item_id)
async def read_items(self, skip: int = 0, limit: int = 10, db: Session = Depends(self.get_db)):
items = self.crud.get_multi(db=db, skip=skip, limit=limit)
return items
使用通用API基类定义用户端点(api/v1/endpoints/users.py
)
from fastapi import APIRouter
from app.crud.user import user as user_crud
from app.schemas.user import User, UserCreate, UserUpdate
from app.api.deps import CRUDRouter
user_router = CRUDRouter[User, UserCreate, UserUpdate](user_crud)
router = user_router.router
注册路由 (main.py
)
rom fastapi import FastAPI
from app.api.v1.endpoints import users
app = FastAPI()
app.include_router(users.router, prefix="/api/v1/users", tags=["users"])
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
通过这种方式,你可以在 CRUDRouter
基类中封装常规的CRUD操作方法,然后在具体的端点文件中继承这个基类并传递相应的CRUD对象。这样可以进一步减少重复代码,提高代码的可维护性和可复用性。
SQLAlchemy模型的基类定义
app.db.base_class
通常是用于定义SQLAlchemy模型基类的文件。在这个文件中,我们会定义一个基本的Base类,这个类是所有SQLAlchemy模型的基类。下面是一个实现示例:
定义 Base
类 (db/base_class.py
)
from sqlalchemy.ext.declarative import as_declarative, declared_attr
@as_declarative()
class Base:
id: int
__name__: str
@declared_attr
def __tablename__(cls) -> str:
return cls.__name__.lower()
详细解释
@as_declarative()
: 这是SQLAlchemy提供的一个装饰器,它会将类装饰为一个声明性基类。所有继承自这个类的子类都会自动成为声明性类。id: int
: 这是一个类型注释,表示每个模型类都会有一个id
属性。具体的字段定义(例如Column(Integer, primary_key=True)
)会在每个具体的模型类中定义。__name__: str
: 这是另一个类型注释,表示每个模型类都会有一个__name__
属性。@declared_attr
: 这是SQLAlchemy提供的一个装饰器,允许我们为声明性基类定义一些通用的属性。在这个例子中,它用于自动生成__tablename__
属性。这个属性的值是模型类的名称的小写形式。
这样定义的 Base
类可以作为所有SQLAlchemy模型的基类,简化模型的定义。
完整示例项目结构:为了更好地理解,这里展示一个包含 Base
类定义的完整项目结构:
.
├── app
│ ├── __init__.py
│ ├── api
│ │ ├── __init__.py
│ │ └── v1
│ │ ├── __init__.py
│ │ └── endpoints
│ │ ├── __init__.py
│ │ └── users.py
│ ├── crud
│ │ ├── __init__.py
│ │ ├── base.py
│ │ └── user.py
│ ├── db
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── base_class.py
│ │ └── session.py
│ ├── models
│ │ ├── __init__.py
│ │ └── user.py
│ ├── schemas
│ │ ├── __init__.py
│ │ └── user.py
│ └── main.py
models/user.py 类文件如下定义
from sqlalchemy import Column, Integer, String
from app.db.base_class import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True, nullable=False)
hashed_password = Column(String, nullable=False)
full_name = Column(String, index=True)