FastAPI 解读 by Gascognya

FastAPI 官方文档解读 (一)

2020-09-04  本文已影响0人  Gascognya

安装方式

标准安装

pip install fastapi & pip install uvicorn

完整安装

pip install fastapi[all]

启动方式

代码式启动(开发时)
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
命令式启动(任何时候)

uvicorn 模块名:app --port=8000 --host=127.0.0.1

API文档

Swagger UI

http://127.0.0.1:8000/docs(常见情况)

ReDoc

http://127.0.0.1:8000/redoc(常见情况)

OpenAPI

API的标准,由Swagger UI提出。API文档的基础便是OpenAPI

http://127.0.0.1:8000/openapi.json(常见情况)

参数

路径参数

例如http://127.0.0.1:8000/items/foo这种形式,在@app.get("/items/{item_id}")环境下,foo{item_id}的值,在下方可以def read_item(item_id: int):这样接收。

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    return {"item_id": item_id}

枚举的用处

新建一个枚举类应用于参数类型

class ModelName(Enum):
    alexnet = "alexnet"
    resnet = "resnet"
    lenet = "lenet"

@app.get("/model/{model_name}")
def get_model(model_name: ModelName):

API文档会很聪明的知道,参数必须限制在枚举类中的一个。但API还不清楚这个参数的类型。

class ModelName(str, Enum):

只需要让枚举类继承例如str即可,请您放心,FastAPI足够聪明。

如果您的路径参数,其值是一个路径

例如/files/home/johndoe/myfile.txt,您的期望是/files/{file_path}。也就是将home/johndoe/myfile.txt解析为file_path
您需要改写为/files/{file_path:path},这样便可捕获到。

查询参数

常见的www.example.com/?name=abc&age=5形式

def test(name: str, age: int):

如果需要接收bool值,不必遵循什么规则。yesonTruetrue1......皆可被转化。请您放心,FastAPI足够聪明。

默认值

就像正常Python那样标注

def test(name: str = `bob`, age: int):

具有默认值的参数,则是非必需的。
当然有时候=后面未必代表的是默认值(这个以后谈起)

类型声明

正常像typing那样使用即可

Model

Model可用于body中的JSON序列,可以是request的body,也可以是response的body

class Item(BaseModel):
    name: str
    description: Optional[str] = None

@app.post("/items/")
def create_item(item: Item):
    return {'msg': 'ok'}

@app.get("/items/", response_model=Item)
async def find_item(item_id: int):
    return {'name': 'bob', 'description': 'good boy'}

requestresponse中分别使用Model

参数验证

@app.get("/items/")
async def read_items(q: Optional[str] = Query(None, max_length=50)):

查询参数可使用Query()进行验证,可设置许多属性,包括大于,小于,长度,默认值等。
q: str = Query(None)&q: str = None这两种情况是等价的。
当然如果你把第一个参数(default=)设置为...是,这就变成了必填参数,即便你已经q = Query()这种形式,但Query这样就不能其默认值作用

def read_items(q: str = Query(..., min_length=3)):

查询参数列表

有趣的是,当你更改一下类型声明例如
def read_items(q: Optional[List[str]] = Query(None)):List[str] 会使得q可以存在多个
http://localhost:8000/items/?q=foo&q=bar例如这样的形式便是被允许的。此时这个q会由str变为list
def read_items(q: List[str] = Query(["foo", "bar"])):像这样你也可以为其添加默认值
def read_items(q: list = Query([]))再例如这样的情况,注意,这个并未限制元素的类型。

添加更多细节

q: Optional[str] = Query(
    None,
    title="Query string",
    description="Query string for the items to search in the database that have a good match",
    min_length=3,
)

例如deprecated=True可以将参数弃用,再例如alias可以给参数起个别名。

路径参数同样也可以

使用Path()便可以达到同样效果,注意,路径参数永远都是必须的。所以您最少也要应该写上...作为default

参数顺序

q: strp: str = Path(...),这些写法都是可以的。但是如果您是强迫症,想定义他们的顺序。可能会碰到一些问题。python不允许带有默认值的参数,在不带有默认值的参数的前面。这会导致您不能像下面这种书写方式

def read_items(item_id: int = Path(...), q: str):

这是python语法上不允许的
为了解决这个问题,您可以在最前面加一个*作为参数

def read_items(
    *, item_id: int = Path(...), q: str
):

python不会对这个*做什么,但是会将其后面所有的参数都解析成kwargs形式。即便其没有写默认值。

def test(a, *, b=5, c, d=5):
    print(a, b, c, d)

test(1, b=2, c=3, d=4)

请注意,这里*前面的参数照旧,但*后面的参数不允许用位置参数,而必须指定参数名。

Body

这里主要是讲JSON格式的Body,当我们需要一个model时,我们期待的body格式如下

class Item(BaseModel):
    name: str
    description: Optional[str] = None

@app.post("/items/")
def create_item(item: Item):
    return {'msg': 'ok'}
{
  "name": "apple",
  "description": "ohhhhhhh!"
}

但是如果我们需要两个model,例如:

class Item(BaseModel):
    name: str
    description: Optional[str] = None

class User(BaseModel):
    name: str
    age: int

@app.post("/items/")
def create_item(item: Item, user: User):
    return {'msg': 'ok'}

这种情况下,如果我们像如下这样是不可以的。因为这不符合JSON的格式。

{
  "name": "apple",
  "description": "ohhhhhhh!"
}
{
  "name": "bob",
  "age": 5
}

正确写法

{
    "item": {
      "name": "apple",
      "description": "ohhhhhhh!"
    },
    "user": {
      "name": "bob",
      "age": 5
    }
}

一定要注意 单model多model 的区别

body内的单值

def create_item(item: Item, user: User, id: int):

这种情况下,这个id会被解析为查询参数。但如果您想让它也成为body体中的一部分。

{
    "item": {
      "name": "apple",
      "description": "ohhhhhhh!"
    },
    "user": {
      "name": "bob",
      "age": 5
    },
    "id": 1
}

例如这是您所期待的,您希望id: int所解析的是body中的这个。那么您可以使用Body()

def create_item(item: Item, user: User, id: int, id: int=Body(...)):

这时,上面形式的JSON就会如您所愿解析了。

单个model也内嵌进JSON

def create_item(item: Item):

我们知道,该参数的期望body是

{
  "name": "apple",
  "description": "ohhhhhhh!"
}

但如果我们希望的格式是如下的,该怎么做?

{
    "item": {
      "name": "apple",
      "description": "ohhhhhhh!"
    }
}

我们知道,只有两个以上model时,才会自动变成key,value形式。
实际上我们只需要稍作加工

def create_item(item: Item=Body(..., embed=True)):

便可以完成我们所期望的

上一篇 下一篇

猜你喜欢

热点阅读