五分钟掌握 FastAPI “依赖注入”

12/2/2024

FastAPI 系列

开始之前

先看看本文简单小结:

image.png

一、什么 FastAPI 的依赖注入

依赖注入是一种用于解耦组件并实现代码复用的机制。

在 FastAPI 中依赖注入与 class 编程风格不一样,FastAPI 会自动将需要注入的函数,注入到依赖试图中,无需手动注入。

二、开始之前我们需要哪些基础知识?

  • Python Callable type hits
  • 如果你熟悉其他的框架的依赖注入,下面的概念应该特别熟悉,同样它们也适用于 FastAPI 只是表现形式不一样而已:
    • 资源(Resource)
    • 提供方(Provider)
    • 服务(Service)
    • 可注入(Injectable)
    • 组件(Component)

三、依赖注入使用场景

  • 数据库注入 db
  • 数据校验注入 token
  • ...

四、什么可以作为依赖

写一个依赖可以是 class 也可以是 function。下面我们简单看看 class 和 function 依赖有什么不同。

类依赖

from fastapi import FastAPI, Depends

app = FastAPI()

class MyDepends:
  def __init__(self, page: int = 1, page_size: int = 10):
    self.page = page
    self.page_size = page_size

@app.get("/")
async def root(item: MyDepends = Depends(MyDepends)):
    return {"message": "Hello World", "page": item.page, "page_size": item.page_size}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, port=8000)

函数依赖

函数依赖只最为常见的依赖,当有需求的时候,依赖自动调用此函数:

from fastapi import FastAPI, Depends

app = FastAPI()

def get_page(page: int = 1, page_size: int = 10):
    return {
        "page": page,
        "page_size": page_size
    }

@app.get("/")
async def root(item: dict = Depends(get_page)):
    return {"page": item["page"], "page_size": item["page_size"]}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, port=8000)

五、依赖形式

fastapi 中依赖有两种形式:

  • Depends + 接口处理函数参数
  • dependencies 配置项目

Depends 依赖

from fastapi import FastAPI, Depends

@app.get("/")
def read_query(query: str = Depends(get_query_param)):
    return {"query": query}

作为一个路由的默认值。

dependencies 配置项目 + Depends

dependencies 可以在:

  • 应用:FastAPI(dependencies=[])
  • 路由:APIRouter(dependencies=[])
  • 接口:async def read_query(query_or_default: str = Depends(query_or_cookie_extractor)):

六、APP 依赖注入

一个简单的示例:

from fastapi import Depends, FastAPI

def get_db(a):
    # do something
    print("xx",a)
    return True

app = FastAPI(dependencies=[Depends(get_db)])

@app.get("/")
async def read_root():
    return {"Hello": "World"}

@app.post("/")
async def create_root():
    return {"Hello": "World"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, port=8000)

两个接口 GET /POST /, 在 FastAPI 的 dependencies 注入了 get_db, get 包含 a 参数此时,表示 url 的 params 参数。post 方法也是,我们看看 docs 的提示。

image.png

访问 http://127.0.0.1:8000/?a=1

image.png

不传递参数:

image.png

FastAPI 会报告一个错误,丢失一个 query 参数 a

七、router 和接口的依赖注入

Router 级有三种方式:routerdependencies 中 , 路由的 dependencies路由处理函数 参数 中

from fastapi import Depends, FastAPI, APIRouter

def get_db(a):
    # do something
    print("xx",a)
    return True

app = FastAPI()

router = APIRouter(dependencies=[Depends(get_db)])

@app.get("/", dependencies=[Depends(get_db)])
async def read_root():
    return {"Hello": "World"}

@app.get("/app")
async def read_root_d(db: bool = Depends(get_db)):
    return {"Hello": "World", db: db}

@router.post("/r")
async def create_root():
    return {"Hello": "World"}

app.include_router(router, prefix="/router")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, port=8000)

八、多依赖 dependencies 配置项

通常使用 dependencies 来管理多依赖:

from fastapi import FastAPI, Depends

app = FastAPI()

def deps_one():
    print("one")
    pass

def deps_two():
    print("two")
    pass

@app.get("/items/", dependencies=[Depends(deps_one), Depends(deps_two)])
async def read_items():
    return [{"item": "Foo"}, {"item": "Bar"}]

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, port=8000)

dependencies 传递数组即可, 一次传递依赖函数,那么实际情况中依赖的执行顺序是什么样的呢?

image.png

执行的顺序与列表的顺序一致。

九、依赖串

Depends 可以在函数参数处构成依赖串,这个实际项目中可能比较常用:

from fastapi import Depends, FastAPI


def get_next_line(a):
    # do something
    print("next", a)
    return True


def get_db(a: str = Depends(get_next_line)):
    # do something
    print("xx", a)
    return True


app = FastAPI()

@app.get("/")
async def read_root(a: str = Depends(get_db)):
    return {"Hello": a}


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, port=8000)

我们访问 http://127.0.0.1:8000/?a=gh, 终端输出如下:

image.png

多依赖的输出顺序是:内部的依赖先输出,外部(靠近路由)后输出的的规则。

十、依赖与 yield

from fastapi import FastAPI, Depends, HTTPException

app = FastAPI()

def get_username():
    try:
        yield "Rick" # 定义一个简单的 yield 语句
    except Exception as e:
        raise HTTPException(status_code=400, detail=f"Owner error: {e}")

@app.get("/")
async def root(uname: str = Depends(get_username)):
    return {"message": "Hello World"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, port=8000)

十一、源码

Depends 的来源:param_functions 中的 Depends:

image.png

Depends 调用了 params 中 Depends:

image.png

最终实现是: 需要一个 dependency 函数(当然是可选的)

image.png

十二、小结

本文较为详细讲解了 FastAPI 中的依赖注入,如果你使用过其他框架依赖注入,虽然表现形式上有所不同,但是所要表达的思想道理基本一致。FastAPI 的依赖项目通过 Depend 函数和 dependencies 配置项进行管理。支持函数配置和class配置。对于复杂的配置支持依赖串和多依赖支持。