如何在Pytest中Patch Pydantic BaseSettings的Annotated类型验证器?
关于Pydantic BaseSettings中Annotated类型BeforeValidator的Patch问题
能不能Patch原代码中的BeforeValidator方法?
不行。因为Pydantic在解析Annotated类型时,会立即捕获BeforeValidator传入的函数引用并保存,后续patch模块级别的asd_func只会替换模块中的函数变量,无法改变Pydantic已经持有的原始函数引用,验证逻辑不会被替换。
为什么当前代码无法运行?
没错,核心原因就是你猜测的那样:当AsdType = Annotated[str, BeforeValidator(asd_func)]执行时,BeforeValidator已经把当时的asd_func引用存储到了类型定义中。后续的patch操作只是修改了模块里的asd_func变量指向,但Pydantic处理Config类字段验证时,调用的还是之前保存的原始函数,所以config.ASD仍然是原函数返回的"asd",断言失败。
如何重构让代码正常工作?
根据你跳过磁盘路径检查的测试需求,提供三种实用方案:
方案1:使用字符串引用函数(推荐)
Pydantic支持用字符串形式指定验证函数,它会在运行时动态从模块中查找函数,此时patch就能生效:
from typing import Annotated from unittest.mock import patch from pydantic import BeforeValidator from pydantic_settings import BaseSettings def asd_func(value): return value # 用字符串引用函数,而非直接传入函数对象 AsdType = Annotated[str, BeforeValidator("asd_func")] class Config(BaseSettings): ASD: AsdType = "asd" @patch(__name__ + ".asd_func", return_value="zzzzzz") def test_config_load(_): config = Config() assert config.ASD == "zzzzzz" # 断言成功 test_config_load()
方案2:测试时直接修改模型字段的验证器
如果不想修改原类型定义,可以在测试中临时替换Config字段的验证器列表:
from typing import Annotated from unittest.mock import patch from pydantic import BeforeValidator from pydantic_settings import BaseSettings def asd_func(value): return value AsdType = Annotated[str, BeforeValidator(asd_func)] class Config(BaseSettings): ASD: AsdType = "asd" def test_config_load(): # 保存原有验证器,测试后恢复 original_validators = Config.model_fields["ASD"].validators try: # 清空验证器,跳过检查 Config.model_fields["ASD"].validators = [] config = Config() assert config.ASD == "asd" # 或者替换为mock验证逻辑 def mock_func(v): return "zzzzzz" Config.model_fields["ASD"].validators = [BeforeValidator(mock_func)] config = Config() assert config.ASD == "zzzzzz" finally: Config.model_fields["ASD"].validators = original_validators test_config_load()
方案3:将验证函数作为类依赖注入
把验证函数绑定到Config类的属性上,方便直接patch类属性:
from typing import Annotated from unittest.mock import patch from pydantic import field_validator from pydantic_settings import BaseSettings def asd_func(value): return value class Config(BaseSettings): ASD: str = "asd" @field_validator("ASD") def validate_asd(cls, value): return cls._asd_validator(value) # 定义类属性存储验证函数,便于patch _asd_validator = staticmethod(asd_func) @patch.object(Config, "_asd_validator", return_value="zzzzzz") def test_config_load(_): config = Config() assert config.ASD == "zzzzzz" # 断言成功 test_config_load()
内容的提问来源于stack exchange,提问作者mrc
相关产品推荐
相关产品推荐

