FastAPI:通过 Depends() 和架构引用相同的根级别键而不以多个主体参数结束的依赖项
FastAPI: Having a dependency through Depends() and a schema refer to the same root level key without ending up with multiple body parameters
我有一种情况,我想授权活跃用户使用 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
无论 pydantic 模型/模式是否包含 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
键内移动(因此 JSON 布局需要更改,而不是“验证” organization_id
字段 - 因为我觉得 organization_id
是一部分在 bundle
描述中,它应该存在于那里 .. 如果可能的话)。
错误消息告诉我 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 布局。
在 FastAPI 0.53.2
之前,body 的依赖项已按照您尝试的方式解决。这样的代码:
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
期待这样的body:
{
"val_1": 0,
"val_2": 0
}
但是 starting from 版本 0.53.2
,不同的 body 依赖项会自动嵌入 (embed=True
) 并且上面的代码需要以下 body:
{
"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
共享依赖更新
你可以为多个模型共享一个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
我有一种情况,我想授权活跃用户使用 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
无论 pydantic 模型/模式是否包含 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
键内移动(因此 JSON 布局需要更改,而不是“验证” organization_id
字段 - 因为我觉得 organization_id
是一部分在 bundle
描述中,它应该存在于那里 .. 如果可能的话)。
错误消息告诉我 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 布局。
在 FastAPI 0.53.2
之前,body 的依赖项已按照您尝试的方式解决。这样的代码:
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
期待这样的body:
{
"val_1": 0,
"val_2": 0
}
但是 starting from 版本 0.53.2
,不同的 body 依赖项会自动嵌入 (embed=True
) 并且上面的代码需要以下 body:
{
"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
共享依赖更新
你可以为多个模型共享一个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