From eba499ff8593bd88757a1f1c2b85c07932f27399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Fri, 13 Nov 2026 09:03:27 -0800 Subject: [PATCH] perf(apps): vendor fastapi without the [standard] extra --- runpod/apps/build.py | 8 ++++++-- tests/test_apps/test_build.py | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/runpod/apps/build.py b/runpod/apps/build.py index c9cb31e0..f392cdec 100644 --- a/runpod/apps/build.py +++ b/runpod/apps/build.py @@ -63,8 +63,12 @@ "tqdm-loggable", ) -# api workers additionally serve over fastapi/uvicorn -API_RUNTIME_REQUIREMENTS = ("fastapi[standard]", "uvicorn>=0.30") +# api workers additionally serve over fastapi/uvicorn. no [standard] extra: +# the runtime imports only fastapi and uvicorn, and the extra's closure +# (fastapi-cloud-cli, sentry, opentelemetry, rich, ...) is ~4,000 files and +# ~18 MB compressed that every cold worker downloads and unpacks. uvicorn +# still picks up uvloop/httptools from the api images, which ship them. +API_RUNTIME_REQUIREMENTS = ("fastapi", "uvicorn>=0.30") # runpod's serverless limit for build artifacts MAX_ARTIFACT_MB = 1500 diff --git a/tests/test_apps/test_build.py b/tests/test_apps/test_build.py index b100387b..68e6460d 100644 --- a/tests/test_apps/test_build.py +++ b/tests/test_apps/test_build.py @@ -476,6 +476,22 @@ def test_gpu_builtin_image_prunes_transitive_torch(self, tmp_path): assert result.excluded == ["torch"] assert _installed(result.env_dir) == ["peft"] + def test_api_runtime_vendors_fastapi_without_the_standard_extra(self, tmp_path): + app = App("a") + + @app.api(name="web", cpu="cpu3c-2-4") + def web(): + pass + + seen = [] + with patch( + "runpod.apps.build.vendor", + side_effect=lambda env_dir, requirements, *a, **k: seen.extend(requirements), + ): + build_environment(app, tmp_path, scratch_dir=tmp_path / "s") + assert "fastapi" in seen and "uvicorn>=0.30" in seen + assert not any(r.startswith("fastapi[") for r in seen) + def test_runpod_in_dependencies_fails_before_vendoring(self, tmp_path): app = App("a")