美文网首页java
这不得学下FastApi吗

这不得学下FastApi吗

作者: DayBreakL | 来源:发表于2023-04-16 00:02 被阅读0次

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

    请求体 - 嵌套模型

    相关文章

      网友评论

        本文标题:这不得学下FastApi吗

        本文链接:https://www.haomeiwen.com/subject/jamzddtx.html