首页 > 其他分享 >在使用FastAPI处理数据输入的时候,对模型数据和路径参数的一些转换处理

在使用FastAPI处理数据输入的时候,对模型数据和路径参数的一些转换处理

时间:2024-08-26 12:28:02浏览次数:8  
标签:str 处理 FastAPI 对模型 items model data 字段名

在开发Python的后端API平台的时候,为了兼容我SqlSugar开发的一些Winform端、BS端、UniApp端、WPF端等接入,由于部分是基于.net的处理,因此可能对于接入对象的属性为常见的Camel的驼峰命名规则,但是Python一般约定属性名称为小写,因此需要对这个模型进行兼容;另外默认FastAPI路由路径也是大小写敏感的,因此也需要做兼容处理,本篇随笔介绍使用FastAPI处理数据输入的时候,对模型数据和路径参数的一些转换处理。

1、默认Pydantic的大小处理

在 Pydantic 中,model_validate 方法用于验证和创建模型实例,并且默认情况下是大小写敏感的。也就是说,JSON 数据中的字段名需要与模型中的字段名完全匹配,包括大小写。

Pydantic 默认不支持直接取消字段名的大小写敏感性。为了处理字段名的大小写敏感问题,我们需要另外处理,有几种方式进行实现。

1)预处理 JSON 数据

在传递 JSON 数据到 model_validate 之前,手动将 JSON 数据中的字段名转换为模型所需的格式(例如,全部小写或全部大写)。

from pydantic import BaseModel, model_validate
from typing import Dict, Any

class MyModel(BaseModel):
    id: int
    name: str
    description: str

def preprocess_data(data: Dict[str, Any]) -> Dict[str, Any]:
    # 转换字段名为小写
    return {k.lower(): v for k, v in data.items()}

data = {
    "ID": 1,
    "NAME": "Test",
    "DESCRIPTION": "Sample description"
}

# 预处理数据
preprocessed_data = preprocess_data(data)

# 使用 model_validate 创建模型实例
model_instance = MyModel.model_validate(preprocessed_data)
print(model_instance)

2)使用自定义字段别名

在 Pydantic 模型中使用字段别名来处理不同的字段名称。这种方法适用于字段名有明确且一致的变化情况(例如,使用不同的大小写风格)。

from pydantic import BaseModel, Field

class MyModel(BaseModel):
    id: int = Field(..., alias='ID')
    name: str = Field(..., alias='NAME')
    description: str = Field(..., alias='DESCRIPTION')

data = {
    "ID": 1,
    "NAME": "Test",
    "DESCRIPTION": "Sample description"
}

# 使用 model_validate 创建模型实例
model_instance = MyModel.model_validate(data)
print(model_instance)

3)使用自定义数据解析

如果需要更复杂的字段名处理,你可以实现自定义解析逻辑。例如,通过编写一个函数来将数据字段名标准化为所需的格式。

from pydantic import BaseModel
from typing import Dict, Any

class MyModel(BaseModel):
    id: int
    name: str
    description: str

def normalize_keys(data: Dict[str, Any]) -> Dict[str, Any]:
    # 自定义字段名标准化规则
    normalized_data = {}
    for key, value in data.items():
        normalized_key = key.lower()  # 或其他规则
        normalized_data[normalized_key] = value
    return normalized_data

data = {
    "ID": 1,
    "NAME": "Test",
    "DESCRIPTION": "Sample description"
}

# 标准化数据
normalized_data = normalize_keys(data)

# 使用 model_validate 创建模型实例
model_instance = MyModel.model_validate(normalized_data)
print(model_instance)

最后这种方式相对比较好,不过每次都要求进行一个函数的转换,着实不太方便,万一忘记了呢?所以我希望使用一个没有显著调用过程的实现,隐式的处理方式,也就是使用使用model_validator进行隐式的转换处理。

model_validator 是 Pydantic v2 中用于模型验证的功能。要使用 model_validator 来处理字段名大小写不敏感的问题,你需要在模型中实现自定义的验证逻辑,以将字段名标准化为一致的格式(如小写)。

以下是如何使用 model_validator 处理字段名大小写不敏感的示例:

from pydantic import BaseModel, model_validator
from typing import Dict, Any

class MyModel(BaseModel):
    id: int
    name: str
    description: str

    @model_validator(mode='before')
    def normalize_fields(cls, values: Dict[str, Any]) -> Dict[str, Any]:
        # 将字段名转换为小写
        normalized_values = {}
        for key, value in values.items():
            normalized_key = key.lower()
            normalized_values[normalized_key] = value
        return normalized_values

# FastAPI 路由
from fastapi import FastAPI, Request

app = FastAPI()

@app.post("/items/")
async def create_item(request: Request):
    data = await request.json()
    model_instance = MyModel.model_validate(data)  # 使用 model_validate 创建模型实例
    return model_instance

详细说明

  1. 模型定义:定义一个继承自 BaseModel 的 Pydantic 模型,如 MyModel

  2. 使用 model_validator:使用 @model_validator 装饰器定义一个自定义的验证方法。在这个方法中,你可以将字段名转换为小写,以处理大小写不敏感的问题。

    • mode='before':指定在模型创建之前执行此验证器。
    • values 参数是一个字典,包含所有传入的数据字段。
    • normalized_values 字典用于存储转换后的字段名和值。
  3. 创建模型实例:在 FastAPI 路由处理函数中,使用 MyModel.model_validate(data) 创建模型实例。这里 data 是原始的 JSON 数据,经过 model_validator 处理后,字段名会被标准化为小写。

但是这样对于获得数据库对象,并转换为DTO对象(或者Schema对象)的时候,会导致模型转换出现问题,如下FastAPI的处理出现问题。

  totalCount, items = await self.crud.get_list(input, db)
  pydantic_items = [self.dto_class.model_validate(item) for item in items]

主要原因是模型对象转换为dict类型的时候出现错误,因此需要限定转换的对象为dict类型,修改下基类的模型处理如下所示。

class SchemaBase(BaseModel):
    """定义的DOT类基类,统一处理一些操作,如大小写不敏感,枚举值处理等"""

    model_config = ConfigDict(use_enum_values=True, from_attributes=True)

    # 要实现字段名的大小写不敏感,你可以在模型中使用 model_validator 来处理字段名的标准化。
    @model_validator(mode="before")
    def normalize_keys(cls, values: Any) -> Dict[str, Any]:
        # 检查请求体是否为空
        if not values:
            raise ValueError("Empty request body")

        # 如果 values 是 dict 类型,将其键名转换为小写
        if isinstance(values, dict):
            return {key.lower(): value for key, value in values.items()}

        else:
            return values

通过Python的继承关系处理,我们所有子类对象,都可以实现查询参数的无感的小写转换,而不影响数据库对象的转换。

转换注意

在 Pydantic v2 中,ConfigDict 是用于配置 Pydantic 模型行为的一个机制。str_to_lower 配置项用于将输入字符串转换为小写,但它主要适用于字符串类型字段的值,而不是字段名。

如果你需要实现模型字段名的大小写不敏感,你可以使用 model_validator 进行自定义处理。

另外,如果仅仅单独使用对request.query_params的键转换小写,那么在Post请求获得的Body内容,无法进行大小写转换的,而且可能触发Body内容提前被消耗而导致再次读取的时候错误,但是使用model_validator 进行自定义处理则是可以的,因此model_validator 是比较推荐的处理方式。

 

2、对路由路径大小写转换处理

 在 FastAPI 中,定义路由路径时,路径是大小写敏感的。这意味着 /items//Items/ 被视为两个不同的路径。如果你希望路由路径不区分大小写,需要在代码中进行自定义处理,因为 FastAPI 不原生支持这一特性。

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/")
async def read_items():
    return {"message": "This is /items/"}

@app.get("/Items/")
async def read_items_uppercase():
    return {"message": "This is /Items/"}

在上面的示例中,访问 /items//Items/ 会触发不同的路由处理函数。

如果你希望所有路由路径都不区分大小写,可以使用中间件来实现。例如,可以编写一个中间件,将请求路径转换为小写。

from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware

app = FastAPI()

class LowercaseMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        # 将路径转换为小写
        request.scope["path"] = request.scope["path"].lower()
        response = await call_next(request)
        return response

app.add_middleware(LowercaseMiddleware)

@app.get("/items/")
async def read_items():
    return {"message": "This is /items/"}

在这个例子中,无论是 /items//Items/ 还是 /ITEMS/,都将触发 read_items 函数,因为路径在中间件中被转换为小写。

这种实现方式会导致所有路由都不区分大小写,因此在设计路由时要考虑是否需要保持路径的区分。

 

3、在FastAPI的控制器处理中,提示获取不到request.user的值?

在 FastAPI 中,request.user 通常与身份验证系统相关,特别是在使用像 fastapi-users 或自定义认证中间件时。如果你在处理请求时无法获取 request.user 的值,可能有以下几个原因:

1)确保身份验证依赖项正确配置

request.user 通常依赖于身份验证依赖项或中间件。例如,如果你使用 OAuth2 或 JWT 验证,需要确保正确设置了依赖项以填充 request.user

 例如我们在FastAPI的路由器中定义一个接口,我们要求该接口读取用户的身份信息(通过token获取身份信息)

# 根据名称获取客户
@router.get(
    "/by-name",
    response_model=AjaxResponse[CustomerDto | None],
    summary="根据名称获取客户",
    dependencies=[DependsJwtAuth],
)
async def get_by_name(
    name: Annotated[str | None, Query()] = None,
    db: AsyncSession = Depends(get_db),
):
    item = await customer_crud.get_by_name(name, db=db)
    item = jsonable_encoder(item)
    return AjaxResponse(success=True, result=item)

其中DependsJwtAuth 就是要求通过Token验证的,否则提示权限不足,无法获得接口正常的数据。而它很简单的处理,如下代码

DependsJwtAuth = Depends(HTTPBearer())

由于我们在用户登录授权生成访问Token的时候,会返回相关的用户身份信息。

也就是验证的时候,可以获得用户的对象信息了

因此获得当前用户身份的信息代码,就可以正常工作了。

@router.get(
    "/me",
    summary="获取当前用户信息",
    response_model=AjaxResponse,
    dependencies=[DependsJwtAuth],
    response_model_exclude={"password"},
)
async def get_current_user(request: Request):
    data = GetCurrentUserInfoDetail(**request.user.model_dump())
    return AjaxResponse(data)

确保请求包含正确的身份验证信息(如 Authorization header)。如果缺少或不正确,request.user 可能无法被填充。

如果我们确认用户身份,可以直接获得相关的用户属性信息了(模型中包含fullname属性等)。

username = request.user.fullname

这样我们可以通过中间件的方式,把用户身份信息提取出来,进行访问的日志的记录用途了。

我们在很多接口里面,都需要用户进行登录获取授权令牌,并设置请求头来确认令牌信息,才能进行下一步的操作接口,也就是FastAPI 中自定义用户身份验证逻辑,需要继承 AuthenticationBackend 类并实现 authenticate 方法。

首先,需要安装 starlette,因为 AuthenticationBackendStarlette 框架的一部分,而 FastAPI 本身是基于 Starlette 的。

 最后通过处理验证后,可以返回相关的验证信息和用户对象。

 

return AuthCredentials(["authenticated"]), user

当然,我们也可以继承BaseUser来获得一些基础信息,返回这个用户对象信息。

class SimpleUser(BaseUser):
    def __init__(self, username: str):
        self.username = username

    @property
    def is_authenticated(self) -> bool:
        return True  # 用户是经过身份验证的

    @property
    def display_name(self) -> str:
        return self.username

你可以创建一个自定义的 AuthenticationBackend 子类,并实现 authenticate 方法。这个方法接收一个 Request 对象,并返回一个包含 AuthCredentialsBaseUser 的元组。

class CustomAuthBackend(AuthenticationBackend):
    async def authenticate(self, request: Request):
        # 从请求头获取认证信息
        auth_header: Optional[str] = request.headers.get("Authorization")

        if auth_header is None or not auth_header.startswith("Bearer "):
            return None  # 如果没有认证信息,返回 None 表示没有通过认证

        token = auth_header[len("Bearer "):]  # 提取令牌

        # 在这里添加你的令牌验证逻辑,例如验证 JWT 或从数据库中查询用户
        if token == "valid_token":  # 示例条件,应该替换为实际验证逻辑
            return AuthCredentials(["authenticated"]), SimpleUser("username")

        return None  # 认证失败时返回 None

最后,将自定义的认证后端添加到 FastAPI 应用中。使用 app.add_middleware 方法将认证后端集成到应用中。

app = FastAPI()
app.add_middleware(AuthenticationMiddleware, backend=CustomAuthBackend())

通过继承 AuthenticationBackend,你可以在 FastAPI 中实现自定义的身份验证逻辑,并将其应用于整个应用程序。这样可以灵活地处理各种身份验证方案,如 JWT、OAuth、或自定义的认证方式。

 

标签:str,处理,FastAPI,对模型,items,model,data,字段名
From: https://www.cnblogs.com/wuhuacong/p/18380729

相关文章

  • Python系列(9)- Python 异常处理机制
    1.错误和异常   编程开发时一般会遇到2种类型的错误,分别为语法错误和运行时错误。   语法错误(SyntaxError):Python解释器在解析代码时遇到的错误,比如拼写错误、不符合语法规则等。Python解释器会提示错误的类型和出错的位置,便于开发者及时纠正错误,在错误没有得......
  • Java 入门指南:异常处理的实践规范
    在Java中处理异常并不是一个简单的事情。需要花费很多时间来思考如何处理异常,包括需要处理哪些异常,怎样处理等等。抛出或捕获异常的时候,有很多不同的情况需要考虑,而且大部分事情都是为了改善代码的可读性或者API的可用性。异常不仅仅是一个错误控制机制,也是一个通信媒......
  • 在 SQLAlchemy 中实现数据处理的时候,实现表自引用、多对多、联合查询,有序id等常见的一
    有时候,我们在使用SQLAlchemy操作某些表的时候,需要使用外键关系来实现一对多或者多对多的关系引用,以及对多表的联合查询,有序列的uuid值或者自增id值,字符串的分拆等常见处理操作。1、在SQLAlchemy中定义具有嵌套children关系的表要在SQLAlchemy中定义具有嵌套children关系......
  • B站宋红康JAVA基础视频教程个人笔记chapter08-09(异常处理+多线程)
    文章目录1.异常处理方式1:try-catch-finally2.异常处理方式1:throws3.程序,进程,线程的区别4.线程的创建4.1线程的创建方式1:4.2线程的创建方式2:5.线程类的常用方法和生命周期5.1线程的生命周期jdk5之前6.线程的安全问题和同步机制6.线程之间的通信6.1为什么需要线程之间......
  • fastapi 跨域请求
    问题描述在前后端开发中遇到一个问题,前端发送请求后,后端报'OPTIONS/mock/user/loginHTTP/1.1'405MethodNotAllowed,如下图:但用fastapi自带的swagger或postman测试又是可以的。 定位原因这是因为在跨域的情况下,在浏览器发起"复杂请求"时主动发起的。跨域共享标准规范......
  • 项目启动端口报冲突如何处理?
    你是否在Angular项目启动的时候试过端口报冲突呢?那么要如何解决呢?vue2又如何处理呢?一、Angular冲突原因AngularCLI默认使用4200端口,如果这个端口已被占用(比如启动了两次,或者本地可能有别的项目已经在使用这个端口),那么启动的时候会报端口冲突二、解决angular项目启动冲......
  • 【Rust光年纪】文本分析利器:探索Rust语言的多功能文本处理库
    从情感分析到关键词提取:Rust语言文本分析库详解前言随着自然语言处理技术的不断发展,对各种文本数据进行分析和处理的需求也在不断增加。本文将介绍一些用于Rust语言的文本分析和处理库,包括情感分析、自然语言处理、中文转换、语言检查和关键词提取等方面的工具和资源。......
  • OpenCV 图像处理中滤波技术介绍
    VS2022配置OpenCV环境关于OpenCV在VS2022上配置的教程可以参考:VS2022配置OpenCV开发环境详细教程图像处理中滤波技术图像滤波是图像处理中的一种重要技术,用于改善图像质量或提取图像中的特定特征。以下是一些常见的图像滤波技术:均值滤波(MeanFilter):简单且广泛使用的......
  • 自然语言处理与情绪智能
    自然语言处理(NLP)基础:语言模型ChatGPT能力语言理解和生成能力抽象能力强大的学习和泛化能力自然语言处理交叉学科:计算机科学、人工智能/机器学习、语言学等自然语言理解:理解文字的含义自然语言生成:用文字表达特定的意图和思想利用计算机对自然语言进行各种加工处理、信息......
  • 理解 Python 中的异常处理机制
    理解Python中的异常处理机制在软件开发中,异常是不可避免的。无论是由于用户输入错误、文件未找到,还是网络连接失败,异常都可能在程序运行时发生。Python提供了一种强大的异常处理机制,使得开发者能够优雅地处理这些错误,而不至于让程序崩溃。本文将深入探讨Python中的异......