From 6e902ac8b947da049c960ee7c8b313dd045bc74f Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 27 Nov 2025 16:58:37 +0100 Subject: [PATCH] Add variants for `custom_request_and_route/tutorial001` --- .../docs/how-to/custom-request-and-route.md | 4 +-- .../tutorial001_an.py | 36 +++++++++++++++++++ .../tutorial001_an_py310.py | 36 +++++++++++++++++++ .../tutorial001_an_py39.py | 35 ++++++++++++++++++ .../tutorial001_py310.py | 35 ++++++++++++++++++ .../tutorial001_py39.py | 35 ++++++++++++++++++ .../test_tutorial001.py | 29 +++++++++++---- 7 files changed, 201 insertions(+), 9 deletions(-) create mode 100644 docs_src/custom_request_and_route/tutorial001_an.py create mode 100644 docs_src/custom_request_and_route/tutorial001_an_py310.py create mode 100644 docs_src/custom_request_and_route/tutorial001_an_py39.py create mode 100644 docs_src/custom_request_and_route/tutorial001_py310.py create mode 100644 docs_src/custom_request_and_route/tutorial001_py39.py diff --git a/docs/en/docs/how-to/custom-request-and-route.md b/docs/en/docs/how-to/custom-request-and-route.md index 884c8ed04..a36f28750 100644 --- a/docs/en/docs/how-to/custom-request-and-route.md +++ b/docs/en/docs/how-to/custom-request-and-route.md @@ -42,7 +42,7 @@ If there's no `gzip` in the header, it will not try to decompress the body. That way, the same route class can handle gzip compressed or uncompressed requests. -{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *} +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *} ### Create a custom `GzipRoute` class { #create-a-custom-gziproute-class } @@ -54,7 +54,7 @@ This method returns a function. And that function is what will receive a request Here we use it to create a `GzipRequest` from the original request. -{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *} +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *} /// note | Technical Details diff --git a/docs_src/custom_request_and_route/tutorial001_an.py b/docs_src/custom_request_and_route/tutorial001_an.py new file mode 100644 index 000000000..6224ba825 --- /dev/null +++ b/docs_src/custom_request_and_route/tutorial001_an.py @@ -0,0 +1,36 @@ +import gzip +from typing import Callable, List + +from fastapi import Body, FastAPI, Request, Response +from fastapi.routing import APIRoute +from typing_extensions import Annotated + + +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: Annotated[List[int], Body()]): + return {"sum": sum(numbers)} diff --git a/docs_src/custom_request_and_route/tutorial001_an_py310.py b/docs_src/custom_request_and_route/tutorial001_an_py310.py new file mode 100644 index 000000000..381bab6d8 --- /dev/null +++ b/docs_src/custom_request_and_route/tutorial001_an_py310.py @@ -0,0 +1,36 @@ +import gzip +from collections.abc import Callable +from typing import Annotated + +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: Annotated[list[int], Body()]): + return {"sum": sum(numbers)} diff --git a/docs_src/custom_request_and_route/tutorial001_an_py39.py b/docs_src/custom_request_and_route/tutorial001_an_py39.py new file mode 100644 index 000000000..076727e64 --- /dev/null +++ b/docs_src/custom_request_and_route/tutorial001_an_py39.py @@ -0,0 +1,35 @@ +import gzip +from typing import Annotated, Callable + +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: Annotated[list[int], Body()]): + return {"sum": sum(numbers)} diff --git a/docs_src/custom_request_and_route/tutorial001_py310.py b/docs_src/custom_request_and_route/tutorial001_py310.py new file mode 100644 index 000000000..c678088ce --- /dev/null +++ b/docs_src/custom_request_and_route/tutorial001_py310.py @@ -0,0 +1,35 @@ +import gzip +from collections.abc import Callable + +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)} diff --git a/docs_src/custom_request_and_route/tutorial001_py39.py b/docs_src/custom_request_and_route/tutorial001_py39.py new file mode 100644 index 000000000..54b20b942 --- /dev/null +++ b/docs_src/custom_request_and_route/tutorial001_py39.py @@ -0,0 +1,35 @@ +import gzip +from typing import Callable + +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)} diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py index e6da630e8..f9fd0d1af 100644 --- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py @@ -1,23 +1,38 @@ import gzip +import importlib import json import pytest from fastapi import Request from fastapi.testclient import TestClient -from docs_src.custom_request_and_route.tutorial001 import app +from tests.utils import needs_py39, needs_py310 -@app.get("/check-class") -async def check_gzip_request(request: Request): - return {"request_class": type(request).__name__} +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial001"), + pytest.param("tutorial001_py39", marks=needs_py39), + pytest.param("tutorial001_py310", marks=needs_py310), + pytest.param("tutorial001_an"), + pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.custom_request_and_route.{request.param}") + @mod.app.get("/check-class") + async def check_gzip_request(request: Request): + return {"request_class": type(request).__name__} -client = TestClient(app) + client = TestClient(mod.app) + return client @pytest.mark.parametrize("compress", [True, False]) -def test_gzip_request(compress): +def test_gzip_request(client: TestClient, compress): n = 1000 headers = {} body = [1] * n @@ -30,6 +45,6 @@ def test_gzip_request(compress): assert response.json() == {"sum": n} -def test_request_class(): +def test_request_class(client: TestClient): response = client.get("/check-class") assert response.json() == {"request_class": "GzipRequest"}