You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FastAPI:能否在Pydantic验证器中访问另一个输入参数?

问题描述

我是FastAPI新手,尝试将输入参数token传入Provision类并在@validator中使用,但无法在验证器中访问token,请问是否可行?

代码示例

from typing import Annotated 
from fastapi import Depends, FastAPI
from pydantic import BaseModel, validator

app = FastAPI()

def decryptJWT(token):
    return {"app": "app1"}

def appAuth(token):
    sub = decryptJWT(token)
    if sub["app"] in ["app1", "app2"]:
        return True
    else:
        return False

class Provision(BaseModel):
    name: str
    app: str

    @validator("app")
    def app_must_be_valid(cls, app):
        apps = appAuth(token)  # 此处无法访问token
        if app not in apps:
            raise ValueError("Invalid app name")
        return app

@app.post("/provision")
async def provision(token, provision: Provision):
    return provision

补充说明

我通过Authorization头获取token,代码为token: Annotated[OAuth2PasswordBearer, Depends(verifyAuthScope('write'))]。

我需要验证provision的app输入是否与用户映射的应用之一匹配,用户(clientid)与应用列表的映射存储在数据库中,无法仅通过token声明获取该信息。目前我在路径操作函数内进行验证,这里可以同时访问token和provision的app输入,但为了保持代码整洁并统一错误类型(用ValueError而非HTTPException),我希望在路径操作函数执行前提前完成输入验证。

当前路径操作函数中使用的验证代码片段(已简化错误处理和日志):

def verify_application_mapping(provision, token):
    applications = clientid_mapped_to_application(token)
    for app in applications:
        if provision.application in app['name']:
            return True
        raise HTTPException(status_code=404, detail='Invalid Application: "{}". Valid Applications are: {}'.format(provision.application, [app['name'] for app in applications]))

    return False
解决方案

直接在Pydantic模型的@validator里访问请求的token不可行,因为Pydantic模型验证独立于FastAPI请求上下文,默认拿不到请求相关的依赖参数。不过可以通过以下几种方式实现需求:

方法1:用Pydantic根验证器结合请求上下文

FastAPI的Request对象可以获取请求上下文,你可以在根验证器里通过context参数传递请求对象,从而拿到token:

修改模型,使用root_validator并接收values和context:

from fastapi import Request
from pydantic import root_validator

class Provision(BaseModel):
    name: str
    app: str

    @root_validator(skip_on_failure=True)
    def app_must_be_valid(cls, values, **kwargs):
        request = kwargs.get('context', {}).get('request')
        if not request:
            raise ValueError("无法获取请求上下文")
        
        # 从Authorization头解析token(假设是Bearer格式)
        auth_header = request.headers.get('Authorization')
        if not auth_header or not auth_header.startswith('Bearer '):
            raise ValueError("无效的Authorization头")
        
        token = auth_header.split(' ')[1]
        applications = clientid_mapped_to_application(token)
        app = values.get('app')
        
        valid_apps = [app['name'] for app in applications]
        if app not in valid_apps:
            raise ValueError(f"无效的应用名称: {app},可用应用为: {valid_apps}")
        
        return values

然后在路径操作中传递上下文:

@app.post("/provision")
async def provision(request: Request, token: Annotated[OAuth2PasswordBearer, Depends(verifyAuthScope('write'))], provision: Provision = Depends(lambda: Provision(**await request.json(), context={'request': request}))):
    return provision

这种方式稍显繁琐,更推荐下面的方法。

方法2:把验证逻辑放到FastAPI依赖中

将验证逻辑封装成依赖函数,同时接收token和Provision模型,依赖会在路径操作前执行,既满足提前验证的需求,又能统一错误类型:

def validate_provision_app(token: Annotated[OAuth2PasswordBearer, Depends(verifyAuthScope('write'))], provision: Provision):
    applications = clientid_mapped_to_application(token)
    valid_apps = [app['name'] for app in applications]
    if provision.app not in valid_apps:
        # 抛出ValueError,FastAPI会自动转换成422验证错误,和Pydantic原生错误格式统一
        raise ValueError(f"无效的应用名称: {provision.app},可用应用为: {valid_apps}")
    return provision

@app.post("/provision")
async def provision(validated_provision: Provision = Depends(validate_provision_app)):
    return validated_provision

这种方式完全贴合FastAPI的依赖注入设计,代码简洁易维护,是最优方案。

方法3:自定义Pydantic字段类型

创建一个自定义的ValidApp字段类型,在字段的验证逻辑中获取token(需要结合请求上下文):

from pydantic import GetCoreSchemaHandler, ValidationInfo
from pydantic_core import core_schema
from fastapi import Request

class ValidApp(str):
    @classmethod
    def __get_pydantic_core_schema__(cls, source_type, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
        return core_schema.no_info_after_validator_function(
            cls.validate,
            handler(str),
        )
    
    @classmethod
    def validate(cls, v: str, info: ValidationInfo) -> str:
        # 从上下文获取request对象
        request = info.context.get('request')
        if not request:
            raise ValueError("无法获取请求上下文")
        
        auth_header = request.headers.get('Authorization')
        if not auth_header or not auth_header.startswith('Bearer '):
            raise ValueError("无效的Authorization头")
        
        token = auth_header.split(' ')[1]
        applications = clientid_mapped_to_application(token)
        valid_apps = [app['name'] for app in applications]
        
        if v not in valid_apps:
            raise ValueError(f"无效的应用名称: {v},可用应用为: {valid_apps}")
        return v

class Provision(BaseModel):
    name: str
    app: ValidApp

@app.post("/provision")
async def provision(request: Request, token: Annotated[OAuth2PasswordBearer, Depends(verifyAuthScope('write'))], provision: Provision = Depends(lambda: Provision(**await request.json(), context={'request': request}))):
    return provision

这种方式适合需要复用验证逻辑的场景,但同样需要处理请求上下文传递。

内容的提问来源于stack exchange,提问作者mosbricka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 12:01:01