当前位置: 首页 > news >正文

fastapi: 参数校验时自定义报错信息(get/post/json/基于basemodel类/基于Annotated参数)

一,代码:

异常处理

    # 2. 捕获 FastAPI 参数校验异常 (422 Unprocessable Entity)@app.exception_handler(RequestValidationError)async def validation_exception_handler(request: Request, exc: RequestValidationError):# 获取当前请求路由的匹配结果route = request.scope.get("route")custom_errors = []str_errors = ''# 获取异常自带的错误详情errors = exc.errors()for error in errors:# loc 包含了出错字段的路径,例如 ("body", "username") 或 ("query", "age")print("error:", error )loc = error.get("loc", [])print("loc:", loc)field_name = loc[-1] if loc else Nonelocation_type = loc[0] if len(loc) >= 1 else Noneprint("field_name:", field_name)description = "未定义描述"print("location_type:", location_type)# 如果是请求体(Body)校验失败,尝试从路由函数签名中找 Pydantic Model 并提取 descriptionis_base = is_basemodel(route, field_name)   # 判断字段是否是基于BaseModel的子类print("is_base:", is_base)if route and hasattr(route, "dependant") and field_name:print("开始查找数据:", field_name)# 遍历路由中所有的 Query 参数定义all_flat_params = (route.dependant.query_params +route.dependant.path_params +route.dependant.header_params +route.dependant.cookie_params# route.dependant.body_params)for param in all_flat_params:print("dir_param:", dir(param))# 匹配当前报错的字段名(比如 "q")print("param.name:", param.name)if param.name == field_name:# 关键点:FastAPI 把 Query(description="...") 存放在了 param.field_info 中if hasattr(param, "field_info") and getattr(param.field_info, "description", None):description = param.field_info.descriptionprint("description1:",description)error_msg = f"{description}"if str_errors == '':str_errors = error_msgelse:str_errors += ";" + error_msgbreakbody_flat_params = (route.dependant.body_params)for param in body_flat_params:print("dir_param:", dir(param))# 匹配当前报错的字段名(比如 "q")print("param.name:", param.name)# if param.name == field_name:# 关键点:FastAPI 把 Query(description="...") 存放在了 param.field_info 中if hasattr(param, "field_info") and getattr(param.field_info, "description", None):description = param.field_info.descriptionprint("description1:",description)error_msg = f"{description}"if str_errors == '':str_errors = error_msgelse:str_errors += ";" + error_msgbreakif (location_type == "body" or location_type == "query") and route and field_name:endpoint = route.endpoint# 遍历路由函数的参数,寻找继承自 BaseModel 的参数import inspectsig = inspect.signature(endpoint)print(sig.parameters.items())field_info = ''for param_name, param in sig.parameters.items():model_cls = param.annotationprint("model_cls:",model_cls)print(dir(model_cls))if hasattr(model_cls, 'model_fields'):field_info = model_cls.model_fields.get(field_name)print("field_info:", field_info)if field_info and field_info.description:error_msg = f"{field_info.description}"print("error_msg:", error_msg)if str_errors == '':str_errors = error_msgelse:str_errors += ";" + error_msgbreakelse:print("没有model_fields属性")if str_errors == '':one_str_error = field_name + ":" + error['msg']str_errors = one_str_errorcontent = BaseResponse(code=422, msg=str_errors, data=custom_errors).model_dump()return JSONResponse(status_code=422,content=content)

调用:使用Annotated参数

@router.post("/onepost")
def get_one_post(# 校验 Query (GET) 参数:长度必须在 3 到 10 之间# q: Annotated[str, Query(min_length=3, max_length=10)],# 校验 Body (POST) 参数:必须大于 0# product_id: Annotated[float, Body(gt=0.0)],product_id: Annotated[int, Body(gt=0,description="商品id,需要是大于0的整数")],
):print("测试参数检测:",)return {"id": product_id, "name": f"未知"}@router.get("/oneprod")
def get_one_product(# 校验 Query (GET) 参数:长度必须在 3 到 10 之间# q: Annotated[str, Query(min_length=3, max_length=10)],# 校验 Body (POST) 参数:必须大于 0# product_id: Annotated[float, Body(gt=0.0)],product_id: Annotated[int, Query(gt=0,description="商品id,需要是大于0的整数")],
):print("测试参数检测:",)return {"id": product_id, "name": f"未知"}

基于basemodel类:get/post

# 1. 定义请求体数据模型
class ProductPage(BaseModel):category: str = Field(..., min_length=3, max_length=20,description="分类名长度是3-20个字符")page: int = Field(..., gt=0, description="页数需要大于0")per_page: int = Field(..., gt=0, description="每页数量需要大于0")@router.get("/all")
async def get_all_products(prod_data: Annotated[ProductPage, Query()],db: AsyncSession = Depends(get_db)):page = prod_data.pageper_page = prod_data.per_page# 异步写法不能直接用 db.query(),必须用 selectskip = (page-1) * per_pagelimit = per_pagestmt = select(Product).offset(skip).limit(limit)result = await db.execute(stmt)  # 必须 await 数据库 I/Oprods = result.scalars().all()# 异步查询总条数 (注意 await)count_stmt = select(func.count()).select_from(Product)total_items = await db.scalar(count_stmt)# 计算分页元数据total_pages = math.ceil(total_items / per_page) if total_items > 0 else 1has_prev = page > 1has_next = page < total_pagesres = {"list": prods,"meta": {"total_items": total_items,"total_pages": total_pages,"current_page": page,"per_page": per_page,"has_prev": has_prev,"has_next": has_next,"prev_page": page - 1 if has_prev else None,"next_page": page + 1 if has_next else None}}return success(data=res)# 1. 定义请求体数据模型
class ProductCreate(BaseModel):name: str = Field(..., min_length=3, max_length=20,description="商品名称,长度是3-20个字符")price: int = Field(..., gt=0, description="价格需要大于0")stock: int = Field(..., ge=0, description="库存不能小于0")# 如果请求方式是get,使用: params: Annotated[FilterParams, Query()]@router.post("/add")
async def create_user(prod_data: Annotated[ProductCreate, Form()],db: AsyncSession = Depends(get_db)):# 检查用户名是否已存在result = await db.execute(select(Product).where(Product.name == prod_data.name))existing_prod = result.scalar_one_or_none()if existing_prod:raise HTTPException(status_code=400, detail="产品名已被使用")# 创建新产品new_prod = Product(name=prod_data.name,stock=prod_data.stock,price = prod_data.price,)# 添加到数据库db.add(new_prod)await db.commit()  # 提交事务await db.refresh(new_prod)  # 刷新,获取数据库生成的字段(如id)return success(data=new_prod)

基于basemodel类:json

# 1. 定义请求体数据模型
class UserLogin(BaseModel):username: str = Field(..., min_length=3, max_length=20,description="用户名,长度是3-20个字符")age: int = Field(..., ge=18, le=100, description="年龄必须在18到100岁之间")password: str = Field(..., min_length=6, description="密码长度最少6个字符")@router.post("/login")
async def account_login(user: UserLogin):user_info = {"age": user.age, "name": f"用户{user.username}"}return success(data=user_info)

 

二,测试效果:

get

image

get

image

post

image

post:

image

post:

image

 

http://www.jsqmd.com/news/1055003/

相关文章:

  • 闲置翡翠怎么卖?2026上海正规回收平台实测与价格参考 - 奢侈品交易观察员
  • i.MX6启动配置全解析:从引脚、熔丝到硬件设计的实战指南
  • 手机端音频格式转换工具实测,这4款最稳定 - 软件工具教程方法
  • 朋友圈九宫格拼图怎么做,这些工具能一键搞定 - 软件工具教程方法
  • 10种方法将图片快速转换为JPG格式 - 软件工具教程方法
  • MC68HC908MR24 TIMA模块PWM配置详解:无缓冲与缓冲模式实战
  • Reloaded-II终极指南:如何快速搭建跨平台游戏修改框架
  • DeepSeek稳定调用工程实践:硅基流动+华为云+Chatbox三端协同方案
  • 无人机培训费用模型:培训价、考试服务、设备使用和补训补考怎么拆 - 资讯速览
  • 枣恒一体化泵站厂家推荐 :一体式预制雨水泵站安装全流程教程 从基坑开挖到调试验收标准化操作指南 - 泵站19832680777
  • MC68HC908AT32定时器与ADC实战:从寄存器配置到低功耗数据采集系统
  • 深度解析Defender-Control:如何彻底禁用Windows Defender的完整技术方案
  • 图片尺寸调整工具怎么选?看看这6个实用推荐 - 软件工具教程方法
  • 深圳星级酒店民宿商旅客房隔音怎么做?|静华轩隔音窗|隔绝过道人声、外机噪音、临街车流、客房隔墙传声,商旅项目批量隔声定制 - 维小达科技
  • Ubuntu 18.04 下用 apt 精准安装 OpenJDK 11 的避坑指南
  • 2026上海奢侈品首饰回收攻略:麒麟首饰报价与交易避坑 - 奢侈品交易观察员
  • 2027澳洲留学中介推荐前十名,QS澳八大集体上升后真实案例看口碑 - 资讯速览
  • 2026年6月口碑好的涡轮增压器组件口碑推荐,北汽2.0增压器/威孚增压器/7830增压器,涡轮增压器厂家推荐分析 - 品牌推荐师
  • B站会员购抢票神器:biliTickerBuy终极使用指南
  • mp4转换成mp3,用手机自带工具就能做到 - 软件工具教程方法
  • 2026 年 6 月百达翡丽中国官方售后维修网点全面整改升级 全新专线咨询电话正式上线 - 百达翡丽中国服务中心
  • 2026年6月目前靠谱的氢氧化钠回收公司怎么选择,氢氧化钠回收厂家,氢氧化钠回收,快速处理提高效率 - 品牌推荐师
  • 2026 年 6 月百达翡丽官方售后门店资质实地查验报告 覆盖全国 60 + 正规服务点 - 百达翡丽中国服务中心
  • 北京猎头公司哪家好?哪家口碑好?推荐南方新华 - 榜单推荐
  • 重庆天寿建筑工程有限公司:乡村别墅/自建房/商场改造等土建现浇服务优选 - 品牌推荐官
  • i.MX53xD处理器I/O阻抗匹配与信号完整性设计实战指南
  • 2026成都二手房装修公司线下实地测评10家,老房全屋改造避坑与防潮工艺详解 - 推荐官
  • 2026孝感本地正规瓷砖空鼓维修服务商盘点|无损免拆砖修复,全域上门售后有保障 - 宅安选房屋修缮
  • 2026最新!青海旅游公司综合服务能力深度盘点推荐:谁在真正为游客兜底保驾护航! - 资讯速览
  • 从入门到实用:推荐 5 款免费且干净的压缩照片工具 - 软件工具教程方法