FastAPI与ODMantic嵌套模型验证时触发'string indices must be integers, not str'错误的解决方案咨询
嗨,我来帮你排查这个问题~
首先,这个错误的根源其实很明确:你的字段验证器在嵌套模型还没被解析成对象的时候,就尝试用属性访问的方式去操作它了。
在Pydantic v2中,@field_validator默认是在字段完成解析之前执行的(也就是before=True的默认行为)。这时候contact_details还只是原始的输入字典,而不是FoundContactDetails实例——你尝试用value.address.street这种属性访问方式去操作字典,自然会触发string indices must be integers, not 'str'的错误,因为字典只能通过['key']的方式访问内容,不能用.。
给你几个可行的解决方案,按推荐程度排序:
方案1:调整验证器的执行时机(最推荐)
在@field_validator装饰器里加上mode='after'参数,让Pydantic先把contact_details完整解析成FoundContactDetails实例,再执行你的验证逻辑。这时候你用.访问属性就完全没问题了:
class FoundFormCreate(FoundFormBase): @field_validator('contact_details', mode='after') def validate_contact_details(cls, value): if value.bank_account is not None and value.bank_name is None: raise ValueError('Bank name is required if bank account is provided') if value.address is not None: # 此时value.address已经是DetailedAddress实例,可正常用.访问属性 if not all([ value.address.street, value.address.ward, value.address.district, value.address.city, value.address.country ]): raise ValueError('All address fields (street, ward, district, city, country) are required if address is provided') return value
方案2:利用Pydantic自身的嵌套验证简化逻辑(更优雅)
仔细看你的DetailedAddress模型,所有字段都是必填的str类型,没有设置Optional。这意味着Pydantic会自动帮你验证:如果用户提交了address,就必须提供所有字段,否则会直接抛出缺少字段的错误——完全不需要你手动写all([...])的检查逻辑。
这样你可以把验证器简化成只处理银行账户和银行名称的关联逻辑:
class FoundFormCreate(FoundFormBase): @field_validator('contact_details', mode='after') def validate_contact_details(cls, value): if value.bank_account is not None and value.bank_name is None: raise ValueError('Bank name is required if bank account is provided') # address的完整性由DetailedAddress模型自动验证,无需额外代码 return value
这种方式既简洁又不容易出错,把字段级的验证交给Pydantic原生处理,你只需要处理跨字段的关联规则就行。
方案3:直接操作原始字典(不推荐,仅作参考)
如果你非要保持验证器的默认执行时机,那可以改成用字典的键访问方式来检查原始输入数据,但这种代码可读性较差,不推荐:
class FoundFormCreate(FoundFormBase): @field_validator('contact_details') def validate_contact_details(cls, value): # 此时value是原始输入字典,不是模型实例 if value.get('bank_account') is not None and value.get('bank_name') is None: raise ValueError('Bank name is required if bank account is provided') address = value.get('address') if address is not None: if not all([ address.get('street'), address.get('ward'), address.get('district'), address.get('city'), address.get('country') ]): raise ValueError('All address fields (street, ward, district, city, country) are required if address is provided') return value
试一下方案1或者方案2,应该就能解决你遇到的问题了~
备注:内容来源于stack exchange,提问作者Quang Truong Nguyen

