三周精通FastAPI:21 子依赖项和路径操作装饰器依赖项

官方文档:https://fastapi.tiangolo.com/zh/tutorial/dependencies/sub-dependencies/#_6

子依赖项

FastAPI 支持创建含子依赖项的依赖项。

并且,可以按需声明任意深度的子依赖项嵌套层级。

FastAPI 负责处理解析不同深度的子依赖项。

第一层依赖项

下列代码创建了第一层依赖项:

def query_extractor(q: Union[str, None] = None):
    return q

这段代码声明了类型为 str 的可选查询参数 q,然后返回这个查询参数。

这个函数很简单(不过也没什么用),但却有助于让我们专注于了解子依赖项的工作方式。

第二层依赖项

接下来,创建另一个依赖项函数,并同时用该依赖项自身再声明一个依赖项(所以这也是一个「依赖项」):

def query_or_cookie_extractor(
    q: str = Depends(query_extractor),
    last_query: Union[str, None] = Cookie(default=None),
):
    if not q:
        return last_query
    return q

这里重点说明一下声明的参数:

  • 尽管该函数自身是依赖项,但还声明了另一个依赖项(它「依赖」于其他对象)
    • 该函数依赖 query_extractor, 并把 query_extractor 的返回值赋给参数 q
  • 同时,该函数还声明了类型是 str 的可选 cookie(last_query
    • 用户未提供查询参数 q 时,则使用上次使用后保存在 cookie 中的查询

使用依赖项

接下来,就可以使用依赖项:

from typing import Union

from fastapi import Cookie, Depends, FastAPI

app = FastAPI()


def query_extractor(q: Union[str, None] = None):
    return q


def query_or_cookie_extractor(
    q: str = Depends(query_extractor),
    last_query: Union[str, None] = Cookie(default=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}

"信息"

注意,这里在路径操作函数中只声明了一个依赖项,即 query_or_cookie_extractor

FastAPI 必须先处理 query_extractor,以便在调用 query_or_cookie_extractor 时使用 query_extractor 返回的结果。

多次使用同一个依赖项

如果在同一个路径操作 多次声明了同一个依赖项,例如,多个依赖项共用一个子依赖项,FastAPI 在处理同一请求时,只调用一次该子依赖项。

FastAPI 不会为同一个请求多次调用同一个依赖项,而是把依赖项的返回值进行「缓存」,并把它传递给同一请求中所有需要使用该返回值的「依赖项」。

在高级使用场景中,如果不想使用「缓存」值,而是为需要在同一请求的每一步操作(多次)中都实际调用依赖项,可以把 Depends 的参数 use_cache 的值设置为 False :

async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)):
    return {"fresh_value": fresh_value}

小结

千万别被本章里这些花里胡哨的词藻吓倒了,其实依赖注入系统非常简单。

依赖注入无非是与路径操作函数一样的函数罢了。

但它依然非常强大,能够声明任意嵌套深度的「图」或树状的依赖结构。

"提示"

这些简单的例子现在看上去虽然没有什么实用价值,

但在安全一章中,您会了解到这些例子的用途,

以及这些例子所能节省的代码量。

路径操作装饰器依赖项

有时,我们并不需要在路径操作函数中使用依赖项的返回值。

或者说,有些依赖项不返回值。

但仍要执行或解析该依赖项。

对于这种情况,不必在声明路径操作函数的参数时使用 Depends,而是可以在路径操作装饰器中添加一个由 dependencies 组成的 list

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"}]

路径操作装饰器中添加 dependencies 参数

路径操作装饰器支持可选参数 ~ dependencies

该参数的值是由 Depends() 组成的 list

@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])

路径操作装饰器依赖项(以下简称为“路径装饰器依赖项”)的执行或解析方式和普通依赖项一样,但就算这些依赖项会返回值,它们的值也不会传递给路径操作函数

"提示"

有些编辑器会检查代码中没使用过的函数参数,并显示错误提示。

路径操作装饰器中使用 dependencies 参数,可以确保在执行依赖项的同时,避免编辑器显示错误提示。

使用路径装饰器依赖项还可以避免开发新人误会代码中包含无用的未使用参数。

"说明"

本例中,使用的是自定义响应头 X-KeyX-Token

但实际开发中,尤其是在实现安全措施时,最好使用 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

触发异常

路径装饰器依赖项与正常的依赖项一样,可以 raise 异常:


        raise HTTPException(status_code=400, detail="X-Token header invalid")



        raise HTTPException(status_code=400, detail="X-Key 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

为一组路径操作定义依赖项

稍后,大型应用 - 多文件一章中会介绍如何使用多个文件创建大型应用程序,在这一章中,您将了解到如何为一组路径操作声明单个 dependencies 参数。

全局依赖项

接下来,我们将学习如何为 FastAPI 应用程序添加全局依赖项,创建应用于每个路径操作的依赖项。

全局依赖项

有时,我们要为整个应用添加依赖项。

通过与定义路径装饰器依赖项 类似的方式,可以把依赖项添加至整个 FastAPI 应用。

这样一来,就可以为所有路径操作应用该依赖项:

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 参数。

实践

子依赖项

源代码

将代码写入dependssub.py文件

from typing import Union

from fastapi import Cookie, Depends, FastAPI

app = FastAPI()


def query_extractor(q: Union[str, None] = None):
    return q


def query_or_cookie_extractor(
    q: str = Depends(query_extractor),
    last_query: Union[str, None] = Cookie(default=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}

启动服务

uvicorn dependssub:app --reload

测试

curl "127.0.0.1:8000/items/?q=hello"
{"q_or_cookie":"hello"}

全局依赖项

源代码

将代码写入dependsglobal.py文件

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"}]

启动服务

uvicorn dependsglobal:app --reload

测试

curl -X GET "http://127.0.0.1:8000/items/" -H "Content-Type: application/json" -H "x-token:fake-super-secret-token" -H "x-key:fake-super-secret-key"

 输出:

curl -X GET "http://127.0.0.1:8000/items/" -H "Content-Type: application/json" -H "x-token:fake-super-secret-token" -H "x-key:fake-super-secret-key"
[{"item":"Portal Gun"},{"item":"Plumbus"}]


 

猜你喜欢

转载自blog.csdn.net/skywalk8163/article/details/143324445