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 或模拟些简单行为以避免需要对整个系统进行测试。
是的,即使路由器也决定了哪里去获取服务实例,但它并不是该实例的生命周期负责,服务实例是通过应用程序状态注入的至此。
我还可以单独维护整个应用程序的数据库实例,没有创建一个新实例的需求。
评论 (0)