FastAPI 教程(七)

异常处理

异常处理需要先从 fastapi 中引入 HTTPException,有异常的时候就 raise 一个 HTTPException 对象,该对象有一些属性,包括status_code、detail等,例如:

from fastapi import FastAPI, HTTPException

app = FastAPI()

items = {"foo": "The Foo Wrestlers"}


@app.get("/items/{item_id}")
async def read_item(item_id: str):
    if item_id not in items:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item": items[item_id]}

建立 HTTPException 对象的时候还可以加入自定义的头,比如:

raise HTTPException(
    status_code=404,
    detail="Item not found",
    headers={"X-Error": "There goes my error"},
)

自定义异常处理器

可以使用 @app.exception_handler() 装饰器来自定义异常处理器,处理器函数需要有两个参数,一个是 request 对象,一个是异常类的对象,例如:

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse


class UnicornException(Exception):
    def __init__(self, name: str):
        self.name = name


app = FastAPI()


@app.exception_handler(UnicornException)
async def unicorn_exception_handler(request: Request, exc: UnicornException):
    return JSONResponse(
        status_code=418,
        content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."}
    )


@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
    if name == "yolo":
        raise UnicornException(name=name)
    return {"unicorn_name": name}

覆写默认的异常处理方法

我们可以从 fastapi.exceptions 中引入相应的异常类,然后再 @app.exception_handler装饰器中把异常类作为参数,就可以覆写异常类的默认处理方法了,例如:

from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
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.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
    return PlainTextResponse(str(exc), status_code=400)


@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id == 3:
        raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
    return {"item_id": item_id}

此时再去访问 http://localhost:8000/items/foo 就不会得到一个 JSON 的报错信息,而是得到一个纯文本的报错消息。

使用 jsonable_encoder 来编码数据

使用 fastapi.encoders 中的 jsonable_encoder 可以把数据编码成 json 格式,并自动做一些类型转换,比如把 python 中的日期型转成字符串,例如:

from datetime import datetime
from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel

fake_db = {}


class Item(BaseModel):
    title: str
    timestamp: datetime
    description: str = None


app = FastAPI()


@app.put("/items/{id}")
def update_item(id: str, item: Item):
    json_compatible_item_data = jsonable_encoder(item)
    fake_db[id] = json_compatible_item_data
    print(json_compatible_item_data)
    print(type(json_compatible_item_data))
    return fake_db

整体更新与部分更新

我们在更新的时候,如果参数的类定义中有默认值,而传入的参数中为指定明确的值,则生成的对象就会使用默认值,从而对数据进行整体更新,例如:

from typing import List, Optional
from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: Optional[str] = None
    description: Optional[str] = None
    price: Optional[float] = None
    tax: float = 10.5
    tags: List[str] = []


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
    return items[item_id]


@app.put("/items/{item_id}", response_model=Item)
async def update_item(item_id: str, item: Item):
    update_item_encoded = jsonable_encoder(item)
    items[item_id] = update_item_encoded   # 这种方式就是整体更新
    return update_item_encoded

此时如果我们传入这样的 json 参数:

{
    "name": "Barz",
    "price": 3,
    "description": None,
}

在更新这三个字段的同时,还会把 tax 更新程默认的 10.5 ,这是不符合我们需求的。

部分更新,使用 .dict(exclude_unset=True)

使用 Pydantic 中的 .dict() 方法,降参数 exclude_unset 设置为 True,就可以只更新我们给定的字段。通常在这种情况下,我们会使用 PATCH 请求而不是 POST 请求,不过其实是都可以的。

from typing import List, Optional
from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: Optional[str] = None
    description: Optional[str] = None
    price: Optional[float] = None
    tax: float = 10.5
    tags: List[str] = []


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
    return items[item_id]


@app.patch("/items/{item_id}", response_model=Item)  # 这里也可以用 post
async def update_item(item_id: str, item: Item):
    stored_item_data = items[item_id]
    stored_item_model = Item(**stored_item_data)
    update_data = item.dict(exclude_unset=True)
    updated_item = stored_item_model.copy(update=update_data)  # 拷贝一份出来更新
    items[item_id] = jsonable_encoder(updated_item)
    return updated_item

你可能感兴趣的:(FastAPI 教程(七))