假设我有两个实体,UsersCouncils,以及一个M2M关联表UserCouncils.Users可以从Councils中添加/删除,只有管理员可以这样做(在UserCouncil关系的role属性中定义).


@router.delete("/{council_id}/remove", response_model=responses.CouncilDetail)
def remove_user_from_council(
    council_id: int | UUID = Path(...),
    *,
    user_in: schemas.CouncilUser,
    db: Session = Depends(get_db),
    current_user: Users = Depends(get_current_user),
    council: Councils = Depends(council_id_dep),
) -> dict[str, Any]:
    """

    DELETE /councils/:id/remove (auth)

    remove user with `user_in` from council
    current user must be ADMIN of council
    """

    # check if input user exists
    if not Users.get(db=db, id=user_in.user_id):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
        )

    if not UserCouncil.get(db=db, user_id=user_in.user_id, council_id=council.id):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Cannot delete user who is not part of council",
        )

    # check if current user exists in council
    if not (
        relation := UserCouncil.get(
            db=db, user_id=current_user.id, council_id=council.id
        )
    ):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Current user not part of council",
        )

    # check if current user is Admin
    if relation.role != Roles.ADMIN:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN, detail="Unauthorized"
        )

    elif current_user.id == user_in.user_id:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Admin cannot delete themselves",
        )

    else:
        updated_users = council.remove_member(db=db, user_id=user_in.user_id)
        result = {"council": council, "users": updated_users}
        return result

这些判断很容易解释.然而,这在端点定义中添加了大量代码.端点定义通常应该是最低限度的吗?我可以在Councils个crud方法(即council.remove_member())中封装所有这些判断,但这意味着在crud类中添加HTTPExceptions,我不想这样做.

解决此类情况的一般最佳做法是什么?我在哪里可以了解更多?任何帮助都将不胜感激.

谢谢

推荐答案

那么,我将告诉你我将如何用你的例子来做.

一般来说,我喜欢保持我的端点非常小.您要使用的是构建API时使用的常见模式,即将业务逻辑绑定到服务类中.这个服务类允许您重用逻辑.假设您想从队列或cron作业(job)中删除一名委员会成员.这带来了您强调的下一个问题,即在您的服务类中存在可能不在HTTP上下文中使用的HTTP特定异常.幸运的是,这不是一个很难解决的问题,您只需定义自己的异常,并要求API框架捕捉它们,只需重新引发所需的HTTP异常.

定义自定义异常:

class UnauthorizedException(Exception):
    def __init__(self, message: str):
        super().__init__(message)
        self.message = message


class InvalidActionException(Exception):
    ...


class NotFoundException(Exception):
    ...

在Fast API中,您可以捕捉应用程序抛出的特定异常

@app.exception_handler(UnauthorizedException)
async def unauthorized_exception_handler(request: Request, exc: UnauthorizedException):
    return JSONResponse(
            status_code=status.HTTP_403_FORBIDDEN,
            content={"message": exc.message},
    )

@app.exception_handler(InvalidActionException)
async def unauthorized_exception_handler(request: Request, exc: InvalidActionException):
    ...

用合理的方法将业务逻辑封装到服务类中,并提出您为服务定义的异常

class CouncilService:
    def __init__(self, db: Session):
        self.db = db

    def ensure_admin_council_member(self, user_id: int, council_id: int):
        # check if current user exists in council
        if not (
                relation := UserCouncil.get(
                        db=self.db, user_id=user_id, council_id=council_id
                )
        ):
            raise UnauthorizedException("Current user not part of council")

        # check if current user is Admin
        if relation.role != Roles.ADMIN:
            raise UnauthorizedException("Unauthorized")

    def remove_council_member(self, user_in: schemas.CouncilUser, council: Councils):
        # check if input user exists
        if not Users.get(db=self.db, id=user_in.user_id):
            raise NotFoundException("User not found")

        if not UserCouncil.get(db=self.db, user_id=user_in.user_id, council_id=council.id):
            raise InvalidActionException("Cannot delete user who is not part of council")

        if current_user.id == user_in.user_id:
            raise InvalidActionException("Admin cannot delete themselves")

        updated_users = council.remove_member(db=self.db, user_id=user_in.user_id)
        result = {"council": council, "users": updated_users}
        return result

最后,您的端点定义非常精简

编辑:从路径中删除了/remove个动词,正如注释中所指出的,该动词已经指定.理想情况下,路径应该包含指向资源的名词.

@router.delete("/{council_id}", response_model=responses.CouncilDetail)
def remove_user_from_council(
    council_id: int | UUID = Path(...),
    *,
    user_in: schemas.CouncilUser,
    current_user: Users = Depends(get_current_user),
    council: Councils = Depends(council_id_dep),
    council_service: CouncilService = Depends(get_council_service),
) -> responses.CouncilDetail:
    """

    DELETE /councils/:id (auth)

    remove user with `user_in` from council
    current user must be ADMIN of council
    """
    council_service.ensure_admin_council_member(current_user.id, council_id)
    return council_service.remove_council_member(user_in, council)

Python相关问答推荐

如何在msgraph.GraphServiceClient上进行身份验证?

Pandas实际上如何对基于自定义的索引(integer和非integer)执行索引

Pytest两个具有无限循环和await命令的Deliverc函数

pandas滚动和窗口中有效观察的最大数量

在线条上绘制表面

如何使用数组的最小条目拆分数组

我们可以为Flask模型中的id字段主键设置默认uuid吗

把一个pandas文件夹从juyter笔记本放到堆栈溢出问题中的最快方法?

使用groupby方法移除公共子字符串

matplotlib图中的复杂箭头形状

如何防止Pandas将索引标为周期?

替换现有列名中的字符,而不创建新列

如何使用正则表达式修改toml文件中指定字段中的参数值

语法错误:文档. evaluate:表达式不是合法表达式

使用Python TCP套接字发送整数并使用C#接收—接收正确数据时出错

获取git修订版中每个文件的最后修改时间的最有效方法是什么?

Python:从目录内的文件导入目录

如何在Python中解析特定的文本,这些文本包含了同一行中的所有内容,

根据边界点的属性将图划分为子图

如何在networkx图中提取和绘制直接邻居(以及邻居的邻居)?