跳到内容

路径参数

你可以使用 Python 格式化字符串时相同的语法来声明路径「参数」或「变量」

from fastapi import FastAPI

app = FastAPI()


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

路径参数 item_id 的值将作为参数 item_id 传递给你的函数。

因此,如果你运行此示例并访问 http://127.0.0.1:8000/items/foo,你将看到如下响应:

{"item_id":"foo"}

带类型的路径参数

你可以使用标准的 Python 类型注解,在函数中声明路径参数的类型

from fastapi import FastAPI

app = FastAPI()


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

在这种情况下,item_id 被声明为 int 类型。

检查

这将在你的函数内部为你提供编辑器支持,包括错误检查、补全等。

数据转换

如果你运行此示例并在浏览器中打开 http://127.0.0.1:8000/items/3,你将看到如下响应:

{"item_id":3}

检查

请注意,你的函数接收(并返回)的值是 3,一个 Python 的 int 类型,而不是字符串 "3"

因此,通过该类型声明,FastAPI 为你提供了自动的请求「解析」

数据校验

但是,如果你在浏览器中访问 http://127.0.0.1:8000/items/foo,你会看到一个清晰的 HTTP 错误:

{
  "detail": [
    {
      "type": "int_parsing",
      "loc": [
        "path",
        "item_id"
      ],
      "msg": "Input should be a valid integer, unable to parse string as an integer",
      "input": "foo"
    }
  ]
}

因为路径参数 item_id 的值为 "foo",而不是一个 int

如果你提供的是 float 而不是 int,也会出现同样的错误,例如:http://127.0.0.1:8000/items/4.2

检查

所以,通过相同的 Python 类型声明,FastAPI 为你提供了数据校验。

请注意,错误信息也清楚地指出了校验失败的确切位置。

这在开发和调试与你的 API 交互的代码时非常有帮助。

文档

当你用浏览器访问 http://127.0.0.1:8000/docs 时,你将看到一个自动、交互式的 API 文档,如下所示:

检查

同样,仅凭这同一个 Python 类型声明,FastAPI 就为你提供了自动、交互式的文档(集成了 Swagger UI)。

请注意,路径参数被声明为整数。

基于标准的好处,替代文档

而且由于生成的模式遵循 OpenAPI 标准,因此有许多兼容的工具。

因此,FastAPI 本身提供了一个替代的 API 文档(使用 ReDoc),你可以通过 http://127.0.0.1:8000/redoc 访问它。

同样地,还有许多兼容的工具。包括适用于多种语言的代码生成工具。

Pydantic

所有的数据校验都是由 Pydantic 在底层完成的,所以你可以获得它的所有好处。而且你知道你得到了很好的保障。

你可以对 strfloatbool 以及许多其他复杂数据类型使用相同的类型声明。

其中一些将在本教程的后续章节中探讨。

顺序很重要

在创建路径操作时,你可能会遇到有固定路径的情况。

比如 /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

同样,你不能重新定义一个路径操作

from fastapi import FastAPI

app = FastAPI()


@app.get("/users")
async def read_users():
    return ["Rick", "Morty"]


@app.get("/users")
async def read_users2():
    return ["Bean", "Elfo"]

第一个将总是被使用,因为它的路径先匹配。

预定义值

如果你有一个接收路径参数路径操作,但你希望可能的有效路径参数值是预定义的,你可以使用标准的 Python Enum

创建一个 Enum

导入 Enum 并创建一个同时继承自 strEnum 的子类。

通过继承 str,API 文档将能够知道这些值必须是 string 类型,并能够正确地渲染。

然后创建具有固定值的类属性,这些值将是可用的有效值

from enum import Enum

from fastapi import FastAPI


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


app = FastAPI()


@app.get("/models/{model_name}")
async 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"}

信息

自 Python 3.4 版本起,枚举(enums)在 Python 中可用

提示

如果你想知道,「AlexNet」、「ResNet」和「LeNet」只是一些机器学习模型的名称。

声明一个路径参数

然后创建一个路径参数,使用你创建的枚举类(ModelName)作为类型注解

from enum import Enum

from fastapi import FastAPI


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


app = FastAPI()


@app.get("/models/{model_name}")
async 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"}

检查文档

因为路径参数的可用值是预定义的,所以交互式文档可以很好地显示它们

使用 Python 枚举

路径参数的值将是一个枚举成员

比较枚举成员

你可以将它与你创建的枚举 ModelName 中的枚举成员进行比较

from enum import Enum

from fastapi import FastAPI


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


app = FastAPI()


@app.get("/models/{model_name}")
async 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"}

获取枚举值

你可以使用 model_name.value,或者通常是 your_enum_member.value 来获取实际值(在这种情况下是一个 str)。

from enum import Enum

from fastapi import FastAPI


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


app = FastAPI()


@app.get("/models/{model_name}")
async 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"}

提示

你也可以通过 ModelName.lenet.value 来访问值 "lenet"

返回枚举成员

你可以从你的路径操作中返回枚举成员,即使它们嵌套在 JSON 主体中(例如一个 dict)。

它们在返回给客户端之前,会被转换为它们对应的值(在这种情况下是字符串)

from enum import Enum

from fastapi import FastAPI


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


app = FastAPI()


@app.get("/models/{model_name}")
async 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"}

在你的客户端,你将得到一个像这样的 JSON 响应

{
  "model_name": "alexnet",
  "message": "Deep Learning FTW!"
}

包含路径的路径参数

假设你有一个路径为 /files/{file_path}路径操作

但你需要 file_path 本身包含一个路径,比如 home/johndoe/myfile.txt

所以,该文件的 URL 就会是这样:/files/home/johndoe/myfile.txt

OpenAPI 支持

OpenAPI 不支持声明一个路径参数来包含内部路径的方式,因为这可能导致难以测试和定义的场景。

尽管如此,你仍然可以在 FastAPI 中做到这一点,使用 Starlette 的一个内部工具。

并且文档仍然可以工作,尽管不会添加任何说明该参数应包含路径的文档。

路径转换器

直接使用 Starlette 的一个选项,你可以声明一个包含路径路径参数,使用的 URL 像这样:

/files/{file_path:path}

在这种情况下,参数的名称是 file_path,最后一部分 :path 告诉它该参数应该匹配任何路径

所以,你可以这样使用它:

from fastapi import FastAPI

app = FastAPI()


@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
    return {"file_path": file_path}

提示

你可能需要参数包含 /home/johndoe/myfile.txt,带有一个前导斜杠 (/)。

在这种情况下,URL 将是:/files//home/johndoe/myfile.txt,在 fileshome 之间有一个双斜杠 (//)。

总结

通过使用 FastAPI,凭借简短、直观和标准的 Python 类型声明,你可以获得:

  • 编辑器支持:错误检查、自动补全等。
  • 数据「解析
  • 数据验证
  • API 注解和自动文档

而且你只需要声明它们一次。

这可能是 FastAPI 相较于其他框架最明显的优势(除了原始性能)。