FastApi教程|自定義請求和APIRoute類


 

在某些情況下,您可能想覆蓋 Request 和 APIRoute 類 使用的邏輯 。

特別是,這可能是中間件中邏輯的一個很好的選擇。

例如,如果您想在應用程序處理請求主體之前讀取或操縱該請求主體。

危險

這是“高級”功能。

如果您只是從 FastAPI 開始, 則 可能要跳過本節。

用例

一些用例包括:

  • 將非JSON請求正文轉換為JSON(例如 msgpack )。
  • 解壓縮gzip壓縮的請求正文。
  • 自動記錄所有請求正文。

處理自定義請求主體編碼

讓我們看看如何利用自定義 Request 子類解壓縮gzip請求。

還有一個 APIRoute 使用該自定義請求類 的 子類。

創建自定義 GzipRequest 

小費

這是一個玩具示例,用於演示其工作原理。如果需要Gzip支持,則可以使用提供的 GzipMiddleware 

首先,我們創建一個 GzipRequest 類,該類將 Request.body() 在存在適當頭的情況下 覆蓋 用於解壓縮主體 的 方法。

如果 gzip 標題中 沒有 ,它將不會嘗試解壓縮主體。

這樣,相同的路由類可以處理gzip壓縮或未壓縮的請求。

import gzip
from typing import Callable, List

from fastapi import Body, FastAPI, Request, Response
from fastapi.routing import APIRoute


class GzipRequest(Request):
    async def body(self) -> bytes:
        if not hasattr(self, "_body"):
            body = await super().body()
            if "gzip" in self.headers.getlist("Content-Encoding"):
                body = gzip.decompress(body)
            self._body = body
        return self._body


class GzipRoute(APIRoute):
    def get_route_handler(self) -> Callable:
        original_route_handler = super().get_route_handler()

        async def custom_route_handler(request: Request) -> Response:
            request = GzipRequest(request.scope, request.receive)
            return await original_route_handler(request)

        return custom_route_handler


app = FastAPI()
app.router.route_class = GzipRoute


@app.post("/sum")
async def sum_numbers(numbers: List[int] = Body(...)):
    return {"sum": sum(numbers)}

創建自定義 GzipRoute 

接下來,我們將創建一個自定義子類 fastapi.routing.APIRoute ,以使用 GzipRequest 

這次,它將覆蓋方法 APIRoute.get_route_handler() 

此方法返回一個函數。 該函數將接收請求並返回響應。

在這里,我們使用它 GzipRequest 從原始請求中 創建一個 。

import gzip
from typing import Callable, List

from fastapi import Body, FastAPI, Request, Response
from fastapi.routing import APIRoute


class GzipRequest(Request):
    async def body(self) -> bytes:
        if not hasattr(self, "_body"):
            body = await super().body()
            if "gzip" in self.headers.getlist("Content-Encoding"):
                body = gzip.decompress(body)
            self._body = body
        return self._body


class GzipRoute(APIRoute):
    def get_route_handler(self) -> Callable:
        original_route_handler = super().get_route_handler()

        async def custom_route_handler(request: Request) -> Response:
            request = GzipRequest(request.scope, request.receive)
            return await original_route_handler(request)

        return custom_route_handler


app = FastAPI()
app.router.route_class = GzipRoute


@app.post("/sum")
async def sum_numbers(numbers: List[int] = Body(...)):
    return {"sum": sum(numbers)}

技術細節

一個 Request 具有 request.scope 屬性,這只是一個Python dict 包含相關請求的元數據。

Request 也有一個 request.receive ,即“接收”請求正文的功能。

該 scope dict 和 receive 功能是ASGI技術規格的一部分。

而這兩個 scope 和 receive 是創建新 Request 實例 所需的 。

要了解有關 Request 檢查 Starlette的關於Requests的文檔的 更多信息 。

函數返回的唯一 GzipRequest.get_route_handler 不同之處是將轉換 Request 為 GzipRequest 

這樣做,我們 GzipRequest 將在將數據傳遞給我們的 path操作 之前,對數據進行解壓縮(如果需要) 。

之后,所有處理邏輯都是相同的。

但是由於我們的更改 GzipRequest.body , 在需要時 由 FastAPI 加載請求主體時,請求主體將自動解壓縮 。

在異常處理程序中訪問請求主體

小費

為了解決這個問題, body 在 RequestValidationError ( 處理錯誤 ) 的自定義處理程序中 使用可能要容易 得多 。

但是此示例仍然有效,並且顯示了如何與內部組件進行交互。

我們還可以使用相同的方法在異常處理程序中訪問請求正文。

我們需要做的就是在 try except 塊中 處理請求 :

from typing import Callable, List

from fastapi import Body, FastAPI, HTTPException, Request, Response
from fastapi.exceptions import RequestValidationError
from fastapi.routing import APIRoute


class ValidationErrorLoggingRoute(APIRoute):
    def get_route_handler(self) -> Callable:
        original_route_handler = super().get_route_handler()

        async def custom_route_handler(request: Request) -> Response:
            try:
                return await original_route_handler(request)
            except RequestValidationError as exc:
                body = await request.body()
                detail = {"errors": exc.errors(), "body": body.decode()}
                raise HTTPException(status_code=422, detail=detail)

        return custom_route_handler


app = FastAPI()
app.router.route_class = ValidationErrorLoggingRoute


@app.post("/")
async def sum_numbers(numbers: List[int] = Body(...)):
    return sum(numbers)

如果發生異常, Request 實例仍將在范圍內,因此在處理錯誤時我們可以讀取並利用請求正文:

from typing import Callable, List

from fastapi import Body, FastAPI, HTTPException, Request, Response
from fastapi.exceptions import RequestValidationError
from fastapi.routing import APIRoute


class ValidationErrorLoggingRoute(APIRoute):
    def get_route_handler(self) -> Callable:
        original_route_handler = super().get_route_handler()

        async def custom_route_handler(request: Request) -> Response:
            try:
                return await original_route_handler(request)
            except RequestValidationError as exc:
                body = await request.body()
                detail = {"errors": exc.errors(), "body": body.decode()}
                raise HTTPException(status_code=422, detail=detail)

        return custom_route_handler


app = FastAPI()
app.router.route_class = ValidationErrorLoggingRoute


@app.post("/")
async def sum_numbers(numbers: List[int] = Body(...)):
    return sum(numbers)

自定義 APIRoute 一個路由器類

您還可以設置 route_class 參數 APIRouter 

import time
from typing import Callable

from fastapi import APIRouter, FastAPI, Request, Response
from fastapi.routing import APIRoute


class TimedRoute(APIRoute):
    def get_route_handler(self) -> Callable:
        original_route_handler = super().get_route_handler()

        async def custom_route_handler(request: Request) -> Response:
            before = time.time()
            response: Response = await original_route_handler(request)
            duration = time.time() - before
            response.headers["X-Response-Time"] = str(duration)
            print(f"route duration: {duration}")
            print(f"route response: {response}")
            print(f"route response headers: {response.headers}")
            return response

        return custom_route_handler


app = FastAPI()
router = APIRouter(route_class=TimedRoute)


@app.get("/")
async def not_timed():
    return {"message": "Not timed"}


@router.get("/timed")
async def timed():
    return {"message": "It's the time of my life"}


app.include_router(router)

在這個例子中, 路徑操作 下, router 將使用自定義 TimedRoute 類,並有一個額外的 X-Response-Time 與它采取產生響應的時間響應頭:

import time
from typing import Callable

from fastapi import APIRouter, FastAPI, Request, Response
from fastapi.routing import APIRoute


class TimedRoute(APIRoute):
    def get_route_handler(self) -> Callable:
        original_route_handler = super().get_route_handler()

        async def custom_route_handler(request: Request) -> Response:
            before = time.time()
            response: Response = await original_route_handler(request)
            duration = time.time() - before
            response.headers["X-Response-Time"] = str(duration)
            print(f"route duration: {duration}")
            print(f"route response: {response}")
            print(f"route response headers: {response.headers}")
            return response

        return custom_route_handler


app = FastAPI()
router = APIRouter(route_class=TimedRoute)


@app.get("/")
async def not_timed():
    return {"message": "Not timed"}


@router.get("/timed")
async def timed():
    return {"message": "It's the time of my life"}


app.include_router(router)

 

轉:https://www.pythonf.cn/read/56960


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM