FastAPI使用
安装
pip install fastapi
你还会需要一个 ASGI 服务器,生产环境可以使用 Uvicorn 或者 Hypercorn。
pip install uvicorn
最简单的程序
from fastapi import FastAPI
app = FastAPI()
@app.get("/") // app.get指的是get请求,还可以是app.post,app.put,app.delete等
async def root():
return {"message": "Hello World"}
运行实时服务器
uvicorn main:app --reload --port 5000 --host 0.0.0.0
# 参数信息
main指的是程序入口文件名
app是实例名称app = FastAPI()
--reload指程序修改后自动重启
也可以使用下面方式运行,方便进行调试
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_main():
return {"msg": "Hello World"}
if __name__ == '__main__':
import uvicorn
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
交互式API文档
访问 http://127.0.0.1:8000/docs。
你将会看到自动生成的交互式 API 文档(由 Swagger UI 提供):
可选的 API 文档
访问 http://127.0.0.1:8000/redoc。
你将会看到可选的自动生成文档 (由 ReDoc 提供):
查询参数和字符串校验
简单查询参数
url上的参数会作为函数的参数传递
from fastapi import FastAPI
app = FastAPI()
@app.get("/test")
async def test(skip: int = 0, limit: int = 3): # skip默认值为0,limit默认值为3
print(skip, limit)
return skip + limit
访问http://127.0.0.1:8000/items/?skip=0&limit=10,页面会显示10。
访问http://localhost:8000/test?limit=20,页面显示20,skip默认为0,limit为url传入为20
查询参数为:
skip
:对应的值为0
limit
:对应的值为10
由于它们是 URL 的一部分,因此它们的"原始值"是字符串。
但是,当你为它们声明了 Python 类型(在上面的示例中为 int
)时,它们将转换为该类型并针对该类型进行校验。
可选参数
通过同样的方式,你可以将它们的默认值设置为 None
来声明可选查询参数:
Typing.Optional可选类型,作用几乎和带默认值的参数等价,不同的是使用Optional会告诉你的IDE或者框架:这个参数除了给定的默认值外还可以是None,而且使用有些静态检查工具如mypy时,对 a: int =None这样类似的声明可能会提示报错,但使用a :Optional[int] = None不会。
from typing import Optional
from fastapi import FastAPI
app = FastAPI()
@app.get("/test/{id}")
async def test(id: int, q: Optional[str] = None): # q不是必须的
if q:
return {"id": id, "q": q}
return {"id": id}
在这个例子中,函数参数 q
将是可选的,并且默认值为 None
。
查询参数类型转换
from typing import Optional
from fastapi import FastAPI
app = FastAPI()
@app.get("/test/{id}")
async def test(id: int, q: Optional[str] = None, short: bool = False):
print(type(short)) # <class 'bool'>
if q:
return {"id": id, "q": q}
return {"id": id}
这个例子中,如果你访问:
http://127.0.0.1:8000/items/foo?short=1
或
http://127.0.0.1:8000/items/foo?short=True
或
http://127.0.0.1:8000/items/foo?short=true
或
http://127.0.0.1:8000/items/foo?short=on
或
http://127.0.0.1:8000/items/foo?short=yes
或任何其他的变体形式(大写,首字母大写等等),你的函数接收的 short
参数都会是布尔值 True
。对于值为 False
的情况也是一样的。
多个路径和查询参数
你可以同时声明多个路径参数和查询参数,FastAPI 能够通过名称识别它们。
from typing import Optional
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(user_id: int, item_id: str, q: Optional[str] = None, short: bool = False):
item = {"item_id": item_id, "owner_id": user_id}
if q:
item.update({"q": q})
if not short:
item.update(
{"description": "This is an amazing item that has a long description"}
)
return item
必需查询参数
当你为非路径参数声明了默认值时(目前而言,我们所知道的仅有查询参数),则该参数不是必需的。
如果你不想添加一个特定的值,而只是想使该参数成为可选的,则将默认值设置为 None
。
但当你想让一个查询参数成为必需的,不声明任何默认值就可以:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_user_item(item_id: str, needy: str):
item = {"item_id": item_id, "needy": needy}
return item
这里的查询参数 needy
是类型为 str
的必需查询参数。
当访问http://localhost:8000/items/1时,没有needy参数,无法访问。
访问http://localhost:8000/items/1?needy=needy才可以访问。
使用Query参数校验
我们打算添加约束条件:即使 q
是可选的,但只要提供了该参数,则该参数值不能超过5个字符的长度。
from typing import Optional
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: Optional[str] = Query(None, max_length=5)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
使用 Query
作为默认值
现在,将 Query
用作查询参数的默认值,并将它的 max_length
参数设置为 5
from typing import Optional
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: Optional[str] = Query(123, max_length=5)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
由于我们必须用 Query(None)
替换默认值 None
,Query
的第一个参数同样也是用于定义默认值。
所以:
q: str = Query(None)
...使得参数可选,等同于:
q: str = None
但是 Query
显式地将其声明为查询参数。
然后,我们可以将更多的参数传递给 Query
。在本例中,适用于字符串的 max_length
参数:
q: str = Query(None, max_length=5)
添加正则表达式校验
你可以定义一个参数值必须匹配的正则表达式:
from typing import Optional
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: Optional[str] = Query(123, max_length=5, regex=r"\d+")): # 使用正则显示参数为整数
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
Query声明为必需参数
当我们不需要声明额外的校验或元数据时,只需不声明默认值就可以使 q
参数成为必需参数,例如:
q: str
代替:
q: str = None
但是现在我们正在用 Query
声明它,例如:
q: str = Query(None, min_length=3)
因此,当你在使用 Query
且需要声明一个值是必需的时,可以将 ...
用作第一个参数值:
from typing import Optional
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: Optional[str] = Query(..., max_length=5, regex=r"\d+")):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
Query查询参数列表 / 多个值
当你使用 Query
显式地定义查询参数时,你还可以声明它去接收一组值,或换句话来说,接收多个值。
例如,要声明一个可在 URL 中出现多次的查询参数 q
,你可以这样写:
from typing import List, Optional
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: Optional[List[str]] = Query(None)):
query_items = {"q": q}
return query_items # {"q":["123","abc"]}
输入网址:http://localhost:8000/items/?q=123&q=abc
响应为:{"q":["123","abc"]}
具有默认值的查询参数列表
from typing import List, Optional
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: Optional[List[str]] = Query(["lowell", "xiaoqi"])):
query_items = {"q": q}
return query_items # {"q":["lowell","xiaoqi"]}
使用 list
你也可以直接使用 list
代替 List [str]
:
from typing import List, Optional
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: list = Query(["lowell", "xiaoqi"])):
query_items = {"q": q}
return query_items # {"q":["lowell","xiaoqi"]}
注意:在这种情况下 FastAPI 将不会检查列表的内容。
例如,List[int]
将检查(并记录到文档)列表的内容必须是整数。但是单独的 list
不会。
Query更多元数据
使用接口文档的时候可以看到
from typing import List, Optional
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: list = Query(["lowell", "xiaoqi"], title="姓名列表", description="涉及到同学的名字")):
query_items = {"q": q}
return query_items # {"q":["lowell","xiaoqi"]}
Query中别名参数
假设你想要查询参数为name
像下面这样:
http://localhost:8000/items/?name=123&name=456
但是代码中你要是用q
这时你可以用 alias
参数声明一个别名,该别名将用于在 URL 中查找查询参数值:
from typing import List, Optional
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: list = Query(None, alias="name")):
query_items = {"q": q}
return query_items # {"q":["lowell","xiaoqi"]}
访问http://localhost:8000/items/?name=123&name=456才可以
弃用参数
现在假设你不再喜欢此参数。
你不得不将其保留一段时间,因为有些客户端正在使用它,但你希望文档清楚地将其展示为已弃用。
那么将参数 deprecated=True
传入 Query
:
from typing import List, Optional
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: list = Query(None, alias="name", deprecated=True)):
query_items = {"q": q}
return query_items
路径参数和数值校验
路径参数
路径参数使用{}
包裹的参数将作为参数传递给你的函数。
from fastapi import FastAPI
app = FastAPI()
@app.get("/test/{id}")
async def test(id):
return {"id": id}
有类型的路径参数
from fastapi import FastAPI
app = FastAPI()
@app.get("/test/{id}")
async def test(id: int): # 定义了参数为int类型
return {"id": id}
上述代码如果访问http://localhost:8000/test/lowell会显示如下信息。
{"detail":[{"loc":["path","id"],"msg":"value is not a valid integer","type":"type_error.integer"}]}
只有输入的路径id为int类型才可以正常访问。
{"id":1}
注意函数接收(并返回)的值为1,是一个 Python int
值,而不是字符串 "1"
。
所以,FastAPI 通过上面的类型声明提供了对请求的自动"解析"。自动进行数据转换。
顺序很重要
在创建路径操作时,你会发现有些情况下路径是固定的。
比如 /users/me
,我们假设它用来获取关于当前用户的数据.
然后,你还可以使用路径 /users/{user_id}
来通过用户 ID 获取关于特定用户的数据。
由于路径操作是按顺序依次运行的,你需要确保路径 /users/me
声明在路径 /users/{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}
否则,/users/{user_id}
的路径还将与 /users/me
相匹配,"认为"自己正在接收一个值为 "me"
的 user_id
参数。
预设值
如果你有一个接收路径参数的路径操作,但你希望预先设定可能的有效参数值,则可以使用标准的 Python Enum
类型。
创建一个 Enum
类
导入 Enum
并创建一个继承自 str
和 Enum
的子类。
通过从 str
继承,API 文档将能够知道这些值必须为 string
类型并且能够正确地展示出来。
然后创建具有固定值的类属性,这些固定值将是可用的有效值:
from fastapi import FastAPI
from enum import Enum
app = FastAPI()
class Name(str, Enum):
a = "hello"
b = "lowell"
c = "xxxx"
@app.get("/test/{name}")
async def test(name: Name):
if name == Name.a:
return {"name": name}
elif name == Name.b:
return {"name": name}
return {"name": ""}
你也可以通过 ModelName.lenet.value
来获取值 。
from fastapi import FastAPI
from enum import Enum
app = FastAPI()
class Name(str, Enum):
a = "hello"
b = "lowell"
c = "xxxx"
@app.get("/test/{name}")
async def test(name: Name):
print(Name.a.value) # hello
if name == Name.a:
return {"name": name}
elif name == Name.b:
return {"name": name}
return {"name": ""}
路径转换器
你可以使用直接来自 Starlette 的选项来声明一个包含路径的路径参数:
/files/{file_path:path}
在这种情况下,参数的名称为 file_path
,结尾部分的 :path
说明该参数应匹配任意的路径。
因此,你可以这样使用它:
from fastapi import FastAPI
from enum import Enum
app = FastAPI()
@app.get("/test/{name:path}")
async def test(name):
return {"name": name}
可以访问http://localhost:8000/test/hello,正常显示{"name":"hello"}
也可以访问name为一个路径,http://localhost:8000/test//bin/downlaod/test,显示{"name":"/bin/downlaod/test"},此时路径的位置是双斜杠。
声明元数据
你可以声明与 Query
相同的所有参数。
例如,要声明路径参数 item_id
的 title
元数据值,你可以输入:
from typing import List, Optional
from fastapi import FastAPI, Query
from fastapi.param_functions import Path
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(item_id: int = Path(..., title="item的id", description="前端传过来的id", alias="id")):
return {"item_id": item_id}
路径参数总是必需的,因为它必须是路径的一部分。
所以,你应该在声明时使用 ...
将其标记为必需参数。
然而,即使你使用 None
声明路径参数或设置一个其他默认值也不会有任何影响,它依然会是必需参数。
对参数排序的技巧
如果你想不使用 Query
声明没有默认值的查询参数 q
,同时使用 Path
声明路径参数 item_id
,并使它们的顺序与上面不同,Python 对此有一些特殊的语法。
传递 *
作为函数的第一个参数。
Python 不会对该 *
做任何事情,但是它将知道之后的所有参数都应作为关键字参数(键值对),也被称为 kwargs
,来调用。即使它们没有默认值。
from fastapi import FastAPI, Path
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(
*, item_id: int = Path(..., title="The ID of the item to get"), q: str
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results
数值校验
大于等于ge
使用 Query
和 Path
(以及你将在后面看到的其他类)可以声明字符串约束,但也可以声明数值约束。
像下面这样,添加 ge=1
后,item_id
将必须是一个大于(g
reater than)或等于(e
qual)1
的整数。
from typing import List, Optional
from fastapi import FastAPI, Query
from fastapi.param_functions import Path
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(item_id: int = Path(..., ge=1)):
return {"item_id": item_id}
小于等于le
from typing import List, Optional
from fastapi import FastAPI, Path
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(item_id: int = Path(..., le=10)):
return {"item_id": item_id}
大于gt
小于lt
from typing import List, Optional
from fastapi import FastAPI, Path
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(item_id: int = Path(..., gt=2, lt=10)):
return {"item_id": item_id}
标签
from typing import Optional, Set
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = []
@app.post("/items/", response_model=Item, tags=["items"])
async def create_item(item: Item):
return item
@app.get("/items/", tags=["items"])
async def read_items():
return [{"name": "Foo", "price": 42}]
@app.get("/users/", tags=["users"])
async def read_users():
return [{"username": "johndoe"}]
摘要说明
from typing import Optional, Set
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = []
@app.post(
"/items/",
response_model=Item,
summary="Create an item",
description="Create an item with all the information, name, description, price, tax and a set of unique tags",
)
async def create_item(item: Item):
return item
文档字符串的描述
描述往往很长,并且覆盖多行,您可以在函数文档字符串中声明
from typing import Optional, Set
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = []
@app.post("/items/", response_model=Item, summary="Create an item")
async def create_item(item: Item):
"""
Create an item with all the information:
- **name**: each item must have a name
- **description**: a long description
- **price**: required
- **tax**: if the item doesn't have tax, you can omit this
- **tags**: a set of unique tag strings for this item
"""
return item
响应描述
from typing import Optional, Set
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = []
@app.post("/items/", response_model=Item, response_description="The created item")
async def create_item(item: Item):
return item
不显示某个api接口
使用include_in_schema
参数,并设置为False
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/", include_in_schema=False)
async def read_items():
return [{"item_id": "Foo"}]
if __name__ == '__main__':
import uvicorn
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
访问http://localhost:8000/docs文档接口的时候,不会显示该接口。
API接口弃用
如果需要将路径操作标记为已弃用 ,但在不删除它的情况下,请传递 参数:deprecated
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/", tags=["items"])
async def read_items():
return [{"name": "Foo", "price": 42}]
@app.get("/users/", tags=["users"])
async def read_users():
return [{"username": "johndoe"}]
@app.get("/elements/", tags=["items"], deprecated=True)
async def read_elements():
return [{"item_id": "Foo"}]
请求
当你需要将数据从客户端(例如浏览器)发送给 API 时,你将其作为「请求体」发送。
请求体是客户端发送给 API 的数据。响应体是 API 发送给客户端的数据。
你的 API 几乎总是要发送响应体。但是客户端并不总是需要发送请求体。
我们使用 Pydantic 模型来声明请求体,并能够获得它们所具有的所有能力和优点。
你不能使用 GET
操作(HTTP 方法)发送请求体。
要发送数据,你必须使用下列方法之一:POST
(较常见)、PUT
、DELETE
或 PATCH
。
首先,你需要从 pydantic
中导入 BaseModel
:
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
// json格式传输,相当于定义了json数据的格式
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
return item
和声明查询参数时一样,当一个模型属性具有默认值时,它不是必需的。否则它是一个必需属性。将默认值设为 None
可使其成为可选属性。
使用模型
在函数内部,你可以直接访问模型对象的所有属性:
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
app = FastAPI()
@app.post("/items")
async def create_item(item: Item):
item_dict = item.dict()
print(item_dict)
print(item.name)
print(item.description)
return item
请求体中的单一值
与使用 Query
和 Path
为查询参数和路径参数定义额外数据的方式相同,FastAPI 提供了一个同等的 Body
。
例如,为了扩展先前的模型,你可能决定除了 item
和 user
之外,还想在同一请求体中具有另一个键 importance
。
如果你就按原样声明它,因为它是一个单一值,FastAPI 将假定它是一个查询参数。
但是你可以使用 Body
指示 FastAPI 将其作为请求体的另一个键进行处理。
from typing import List, Optional
from fastapi import FastAPI, Path
from fastapi.param_functions import Body
app = FastAPI()
@app.post("/items/{item_id}")
async def read_items(item_id: int = Path(..., gt=2, lt=10), importance: int = Body(...)):
return {"item_id": item_id, "importance": importance}
嵌入单个请求体参数
假设你只有一个来自 Pydantic 模型 Item
的请求体参数 item
。
默认情况下,FastAPI 将直接期望这样的请求体。
但是,如果你希望它期望一个拥有 item
键并在值中包含模型内容的 JSON,就像在声明额外的请求体参数时所做的那样,则可以使用一个特殊的 Body
参数 embed
:
item: Item = Body(..., embed=True)
比如:
from typing import Optional
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item = Body(..., embed=True)):
results = {"item_id": item_id, "item": item}
return results
在这种情况下,FastAPI 将期望像这样的请求体:
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
}
}
而不是:
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
}
请求体字段
声明模型属性
然后,你可以对模型属性使用 Field
:
from typing import Optional
from fastapi import Body, FastAPI
from pydantic import BaseModel
from pydantic.fields import Field
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = Field(None, title="The description of item", max_length=256)
price: float = Field(None, ge=0, description="产品的价格")
tax: Optional[float] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item = Body(..., embed=True)):
results = {"item_id": item_id, "item": item}
return results
Field
的工作方式和 Query
、Path
和 Body
相同,包括它们的参数等等也完全相同。
请求体 - 嵌套模型
List 字段
你可以将一个属性定义为拥有子元素的类型。例如 Python list
:
from typing import List, Optional
from fastapi import Body, FastAPI
from pydantic import BaseModel
from pydantic.fields import Field
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = Field(None, title="The description of item", max_length=256)
price: float = Field(None, ge=0, description="产品的价格")
tax: Optional[float] = None
tags: List = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item = Body(..., embed=True)):
results = {"item_id": item_id, "item": item}
return results
具有子类型的 List 字段
from typing import List, Optional
from fastapi import Body, FastAPI
from pydantic import BaseModel
from pydantic.fields import Field
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = Field(None, title="The description of item", max_length=256)
price: float = Field(None, ge=0, description="产品的价格")
tax: Optional[float] = None
# 定义有类型的List字段
tags: List[str] = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item = Body(..., embed=True)):
results = {"item_id": item_id, "item": item}
return results
Set 类型
但是随后我们考虑了一下,意识到标签不应该重复,它们很大可能会是唯一的字符串。
Python 具有一种特殊的数据类型来保存一组唯一的元素,即 set
。
然后我们可以导入 Set
并将 tag
声明为一个由 str
组成的 set
:
from typing import List, Optional, Set
from fastapi import Body, FastAPI
from pydantic import BaseModel
from pydantic.fields import Field
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = Field(None, title="The description of item", max_length=256)
price: float = Field(None, ge=0, description="产品的价格")
tax: Optional[float] = None
tags: Set[str] = set()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item = Body(..., embed=True)):
results = {"item_id": item_id, "item": item}
return results
嵌套模型
from typing import List, Optional, Set
from fastapi import Body, FastAPI
from pydantic import BaseModel
from pydantic.fields import Field
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Optional[str] = Field(
None, title="The description of item", max_length=256)
price: float = Field(None, ge=0, description="产品的价格")
tax: Optional[float] = None
tags: Set[str] = set()
image: Optional[Image] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item = Body(..., embed=True)):
results = {"item_id": item_id, "item": item}
return results
这意味着 FastAPI 将期望类似于以下内容的请求体:
{
"item_id": 6,
"item": {
"name": "string",
"description": "string",
"price": 0,
"tax": 0,
"tags": [],
"image": {
"url": "https://www.baidu.com",
"name": "百度"
}
}
}
模式的额外信息
schema_extra
您可以使用 Config
和 schema_extra
为Pydantic模型声明一个示例,如Pydantic 文档:定制 Schema 中所述:
from typing import List, Optional, Set
from fastapi import Body, FastAPI
from pydantic import BaseModel
from pydantic.fields import Field
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Optional[str] = Field(
None, title="The description of item", max_length=256)
price: float = Field(None, ge=0, description="产品的价格")
tax: Optional[float] = None
tags: Set[str] = set()
image: Optional[Image] = None
class Config:
schema_extra = {
"example": {
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
}
}
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item = Body(..., embed=True)):
results = {"item_id": item_id, "item": item}
return results
这些额外的信息将按原样添加到输出的JSON模式中。相当于定义了默认值。
Field
的附加参数
在 Field
, Path
, Query
, Body
和其他你之后将会看到的工厂函数,你可以为JSON 模式声明额外信息,你也可以通过给工厂函数传递其他的任意参数来给JSON 模式声明额外信息,比如增加 example
:
from typing import List, Optional, Set
from fastapi import Body, FastAPI
from pydantic import BaseModel
from pydantic.fields import Field
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Optional[str] = Field(
None, title="The description of item", max_length=256)
price: float = Field(None, example=3.5)
tax: Optional[float] = None
tags: Set[str] = set()
image: Optional[Image] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item = Body(..., embed=True)):
results = {"item_id": item_id, "item": item}
return results
请记住,传递的那些额外参数不会添加任何验证,只会添加注释,用于文档的目的。
Body
额外参数
你可以将请求体的一个 example
传递给 Body
:
from typing import List, Optional, Set
from fastapi import Body, FastAPI
from pydantic import BaseModel
from pydantic.fields import Field
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Optional[str] = Field(
None, title="The description of item", max_length=256)
price: float = Field(None, example=3.5)
tax: Optional[float] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item = Body(..., example={"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2, })):
results = {"item_id": item_id, "item": item}
return results
使用上面的任何方法,它在 /docs
中看起来都是这样的:
直接使用请求对象
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/items/{item_id}")
def read_root(item_id: str, request: Request):
client_host = request.client.host
port = request.client.port
url = request.url
base_url = request.base_url
headers = request.headers
method = request.method
path_params = request.path_params
query_params = request.query_params
state = request.state
return {"item_id": item_id, "client_host": client_host, "port": port, "url": url, "base_url": base_url,
"headers": headers, "method": method, "path_params": path_params, "query_params": query_params,
"state": state}
响应体
{
"item_id": "lowell",
"client_host": "127.0.0.1",
"port": 49432,
"url": {
"_url": "http://localhost:8000/items/lowell"
},
"base_url": {
"_url": "http://localhost:8000/"
},
"headers": {
"host": "localhost:8000",
"connection": "keep-alive",
"accept": "application/json",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.50",
"sec-fetch-site": "same-origin",
"sec-fetch-mode": "cors",
"sec-fetch-dest": "empty",
"referer": "http://localhost:8000/docs",
"accept-encoding": "gzip, deflate, br",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"cookie": "Pycharm-54243081=cd6e9076-310e-48d0-adb5-0c836dc36a30; fakesession=fake-cookie-session-value"
},
"method": "GET",
"path_params": {
"item_id": "lowell"
},
"query_params": {},
"state": {
"_state": {}
}
}
Cookie
从前端获取cookie
要获取cookie,必须需要使用Cookie
来声明,否则参数将被解释为查询参数。
from typing import Optional
from fastapi import Cookie, FastAPI
app = FastAPI()
@app.get("/items/")
async def read_items(ads_id: Optional[str] = Cookie(None)):
return {"ads_id": ads_id}
设置cookie
您可以在路径操作函数中声明类型的参数。Response
然后,您可以在该时态响应对象中设置 Cookie。
from fastapi import FastAPI, Response
app = FastAPI()
@app.post("/cookie-and-object/")
def create_cookie(response: Response):
response.set_cookie(key="fakesession", value="fake-cookie-session-value")
return {"message": "Come to the dark side, we have cookies"}
还可以在直接返回代码时创建 Cookie。
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.post("/cookie/")
def create_cookie():
content = {"message": "Come to the dark side, we have cookies"}
response = JSONResponse(content=content)
response.set_cookie(key="fakesession", value="fake-cookie-session-value")
return response
Header
从请求中获取header
获取头信息需要使用Header
from typing import Optional
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(user_agent: Optional[str] = Header(None)):
return {"User-Agent": user_agent}
通过浏览器访问http://localhost:8000/items/就会显示当前浏览器的UA信息。
我们会发现,代码中的user_agent
会自动获取请求头中的User-Agent
的值,但是他们的大小写和符号并不相同,这是为什么呢?
大部分请求头中的key是用
-
来分割的,比如User-Agent
,但是这种命名在python中是不符合规范的,因此,Header
会自动将参数名称中的下划线_
转换为连字符-
。另外,http请求头不区分大小写,因此我们可以用符合python规范的命名方法来表示他们。
如果由于某种原因需要禁用下划线_
到连字符-
的自动转换,需要将Header的参数convert_underscores设置为False:
from typing import Optional
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(user_agent: Optional[str] = Header(None, convert_underscores=False)):
return {"User-Agent": user_agent}
这时我们在请求http://127.0.0.1:8000/items/
时,会返回:
{"User-Agent":null} # 因为下划线没有自动转换成-,所以无法获取UA
请求中重复出现头信息
可以接收重复的标头。这意味着,具有多个值的同一标头。
您可以使用类型声明中的列表定义这些情况。
您将作为 Python 从重复标头接收所有值。list
例如,若要声明可以出现多个次的标头,可以编写:X-Token
from typing import List, Optional
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(x_token: Optional[List[str]] = Header(None)):
return {"X-Token values": x_token}
如果与该路径操作通信,发送两个 HTTP 标头,如:
X-Token: foo
X-Token: bar
响应将像:
{
"X-Token values": [
"bar",
"foo"
]
}
响应时设置头信息
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/headers-and-object/")
def get_headers(response: Response):
response.headers["X-Cat-Dog"] = "alone in the world"
return {"message": "Hello World"}
也可以在直接返回时设置响应头
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/headers/")
def get_headers():
content = {"message": "Hello World"}
headers = {"X-Cat-Dog": "alone in the world", "Content-Language": "en-US"}
return JSONResponse(content=content, headers=headers)
响应
返回响应与请求相同的数据
你可以在任意的路径操作中使用 response_model
参数来声明用于响应的模型:
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Optional[str] = None
# Don't do this in production!
@app.post("/user/", response_model=UserIn)
async def create_user(user: UserIn):
return user
指定响应字段
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Optional[str] = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: Optional[str] = None
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
return user
因此,FastAPI 将会负责过滤掉未在输出模型中声明的所有数据
排除响应体中字段
response_model_exclude_unset=True
:排除响应体中默认值字段,只返回传入实际值的字段
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: float = 10.5
tags: List[str] = []
以上述响应模型为例,如果它们并没有存储实际的值,你可能想不想在响应体中显示默认值的字段,使用response_model_exclude_unset=True
排除未传值字段。代码如下:
from typing import List, Optional
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: float = 10.5
tags: List[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
@app.get("/items/{item_name}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_name: str):
return items[item_name]
访问http://localhost:8000/items/foo响应中将不会包含那些默认值,而是仅有实际设置的值。显示的结果如下:
{
"name": "Foo",
"price": 50.2
}
而不是:
{
"name": "Foo",
"description": "string",
"price": 50.2,
"tax": 10.5,
"tags": []
}
你还可以使用:
response_model_exclude_defaults=True
:是否从返回的字典中排除等于其默认值的字段,默认是False
from typing import List, Optional
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: float = 10.5
tags: List[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2, "tax": 10.5},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
@app.get("/items/{item_name}", response_model=Item, response_model_exclude_defaults=True)
async def read_item(item_name: str):
return items[item_name]
访问http://localhost:8000/items/foo,会显示如下:
{
"name": "Foo",
"price": 50.2
}
而不是:
{
"name": "Foo",
"description": "string",
"price": 50.2,
"tax": 10.5,
"tags": []
}
// 或
{
"name": "Foo",
"price": 50.2,
"tax": 10.5,
}
response_model_exclude_none=True
:是否从返回的字典中排除等于None字段
from typing import List, Optional
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[str] = "a"
tags: List[str] = []
items = {
"foo": {"name": "Foo", "price": 18.0, "tax": None},
}
@app.get("/items/{item_name}", response_model=Item, response_model_exclude_none=True)
async def read_item(item_name: str):
return items[item_name]
访问http://localhost:8000/items/foo,会显示
{
"name": "Foo",
"price": 18,
"tags": []
}
而不是
{
"name": "Foo",
"description": None,
"price": 18,
"tax": None,
"tags": []
}
response_model_include
:显示指定字段
from typing import List, Optional
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[str] = "a"
tags: List[str] = []
items = {
"foo": {"name": "Foo", "price": 18.0, "tax": "abc", "tags": ["lowell", "xiaoqi"]},
}
@app.get("/items/{item_name}", response_model=Item, response_model_include={"name", "price"})
async def read_item(item_name: str):
return items[item_name]
response_model_exclude
:排除指定字段
from typing import List, Optional
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[str] = "a"
tags: List[str] = []
items = {
"foo": {"name": "Foo", "price": 18.0, "tax": "abc", "tags": ["lowell", "xiaoqi"]},
}
@app.get("/items/{item_name}", response_model=Item, response_model_exclude={"name", "price"})
async def read_item(item_name: str):
return items[item_name]
建议:建议你使用上面提到的主意,使用多个类而不是这些参数。这是因为即使使用 response_model_include
或 response_model_exclude
来省略某些属性,在应用程序的 OpenAPI 定义(和文档)中生成的 JSON Schema 仍将是完整的模型。
如果你忘记使用 set
而是使用 list
或 tuple
,FastAPI 仍会将其转换为 set
并且正常工作:
from typing import List, Optional
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[str] = "a"
tags: List[str] = []
items = {
"foo": {"name": "Foo", "price": 18.0, "tax": "abc", "tags": ["lowell", "xiaoqi"]},
}
@app.get("/items/{item_name}", response_model=Item, response_model_include=["name", "price"])
async def read_item(item_name: str):
return items[item_name]
Union
或者 anyOf
你可以将一个响应声明为两种类型的 Union
,这意味着该响应将是两种类型中的任何一种。这将在 OpenAPI 中使用 anyOf
进行定义。
定义一个 Union
类型时,首先包括最详细的类型,然后是不太详细的类型。在下面的示例中,更详细的 PlaneItem
位于 Union[PlaneItem,CarItem]
中的 CarItem
之前。
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class BaseItem(BaseModel):
description: str
type: str
class CarItem(BaseItem):
type = "car"
class PlaneItem(BaseItem):
type = "plane"
size: int
items = {
"item1": {"description": "All my friends drive a low rider", "type": "car"},
"item2": {
"description": "Music is my aeroplane, it's my aeroplane",
"type": "plane",
"size": 5,
},
}
@app.get("/items/{item_id}", response_model=Union[PlaneItem, CarItem])
async def read_item(item_id: str):
return items[item_id]
定义响应结构为列表
你可以用同样的方式声明由对象列表构成的响应。
为此,请使用标准的 Python typing.List
:
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str
items = [
{"name": "Foo", "description": "There comes my hero"},
{"name": "Red", "description": "It's my aeroplane"},
]
@app.get("/items/", response_model=List[Item])
async def read_items():
return items
任意 dict
构成的响应
你还可以使用一个任意的普通 dict
声明响应,仅声明键和值的类型,而不使用 Pydantic 模型。
如果你事先不知道有效的字段/属性名称(对于 Pydantic 模型是必需的),这将很有用。
在这种情况下,你可以使用 typing.Dict
:
from typing import Dict
from fastapi import FastAPI
app = FastAPI()
@app.get("/keyword-weights/", response_model=Dict[str, float])
async def read_keyword_weights():
return {"foo": 2.3, "bar": 3.4}
直接返回一个json
当你创建一个 FastAPI 路径操作 时,你可以正常返回以下任意一种数据:dict
,list
,Pydantic 模型,数据库模型等等。
FastAPI 默认会使用 jsonable_encoder
将这些类型的返回值转换成 JSON 格式,jsonable_encoder
在 JSON 兼容编码器 中有阐述。
然后,FastAPI 会在后台将这些兼容 JSON 的数据(比如字典)放到一个 JSONResponse
中,该 JSONResponse
会用来发送响应给客户端。
但是你可以在你的 路径操作 中直接返回一个 JSONResponse
。
当你直接使用JsonResponse
时候,FastAPI 不会用 Pydantic 模型做任何数据转换,不会将响应内容转换成任何类型。需要我们手动使用jsonable_encoder
将数据模型转换
from datetime import datetime
from typing import Optional
from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from pydantic import BaseModel
class Item(BaseModel):
title: str
timestamp: datetime
description: Optional[str] = None
app = FastAPI()
@app.put("/items/{id}")
def update_item(id: str, item: Item):
json_compatible_item_data = jsonable_encoder(item)
return JSONResponse(content=json_compatible_item_data)
返回自定义Response
假设你想要返回一个 XML 响应。
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/legacy/")
def get_legacy_data():
data = """<?xml version="1.0"?>
<shampoo>
<Header>
Apply shampoo here.
</Header>
<Body>
You'll have to use soap here.
</Body>
</shampoo>
"""
return Response(content=data, media_type="application/xml")
当你直接返回 Response
时,它的数据既没有校验,又不会进行转换(序列化),也不会自动生成文档。
OpenAPI 中的文档和重载 Response
直接返回 HTMLResponse
比如像这样:
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
def generate_html_response():
html_content = """
<html>
<head>
<title>Some HTML in here</title>
</head>
<body>
<h1>Look ma! HTML!</h1>
</body>
</html>
"""
return HTMLResponse(content=html_content, status_code=200)
@app.get("/items/", response_class=HTMLResponse)
async def read_items():
return generate_html_response()
如果你在 response_class
中也传入了 HTMLResponse
,FastAPI 会知道如何在 OpenAPI 和交互式文档中使用 text/html
将其文档化为 HTML。
可用响应
Response
其他全部的响应都继承自主类 Response
。
你可以直接返回它。
Response
类接受如下参数:
content
- 一个str
或者bytes
。status_code
- 一个int
类型的 HTTP 状态码。headers
- 一个由字符串组成的dict
。media_type
- 一个给出媒体类型的str
,比如"text/html"
。
FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它还将包含一个基于 media_type 的 Content-Type 头,并为文本类型附加一个字符集。
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/legacy/")
def get_legacy_data():
data = """<?xml version="1.0"?>
<shampoo>
<Header>
Apply shampoo here.
</Header>
<Body>
You'll have to use soap here.
</Body>
</shampoo>
"""
return Response(content=data, media_type="application/xml")
HTMLResponse
接受文本或字节并返回 HTML 响应
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.get("/items/")
async def read_items():
html_content = """
<html>
<head>
<title>Some HTML in here</title>
</head>
<body>
<h1>Look ma! HTML!</h1>
</body>
</html>
"""
return HTMLResponse(content=html_content, status_code=200)
PlainTextResponse
接受文本或字节并返回纯文本响应。
from fastapi import FastAPI
from fastapi.responses import PlainTextResponse
app = FastAPI()
@app.get("/", response_class=PlainTextResponse)
async def main():
return "Hello World"
JSONResponse
接受数据并返回一个 application/json
编码的响应。
ORJSONResponse
如果你需要压榨性能,你可以安装并使用 orjson
并将响应设置为 ORJSONResponse
。
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse
app = FastAPI()
@app.get("/items/", response_class=ORJSONResponse)
async def read_items():
return [{"item_id": "Foo"}]
UJSONResponse
UJSONResponse
是一个使用 ujson
的可选 JSON 响应。
from fastapi import FastAPI
from fastapi.responses import UJSONResponse
app = FastAPI()
@app.get("/items/", response_class=UJSONResponse)
async def read_items():
return [{"item_id": "Foo"}]
RedirectResponse
返回 HTTP 重定向。默认情况下使用 307 状态代码(临时重定向)。
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.get("/typer")
async def read_typer():
return RedirectResponse("https://typer.tiangolo.com")
StreamingResponse
采用异步生成器或普通生成器/迭代器,然后流式传输响应主体。
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
async def fake_video_streamer():
for i in range(10):
yield b"some fake video bytes"
@app.get("/")
async def main():
return StreamingResponse(fake_video_streamer())
如果您有类似文件的对象(例如,由 open()
返回的对象),则可以在 StreamingResponse
中将其返回。
包括许多与云存储,视频处理等交互的库。
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
some_file_path = "large-video-file.mp4"
app = FastAPI()
@app.get("/")
def main():
file_like = open(some_file_path, mode="rb")
return StreamingResponse(file_like, media_type="video/mp4")
FileResponse
异步传输文件作为响应。
与其他响应类型相比,接受不同的参数集进行实例化:
path
- 要流式传输的文件的文件路径。headers
- 任何自定义响应头,传入字典类型。media_type
- 给出媒体类型的字符串。如果未设置,则文件名或路径将用于推断媒体类型。filename
- 如果给出,它将包含在响应的Content-Disposition
中。
文件响应将包含适当的 Content-Length
,Last-Modified
和 ETag
的响应头。
from fastapi import FastAPI
from fastapi.responses import FileResponse
some_file_path = "large-video-file.mp4"
app = FastAPI()
@app.get("/")
async def main():
return FileResponse(some_file_path)
OpenAPI 中的其他响应
普通Json响应
FastAPI将接受该模型,生成其 JSON 架构,并在 OpenAPI 中的正确位置包含它。
例如,若要使用状态代码和 Pydantic 模型声明另一个响应,可以编写:404``Message
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pydantic import BaseModel
class Item(BaseModel):
id: str
value: str
class Message(BaseModel):
message: str
app = FastAPI()
@app.get("/items/{item_id}", response_model=Item, responses={404: {"model": Message}})
async def read_item(item_id: str):
if item_id == "foo":
return {"id": "foo", "value": "there goes my hero"}
else:
return JSONResponse(status_code=404, content={"message": "Item not found"})
记住,你必须直接返回。JSONResponse
会显示404错误的信息。或者访问/openapi.json也可以访问到404出错信息是什么。
媒体类型响应
可以添加 的其他媒体类型 ,声明路径操作可以返回 JSON 对象(具有媒体类型)或 PNG 图像:image/png
application/json
from typing import Optional
from fastapi import FastAPI
from fastapi.responses import FileResponse
from pydantic import BaseModel
class Item(BaseModel):
id: str
value: str
app = FastAPI()
@app.get(
"/items/{item_id}",
response_model=Item,
responses={
200: {
"content": {"image/png": {}},
"description": "Return the JSON item or an image.",
}
},
)
async def read_item(item_id: str, img: Optional[bool] = None):
if img:
return FileResponse("image.png", media_type="image/png")
else:
return {"id": "foo", "value": "there goes my hero"}
注意:媒体类型必须使用FileResponse
响应状态码
可以在路径操作中使用 status_code
参数来声明用于响应的 HTTP 状态码:
from fastapi import FastAPI, status
app = FastAPI()
@app.post("/items/", status_code=203)
async def create_item(name: str):
return {"name": name}
可以使用来自 fastapi.status
的便捷变量。
from fastapi import FastAPI, status
app = FastAPI()
@app.post("/items/", status_code=status.HTTP_200_OK)
async def create_item(name: str):
return {"name": name}
它们只是一种便捷方式,它们具有同样的数字代码,但是这样使用你就可以使用编辑器的自动补全功能来查找它们。
额外的状态码
如果你想要返回主要状态码之外的状态码,你可以通过直接返回一个 Response
来实现,比如 JSONResponse
,然后直接设置额外的状态码。
from fastapi import FastAPI, status
from fastapi.responses import JSONResponse
app = FastAPI()
items = {"foo": {"name": "Fighters", "size": 6}, "bar": {"name": "Tenders", "size": 3}}
@app.get("/items/{item_id}")
async def upsert_item(item_id: str):
if item_id in items:
return {"item_id": item_id}
else:
return JSONResponse(status_code=status.HTTP_201_CREATED, content={"item_id": item_id})
if __name__ == '__main__':
import uvicorn
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
此时访问地址会返回201的状态码。
响应时修改状态码
from fastapi import FastAPI, Response, status
app = FastAPI()
tasks = {"foo": "Listen to the Bar Fighters"}
@app.put("/get-or-create-task/{task_id}", status_code=200)
def get_or_create_task(task_id: str, response: Response):
if task_id not in tasks:
tasks[task_id] = "This didn't exist before"
response.status_code = status.HTTP_201_CREATED
return tasks[task_id]
表单数据
如果前端传的不是json格式数据,而是Form表单数据。通过Form()可以获取到Form表单中的数据
使用Form表单首先要安装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}
前端通过Form表单输入username和password会通过Form接收到表单数据。
传输文件
传输文件需要先下载安装python-multipart
pip install python-multipart
上传文件
from fastapi import FastAPI, File, UploadFile
from starlette.routing import Host
from uvicorn import config
app = FastAPI()
@app.post("/files/")
async def create_file(file: bytes = File(...)):
return {"file_size": len(file)}
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile = File(...)):
return {"filename": file.filename}
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", host="127.0.0.1", port=8000)
UploadFile
具有以下属性:
filename
: 具有上载的原始文件名(例如 )。str``myimage.jpg
content_type
: 具有内容类型(MIME 类型/媒体类型)的 A(例如 )。str``image/jpeg
file
:后台文件(
文件类对象)。这是实际的 Python 文件,您可以直接传递给需要"文件样"对象的其他函数或库。
UploadFile有以下方法。它们都调用下面的相应文件方法(使用内部 )。
async``SpooledTemporaryFile
write(data)
:将 ( 或 ) 写入文件。data``str``bytes
read(size)
: 读取 () 文件的字节/字符。size``int
seek(offset)
:转到文件中的字节位置 ()。offset int- 例如,将转到文件的开始。
await myfile.seek(0)
- 如果运行一次,然后需要再次读取内容,这尤其有用。
await myfile.read()
- 例如,将转到文件的开始。
close()
:关闭文件。
例如,在路径操作函数中,您可以获取以下内容:async
contents = await myfile.read()
如果位于正常路径操作函数内,可以直接访问 ,例如:def``UploadFile.file
contents = myfile.file.read()
多个文件上传
要使用它,请声明 或 :List
bytes
UploadFile
from typing import List
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.post("/files/")
async def create_files(files: List[bytes] = File(...)):
return {"file_sizes": [len(file) for file in files]}
@app.post("/uploadfiles/")
async def create_upload_files(files: List[UploadFile] = File(...)):
return {"filenames": [file.filename for file in files]}
错误处理
在许多情况下,您需要将错误通知使用 API 的客户端。fastapi通过HTTPException返回错误信息
使用HTTPException
from fastapi import FastAPI, HTTPException
app = FastAPI()
items = {"foo": "The Foo Wrestlers"}
@app.get("/items/{item_id}")
async def read_item(item_id: str):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item": items[item_id]}
添加自定义错误响应头
from fastapi import FastAPI, HTTPException
app = FastAPI()
items = {"foo": "The Foo Wrestlers"}
@app.get("/items-header/{item_id}")
async def read_item_header(item_id: str):
if item_id not in items:
raise HTTPException(
status_code=404,
detail="Item not found",
headers={"X-Error": "There goes my error"},
)
return {"item": items[item_id]}
自定义异常处理
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
class UnicornException(Exception):
def __init__(self, name: str):
self.name = name
app = FastAPI()
@app.exception_handler(UnicornException)
async def unicorn_exception_handler(request: Request, exc: UnicornException):
return JSONResponse(
status_code=418,
content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
)
@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
if name == "yolo":
raise UnicornException(name=name)
return {"unicorn_name": name}
Json兼容性编码
当需要将json数据转换为list/dict等数据类型时,我们需要使用json兼容性编码模块jsonable_encoder模块
from os import name
from typing import Optional
from fastapi import FastAPI
from datetime import datetime
from fastapi.encoders import jsonable_encoder
from pydantic.main import BaseModel
class Item(BaseModel):
title: str
timestamp: datetime
description: Optional[str] = None
app = FastAPI()
@app.put("/items/{id}")
def update_item(id: int, item: Item):
print(type(item)) # <class 'main.Item'>
json_compatible_item_data = jsonable_encoder(item)
print(type(json_compatible_item_data)) # <class 'dict'>
return json_compatible_item_data
依赖注入
函数依赖项
参数会使用所依赖的函数的参数,被依赖函数可供多个函数使用
from typing import Optional
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: Optional[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
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
类作为依赖项
from typing import Optional
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: Optional[str] = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
另外的写法
commons: CommonQueryParams = Depends()
子依赖
from typing import Optional
from fastapi import Cookie, Depends, FastAPI
app = FastAPI()
def query_extractor(q: Optional[str] = None):
return q
def query_or_cookie_extractor(
q: str = Depends(query_extractor), last_query: Optional[str] = Cookie(None)
):
if not q:
return last_query
return q
@app.get("/items/")
async def read_query(query_or_default: str = Depends(query_or_cookie_extractor)):
return {"q_or_cookie": query_or_default}
路径操作修饰器中的依赖项
from fastapi import Depends, FastAPI, Header, HTTPException
app = FastAPI()
async def verify_token(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
async def verify_key(x_key: str = Header(...)):
if x_key != "fake-super-secret-key":
raise HTTPException(status_code=400, detail="X-Key header invalid")
return x_key
@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
async def read_items():
return [{"item": "Foo"}, {"item": "Bar"}]
这些依赖项的执行/解决方式与正常依赖项相同。但是它们的值(如果返回任何值)不会传递给您的路径操作函数,即read_items
。
该依赖项可以理解为访问该路径的必要条件,前端不携带者两个参数不能访问这个路径。
全局依赖
向整个应用程序添加依赖,所有的路径都依赖
from fastapi import Depends, FastAPI, Header, HTTPException
async def verify_token(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
async def verify_key(x_key: str = Header(...)):
if x_key != "fake-super-secret-key":
raise HTTPException(status_code=400, detail="X-Key header invalid")
return x_key
app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)])
@app.get("/items/")
async def read_items():
return [{"item": "Portal Gun"}, {"item": "Plumbus"}]
@app.get("/users/")
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
Dependencies with yield
详细见官网https://fastapi.tiangolo.com/zh/tutorial/dependencies/dependencies-with-yield/#using-context-managers-in-dependencies-with-yield
必须要使用yield,python版本 3.7+,如果使用python3.6需要安装依赖库,如下
pip install async-exit-stack async-generator
这是一个依赖项函数的例子,我们可以创建一个数据库session,然后在请求结束后关闭这个session。
async def get_db():
db = DBSession()
try:
yield db
finally:
db.close()
yield db
后面的值db
会注入给路径操作或者其他依赖项。
yield db
后面的代码在response
提交之后才会执行。
中间件
“中间件”是一种功能,它可以在通过任何特定路径操作处理每个请求之前处理每个请求。以及返回之前的每个响应。
如果您具有依赖项yield
,则退出代码将在中间件之后运行。
如果有任何后台任务,它们将在所有中间件之后运行。
创建一个中间件
要创建中间件,请@app.middleware("http")
在函数顶部使用装饰器。
中间件函数的参数:
request
call_next函数
:会接收一个request作为参数,会返回一个当前路径的响应对象response,可以修改response,然后返回。
import time
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
return response
您也可以使用from starlette.requests import Request
。
FastAPI为开发人员提供了便利。但它直接来自Starlette。
FastAPI内置中间件
HTTPSRedirectMiddleware
强制所有传入请求都必须是https或wss,任何传入的http或ws请求将重定向到https或wss
from fastapi import FastAPI
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
app = FastAPI()
app.add_middleware(HTTPSRedirectMiddleware)
@app.get("/")
async def main():
return {"message": "Hello World"}
TrustedHostMiddleware
强制所有传入请求都有一个正确设置的头,以便防范HTTP主机头攻击
from fastapi import FastAPI
from fastapi.middleware.trustedhost import TrustedHostMiddleware
app = FastAPI()
app.add_middleware(
TrustedHostMiddleware, allowed_hosts=["example.com", "*.example.com"]
)
@app.get("/")
async def main():
return {"message": "Hello World"}
GZipMiddleware
处理请求头信息中包含的 GZip 响应,
from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware
app = FastAPI()
app.add_middleware(GZipMiddleware, minimum_size=1000)
@app.get("/")
async def main():
return "somebigcontent"
minimum_size
:不要对小于此最小字节大小的响应进行GZip处理。默认500
CORS跨域资源共享
您可以使用来在FastAPI应用程序中对其进行配置CORSMiddleware
。
- 导入
CORSMiddleware
。 - 创建允许的来源列表(作为字符串)。
- 将其作为“中间件”添加到FastAPI应用程序。
您还可以指定后端是否允许:
- 凭证(授权标头,Cookie等)。
- 特定的HTTP方法(
POST
,PUT
)或所有带通配符的方法"*"
。 - 特定的HTTP标头或所有通配符
"*"
。
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = [
"http://localhost.tiangolo.com",
"https://localhost.tiangolo.com",
"http://localhost",
"http://localhost:8080",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
async def main():
return {"message": "Hello World"}
该CORSMiddleware
实现使用的默认参数默认情况下是限制性的,因此您需要显式启用特定的来源,方法或标头,以便允许浏览器在跨域上下文中使用它们。
支持以下参数:
-
allow_origins
-应该允许进行跨域请求的来源列表。例如['https://example.org', 'https://www.example.org']
。您可以['*']
用来允许任何来源。 -
allow_origin_regex
-一个正则表达式字符串,与应允许进行跨域请求的原点匹配。例如'https://.*\.example\.org'
。 -
allow_methods
-跨域请求应允许的HTTP方法列表。默认为['GET']
。您可以使用['*']
允许所有标准方法。 -
allow_headers
-跨域请求应支持的HTTP请求标头列表。默认为[]
。您可以['*']
用来允许所有标头。Accept
,Accept-Language
,Content-Language
和Content-Type
头总是允许CORS请求。 -
allow_credentials
-表示跨域请求应支持cookie。默认为False
。另外,allow_origins
不能设置['*']
为允许凭据,必须指定来源。 -
expose_headers
-指出应使浏览器可以访问的任何响应头。默认为[]
。 -
max_age
-设置浏览器缓存CORS响应的最长时间(以秒为单位)。默认为600
您也可以使用from starlette.middleware.cors import CORSMiddleware
。
FastAPIfastapi.middleware
为您(开发人员)的方便提供了几种中间件。但是大多数可用的中间件直接来自Starlette。
项目开发写法
如果要构建应用程序或 Web API,则很少可以将所有内容放在单个文件中。FastAPI提供了一个方便的工具来构建应用程序,同时保持所有灵活性。
类似于Flask的蓝图。
文件结构目录
假设您有一个这样的文件结构:
.
├── app
│ ├── __init__.py
│ ├── main.py
│ ├── dependencies.py
│ └── routers
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ └── internal
│ ├── __init__.py
│ └── admin.py
类似于Flask的蓝图,将模块拆分解耦,便于理解。
示例程序
main.py
from fastapi import Depends, FastAPI
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
dependencies.py
from fastapi import Header, HTTPException
async def get_token_header(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
async def get_query_token(token: str):
if token != "jessica":
raise HTTPException(status_code=400, detail="No Jessica token provided")
routers.items.py
from fastapi import APIRouter, Depends, HTTPException
from ..dependencies import get_token_header
router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}
@router.get("/")
async def read_items():
return fake_items_db
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id not in fake_items_db:
raise HTTPException(status_code=404, detail="Item not found")
return {"name": fake_items_db[item_id]["name"], "item_id": item_id}
@router.put(
"/{item_id}",
tags=["custom"],
responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
if item_id != "plumbus":
raise HTTPException(
status_code=403, detail="You can only update the item: plumbus"
)
return {"item_id": item_id, "name": "The great Plumbus"}
routers.users.py
from fastapi import APIRouter
router = APIRouter()
@router.get("/users/", tags=["users"])
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
@router.get("/users/me", tags=["users"])
async def read_user_me():
return {"username": "fakecurrentuser"}
@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
return {"username": username}
internal.admin.py
from fastapi import APIRouter
router = APIRouter()
@router.post("/")
async def update_admin():
return {"message": "Admin getting schwifty"}
启动程序:
uvicorn app.main:app --reload
后台任务
后台任务对于请求后需要执行的操作很有用,但客户端不必在收到响应之前等待操作完成。
这包括, 例如:
- 执行操作后发送的电子邮件通知:
- 由于连接到电子邮件服务器和发送电子邮件往往"慢"(几秒钟),您可以立即返回响应并在后台发送电子邮件通知。
- 处理数据:
- 例如,假设您收到的文件必须经过一个缓慢的过程,您可以返回"已接受"(HTTP 202)的响应并在后台处理它。
创建任务函数
创建要作为后台任务运行的函数。
from fastapi import BackgroundTasks, FastAPI
app = FastAPI()
def write_notification(email: str, message=""):
with open("log.txt", mode="w") as email_file:
content = f"notification for {email}: {message}"
email_file.write(content)
@app.post("/send-notification/{email}")
async def send_notification(email: str, background_tasks: BackgroundTasks):
background_tasks.add_task(write_notification, email, message="some notification")
return {"message": "Notification sent in the background"}
.add_task()
接收的参数
-
write_notification
:要在后台运行的任务函数 -
email
:应按顺序传递给任务函数的参数 -
message="some notification"
:应按顺序传递给任务函数的参数
如果您需要执行繁重的背景计算,但不一定需要由同一进程运行(例如,您不需要共享内存、变量等),则使用其他更大的工具(如 Celery)
元数据和文档 URL
为项目创建元数据
- 标题: 在 OpenAPI 和自动 API 文档 UI 中用作 API 的标题/名称。
- 描述: OpenAPI 和自动 API 文档 UI 中 API 的描述。
- 版本:API 的版本,例如 :v2 2.5.0
- 例如,如果您有应用程序的早期版本,也使用 OpenAPI,这非常有用。
若要设置它们,请使用参数 和 :title
description
version
from fastapi import FastAPI
app = FastAPI(
title="My Super Project",
description="This is a very fancy project, with auto docs for the API and everything",
version="2.5.0",
)
@app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
为tag创建元数据
from fastapi import FastAPI
tags_metadata = [
{
"name": "users",
"description": "Operations with users. The **login** logic is also here.",
},
{
"name": "items",
"description": "Manage items. So _fancy_ they have their own docs.",
"externalDocs": {
"description": "Items external docs",
"url": "https://fastapi.tiangolo.com/",
},
},
]
app = FastAPI(openapi_tags=tags_metadata)
@app.get("/users/", tags=["users"])
async def get_users():
return [{"name": "Harry"}, {"name": "Ron"}]
@app.get("/items/", tags=["items"])
async def get_items():
return [{"name": "wand"}, {"name": "flying broom"}]
OpenAPI URL
默认情况下,OpenAPI 架构在 中提供。/openapi.json
但是,您可以使用 参数对其进行配置。openapi_url
例如,要将它设置为在 :/api/v1/openapi.json
from fastapi import FastAPI
app = FastAPI(openapi_url="/api/v1/openapi.json")
@app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
如果要完全禁用 OpenAPI 架构,可以设置 ,这也将禁用使用它的文档用户界面。openapi_url=None
文档 Urls
通过docs_url
和redoc_url
设置文档url,也可以设置为None禁止访问
from fastapi import FastAPI
app = FastAPI(docs_url="/documentation", redoc_url=None)
@app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
静态文件
使用静态文件需要下载aiofiles
库
pip install aiofiles
使用模板文件需要安装jinja2
pip install jinja2
应用示例:
from fastapi import FastAPI, Form
from starlette.requests import Request
from starlette.staticfiles import StaticFiles
from starlette.templating import Jinja2Templates
app = FastAPI()
templates = Jinja2Templates(directory="templates")
app.mount('/static', StaticFiles(directory='static'), name='static')
@app.post("/user/")
async def files(
request: Request,
username: str = Form(...),
password: str = Form(...),
):
print('username', username)
print('password', password)
return templates.TemplateResponse(
'index.html',
{
'request': request,
'username': username,
}
)
@app.get("/")
async def main(request: Request):
return templates.TemplateResponse('signin.html', {'request': request})
测试
普通测试
测试依赖于requests,所以需要安装
pip install requests
使用测试需要安装pytest
pip install pytest
假设您有一个包含FastAPI 应用的文件:main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_main():
return {"msg": "Hello World"}
测试文件
from fastapi.testclient import TestClient
from .main import app
client = TestClient(app)
def test_read_main():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"msg": "Hello World"}
执行测试文件
pytest
异步测试
安装异步测试库
pip install pytest-asyncio
需要httpx库
pip install httpx
对于一个简单的示例,让我们考虑以下模块:main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Tomato"}
包含 的模块现在可能看起来像这样:test_main.py``main.py
import pytest
from httpx import AsyncClient
from .main import app
@pytest.mark.asyncio
async def test_root():
async with AsyncClient(app=app, base_url="http://test") as ac:
response = await ac.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Tomato"}
子应用程序
首先,创建主、顶级、FastAPI应用程序及其路径操作:
from fastapi import FastAPI
app = FastAPI()
# 顶级应用
@app.get("/app")
def read_main():
return {"message": "Hello World from main app"}
subapi = FastAPI()
# 子应用
@subapi.get("/sub")
def read_sub():
return {"message": "Hello World from sub API"}
# 装载子应用
app.mount("/subapi", subapi)
主应用访问http://localhost:8000/docs
子应用访问http://localhost:8000/subapi/docs
websocket
示例
from fastapi import FastAPI, WebSocket
from fastapi.responses import HTMLResponse
app = FastAPI()
html = """
<!DOCTYPE html>
<html>
<head>
<title>Chat</title>
</head>
<body>
<h1>WebSocket Chat</h1>
<form action="" onsubmit="sendMessage(event)">
<input type="text" id="messageText" autocomplete="off"/>
<button>Send</button>
</form>
<ul id='messages'>
</ul>
<script>
var ws = new WebSocket("ws://localhost:8000/ws");
ws.onmessage = function(event) {
var messages = document.getElementById('messages')
var message = document.createElement('li')
var content = document.createTextNode(event.data)
message.appendChild(content)
messages.appendChild(message)
};
function sendMessage(event) {
var input = document.getElementById("messageText")
ws.send(input.value)
input.value = ''
event.preventDefault()
}
</script>
</body>
</html>
"""
@app.get("/")
async def get():
return HTMLResponse(html)
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Message text was: {data}")
处理断开连接和多个客户端
当 WebSocket 连接关闭时,将引发一个异常,然后您可以捕获并处理该异常,就像在此示例中所示。await websocket.receive_text()``WebSocketDisconnect
from typing import List
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
app = FastAPI()
html = """
<!DOCTYPE html>
<html>
<head>
<title>Chat</title>
</head>
<body>
<h1>WebSocket Chat</h1>
<h2>Your ID: <span id="ws-id"></span></h2>
<form action="" onsubmit="sendMessage(event)">
<input type="text" id="messageText" autocomplete="off"/>
<button>Send</button>
</form>
<ul id='messages'>
</ul>
<script>
var client_id = Date.now()
document.querySelector("#ws-id").textContent = client_id;
var ws = new WebSocket(`ws://localhost:8000/ws/${client_id}`);
ws.onmessage = function(event) {
var messages = document.getElementById('messages')
var message = document.createElement('li')
var content = document.createTextNode(event.data)
message.appendChild(content)
messages.appendChild(message)
};
function sendMessage(event) {
var input = document.getElementById("messageText")
ws.send(input.value)
input.value = ''
event.preventDefault()
}
</script>
</body>
</html>
"""
class ConnectionManager:
def __init__(self):
self.active_connections: List[WebSocket] = []
async def connect(self, websocket: WebSocket):
await websocket.accept()
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
async def send_personal_message(self, message: str, websocket: WebSocket):
await websocket.send_text(message)
async def broadcast(self, message: str):
for connection in self.active_connections:
await connection.send_text(message)
manager = ConnectionManager()
@app.get("/")
async def get():
return HTMLResponse(html)
@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
await manager.connect(websocket)
try:
while True:
data = await websocket.receive_text()
await manager.send_personal_message(f"You wrote: {data}", websocket)
await manager.broadcast(f"Client #{client_id} says: {data}")
except WebSocketDisconnect:
manager.disconnect(websocket)
await manager.broadcast(f"Client #{client_id} left the chat")
事件:启动 - 关闭
可以定义在应用程序启动之前或应用程序关闭时需要执行的事件处理程序(函数)
startup
事件
若要添加应在应用程序启动之前运行的函数,请使用 事件声明它:"startup"
。可以添加多个事件处理程序函数。
from fastapi import FastAPI
app = FastAPI()
items = {}
@app.on_event("startup")
async def startup_event():
items["foo"] = {"name": "Fighters"}
items["bar"] = {"name": "Tenders"}
@app.get("/items/{item_id}")
async def read_items(item_id: str):
return items[item_id]
shutdown
事件
若要添加应用程序关闭时应运行的函数,请使用 事件声明它:"shutdown"
from fastapi import FastAPI
app = FastAPI()
@app.on_event("shutdown")
def shutdown_event():
with open("log.txt", mode="a") as log:
log.write("Application shutdown")
@app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
配置文件和环境变量
环境变量
读取系统环境变量
import os
name = os.getenv("MY_NAME", "World")
print(f"Hello {name} from Python")
配置文件
第一种:此方式创建全局配置对象
创建config.py
from pydantic import BaseSettings
class Settings(BaseSettings):
app_name: str = "Awesome API"
admin_email: str
items_per_user: int = 50
settings = Settings()
然后在文件中使用它:main.py
from fastapi import FastAPI
from . import config
app = FastAPI()
@app.get("/info")
async def info():
return {
"app_name": config.settings.app_name,
"admin_email": config.settings.admin_email,
"items_per_user": config.settings.items_per_user,
}
第二种:不创建默认实例
从上一个示例来看,您的文件可能看起来像:config.py
from pydantic import BaseSettings
class Settings(BaseSettings):
app_name: str = "Awesome API"
admin_email: str
items_per_user: int = 50
请注意,现在我们不创建默认实例。settings = Settings()
现在我们创建一个返回新的依赖项。config.Settings()
然后,我们可以要求它从路径操作函数作为依赖项,并使用它任何地方,我们需要它。
from functools import lru_cache
from fastapi import Depends, FastAPI
from . import config
app = FastAPI()
@lru_cache()
def get_settings():
return config.Settings()
@app.get("/info")
async def info(settings: config.Settings = Depends(get_settings)):
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}
@lru_cache()
:当我们使用@lru_cache()修饰器时,对象将只创建一次,不然我们每次调用get_settings都会创建一次对象。
读取.env文件
您可以有一个文件,其内容为:.env
ADMIN_EMAIL="deadpool@example.com"
APP_NAME="ChimichangApp"
更新到config.py文件
from pydantic import BaseSettings
class Settings(BaseSettings):
app_name: str = "Awesome API"
admin_email: str
items_per_user: int = 50
class Config:
env_file = ".env"