我们的web应用使用python web的fastapi框架,通过uvicorn开启web服务。
1. refs
官网文档:FastAPI (tiangolo.com)
github:https://github.com/tiangolo/fastapi
2. 环境配置
python:3.11+
uvicorn:0.29.0
pip install "uvicorn[standard]"
什么是uvicorn?
Uvicorn 是一个轻量级的 ASGI(Asynchronous Server Gateway Interface)服务器,用于运行 Python 的 ASGI 应用。ASGI 是一个标准接口,用于异步Web应用程序和服务器之间的通信,它允许你编写异步代码,从而提高应用程序的性能和可伸缩性。
Uvicorn 的主要特点包括:
- 异步支持:Uvicorn 完全支持异步,这意味着它可以处理大量的并发连接,而不会阻塞服务器。
- 性能:Uvicorn 提供了高性能的服务器能力,特别是在与异步框架(如 FastAPI 或 Starlette)结合使用时。
- 简单易用:Uvicorn 的使用非常简单,可以通过命令行启动,也可以作为库在代码中启动。
- 跨平台:Uvicorn 可以在多种操作系统上运行,包括 Windows、macOS 和 Linux。
- 可扩展性:Uvicorn 可以轻松扩展以适应不同的工作负载,适用于从小规模到大规模的生产环境。
- 内置支持:许多现代 Python Web 框架,如 FastAPI 和 Starlette,已经内置了对 Uvicorn 的支持。
- 命令行接口:Uvicorn 提供了一个命令行接口(CLI),允许你快速启动和管理 ASGI 应用。
- WebSockets 支持:Uvicorn 支持 WebSockets,使得实时通信和交互式应用的构建成为可能。
- Gunicorn 集成:Uvicorn 可以与 Gunicorn(一个 Python WSGI HTTP 服务器)集成,通过 Gunicorn 运行 Uvicorn 工作器。
FastAPI的官网介绍自己说是性能最好的Python Web框架之一,主要原因就是web端的Uvicorn和Starlette的功劳。
fastapi:0.111.0
pip install fastapi
可以看到fastapi是依赖于uvicorn做服务器的,所以务必下载这个依赖。
3. Start Off
3.1. 一个最简单的例子
启动服务
先来一个最简单的示例:
# file:main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
@app.get("/hello/{name}")
async def say_hello(name: str):
return {"message": f"Hello {name}"}
随后利用命令:
uvicorn main:app --reload
进行启动
uvicorn main:app
命令含义如下:
main
:main.py
文件(一个 Python「模块」)。app
:在main.py
文件中通过app = FastAPI()
创建的对象。-reload
:让服务器在更新代码后重新启动。仅在开发时使用该选项。
随后可以请求下我们创建的两个接口:
可以看到服务端的所有请求:
同时,可以创建一个.http文件,进行接口测试:
接口文档
跳转到 http://127.0.0.1:8000/docs。
你将会看到自动生成的交互式 API 文档(由 Swagger UI 提供)
以及由 ReDoc提供的可选的文档
OpenAPI规范
FastAPI 使用定义 API 的 OpenAPI 标准将你的所有 API 转换成一种模式描述,或者说是API的规范。
访问127.0.0.1:8000/openapi.json可以看到这个json文档。
3.2. 开放一个新的接口
路径类型
这个在上面的实例中已经有所体现:
@app.get("/")
async def root():
return {"message": "Hello World"}
这里的请求路径方式有很多,包括:
@app.post()
@app.put()
@app.delete()
@app.options()
@app.head()
@app.patch()
@app.trace()
虽然在语义上有所不同,但前三个(post,put,delete)的实际行为是可以任意规定的。比如本来该delete的行为,用post来传递参数,其实也无所谓。
另外我们可以用async关键字来规定某个接口的行为是否是异步的,对于那些不需要等待其他子程序的请求,我们可以允许这样的异步行为,利用await关键字告知python在这段程序执行时你可以转而执行其他子程序,等到这段程序执行完毕再返回执行。
路径参数
参数声明+参数类型
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
本例把 item_id
的类型声明为 int
。
同时会对这个参数进行校验,如果你传个没法转成int类型的数据,(比如food,4.2这种)将报错:
{
"detail": [
{
"loc": [
"path",
"item_id"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}
此外,这个类型可以是枚举类型:路径操作使用 Python 的 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"}
最后,这个参数本身可能也是个路径,例如/home/myfile.txt这种,此时要用到路径转换器:
from fastapi import FastAPI
app = FastAPI()
@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
return {"file_path": file_path}
本例中,参数名为 file_path
,结尾部分的 :path
说明该参数应匹配路径。
查询参数
在?后,用&分割
例如:
from fastapi import FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
@app.get("/items/")
async def read_item(skip: int = 0, limit: int = 10):
return fake_items_db[skip : skip + limit]
利用该接口请求:
<http://127.0.0.1:8000/items/?skip=0&limit=10>
如果不指定这两个参数,那么将是默认值
请求体
这里有一个很关键的组件,叫做pydantic,是python生态圈里很有名的做数据校验的组件:Welcome to Pydantic - Pydantic
首先我们需要利用pydantic提供的basemodel来定义一个合法的请求体是什么样子的:
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
return item
其中description和tax是有默认值的,可以不给他们传参,但是没有默认值得必须传参了。
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
return item
此处,请求体参数的类型为 Item
模型。 甚至请求体和路径参数是可以共存的:
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
app = FastAPI()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
return {"item_id": item_id, **item.dict()}