java

这不得学下FastApi吗

2023-04-16  本文已影响0人  DayBreakL

官方文档: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 的类

  1. 使用与声明路径和查询参数的相同方式声明请求体,即可将其添加到「路径操作」中
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

  1. 使用 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)放到最前面。

  1. 按需对参数排序的技巧

如果你想不使用 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

请求体 - 嵌套模型

上一篇下一篇

猜你喜欢

热点阅读