这不得学下FastApi吗
官方文档:https://fastapi.tiangolo.com/zh/tutorial/
Fastapi框架两个核心包:fastapi、uvicorn
FastAPI 是直接从 Starlette 继承的类
安装:
pip install fastapi
pip install uvicorn
导入:
from fastapi import FastAPI
import uvicorn
最简单的运行:
main.py
from fastapi import FastAPI
app =FastAPI()
@app.get("/")
def home():
return {"Hello": "World"}
运行命令:uvicorn main:app --reload
访问:http://127.0.0.1:8000
Swagger UI
可以通过 http://localhost:8000/docs 免费获得 Swagger UI
ReDoc
可以通过 http://127.0.0.1:8000/redoc 免费获得ReDoc
路径参数
@app.get("/items/{item_id}")
async def read_item(item_id):
return {"item_id": item_id}
路径参数 item_id 的值将作为参数 item_id 传递给你的函数。
1.有类型的路径参数
可以使用标准的 Python 类型标注为函数中的路径参数声明类型
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
当你访问:http://127.0.0.1:8000/items/2 得到{"item_id":2}
函数接收(并返回)的值为 2,是一个 Python int 值,而不是字符串 "2"。
所以,FastAPI 通过上面的类型声明提供了对请求的自动"解析"。
2.数据校验
当你访问:http://127.0.0.1:8000/items/foo
{
"detail": [
{
"loc": [
"path",
"item_id"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}
路径参数 item_id 传入的值为 "foo",它不是一个 int。提供数据校验的是Pydantic。
3.预设值
如果你有一个接收路径参数的路径操作,但你希望预先设定可能的有效参数值,则可以使用标准的 Python Enum 类型。
from enum import Enum
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
@app.get("/models/{model_name}")
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"}
查询参数
声明不属于路径参数的其他函数参数时,它们将被自动解释为"查询字符串"参数
查询字符串是键值对的集合,这些键值对位于 URL 的 ?
之后,并以&
符号分隔。
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
skip=0 和 limit=10 的默认值,所以访问http://127.0.0.1:8000/items/结果也是一样的。
1.可选参数
通过同样的方式,你可以将它们的默认值设置为 None 来声明可选查询参数:
from typing import Union
@app.get("/items/{item_id}")
async def read_item(item_id: str, q: Union[str, None] = None):
if q:
return {"item_id": item_id, "q": q}
return {"item_id": item_id}
Union 为python的typing库,typing是Python标准库,用来做类型提示。Union表示联合类型,Union[str, None]表示可以是str或者None
2.查询参数类型转换
还可以声明 bool 类型,它们将被自动转换
@app.get("/items/{item_id}")
async def read_item(item_id: str, q: Union[str, None] = None, short: bool = False):
item = {"item_id": item_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
访问:
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 的情况也是一样的。
3.必需查询参数
当你为非路径参数声明了默认值时(目前而言,我们所知道的仅有查询参数),则该参数不是必需的。
如果你不想添加一个特定的值,而只是想使该参数成为可选的,则将默认值设置为 None。
但当你想让一个查询参数成为必需的,不声明任何默认值就可以
@app.get("/items/{item_id}")
async def read_user_item(item_id: str, needy: str):
item = {"item_id": item_id, "needy": needy}
return item
http://127.0.0.1:8000/items/foo-item 会报错,
需要有必填参数,http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
请求体
1.我们需要引入一个新包pydantic,这个包是用来定义请求体。
2.将你的数据模型声明为继承自 BaseModel 的类
- 使用与声明路径和查询参数的相同方式声明请求体,即可将其添加到「路径操作」中
from pydantic import BaseModel # 1. 导入新包pydantic
# 2. 创建数据模型
class Item(BaseModel):
name: str # 无默认值必填参数
description: Union[str, None] = None # 默认值为None 非必填
price: float # 无默认值必填参数
tax: Union[float, None] = None # 默认值为None 非必填
# 3.使用与声明路径和查询参数的相同方式声明请求体,即可将其添加到「路径操作」中
@app.post("/postitem")
async def postitem(item: Item):
return item
请求参数:
{
"name": "tony",
"description": "helloworld",
"price": 12,
"tax": 1
}
4.使用模型
在函数内部,你可以直接访问模型对象的所有属性
@app.post("/postitem")
async def postitem(item: Item):
item_dict = item.dict()
if item.tax:
price_with_tax = item.price+item.tax
item_dict.update({"price_with_tax":price_with_tax})
return item_dict
5.请求体 + 路径参数 + 查询参数
可以同时声明请求体、路径参数和查询参数
# 创建模型
class Student(BaseModel):
name: str
age: Union[str, None] = None
grade: Union[int, None] = None
score: float
desc: Union[str, None] = None
@app.post("/stu/{stu_id}")
async def stu_detail(stu_id: int, stu: Student,q: Union[str, None] = None):
result = {"stu_id": stu_id, **stu.dict()}
if q:
result.update({"q":q})
return result
查询参数和字符串校验
FastAPI 允许你为参数声明额外的信息和校验。
例如:我们打算添加约束条件:即使 q 是可选的,但只要提供了该参数,则该参数值不能超过50个字符的长度。
1.从 fastapi 导入导入Query
- 使用 Query 作为默认值
# 1. 从 fastapi 导入导入Query
from fastapi import FastAPI, Query
class Student(BaseModel):
name: str
age: Union[str, None] = None
grade: Union[int, None] = None
score: float
desc: Union[str, None] = None
@app.post("/stu/{stu_id}")
async def stu_detail(stu_id: int, stu: Student,
q: Union[str, None] = Query(default=None,max_length=50,min_length=3)):
# 使用 Query 作为默认值
result = {"stu_id": stu_id, **stu.dict()}
if q:
result.update({"q": q})
return result
q: Union[str, None] = Query(default=None)
等同于
q: str = None
添加更多校验
1.正则表达式
q: Union[str, None] = Query(
default=None, min_length=3, max_length=50, regex="^fixedquery$"
)
2.使用省略号(...)声明必需参数
q: str = Query(default=..., min_length=3)
这将使 FastAPI 知道此查询参数是必需的。
3.使用None声明必需参数
q: Union[str, None] = Query(default=..., min_length=3)
声明一个参数可以接收None值,但它仍然是必需的
使用Pydantic中的Required代替省略号(...)
从 Pydantic 导入并使用 Required
from pydantic import Required
q: str = Query(default=Required, min_length=3)
查询参数列表 / 多个值
@app.get("/stu/")
async def read_stu(q: Union[List[str], None] = Query(default=None)):
q_itmes = {"q": q}
return q_itmes
访问:http://127.0.0.1:8000/stu/?q=foo&q=po
响应:
{"q":["foo","po"]}
具有默认值的查询参数列表 / 多个值
@app.get("/items/")
async def read_items(q: List[str] = Query(default=["foo", "bar"])):
query_items = {"q": q}
return query_items
访问:http://localhost:8000/items/
q 的默认值将为:["foo", "bar"],你的响应会是:
{
"q": [
"foo",
"bar"
]
}
声明更多元数据
1.添加 title
q: Union[str, None] = Query(default=None, title="Query string", min_length=3)
2.description
q: Union[str, None] = Query(
default=None,
title="Query string",
description="Query string for the items to search in the database that have a good match",
min_length=3,
)
别名参数
假设你想要查询参数为 item-query,但是 item-query 不是一个有效的 Python 变量名称
可以用 alias 参数声明一个别名,该别名将用于在 URL 中查找查询参数值
q: Union[str, None] = Query(default=None, alias="item-query")
弃用参数
现在假设你不再喜欢此参数。
你不得不将其保留一段时间,因为有些客户端正在使用它,但你希望文档清楚地将其展示为已弃用。
那么将参数 deprecated=True 传入 Query
q: Union[str, None] = Query(
default=None,
alias="item-query",
title="Query string",
description="Query string for the items to search in the database that have a good match",
min_length=3,
max_length=50,
regex="^fixedquery$",
deprecated=True,
)
会在docs文档中提示为:deprecated
路径参数和数值校验
与使用 Query 为查询参数声明更多的校验和元数据的方式相同,你也可以使用 Path 为路径参数声明相同类型的校验和元数据。
1.首先,从 fastapi 导入 Path
from fastapi import Path
2.声明元数据
可以声明与 Query 相同的所有参数,如default、title...
item_id: int = Path(title="The ID of the item to get")
3.按需对参数排序
如果你将带有「默认值」的参数放在没有「默认值」的参数之前,Python 将会报错
item_id: int = Path(..., title="The ID of the item to get"), q:str,
你可以对其重新排序,并将不带默认值的值(查询参数 q)放到最前面。
- 按需对参数排序的技巧
如果你想不使用 Query 声明没有默认值的查询参数 q,同时使用 Path 声明路径参数 item_id,并使它们的顺序与上面不同,Python 对此有一些特殊的语法。
传递*
作为函数的第一个参数。
Python 不会对该*
做任何事情,但是它将知道之后的所有参数都应作为关键字参数(键值对),也被称为 kwargs,来调用。即使它们没有默认值。
*, item_id: int = Path(title="The ID of the item to get"
5.数值校验:大于等于
使用 Query 和 Path(以及你将在后面看到的其他类)可以声明字符串约束,但也可以声明数值约束。
像下面这样,添加 ge=1 后,item_id 将必须是一个大于(greater than)或等于(equal)1 的整数。
item_id: int = Path(title="The ID of the item to get", ge=1)
6.数值校验:大于和小于等于
同样的规则适用于:
gt:大于(greater than)
le:小于等于(less than or equal)
7.数值校验:浮点数、大于和小于
size: float = Query(gt=0, lt=10.5)
请求体 - 多个参数
可以声明多个请求体参数,例如 item 和 user
class Student(BaseModel):
name: str
age: Union[str, None] = None
grade: Union[int, None] = None
desc: Union[str, None] = None
class Score(BaseModel):
math: float
Chinese: float
english: float
@app.post("/stu/{stu_id}")
async def stu_detail(*,stu_id:int = Path(...,ge=1),q:int=Query(default=None),stu: Student, score:Score):
result = {"stu_id": stu_id, **stu.dict(),**score.dict()}
if q:
result.update({"q": q})
return result
1.请求体中的单一值
为了扩展先前的模型,你可能决定除了 item 和 user 之外,还想在同一请求体中具有另一个键 importance
如果你就按原样声明它,因为它是一个单一值,FastAPI 将假定它是一个查询参数
可以使用 Body 指示 FastAPI 将其作为请求体的另一个键进行处理
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
class User(BaseModel):
username: str
full_name: Union[str, None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User, importance: int = Body()):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
return results
FastAPI 将期望像这样的请求体:
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
},
"user": {
"username": "dave",
"full_name": "Dave Grohl"
},
"importance": 5
}
2.多个请求体参数和查询参数
3.嵌入单个请求体参数
如果你希望它期望一个拥有 item 键并在值中包含模型内容的 JSON,就像在声明额外的请求体参数时所做的那样,则可以使用一个特殊的 Body 参数 embed
item: Item = Body(embed=True)
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = 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
}
请求体 - 字段
与使用 Query、Path 和 Body 在路径操作函数中声明额外的校验和元数据的方式相同,你可以使用 Pydantic 的 Field 在 Pydantic 模型内部声明校验和元数据
导入Field
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str
description: Union[str, None] = Field(
default=None, title="The description of the item", max_length=300
)
price: float = Field(gt=0, description="The price must be greater than zero")
tax: Union[float, None] = 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