首页 > 编程问答 >一个 put 端点是否有可能在 fastapi、pydantic 上拥有超过 1 种类型的参数

一个 put 端点是否有可能在 fastapi、pydantic 上拥有超过 1 种类型的参数

时间:2024-07-25 15:51:55浏览次数:11  
标签:python fastapi pydantic

因此,首先,这个问题的目标是获得更多有关是否可能以及害虫做法的知识。

有 3 种类型的用户 7、6为普通用户 5,4 是出版商 3,2 是经理 1 是 admin

我使用此函数来专用 user_role 这是第一个问题 我会经常使用这个函数,我想了一下,

我可以将 user_role 添加到 JWT 令牌,但我认为这会造成安全问题,对吗?

async def check_permission(
    permission: int,
    current_user: user_dependecny,
    db: db_dependecny,
    error: bool = True,
):
    current_user_role = (
        db.query(user.User).filter(user.User.id == current_user.id).first().user_role
    )
    # 7= reader, 6= Pro_reader,
    # 5= publisher, 4= editor,
    # 3= small_manager, 2= big_manager, 1= admin
    if current_user_role <= permission:
        return True
    elif error == False:
        return False
    elif current_user_role > permission:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN, detail="Do not have premisson."
        )

标题上的主要问题

尝试在一个参数上使用多个 data_type 是可以通过使用 Union

from typing import Annotated, Union, cast
type1 = None
type2 = None
type3 = None
var: Union[type1,type2,type3]

第一次尝试使用 model_response 上的 get enpoint 来实现的,它起作用了。

下面的代码正在做的是根据您的 user_role 返回用户数据并执行此操作我需要 3 个不同的基础模型

@router.get(
    "/{user_id}/", response_model=Union[SelfUserInfo, ManagerUserInfo, UserInfo]
)
async def read_user(
    user_id: int,
    db: db_dependecny,
    current_user: user_dependecny,
):
    db_user = db.query(user.User).filter(user.User.id == user_id).first()
    if db_user is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="user is not found"
        )
    if await check_permission(3, current_user, db, error=False) == True:
        user_data = ManagerUserInfo(
            username=db_user.username,
            avatar_url=db_user.avatar_url,
            user_role=db_user.user_role,
            email=db_user.email,
            country_id=db_user.country_id,
            last_login=db_user.last_login,
            date_joined=db_user.date_joined,
            is_verfied=db_user.is_verfied,
            state=db_user.state,
            is_staff=db_user.is_staff,
            is_superuser=db_user.is_superuser,
        )
        return user_data
    else:
        if current_user.id == user_id:
            user_data = SelfUserInfo(
                username=db_user.username,
                avatar_url=db_user.avatar_url,
                user_role=db_user.user_role,
                email=db_user.email,
                country_id=db_user.country_id,
                last_login=db_user.last_login,
                date_joined=db_user.date_joined,
                is_verfied=db_user.is_verfied,
            )
            return user_data
        else:
            user_data = UserInfo(
                username=db_user.username,
                avatar_url=db_user.avatar_url,
                user_role=db_user.user_role,
            )
        return user_data

关于此代码的第二个问题是它没有遵循害虫实践。

最后一个代码是我有问题

我的问题是 user_data 类型始终是 SelfUserUpdate

特别是第一个类型|| |如何解决这个问题,这样做可以吗? 制作 3 个不同的端点。

user_data: Union[SelfUserUpdate, ManagerUserUpdate, AdminUserUpdate], 

how to fix it and is it okey and not a ba practice to do it like that instead of making 3 deferent endpoints.

@router.put("/{user_id}/")
async def Update_user(
    user_id: int,
    current_user: user_dependecny,
    db: db_dependecny,
    user_data: Union[SelfUserUpdate, ManagerUserUpdate, AdminUserUpdate],
):
    db_user = db.query(user.User).filter(user.User.id == user_id).first()
    if db_user is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="user is not found"
        )
    if await check_permission(1, current_user, db, error=False) == True:
        # Admin edit user
        db_user.username = user_data.username
        db_user.avatar_url = user_data.avatar_url
        db_user.email = user_data.email
        db_user.country_id = user_data.country_id
        db_user.user_role = user_data.user_role
        db_user.state = user_data.state
        db_user.is_staff = user_data.is_staff
        db_user.is_verfied = user_data.is_verfied
        db_user.is_superuser = user_data.is_superuser
        db.commit()
        db.refresh(db_user)
        return user_data

    elif check_permission(3, current_user, db, error=False) == True:
        # Manager edit user
        db_user.username = user_data.username
        db_user.avatar_url = user_data.avatar_url
        db_user.email = user_data.email
        db_user.country_id = user_data.country_id
        db_user.user_role = user_data.user_role
        db_user.state = user_data.state
        db_user.is_staff = user_data.is_staff
        db.commit()
        db.refresh(db_user)
        return user_data
    elif current_user.id == user_id:
        # current user edit him self
        db_user.username = user_data.username
        db_user.avatar_url = user_data.avatar_url
        db_user.email = user_data.email
        db_user.country_id = user_data.country_id
        db.commit()
        db.refresh(db_user)
        return user_data
    else:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="You can't change This user.",
        )

好的,让我们来分解一下如何在你的FastAPI应用程序中处理不同用户角色和数据验证。

1. JWT 与用户角色

你问到在JWT令牌中包含 user_role 是否安全。一般来说,最好 不要 在JWT令牌本身中包含敏感信息,如角色。这是因为:

  • JWT令牌未经加密,只是经过签名。 任何拥有该令牌的人都可以对其进行解码并查看其内容。
  • 令牌过期时间可能很长。 如果令牌泄露,它可能会在一段时间内被用于访问敏感信息。

更好的方法:

  • 在用户登录时生成一个JWT令牌,其中仅包含用户ID和其他必要信息。
  • 在每次请求中,使用身份验证中间件从JWT令牌中提取用户ID。
  • 使用用户ID从数据库中查询完整的用户信息,包括角色。

2. 多类型请求体

你想在 PUT /users/{user_id}/ 端点上接受不同的请求体,具体取决于用户角色。虽然使用 Union 在技术上是可行的,但这会导致代码复杂且难以维护。

更好的方法:

  • 为每种用户角色创建单独的Pydantic模型。 这可以更清晰地定义每种角色允许更新哪些字段。
  • 创建一个单一的 UserUpdate 模型,并在其中使用可选字段。 这可以简化路由逻辑,但在处理角色特定验证时需要额外的代码。

以下是使用单独模型的示例:

from typing import Optional
from pydantic import BaseModel

class SelfUserUpdate(BaseModel):
    username: Optional[str] 
    avatar_url: Optional[str] 
    email: Optional[str]
    country_id: Optional[int]

class ManagerUserUpdate(BaseModel):
    username: Optional[str] 
    avatar_url: Optional[str] 
    email: Optional[str]
    country_id: Optional[int]
    user_role: Optional[int]
    state: Optional[str]
    is_staff: Optional[bool]

class AdminUserUpdate(BaseModel):
    # 所有字段都是可选的,因为管理员可以更新任何内容
    username: Optional[str] 
    avatar_url: Optional[str] 
    email: Optional[str] 
    country_id: Optional[int]
    user_role: Optional[int]
    state: Optional[str]
    is_staff: Optional[bool]
    is_verified: Optional[bool]
    is_superuser: Optional[bool]

@router.put("/{user_id}/")
async def update_user(
    user_id: int,
    current_user: user_dependecny,
    db: db_dependecny,
    user_data: Union[SelfUserUpdate, ManagerUserUpdate, AdminUserUpdate] = Body(...),
):
    db_user = db.query(user.User).filter(user.User.id == user_id).first()

    if db_user is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
        )

    if current_user.id == user_id:
        # 自我更新
        if isinstance(user_data, SelfUserUpdate):
            # 应用更新
            update_data = user_data.dict(exclude_unset=True)
            for key, value in update_data.items():
                setattr(db_user, key, value)
        else:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions"
            )

    elif await check_permission(3, current_user, db, error=False):
        # 管理员更新
        if isinstance(user_data, (ManagerUserUpdate, AdminUserUpdate)):
            # 应用更新
            update_data = user_data.dict(exclude_unset=True)
            for key, value in update_data.items():
                setattr(db_user, key, value)
        else:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions"
            )

    elif await check_permission(1, current_user, db, error=False):
        # 超级管理员更新
        if isinstance(user_data, AdminUserUpdate):
            # 应用更新
            update_data = user_data.dict(exclude_unset=True)
            for key, value in update_data.items():
                setattr(db_user, key, value)
        else:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions"
            )
    else:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions"
        )

    db.commit()
    db.refresh(db_user)
    return user_data

3. 代码改进建议

  • 不要在 check_permission 函数中使用 error 参数。 直接引发异常会更清晰,并且可以让你利用FastAPI的自动异常处理机制。
  • 使用Pydantic模型来验证数据库查询结果。 这可以确保数据一致性,并简化代码。
  • 考虑使用依赖注入来简化数据库访问。

通过遵循这些建议,你可以创建一个更安全、更易于维护和更符合RESTful API最佳实践的FastAPI应用程序。

标签:python,fastapi,pydantic
From: 78790857

相关文章

  • 在Python中字典是如何通过哈希表实现的以及哈希冲突是如何解决的
    哈希表(散列表)的工作原理哈希表是一种使用哈希函数组织数据,以支持快速插入和搜索的数据结构。它通过哈希函数将输入的键(key)映射到表中的一个位置(即索引或槽位),从而以接近常数时间复杂度进行查找、插入和删除操作。哈希表的基本工作流程如下:哈希函数:哈希函数接受一个输入(键),并......
  • python cobs协议编解码算法demo
    1.SummaryCOBS(ConsistentOverheadByteStuffing)是一种算法,直译为一致的开销字节填充。简而言之,无论数据包的内容如何,都能通过产生高效可靠明确的数据包帧,从而使接受端能够从损坏的包中恢复。通常使用0x00来作为数据包的分隔符,即切割数据包的片分隔符。当使用0x00作为......
  • 如何将unicode编码为字节,以便可以检索到原始字符串?在Python 3.11中
    在python3.11中,我们可以对字符串进行编码,如:string.encode('ascii','backslashreplace')这对于说:hellö=>hell\\xf6但是当我插入时hellöw\\xf6rldIgethell\\xf6w\\xf6rld(注意第二个有一个看起来像字符转义序列的文字部分)......
  • python flask允许跨域
    flask接口支持跨域设置方法在Flask中,可以通过安装flask-cors扩展来支持跨域请求。下面是使用flask-cors扩展的示例代码:fromflaskimportFlaskfromflask_corsimportCORS#ipinstallflask-corsapp=Flask(__name__)CORS(app)可以通过CORS扩展的origins参数......
  • FastAPI - 如何处理 websocket 端点中的通用异常
    我想了解在FastAPI应用程序中处理websocket端点异常的推荐方法是什么。我尝试过:app.add_exception_handler(Exception,handle_generic_exception)它捕获Exception,但它没有捕获,例如ValueError|||我也尝试过使用但它似乎不适用于web......
  • Pydantic 的基本模型中不需要
    我尝试从API接受数据,然后使用Pydantic基本模型验证响应结构。但是,我遇到的情况是,有时某些字段不会包含在响应中,而有时会包含在响应中。问题是,当我尝试验证结构时,Pydantic开始抱怨这些字段“丢失”,尽管它们有时可能会丢失。我真的不明白如何将一个字段定义为“missible”。文......
  • 在 Python 中动态定义文字字符串排列的并集
    我有一个字符串列表:strings=['a','b','c']我想声明列表中所有可能的有序对的Union类型。硬编码,这看起来像:Literal我如何动态定义CustomType=Literal['ab','ac','aa','ba','bb','bc�......
  • 关于 Python 中装饰器缓存的困惑
    我正在使用Python装饰器来实现函数的缓存。我了解缓存结果以提高性能的基本概念,但我正在努力解决如何处理不同的函数参数并确保底层数据更改时缓存更新。我已经实现了一个基本装饰器,它将函数结果存储在基于参数的字典。但是,此方法无法处理函数参数可能具有复杂结构(如嵌套列......
  • Python:__add__ 和 +,浮点数和整数的不同行为
    当将整数值添加到浮点值时,我意识到如果在浮点上调用该方法可以正常工作,例如:__add__但如果在整数上调用则不行:>>>n=2.0>>>m=1>>>n.__add__(m)3.0起初我认为|||只是对>>>m.__add__(n)NotImplemented和__add__类型的实现方式不同(例如f......
  • python中scrapy爬取数据get()与getall()区别
    在使用scrapy进行爬取数据的时候,有些时候需要爬取的是一段文本,或者一个div里面有很多内容,这时候我们就要使用到get()或者getall()来获取数据: get():是获取的满足条件的第一个数据。getall():是获取的满足条件的所有数据。scrapyget()getall()原理在Scrapy中,get(......