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) 替换默认值 NoneQuery 的第一个参数同样也是用于定义默认值。

所以:

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 并创建一个继承自 strEnum 的子类。

通过从 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_idtitle 元数据值,你可以输入:

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

使用 QueryPath(以及你将在后面看到的其他类)可以声明字符串约束,但也可以声明数值约束。

像下面这样,添加 ge=1 后,item_id 将必须是一个大于(greater than)或等于(equal)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(较常见)、PUTDELETEPATCH

首先,你需要从 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

请求体中的单一值

与使用 QueryPath 为查询参数和路径参数定义额外数据的方式相同,FastAPI 提供了一个同等的 Body

例如,为了扩展先前的模型,你可能决定除了 itemuser 之外,还想在同一请求体中具有另一个键 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 的工作方式和 QueryPathBody 相同,包括它们的参数等等也完全相同。

请求体 - 嵌套模型

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

您可以使用 Configschema_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来声明,否则参数将被解释为查询参数。

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

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_includeresponse_model_exclude 来省略某些属性,在应用程序的 OpenAPI 定义(和文档)中生成的 JSON Schema 仍将是完整的模型。

如果你忘记使用 set 而是使用 listtuple,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 路径操作 时,你可以正常返回以下任意一种数据:dictlist,Pydantic 模型,数据库模型等等。

FastAPI 默认会使用 jsonable_encoder 将这些类型的返回值转换成 JSON 格式,jsonable_encoderJSON 兼容编码器 中有阐述。

然后,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 中也传入了 HTMLResponseFastAPI 会知道如何在 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-LengthLast-ModifiedETag 的响应头。

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方法(POSTPUT)或所有带通配符的方法"*"
  • 特定的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请求标头列表。默认为[]。您可以['*']用来允许所有标头。AcceptAccept-LanguageContent-LanguageContent-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_urlredoc_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"
posted @ 2021-01-25 12:46  Lowell  阅读(1434)  评论(0编辑  收藏  举报