FastAPI接口抽象层需让路由函数仅依赖协议而非具体实现,通过工厂函数或__call__类配合Depends注入具体实例,避免硬引用和抽象类直接传入Depends;Pydantic模型中禁用抽象类型标注;测试时需谨慎管理dependency_overrides生命周期。

FastAPI里怎么写接口抽象层,而不是直接依赖具体类
依赖倒置不是加个ABC就完事——关键在于让路由函数只跟协议(interface)打交道,不 import 具体实现。否则改个数据库驱动或换种缓存策略,就得挨个翻app.py和router.py去修from services.user_service import UserService这类硬引用。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 定义抽象类时用
abc.ABC,但方法签名要贴业务语义,比如get_user_by_id(self, user_id: int) -> UserSchema,别写execute_query(self, sql: str) - 具体实现类不放在
main或router目录下,统一进impl/子包,避免被误导入 - FastAPI的
Depends必须接收可调用对象,所以注册依赖时别传实例,而是传工厂函数或带__call__的类
如何用Depends正确注入抽象接口实例
常见错误是把抽象类当依赖直接塞进Depends,比如Depends(UserRepository)——这会触发TypeError: UserRepository is an abstract class。FastAPI不负责实例化抽象类,它只负责调用你给它的可调用对象。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 写一个工厂函数,内部判断环境变量或配置决定返回
PostgresUserRepo()还是MockUserRepo(),然后传给Depends - 如果用类做依赖,确保它实现了
__call__,且返回的是具体实现实例,不是self - 别在
Depends里做耗时操作(如连接数据库),初始化应提前完成,依赖函数只负责“取用”
示例:
def get_user_repo() -> UserRepository:
if settings.USE_MOCK_DB:
return MockUserRepo()
return PostgresUserRepo(pool=database_pool)然后路由里写def read_user(repo: UserRepository = Depends(get_user_repo))
为什么不能把抽象类放进Pydantic模型或路径参数
Pydantic v2+ 会对字段类型做运行时校验,如果字段类型是AbstractUserRepo这种抽象类,启动时就会报pydantic.errors.PydanticUserError: Field 'repo' has type 'AbstractUserRepo' which is not a valid field type。这不是FastAPI的问题,是Pydantic拒绝序列化/反序列化抽象类型。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 所有Pydantic模型(
BaseModel子类)、路径参数、查询参数、请求体,类型标注只能是具体类、内置类型、泛型(如List[str])或Union组合 - 抽象接口只用于
Depends注入、函数参数标注、以及模块内类型提示(如repo: UserRepository),不出现在任何数据进出边界处 - 如果需要在日志或调试中体现实现类型,用
repo.__class__.__name__,别试图塞进响应模型
测试时替换依赖容易踩的三个坑
单元测试里用override_dependencies看似简单,但实际常因作用域或生命周期错乱导致“mock没生效”。最典型的是:测试跑完,下个测试发现上个测试的Depends还挂着 mock 实例。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 每个测试用
app.dependency_overrides.clear()收尾,或者用pytest的yieldfixture 确保自动清理 - mock对象别复用全局单例,每次测试都新建,否则状态污染(比如
MockUserRepo().users列表被多个测试反复.append()) - 别在
setup_function里覆盖依赖,而要在test_xxx函数内部用app.dependency_overrides[get_user_repo] = lambda: MockUserRepo(),避免fixture之间干扰
复杂点在于:抽象接口的契约一致性没法靠类型系统完全守住。比如UserRepository.get_user_by_id约定返回None表示不存在,但某个实现返回了ValueError,调用方没做try/except——这种问题只有集成测试能暴露,光靠mypy或IDE提示没用。


















