python:Fastapi - 异常处理、路径配置及JSON编码器
简单絮叨一些
上篇文章主要唠了表单和文件上传等功能,这次主要是唠下异常处理、路径操作配置和JSON
兼容编码器等。异常处理就是针对
某些情况下,需要向客户端返回错误提示。路径操作配置就是路径操作装饰器支持多种配置参数。JSON
兼容器是在某些情况
下,您可能需要将数据类型转换为与 JSON
兼容的类型等,然后存储起来。
处理错误
使用HTTPException
某些情况下,需要向客户端返回错误提示。
需要向客户端返回错误提示的场景主要如下:
- 客户端没有执行操作的权限
- 客户端没有访问资源的权限
- 客户端要访问的项目不存在
- 等等 …
以上情况通常返回4xx
的HTTP
状态码,4xx
的状态码通常是指客户端发生的错误。
向客户端返回 HTTP
错误响应,可以使用 HTTPException
,这也是fastapi
默认。
from fastapi import FastAPI
from fastapi import HTTPException
app = FastAPI()
items = {
"foo": "333_2222_666"
}
@app.get("/items/{item_id}")
async def read_item(item_id: str):
"""
初次使用HTTPException
:param item_id:
:return:
"""
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item": items[item_id]}
注释信息:
raise HTTPException(status_code=404, detail="Item not found")
是触发异常并抛出detail="Item not found"
参数detail
传递任何能转换为JSON
的值,不仅限于str
,还支持传递dict
、list
等数据结构
注意点:
Python
异常,不能return
,只能raise
。使用
return
虽然不会报错,但牵扯到多层调用时,异常信息不一定会全部抛出,而raise
是专门抛异常的关键字。
启动服务:
PS E:\git_code\python-code\fastapiProject> uvicorn handle_main:app --reload
请求接口:
GET http://127.0.0.1:8000/items/bee
请求结果:
{
"detail": "Item not found"
}
因为bee
不在items
字典中,故触发异常并抛出异常信息。
添加自定义响应头
为HTTP
错误添加自定义信息头,暂时使用场景还没实操,可先了解下即可。
from fastapi import FastAPI
from fastapi import HTTPException
app = FastAPI()
@app.get("/items-header/{item_id}")
async def read_item_header(item_id: str):
"""
自定义响应头
:param item_id:
:return:
"""
if item_id not in items:
raise HTTPException(
status_code=404,
detail="Item not found",
headers={"H-Error": "This is an error message."}
)
return {"item": items[item_id]}
注释信息:
headers={"H-Error": "This is an error message."}
添加自定义响应头
自定义异常处理器
自定义异常顾名思义就是自己去写一个异常处理器,不过也是继承python
中异常的基类Exception
。
from fastapi import FastAPI
from fastapi import Request
from fastapi.responses import JSONResponse
class UnicornException(Exception):
def __init__(self, name):
self.name = name
app = FastAPI()
@app.exception_handler(UnicornException)
async def unicorn_exception_handle(request: Request, exc: UnicornException):
return JSONResponse(
status_code=418,
content={"message": f"{exc.name} 错误..."}
)
@app.get("/unicorn/{name}")
async def read_unicorn(name: str):
"""
引用自定义异常UnicornException
:param name:
:return:
"""
if name == "lifeng":
raise UnicornException(name=name)
return {"unicorn_name": name}
注释信息:
class UnicornException(Exception):
定义一个继承Exception
的类,并顶一个name
属性@app.exception_handler(UnicornException)
是添加自定义异常控制器
启动服务:
PS E:\git_code\python-code\fastapiProject> uvicorn handle_main:app --reload
请求接口:
GET http://127.0.0.1:8000/unicorn/lifeng
请求 unicorn/lifeng
时,路径操作会触发 UnicornException
。
因该异常将会被 unicorn_exception_handler
处理。
接收到的错误信息清晰明了,HTTP
状态码为 418
,请求结果的JSON
内容如下:
{
"message": "lifeng 错误..."
}
覆盖默认异常处理器
触发 HTTPException
或请求无效数据时,这些处理器返回默认的 JSON
响应结果。
不过,也可以使用自定义处理器覆盖默认异常处理器。
1 - 覆盖请求验证异常
覆盖默认异常处理器时需要导入 RequestValidationError
,并用 @app.excption_handler(RequestValidationError)
装饰异常处理器。
这样,异常处理器就可以接收 Request
与异常。
from fastapi import FastAPI
from fastapi import HTTPException
from fastapi.responses import PlainTextResponse
from fastapi.exceptions import RequestValidationError
app = FastAPI()
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
return PlainTextResponse(str(exc), status_code=400)
@app.get("/cover/{cover_id}")
async def read_cover(cover_id: int):
if cover_id == 3:
raise HTTPException(status_code=418, detail="hahahha")
return {"cover_id": cover_id}
注释信息:
@app.exception_handler(RequestValidationError)
是添加自定义异常控制器return PlainTextResponse(str(exc), status_code=400)
是返回字符串类型的响应数据
启动服务:
PS E:\git_code\python-code\fastapiProject> uvicorn handle_main:app --reload
请求接口:
GET http://127.0.0.1:8000/cover/ttt
请求结果:
文本格式的错误信息
1 validation error for Request
path -> cover_id
value is not a valid integer (type=type_error.integer)
2 - 覆盖HTTPException
错误处理器
同理,也可以覆盖 HTTPException
处理器。
例如,只为错误返回纯文本响应,而不是返回 JSON
格式的内容:
from fastapi import FastAPI
from fastapi import HTTPException
from fastapi.responses import PlainTextResponse
from starlette.exceptions import HTTPException as StarletteHTTPException
app = FastAPI()
@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc):
return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
@app.get("/cover/{cover_id}")
async def read_cover(cover_id: int):
if cover_id == 3:
raise HTTPException(status_code=418, detail="hahahha")
return {"cover_id": cover_id}
注释信息:
from starlette.exceptions import HTTPException as StarletteHTTPException
引包后起别名@app.exception_handler(StarletteHTTPException)
是添加自定义异常控制器return PlainTextResponse(str(exc), status_code=400)
是返回字符串类型的响应数据
注意点:
from fastapi import HTTPException
实际就是继承的
from starlette.exceptions import HTTPException as StarletteHTTPException
所以http_exception_handler
实际就是重写了StarletteHTTPException
基类,自然就改变了默认的HTTPException
启动服务:
PS E:\git_code\python-code\fastapiProject> uvicorn handle_main:app --reload
请求接口:
GET http://127.0.0.1:8000/cover/3
请求结果:
文本格式的错误信息
hahahha
使用RequestValidationError
的请求体
使用RequestValidationError
包含其接收到的无效数据请求的 body
。
from fastapi import FastAPI
from fastapi import Request
from fastapi import status
from pydantic import BaseModel
from fastapi import HTTPException
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
app = FastAPI()
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}),
)
class Item(BaseModel):
title: str
size: int
@app.post("/items/")
async def create_item(item: Item):
return item
启动服务:
PS E:\git_code\python-code\fastapiProject> uvicorn handle_main:app --reload
请求接口:
POST http://127.0.0.1:8000/items
请求参数:
{
"title": "222",
"size": "####"
}
请求结果:
{
"detail": [
{
"loc": [
"body",
"size"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
],
"body": {
"title": "222",
"size": "####"
}
}
请求结果最后的body
字典就是说明数据是无效的。
复用 FastAPI
异常处理器
FastAPI
支持先对异常进行某些处理,然后再使用 FastAPI
中处理该异常的默认异常处理器。
从 fastapi.exception_handlers
中导入要复用的默认异常处理器:
from fastapi import FastAPI
from fastapi import HTTPException
from fastapi.exception_handlers import (
http_exception_handler,
request_validation_exception_handler
)
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException
app = FastAPI()
@app.exception_handler(StarletteHTTPException)
async def custom_http_exception_handler(request, exc):
print(f"333: {repr(request)}, 444: {repr(exc)}")
if exc.status_code == 418:
print(exc.detail)
return await http_exception_handler(request, exc)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
print(f"555: {repr(request)}, 666: {repr(exc)}")
return await request_validation_exception_handler(request, exc)
@app.get("/cover/{cover_id}")
async def read_cover(cover_id: int):
if cover_id == 3:
raise HTTPException(status_code=418, detail="hahahha")
return {"cover_id": cover_id}
上述代码中exc.status_code
是后台读出来的,这样我们就可以对异常处理后再返回了,repr
是可以看到源数据。
INFO: 127.0.0.1:51952 - "GET /cover/3 HTTP/1.1" 418 I'm a Teapot
333: <starlette.requests.Request object at 0x00000160206E2560>, 444: HTTPException(status_code=418, detail='hahahha')
hahahha
INFO: 127.0.0.1:52027 - "GET /cover/3 HTTP/1.1" 418 I'm a Teapot
555: <starlette.requests.Request object at 0x00000160206E2AD0>, 666: RequestValidationError(model='Request', errors=[{'loc': ('path', 'cover_id'), 'msg': 'value is not a valid integer', 'type': 'type_error.in
teger'}])
INFO: 127.0.0.1:52032 - "GET /cover/tt HTTP/1.1" 422 Unprocessable Entity
路径操作配置
status_code
用于定义路径操作响应中的HTTP
状态码。可以直接传递int
代码, 比如404
。如果记不住数字码的涵义,也可以用status
的快捷常量。tags
参数的值是由str
组成的list
(一般只有一个 str ),tags
用于为路径操作添加标签路径装饰器还支持
summary
和description
这两个参数,summary
是文档中的一个标题,description
是文档中接口的描述信息response_description
参数用于定义响应的描述说明(注意,response_description
只用于描述响应,description
一般则用于描述路径操作)。deprecated
参数可以把路径操作标记为弃用,无需直接删除,文档中显示为置灰
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
from fastapi import status
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
@app.post("/items/", response_model=Item,
tags=["itemss"], status_code=201 or status.HTTP_201_CREATED,
summary="Testing",
description="描述信息",
response_description="参数用于定义响应的描述说明",
deprecated=True
)
def read_item(item: Item):
"""
状态码可以是纯数字或者快捷常量都可以
:param item:
:return:
"""
return item
启动服务:
PS E:\git_code\python-code\fastapiProject> uvicorn config_main:app --reload
浏览器请求接口:
http://127.0.0.1:8000/docs
即可看到对应参数对应的显示效果。
JSON兼容编码器
在某些情况下,您可能需要将数据类型转换为与 JSON
兼容的类型(如dict
、list
等)。
然后需要将其存储在数据库中,为此,FastAPI
提供了一个jsonable_encoder()
功能。
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
from fastapi import status
from fastapi.encoders import jsonable_encoder
results_dict = {}
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
app = FastAPI()
@app.put("/items/{item_id}", status_code=201 or status.HTTP_201_CREATED)
def read_item(item_id, item: Item):
"""
jsonable_encoder实际上是FastAPI内部用来转换数据的。
:param item_id:
:param item:
:return:
"""
data = jsonable_encoder(item)
results_dict[item_id] = data
return results_dict
启动服务:
PS E:\git_code\python-code\fastapiProject> uvicorn config_main:app --reload
请求接口:
PUT http://127.0.0.1:8000/items/foo
请求参数:
{
"name": "lifeng",
"price": 3.3
}
请求参数:
{
"foo": {
"name": "lifeng",
"description": null,
"price": 3.3
}
}
jsonable_encoder
实际上是FastAPI
内部用来转换数据的。但它在许多其他场景中很有用。
今天先聊到这里吧,以上总结或许能帮助到你,或许帮助不到你,但还是希望能帮助到你,如有疑问、歧义,直接私信留言会及时修正发布;非常期待你的一键 3 连【 点赞、收藏、分享 】哟,谢谢!
未完成,待续……
一直在努力,希望你也是!
微信搜索公众号:就用python