头图

FastAPI 快速开发 Web API 项目学习笔记:

1 介绍

FastAPI 允许您定义客户端在向 API 或 Web 应用程序发出请求时可以包含在 URL 中的参数和变量。这些参数可用于查询数据库排序过滤数据以及影响返回响应的许多其他事情。

在本篇文章中,将会介绍 FastAPI 的路径和查询参数以及它们在 FastAPI 的工作方式。

2 路径参数

路径参数是什么?

可以作为URL的一部分的变量,通常会应用在指定路径的URI

2.1 定义

定义:路径参数通过预先定义好的位置来接受参数。路径参数是包含在API路由中的参数,用于识别资源。这些参数作为一个标识符,有时也是一个桥梁,可以在网络应用中进行进一步的操作。

根据 Restful 的设计风格,在设计一个 GET 方法 API 时,如果是要读取单一资源,可以通过提供 URI 并带入路径参数的方式,来获取特定资源。比如,在我们的 todo 项目中,如果想要获取 id=1 的待办事项的详细信息,就可以通过将数字 1 作为路径参数,然后将其作为参数传递给路径操作函数:

http://localhost:8888/todo/1

在 FastAPI 中,我们可以使用与 Python 格式字符串相同的语法声明路径“参数”或“变量”,使用 Path Parameter 时,使用 {} 在路径中设定一个参数,例如官方文档中的这个例子:

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 传递给您的函数 read_item()

/items/{id}路由装饰器中,我们使用花括号来声明路径参数_id_。这个参数被传递到路径操作函数中,我们可以用它作为键从库存字典中获取相应的项目。

2.2 带类型的路径参数

路径参数也可以声明类型,通过使用 Python 的类型注解,比如定义 async def read_item(item_id: int): 此处的 item_id 的类型就被声明为 int 类型。如果我们访问端点http://127.0.0.1:8888/todo/test,我们会得到以下错误响应:

{
  "detail": [
    {
      "loc": [
        "path",
        "todo_id"
      ],
      "msg": "value is not a valid integer",
      "type": "type_error.integer"
    }
  ]
}

如图:

2.3 路径顺序

通常我们需要制作具有相似结构的路径。当固定路径相同时会发生这种情况。例如,假设我们有两个端点,如下所示:

/todo/default
/todo/{todo_id}

第一个返回默认待办事项。第二个返回具有特定 todo_id 的待办事项。在这种情况下,声明这些端点的顺序很重要。路径按顺序评估。因此,我们需要在 /todo/{todo_id} 之前声明 /todo/。见下面的代码:

@todo_router.get("/todo/")
async def get_default_todo():
    if todo_list:
        return {
            "todo_1": todo_list[0]
        }
    return {
        "message": "Todo is initial."
    }

@todo_router.get("/todo/{todo_id}")
async def get_single_todo(todo_id: int) -> dict:
    for todo in todo_list:
        if todo.id == todo_id:
            return {
                "todo": todo
            }
    return {
        "message": "Todo with supplied ID doesn't exist."
    }

2.4 数字校验

FastAPI 能够在将路径参数传递给我们的函数之前验证路径参数中的数据。它通过类来实现这一点Path

更新您的 main.py文件以匹配以下内容:

@todo_router.get("/todo/{todo_id}")
async def get_single_todo(todo_id: int = Path(ge=1, le=len(todo_list), 
                                              title="The ID of the todo to be retrieved.")) -> dict:

我们为 Path 添加了额外的导入。然后在路径操作函数中,我们在 todo_id 上创建一个数字限制,使其仅包含大于或等于 1 且小于或等于我们 todo 列表长度的值。

有更好的方法来验证库存项目是否存在,我们稍后会讲到,但现在,我们只是演示如何使用 Path 类。

以下是您可以在 Path 类中设置的数字验证列表。

  • gt:大于
  • ge:大于或等于
  • lt:小于
  • le:小于或等于

2.5 字符串验证

您也可以对字符串参数施加约束。将以下路由添加到您的 main.py 文件中。

@todo_router.get("/todos/{todo_name}")
async def get_todo(todo_name: str = Path(max_length=20)) -> dict:

    for todo in todo_list:
        if todo_name in todo["name"] :
            return {
                "todo": todo
            }
    return {
        "message": "Todo with supplied name doesn't exist."
    }

该路径将参数解释为字符串而不是数字,并将最大长度设置为 6 个字符。可以将其他约束添加到参数,例如最小长度和正则表达式。查看FastAPI 文档了解更多。

我们希望建立一个 GET 请求,通过查询 id 来 获取 todo 的 API,因此可以接受 todo 的 id todo_id,可以通过以下的方式:

from fastapi import APIRouter, Path
from models.model import Todo

todo_router = APIRouter()

todo_list = []

@todo_router.get("/todo/{todo_id}")
async def get_single_todo(todo_id: int = Path(..., title="The ID of the todo to be retrieved.")) -> dict:
    for todo in todo_list:
        if todo.id == todo_id:
            return {
                "todo": todo
            }
    return {
        "message": "Todo with supplied ID doesn't exist."
    }

在上面的例子中,我们在 get_single_todo() 中定义了 todo_id 参数后,FastAPI 会自动注册的路径中寻找 {todo_id} 的位置,当接受到符合条件的 Request(/todo/{todo_id}) 时,会自动将 {todo_id} 位置的值当成 get_single_todo() 的参数填入。

如果是要读取所有资源,并希望能够进行排序、数量限制等操作,则可以通过带入查询参数。在上一节中,我们已经能通过 @todo_router.get("/todo") 获取到所有的待办事项。

启动 todo 项目:

uvicorn main:app --port 8888 --reload

启动成功如下:

打开另外一个 Bash 终端:

$ curl -X 'GET' 'http://127.0.0.1:8888/todo/2' -H 'accept: applicaion/json'
{"message":"Todo with supplied ID doesn't exist."}

说明此时通过查询 id 为 2 的待办事项,并不存在,因此,我们通过 POST 请求新建一个 id 为 2 的代办事项,命令如下:

curl -X POST http://127.0.0.1:8888/todo -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"id": 2, "item": "learning path and query"}' 

建立成功之后,截图如下:

再通过查询来获取这个 id 为 2 的待办事项,可以看到刚刚新建的 item 信息为 "learning path and query" :

$ curl -X 'GET' 'http://127.0.0.1:8888/todo/2' -H 'accept: applicaion/json'
{"todo":{"id":2,"item":"learning path and query"}}(fastwebprojects) 

后台服务器也是显示 200 正常:

3 查询参数

3.1 定义

查询参数对于构建灵活的 API 至关重要。顾名思义,带有查询参数的端点通常可以帮助客户端根据动态参数查询特定数据。查询参数是一个可选的参数,通常出现在 URL 的问号 ? 之后。它用于过滤请求,并根据提供的查询返回特定的数据。

查询参数是什么?

通常会放于URL最后面,并以 ? 区隔开来。查询参数用来接受 URL 所附加的参数,与 Path Parameter 最大的差别在于没有预先定义位置。

在一个路由处理函数中,当我们声明一个函数时,其参数不作为路径参数出现,它们将被解释为查询参数。你也可以通过在函数参数中创建 FastAPI Query() 类的一个实例来定义一个查询,比如下面的例子:

async def query_route(query: str = Query(None)):
    return query

回到我们的 todo 项目中,如何定义一个查询参数呢?查看下面的例子:

@todo_router.get("/todos/")
async def get_default_todo(skip: int = 0, limit: int = 10):
    if todo_list:
        return todo_list[skip : skip + limit]
    return {
        "message": "Todo is initial."
    }

我们有两个参数 skiplimit 。这些参数不是路径参数的一部分。相反,它们以查询参数的形式提供,同时以 http://localhost:8000/todos?skip=0&limit=10 调用 API 端点。

此处,skip 的初始值为 0,limit 的初始值为 10。由于它们是 URL 的一部分,因此它们被视为字符串。但是,我们使用 Python 类型声明它们。因此,它们会自动转换并根据它进行验证。

3.2 查询参数默认值

如我们所见,查询参数不是固定路径的一部分。因此,它们是可选的。此外,它们可以具有默认值,以防我们不传递任何值作为输入。

换句话说,如果我们简单地调用 http://localhost:8888/todos,这将意味着 skip 将采用默认值 0,而 limit 将采用默认值 10。

3.3 声明一个可选查询参数

同样,您可以通过将其默认设置为 None 来声明可选查询参数:

在 Python 3.10 和 Python 3.6 还有一点点不一样,这一点建议多看看官方文档,这里以 3.10 为例:

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def read_item(item_id: str, q: str | None = None):
    if q:
        return {"item_id": item_id, "q": q}
    return {"item_id": item_id}

在这种情况下,函数参数 q 是可选的,默认情况下为 None

更改 todo_router.py 文件:

from fastapi import APIRouter, Path
from models.model import Todo

todo_router = APIRouter()

todo_list = [
    {"id":1,"item":"write a todo project"}, 
    {"id":2,"item":"learning path parameters"},
    {"id":3,"item":"learning query parameters"},
    ]

@todo_router.post("/todo")
async def add_todo(todo: Todo) -> dict:
    todo_list.append(todo)
    return {
        "message": "Todo added successfully"
    }

@todo_router.get("/todo")
async def retrieve_todos() -> dict:
    return {
        "todos": todo_list
    }

@todo_router.get("/todo/{todo_id}")
async def get_single_todo(todo_id: int = Path(..., title="The ID of the todo to be retrieved.")) -> dict:
    for todo in todo_list:
        if todo.id == todo_id:
            return {
                "todo": todo
            }
    return {
        "message": "Todo with supplied ID doesn't exist."
    }

@todo_router.get("/todo/")
async def get_default_todo():
    if todo_list:
        return {
            "todo_1": todo_list[0]
        }
    return {
        "message": "Todo is initial."
    }

@todo_router.get("/todos/")
async def get_default_todo(skip: int = 0, limit: int = 10):
    if todo_list:
        return todo_list[skip : skip + limit]
    return {
        "message": "Todo is initial."
    }

3.4 必输查询参数

当我们声明一个具有默认值的查询参数时,我们将其设为可选。此外,当我们保留默认值 None 时,FastAPI 将其视为可选。

但是,我们也可以强制指定某些查询参数。基本上,我们不必提供默认值。在这种情况下,FastAPI 将查询参数视为必需参数。

请参见下面的示例:

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 类型的查询参数。

3.5 查询参数约束

查询参数可以像路径参数一样被分配约束。在上面的例子中,也许我们会限制 max_price 参数只接受正整数。

该过程与路径约束相同,但导入不同。添加 Query 到您的 fastapi import ,如下所示。

from fastapi import FastAPI, Path, Query

然后更新 get_items() 路径函数以使用 Query 类并设置适当的选项。

@app.get("/items/")
def get_items(vegan: bool = False, max_price: float = Query(default=20, ge=1)):
    results = {}
    counter = 1
    for item in inventory.values():
        if item["vegan"] == vegan and item["price"] <= max_price:
            results[counter] = item
            counter += 1
    return results

现在为参数输入负值 max_price 会返回以下错误消息。

{"detail":[{"loc":["query","max_price"],"msg":"ensure this value is greater than or equal to 1","type":"value_error.number.not_ge","ctx":{"limit_value":1}}]}

查看 FastAPI 文档以了解查询参数和字符串验证看看您还可以用查询参数做什么。

4 总结

路径和查询参数都允许开发人员接收变量输入、处理它并返回基于该输入的响应。

在设计 API 路由时,有时您可以使用路径或查询参数来完成相同的任务。以下是这方面的例子。

# 获取 id 为 1 的 todo
http://localhost:8888/todos/1
http://localhost:8000/todos/?id=1

但是路径参数更适合创建对特定信息的请求。查询参数更适合过滤和排序数据。

在这篇文章中,我们了解了什么是路径和查询参数以及如何在 FastAPI 中实现它们。然后,我们研究了如何通过限制数字和字符串值来对这些参数创建约束。我们还回顾了路径参数和查询参数之间的主要区别,并在 todo 应用中运用了展示了如何去使用路径参加和查询参数。

如果想了解更多的这两个参数的细节,读者可以读取下面的参考资料,PS:官方文档才是第一手学习资料。

希望本文能对你有所帮助,如果喜欢本文,可以点个关注.

下一篇文章见!宇宙古今无有穷期,一生不过须臾,当思奋争。

参考链接:


宇宙之一粟
82 声望9 粉丝

混迹于江湖,江湖却没有我的影子