]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
👷 Add OpenAPI dependency benchmarks (#16075)
authorSebastián Ramírez <tiangolo@gmail.com>
Mon, 27 Jul 2026 16:46:54 +0000 (18:46 +0200)
committerGitHub <noreply@github.com>
Mon, 27 Jul 2026 16:46:54 +0000 (18:46 +0200)
tests/benchmarks/test_openapi.py [new file with mode: 0644]
tests/benchmarks/utils.py [new file with mode: 0644]
tests/memory_benchmarks/test_openapi.py [new file with mode: 0644]

diff --git a/tests/benchmarks/test_openapi.py b/tests/benchmarks/test_openapi.py
new file mode 100644 (file)
index 0000000..ef4e681
--- /dev/null
@@ -0,0 +1,33 @@
+import sys
+
+import pytest
+
+from tests.benchmarks.utils import (
+    ROUTE_COUNT,
+    ROUTE_PATH_PREFIX,
+    create_openapi_app,
+    generate_openapi,
+)
+
+if "--codspeed" not in sys.argv:
+    pytest.skip(
+        "Benchmark tests are skipped by default; run with --codspeed.",
+        allow_module_level=True,
+    )
+
+
+@pytest.mark.timeout(60)
+def test_openapi_dependency_graph(benchmark) -> None:
+    app = create_openapi_app()
+    schema = benchmark(generate_openapi, app)
+    dynamic_paths = [
+        path for path in schema["paths"] if path.startswith(ROUTE_PATH_PREFIX)
+    ]
+    assert len(dynamic_paths) == ROUTE_COUNT
+    assert all(
+        any(
+            parameter["in"] == "query" and parameter["name"] == "query_value"
+            for parameter in schema["paths"][path]["get"]["parameters"]
+        )
+        for path in dynamic_paths
+    )
diff --git a/tests/benchmarks/utils.py b/tests/benchmarks/utils.py
new file mode 100644 (file)
index 0000000..87e0e3f
--- /dev/null
@@ -0,0 +1,51 @@
+from collections.abc import Callable
+from typing import Annotated, Any
+
+from fastapi import Depends, FastAPI
+
+LAST_DEPENDENCY_INDEX = 100
+ROUTE_COUNT = 20
+ROUTE_PATH_PREFIX = "/openapi-route-"
+
+
+def create_openapi_app() -> FastAPI:
+    app = FastAPI()
+    dependencies: dict[int, Callable[..., Any]] = {}
+
+    def create_dependency(index: int) -> Callable[..., Any]:
+        if index == LAST_DEPENDENCY_INDEX:
+
+            def dependency(query_value: int = index) -> str:
+                return str(query_value)
+
+            dependency.__name__ = f"dependency_{index}"
+            return dependency
+
+        next_dependency = dependencies[index + 1]
+
+        async def dependency(
+            sub_dependency: Annotated[str, Depends(next_dependency)],
+            query_value: int = index,
+        ) -> str:
+            return f"{query_value} -> {sub_dependency}"
+
+        dependency.__name__ = f"dependency_{index}"
+        return dependency
+
+    for index in reversed(range(LAST_DEPENDENCY_INDEX + 1)):
+        dependencies[index] = create_dependency(index)
+
+    async def endpoint(
+        value: Annotated[str, Depends(dependencies[0])],
+    ) -> dict[str, str]:
+        return {"value": value}
+
+    for index in range(ROUTE_COUNT):
+        app.add_api_route(f"{ROUTE_PATH_PREFIX}{index}", endpoint, methods=["GET"])
+
+    return app
+
+
+def generate_openapi(app: FastAPI) -> dict[str, Any]:
+    app.openapi_schema = None
+    return app.openapi()
diff --git a/tests/memory_benchmarks/test_openapi.py b/tests/memory_benchmarks/test_openapi.py
new file mode 100644 (file)
index 0000000..ef4e681
--- /dev/null
@@ -0,0 +1,33 @@
+import sys
+
+import pytest
+
+from tests.benchmarks.utils import (
+    ROUTE_COUNT,
+    ROUTE_PATH_PREFIX,
+    create_openapi_app,
+    generate_openapi,
+)
+
+if "--codspeed" not in sys.argv:
+    pytest.skip(
+        "Benchmark tests are skipped by default; run with --codspeed.",
+        allow_module_level=True,
+    )
+
+
+@pytest.mark.timeout(60)
+def test_openapi_dependency_graph(benchmark) -> None:
+    app = create_openapi_app()
+    schema = benchmark(generate_openapi, app)
+    dynamic_paths = [
+        path for path in schema["paths"] if path.startswith(ROUTE_PATH_PREFIX)
+    ]
+    assert len(dynamic_paths) == ROUTE_COUNT
+    assert all(
+        any(
+            parameter["in"] == "query" and parameter["name"] == "query_value"
+            for parameter in schema["paths"][path]["get"]["parameters"]
+        )
+        for path in dynamic_paths
+    )