From: Sebastián Ramírez Date: Fri, 24 Jul 2026 20:21:58 +0000 (+0200) Subject: 👷 Add CI memory benchmark (#16046) X-Git-Tag: 0.140.0~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=513c396322664bcb3bdcb3946c440a8ed8ef080b;p=thirdparty%2Ffastapi%2Ffastapi.git 👷 Add CI memory benchmark (#16046) --- diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6855f0d48e..25af318cf8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -195,6 +195,11 @@ jobs: with: mode: simulation run: uv run --no-sync pytest tests/benchmarks --codspeed + - name: CodSpeed memory benchmark + uses: CodSpeedHQ/action@a4a36bb07c0638b0b4ca52bf1f3dad1b4289e52f # v4.18.1 + with: + mode: memory + run: uv run --no-sync pytest tests/memory_benchmarks --codspeed coverage-combine: needs: diff --git a/pyproject.toml b/pyproject.toml index e33e46fd81..2fd2f2e5d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -168,7 +168,7 @@ tests = [ "pwdlib[argon2] >=0.2.1", "pyjwt >=2.9.0", "pytest >=9.0.0", - "pytest-codspeed >=4.2.0", + "pytest-codspeed >=4.3.0", "pyyaml >=5.3.1,<7.0.0", "sqlmodel >=0.0.31", "strawberry-graphql >=0.200.0,<1.0.0", @@ -244,6 +244,7 @@ relative_files = true context = '${CONTEXT}' omit = [ "tests/benchmarks/*", + "tests/memory_benchmarks/*", "docs_src/response_model/tutorial003_04_py310.py", "docs_src/dependencies/tutorial013_an_py310.py", # temporary code example? "docs_src/dependencies/tutorial014_an_py310.py", # temporary code example? diff --git a/tests/memory_benchmarks/__init__.py b/tests/memory_benchmarks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/memory_benchmarks/test_dependency_graph.py b/tests/memory_benchmarks/test_dependency_graph.py new file mode 100644 index 0000000000..330fe6e540 --- /dev/null +++ b/tests/memory_benchmarks/test_dependency_graph.py @@ -0,0 +1,86 @@ +import inspect +import sys +from collections.abc import Callable +from typing import Any + +import pytest +from fastapi import Depends, FastAPI +from fastapi.routing import APIRoute + +if "--codspeed" not in sys.argv: + pytest.skip( + "Benchmark tests are skipped by default; run with --codspeed.", + allow_module_level=True, + ) + +LAST_DEPENDENCY_INDEX = 100 +ENDPOINT_PARAMETER_COUNT = 50 +ROUTE_PATH = "/dynamic-health" + + +def _create_app() -> FastAPI: + app = FastAPI() + dependencies: dict[int, Callable[..., Any]] = {} + + def create_dependency(index: int) -> Callable[..., Any]: + if index == LAST_DEPENDENCY_INDEX: + + def dependency() -> str: + return str(index) + + dependency.__name__ = f"dependency_{index}" + return dependency + + next_dependency = dependencies[index + 1] + + async def dependency( + sub_dependency: str = Depends(next_dependency), + ) -> str: + return f"{index} -> {sub_dependency}" + + dependency.__name__ = f"dependency_{index}" + return dependency + + for index in reversed(range(LAST_DEPENDENCY_INDEX + 1)): + dependencies[index] = create_dependency(index) + + def create_endpoint() -> Callable[..., Any]: + parameters = [ + inspect.Parameter( + name=f"arg_{index}", + kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, + default=Depends(dependencies[index]), + annotation=str, + ) + for index in range(ENDPOINT_PARAMETER_COUNT) + ] + + async def endpoint(**kwargs: str) -> dict[str, int]: + return {"parameter_count": len(kwargs)} + + endpoint_with_signature: Any = endpoint + endpoint_with_signature.__signature__ = inspect.Signature(parameters) + return endpoint + + for method in ("GET", "POST"): + app.add_api_route( + ROUTE_PATH, + create_endpoint(), + methods=[method], + ) + + return app + + +def test_dependency_graph(benchmark) -> None: + app = benchmark(_create_app) + dynamic_routes = [ + route + for route in app.routes + if isinstance(route, APIRoute) and route.path == ROUTE_PATH + ] + assert len(dynamic_routes) == 2 + assert {method for route in dynamic_routes for method in route.methods} == { + "GET", + "POST", + } diff --git a/uv.lock b/uv.lock index 1918bdb67b..564fcc1904 100644 --- a/uv.lock +++ b/uv.lock @@ -1064,7 +1064,7 @@ dev = [ { name = "pygithub", specifier = ">=2.8.1" }, { name = "pyjwt", specifier = ">=2.9.0" }, { name = "pytest", specifier = ">=9.0.0" }, - { name = "pytest-codspeed", specifier = ">=4.2.0" }, + { name = "pytest-codspeed", specifier = ">=4.3.0" }, { name = "pytest-cov", specifier = ">=4.0.0" }, { name = "pytest-sugar", specifier = ">=1.0.0" }, { name = "pytest-timeout", specifier = ">=2.4.0" }, @@ -1124,7 +1124,7 @@ tests = [ { name = "pwdlib", extras = ["argon2"], specifier = ">=0.2.1" }, { name = "pyjwt", specifier = ">=2.9.0" }, { name = "pytest", specifier = ">=9.0.0" }, - { name = "pytest-codspeed", specifier = ">=4.2.0" }, + { name = "pytest-codspeed", specifier = ">=4.3.0" }, { name = "pytest-cov", specifier = ">=4.0.0" }, { name = "pytest-sugar", specifier = ">=1.0.0" }, { name = "pytest-timeout", specifier = ">=2.4.0" },