Hello! 我正在使用 FastAPI编写一个简单的 web 服务器,但我在尝试了解如何编写干净、可测试的代码时遇到了困难。

背景

我来自多年的 Java 编程经验,一个强烈依赖 OOP 的语言。我之前已经犯了很多次错误,写 Python 代码如同 Java,快速学会了不是每种语言都适用于每种情况。每种语言都有特定的书写方式。

对于我所了解的,FastAPI 不应该使用 Python 的 OOP 方式,而应该使用简单的函数来书写代码。

问题

我面临的问题是,在编写 API 时,没有一个机制能够实现依赖注入。一个正常的项目布局应该是:

  * 路由文件,将注解的方法定义为 API 监听的路径。
  * 主要应用程序定义 FastAPI 对象和所有涉及的路由。

  现在,让我假设我的业务逻辑需要访问一个数据库。我可能会直接将所有相关代码包含在路由文件中,创建一个数据库连接,获取所需的数据。然而,一个好习惯是通过依赖注入将数据库连接注入路由文件中。这样做可以提高可测试性,分离关注点。  

fastAPI 建议使用 `Depends` 语法来实现依赖注入的方式是通过 `Depends()` 传入一个函数。问题在于,如果依赖知晓所需函数的具体实例化方式,那么就不会发生依赖注入。即使是通过一个 Get 语法从容器中获取依赖,也需要注入容器本身到路由文件中。

我知道可以使用 `dependencies_overrides` 函数进行覆盖,但这似乎仅用于测试。

所以,什么是最好的方法来实现依赖注入呢?路由器从不应该了解如何创建数据库连接。

**更新内容**

因此,在更多的阅读和测试之后,我采用了以下内容。感谢 u/mwon 指向正确的方向

service.py

class Service:

def __init__(self, database):

self._database = database

def do_something(self):

// 业务逻辑,可能涉及数据库访问

----------

router.py

my_router = APIRouter(

prefix="/some_path",

@my_router.get("/test")

async def get_test(service: Annotated[Service, Depends(get_service)]):

return service.do_something()

def get_service(request: Request):

return request.app.state.service

---------

app.py

@asynccontextmanager

async def lifespan(app: FastAPI):

database = CreateDatabase()

service = Service (database)

app.state.service = service

yield

database.close()

app = FastAPI(lifespan=lifespan)

app.include_router(my_router)

方式是将所有我的业务逻辑独立在 `service.py`中,我可以单独为它们编写单元测试,无需任何FastAPI相关内容。在结合路由器实例化方法,使用覆盖 `dependencies` 选项进行集成测试是可以的。但是,目的在于运行FastAPI,可能会Mock 或模拟些简单行为以避免需要对整个系统进行测试。

是的,即使路由器也决定了哪里去获取服务实例,但它并不是该实例的生命周期负责,服务实例是通过应用程序状态注入的至此。

我还可以单独维护整个应用程序的数据库实例,没有创建一个新实例的需求。

编辑