FastAPI:通过依赖()和模式引用相同的根级键,而不会以多个主体参数结束



我有一种情况,我想根据FastAPI路由中的一个值(Organization)授权活动用户。当提交特定类型的对象时,应该存在其中一个密钥(organization_id),并且应该验证用户是否具有对组织的访问权限。

我已经把它作为API签名中的一个依赖项来解决,以避免在需要访问此属性的所有路由上复制此代码:

def get_organization_from_body(organization_id: str = Body(None),
user: User = Depends(get_authenticated_user),
organization_service: OrganizationService = Depends(get_organization_service),
) -> Organization:
if not organization_id:
raise HTTPException(status_code=400, detail='Missing organization_id.')
organization = organization_service.get_organization_for_user(organization_id, user)
if not organization:
raise HTTPException(status_code=403, detail='Organization authorization failed.')
return organization

这很好,如果API端点期望通过请求中的organization_id键获得组织,我可以通过在我的路由中引入get_organization_from_body作为依赖项来直接填充组织:

@router.post('', response_model=Bundle)
async def create_bundle([...]
organization: Organization = Depends(get_organization_from_body),
) -> Model:

. .如果用户没有访问组织的权限,则抛出403异常。

然而,我还想通过模式模型在根级别包含我的实际对象。所以我的第一次尝试是做一个JSON请求:

{
'name': generated_name,
'organization_id': created_organization['id_key']
}

然后添加我的传入Pydantic模型:

@router.post('', response_model=Bundle)
async def create_bundle(bundle: BundleCreate,
organization: Organization = Depends(get_organization_from_body),
[...]
) -> BundleModel:
[...]
return bundle

无论pypytic模型/模式是否包含organization_id作为字段,结果都是一样的:

class BundleBase(BaseModel):
name: str
class Config:
orm_mode = True

class BundleCreate(BundleBase):
organization_id: str
client_id: Optional[str]

. .但是当我引入get_organization_from_body依赖项时,FastAPI看到我有另一个引用Body字段的依赖项,并且bundle对象的描述必须移动到bundle键中而不是"验证"。organization_id字段,JSON布局需要改变-因为我觉得organization_idbundle描述的一部分,它应该住在那里。如果可能的话)。

错误信息告诉我bundle应该作为一个单独的字段:

{'detail': [{'loc': ['body', 'bundle'], 'msg': 'field required', 'type': 'value_error.missing'}]}

正确的是,当我将name移动到bundle键中:

{
'bundle': {
'name': generated_name,
},
'organization_id': created_organization['id_key']
}

. .我的测试通过,请求成功。

这可能是稍微自行车掉下来,但如果有一个快速修复工作围绕这个限制以任何方式,我有兴趣找到一种方法来实现验证(无论是通过Depends()或在一些替代的方式没有做它显式在每个API路由函数,需要该功能)和一个平坦的JSON布局匹配我的输出格式更接近。

在FastAPI0.53.2之前,主体的依赖关系是按照您正在尝试的方式解决的。这样的代码:

class Item(BaseModel):
val_1: int
val_2: int

def get_val_1(val_1: int = Body(..., embed=True)):
return val_1

@app.post("/item", response_model=Item)
def handler(full_body: Item, val_1: int = Depends(get_val_1)):
return full_body

预期正文:

{
"val_1": 0,
"val_2": 0
}

但是从0.53.2版本开始,不同的正文依赖被自动嵌入(embed=True),上面的代码需要以下正文:

{
"full_body": {
"val_1": 0,
"val_2": 0
},
"val_1": 0
}

现在,为了访问整个body的模型,并作为单独的依赖项访问其元素,您需要对body模型使用相同的依赖项:

def get_val_1(full_body: Item):
return full_body.val_1

@app.post("/item", response_model=Item)
def handler(full_body: Item, val_1: int = Depends(get_val_1)):
return full_body

更新共享依赖

您可以为多个模型共享一个主体依赖项,但在这种情况下,必须满足两个条件:依赖项的名称必须相同,它们的类型必须兼容(通过继承或不继承)。在下面的例子:

class Base(BaseModel):
val_1: int

class NotBase(BaseModel):
val_1: int

class Item1(Base):
val_2: int

class Item2(Base):
val_3: int

def get_val1_base(full_body: Base):
return full_body.val_1

def get_val1_not_base(full_body: NotBase):
return full_body.val_1

@app.post("/item1", response_model=Item1)
def handler(full_body: Item1, val_1: int = Depends(get_val1_base)):
return full_body

@app.post("/item2", response_model=Item2)
def handler(full_body: Item2, val_1: int = Depends(get_val1_not_base)):
return full_body