From f25d27470f4079f081d8cfac18e0ab2ca4a8a8a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Fri, 22 May 2026 11:28:13 -0700 Subject: [PATCH 1/6] feat(sdk): add python 3.13 to supported versions (AE-3152) Extend GPU_PYTHON_VERSIONS, CPU_PYTHON_VERSIONS, and SUPPORTED_PYTHON_VERSIONS to include "3.13". DEFAULT_PYTHON_VERSION, WORKER_PYTHON_VERSION, and GPU_BASE_IMAGE_PYTHON_VERSION remain "3.12". Update tests to assert 3.13 is valid and use "3.9" as the invalid-version sentinel. --- src/runpod_flash/core/resources/constants.py | 13 ++++++----- tests/unit/core/resources/test_constants.py | 23 ++++++++++++-------- tests/unit/resources/test_serverless.py | 2 +- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/runpod_flash/core/resources/constants.py b/src/runpod_flash/core/resources/constants.py index 188a2567..9aa1db5e 100644 --- a/src/runpod_flash/core/resources/constants.py +++ b/src/runpod_flash/core/resources/constants.py @@ -2,19 +2,20 @@ # Worker runtime Python versions. One tarball serves every resource in an app, # so all resources must share a single Python version. GPU images ship 3.12 -# with torch pre-installed; 3.10 and 3.11 are available via side-by-side -# install (~7 GB alt-Python overhead) in the same base image. +# with torch pre-installed; 3.10, 3.11, and 3.13 are available via side-by-side +# install (deadsnakes PPA) in the same base image. WORKER_PYTHON_VERSION: str = "3.12" -GPU_PYTHON_VERSIONS: tuple[str, ...] = ("3.10", "3.11", "3.12") -CPU_PYTHON_VERSIONS: tuple[str, ...] = ("3.10", "3.11", "3.12") +GPU_PYTHON_VERSIONS: tuple[str, ...] = ("3.10", "3.11", "3.12", "3.13") +CPU_PYTHON_VERSIONS: tuple[str, ...] = ("3.10", "3.11", "3.12", "3.13") # Base image ships 3.12 with torch pre-installed; non-3.12 targets reinstall -# torch side-by-side for the selected interpreter. +# torch side-by-side for the selected interpreter. 3.13 is provided via +# deadsnakes PPA in the worker image. GPU_BASE_IMAGE_PYTHON_VERSION: str = "3.12" DEFAULT_PYTHON_VERSION: str = "3.12" # Python versions that can run the flash SDK locally (for flash build, etc.) -SUPPORTED_PYTHON_VERSIONS: tuple[str, ...] = ("3.10", "3.11", "3.12") +SUPPORTED_PYTHON_VERSIONS: tuple[str, ...] = ("3.10", "3.11", "3.12", "3.13") def local_python_version() -> str: diff --git a/tests/unit/core/resources/test_constants.py b/tests/unit/core/resources/test_constants.py index 6384c6a4..5b9483a9 100644 --- a/tests/unit/core/resources/test_constants.py +++ b/tests/unit/core/resources/test_constants.py @@ -19,13 +19,13 @@ class TestSupportedPythonVersions: def test_supported_versions(self): - assert SUPPORTED_PYTHON_VERSIONS == ("3.10", "3.11", "3.12") + assert SUPPORTED_PYTHON_VERSIONS == ("3.10", "3.11", "3.12", "3.13") def test_gpu_python_versions(self): - assert GPU_PYTHON_VERSIONS == ("3.10", "3.11", "3.12") + assert GPU_PYTHON_VERSIONS == ("3.10", "3.11", "3.12", "3.13") def test_cpu_python_versions(self): - assert CPU_PYTHON_VERSIONS == ("3.10", "3.11", "3.12") + assert CPU_PYTHON_VERSIONS == ("3.10", "3.11", "3.12", "3.13") def test_default_python_version_is_3_12(self): assert DEFAULT_PYTHON_VERSION == "3.12" @@ -40,28 +40,33 @@ def test_gpu_3_12(self): get_image_name("gpu", "3.12", tag="latest") == "runpod/flash:py3.12-latest" ) - @pytest.mark.parametrize("version", ["3.10", "3.11", "3.12"]) + def test_gpu_3_13(self): + assert ( + get_image_name("gpu", "3.13", tag="latest") == "runpod/flash:py3.13-latest" + ) + + @pytest.mark.parametrize("version", ["3.10", "3.11", "3.12", "3.13"]) def test_gpu_all_supported_versions(self, version): assert ( get_image_name("gpu", version, tag="latest") == f"runpod/flash:py{version}-latest" ) - @pytest.mark.parametrize("version", ["3.10", "3.11", "3.12"]) + @pytest.mark.parametrize("version", ["3.10", "3.11", "3.12", "3.13"]) def test_cpu_all_supported_versions(self, version): assert ( get_image_name("cpu", version, tag="latest") == f"runpod/flash-cpu:py{version}-latest" ) - @pytest.mark.parametrize("version", ["3.10", "3.11", "3.12"]) + @pytest.mark.parametrize("version", ["3.10", "3.11", "3.12", "3.13"]) def test_lb_all_supported_versions(self, version): assert ( get_image_name("lb", version, tag="latest") == f"runpod/flash-lb:py{version}-latest" ) - @pytest.mark.parametrize("version", ["3.10", "3.11", "3.12"]) + @pytest.mark.parametrize("version", ["3.10", "3.11", "3.12", "3.13"]) def test_lb_cpu_all_supported_versions(self, version): assert ( get_image_name("lb-cpu", version, tag="latest") @@ -82,7 +87,7 @@ def test_invalid_image_type_raises(self): def test_invalid_python_version_raises(self): with pytest.raises(ValueError, match="not supported"): - get_image_name("gpu", "3.13") + get_image_name("gpu", "3.9") def test_custom_tag(self): assert get_image_name("gpu", "3.12", tag="v2.0") == "runpod/flash:py3.12-v2.0" @@ -128,7 +133,7 @@ def test_valid_versions(self): def test_invalid_version_raises(self): with pytest.raises(ValueError, match="not supported"): - validate_python_version("3.13") + validate_python_version("3.9") def test_old_version_raises(self): with pytest.raises(ValueError, match="not supported"): diff --git a/tests/unit/resources/test_serverless.py b/tests/unit/resources/test_serverless.py index 366d29f3..1f5cc761 100644 --- a/tests/unit/resources/test_serverless.py +++ b/tests/unit/resources/test_serverless.py @@ -2429,7 +2429,7 @@ def test_python_version_accepts_valid_values(self): def test_python_version_rejects_invalid(self): with pytest.raises(ValueError, match="not supported"): ServerlessEndpoint( - name="test", imageName="test:latest", python_version="3.13" + name="test", imageName="test:latest", python_version="3.9" ) def test_python_version_rejects_3_9(self): From a438f91176be8167eba2999431cd9986c7dde01f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Fri, 22 May 2026 11:29:26 -0700 Subject: [PATCH 2/6] test(sdk): assert _reconcile_python_version accepts 3.13 (AE-3152) Direct test that a single resource declaring python_version="3.13" reconciles to "3.13" without raising. --- tests/unit/cli/commands/build_utils/test_manifest.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/unit/cli/commands/build_utils/test_manifest.py b/tests/unit/cli/commands/build_utils/test_manifest.py index 3a86c121..d7b2e28d 100644 --- a/tests/unit/cli/commands/build_utils/test_manifest.py +++ b/tests/unit/cli/commands/build_utils/test_manifest.py @@ -1053,3 +1053,9 @@ def test_unsupported_override_raises(self): def test_unsupported_resource_version_raises(self): with pytest.raises(ValueError, match="not supported"): self._builder()._reconcile_python_version(_make_resources_dict(gpu="3.8")) + + def test_reconcile_python_version_accepts_3_13_declaration(self): + resolved = self._builder()._reconcile_python_version( + _make_resources_dict(gpu="3.13") + ) + assert resolved == "3.13" From f49b2f8b781b4652e9f3b71f30a388dd7a1fba2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Fri, 22 May 2026 11:30:33 -0700 Subject: [PATCH 3/6] chore(sdk): allow python 3.13 in requires-python (AE-3152) Bump upper bound from <3.13 to <3.14 and add Python 3.13 PyPI classifier. --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ba2fd845..43efe987 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,10 +8,12 @@ license = { text = "MIT" } classifiers = [ "Development Status :: 4 - Beta", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] -requires-python = ">=3.10,<3.13" +requires-python = ">=3.10,<3.14" dependencies = [ "cloudpickle>=3.1.1", From 32f238150f20eb5f8813b58d488a74884fab0be5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Fri, 22 May 2026 11:31:41 -0700 Subject: [PATCH 4/6] ci(sdk): add python 3.13 to test matrix (AE-3152) Expand the quality-gates matrix in both ci.yml and release-please.yml to include '3.13'. Single-version setup-python lines are left unchanged. --- .github/workflows/ci.yml | 2 +- .github/workflows/release-please.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eed7aeeb..6e289aa1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12', '3.13'] timeout-minutes: 15 steps: diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 2f1283b0..e81543c5 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -21,7 +21,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12', '3.13'] timeout-minutes: 15 steps: From 529243f81be627f7a6a11f6c41e7635270f8e420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Sat, 23 May 2026 11:11:51 -0700 Subject: [PATCH 5/6] fix(review): address Copilot feedback on PR #338 (AE-3152) - Add Python 3.10/3.11 classifiers (we still support them per requires-python) - Use distinct invalid sentinels: "3.99" for the "unknown version" tests, keep "3.9" for the explicit "too-old" tests --- pyproject.toml | 2 ++ tests/unit/core/resources/test_constants.py | 2 +- tests/unit/resources/test_serverless.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 43efe987..a6a150e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,8 @@ license = { text = "MIT" } classifiers = [ "Development Status :: 4 - Beta", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "License :: OSI Approved :: MIT License", diff --git a/tests/unit/core/resources/test_constants.py b/tests/unit/core/resources/test_constants.py index 5b9483a9..180ac8c2 100644 --- a/tests/unit/core/resources/test_constants.py +++ b/tests/unit/core/resources/test_constants.py @@ -133,7 +133,7 @@ def test_valid_versions(self): def test_invalid_version_raises(self): with pytest.raises(ValueError, match="not supported"): - validate_python_version("3.9") + validate_python_version("3.99") def test_old_version_raises(self): with pytest.raises(ValueError, match="not supported"): diff --git a/tests/unit/resources/test_serverless.py b/tests/unit/resources/test_serverless.py index 1f5cc761..03cf219a 100644 --- a/tests/unit/resources/test_serverless.py +++ b/tests/unit/resources/test_serverless.py @@ -2429,7 +2429,7 @@ def test_python_version_accepts_valid_values(self): def test_python_version_rejects_invalid(self): with pytest.raises(ValueError, match="not supported"): ServerlessEndpoint( - name="test", imageName="test:latest", python_version="3.9" + name="test", imageName="test:latest", python_version="3.99" ) def test_python_version_rejects_3_9(self): From 02924163c5eb4acf9d60e5759be94d0f0e35c2c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Wed, 27 May 2026 14:39:08 -0700 Subject: [PATCH 6/6] docs(review): address Henrik QA feedback on PR #338 (AE-3152) - README/Deploy Guide/Deployment Architecture: list 3.13 alongside 3.10-3.12 - Flash_Deploy_Guide: replace "+~7 GB alt-Python install" with accurate per-ABI torch reinstall note; deadsnakes PPA ships interpreters side-by-side - constants.py: clarify deadsnakes vs torch-reinstall split between the two adjacent comment blocks - pyproject.toml: add "Programming Language :: Python :: 3 :: Only" classifier - test_constants.py: switch test_invalid_python_version_raises sentinel from "3.9" to "3.99" for consistency with test_invalid_version_raises --- README.md | 2 +- docs/Deployment_Architecture.md | 2 +- docs/Flash_Deploy_Guide.md | 9 ++++++--- pyproject.toml | 1 + src/runpod_flash/core/resources/constants.py | 12 ++++++------ tests/unit/core/resources/test_constants.py | 2 +- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 6d5549d2..82b1863a 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,7 @@ Browse working examples: **[github.com/runpod/flash-examples](https://github.com ## Requirements -- Python 3.10-3.12 +- Python 3.10-3.13 - macOS or Linux (Windows support in development) - A [RunPod account](https://runpod.io/console) (email must be verified) with an API key diff --git a/docs/Deployment_Architecture.md b/docs/Deployment_Architecture.md index bdf666d6..eed4529f 100644 --- a/docs/Deployment_Architecture.md +++ b/docs/Deployment_Architecture.md @@ -31,7 +31,7 @@ flash build │ ├── 4. Dependency Installation │ ├── Install Python packages for linux/x86_64 - │ ├── Target the app's python_version for wheel ABI selection (3.10 / 3.11 / 3.12) + │ ├── Target the app's python_version for wheel ABI selection (3.10 / 3.11 / 3.12 / 3.13) │ └── Binary wheels only (no compilation) │ └── 5. Packaging diff --git a/docs/Flash_Deploy_Guide.md b/docs/Flash_Deploy_Guide.md index c2dc3162..90f1ab16 100644 --- a/docs/Flash_Deploy_Guide.md +++ b/docs/Flash_Deploy_Guide.md @@ -6,7 +6,7 @@ This guide walks through deploying a Flash application from local development to ## Prerequisites -- Python 3.10, 3.11, or 3.12 +- Python 3.10, 3.11, 3.12, or 3.13 - `pip install runpod-flash` - A Runpod account with API key ([get one here](https://docs.runpod.io/get-started/api-keys)) @@ -19,9 +19,12 @@ Flash apps ship as a single tarball, so every resource in an app shares one Pyth | Version | Status | GPU cold-start | Notes | |---------|--------|----------------|-------| -| 3.10 | Supported (EOL 2026-10-31) | +~7 GB alt-Python install | Consider migrating to 3.11 before EOL | -| 3.11 | Supported | +~7 GB alt-Python install | | +| 3.10 | Supported (EOL 2026-10-31) | +torch reinstall for cp310 ABI | Consider migrating to 3.11 before EOL | +| 3.11 | Supported | +torch reinstall for cp311 ABI | | | 3.12 | Supported (default) | No overhead | Torch pre-installed in base image | +| 3.13 | Supported | +torch reinstall for cp313 ABI | Requires torch ≥2.5.0 wheels | + +The GPU base image installs Python 3.10-3.13 side-by-side via the deadsnakes PPA, so the "overhead" for non-3.12 targets is the torch wheel reinstall for the matching ABI, not an alternate-interpreter install. ## Quick Start diff --git a/pyproject.toml b/pyproject.toml index 56bfbf1a..fbf0714f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,7 @@ license = { text = "MIT" } classifiers = [ "Development Status :: 4 - Beta", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/src/runpod_flash/core/resources/constants.py b/src/runpod_flash/core/resources/constants.py index 9aa1db5e..48cd2986 100644 --- a/src/runpod_flash/core/resources/constants.py +++ b/src/runpod_flash/core/resources/constants.py @@ -1,16 +1,16 @@ import os # Worker runtime Python versions. One tarball serves every resource in an app, -# so all resources must share a single Python version. GPU images ship 3.12 -# with torch pre-installed; 3.10, 3.11, and 3.13 are available via side-by-side -# install (deadsnakes PPA) in the same base image. +# so all resources must share a single Python version. The GPU base image +# ships every supported interpreter (3.10-3.13) side-by-side via the deadsnakes +# PPA, but torch and CUDA-linked packages are pre-installed only for 3.12. WORKER_PYTHON_VERSION: str = "3.12" GPU_PYTHON_VERSIONS: tuple[str, ...] = ("3.10", "3.11", "3.12", "3.13") CPU_PYTHON_VERSIONS: tuple[str, ...] = ("3.10", "3.11", "3.12", "3.13") -# Base image ships 3.12 with torch pre-installed; non-3.12 targets reinstall -# torch side-by-side for the selected interpreter. 3.13 is provided via -# deadsnakes PPA in the worker image. +# Non-3.12 targets use the same deadsnakes interpreter already in the image but +# reinstall torch against the selected interpreter's ABI (cp310/cp311/cp313) +# during build — the alternate Python itself is not re-downloaded. GPU_BASE_IMAGE_PYTHON_VERSION: str = "3.12" DEFAULT_PYTHON_VERSION: str = "3.12" diff --git a/tests/unit/core/resources/test_constants.py b/tests/unit/core/resources/test_constants.py index 180ac8c2..19d27d6a 100644 --- a/tests/unit/core/resources/test_constants.py +++ b/tests/unit/core/resources/test_constants.py @@ -87,7 +87,7 @@ def test_invalid_image_type_raises(self): def test_invalid_python_version_raises(self): with pytest.raises(ValueError, match="not supported"): - get_image_name("gpu", "3.9") + get_image_name("gpu", "3.99") def test_custom_tag(self): assert get_image_name("gpu", "3.12", tag="v2.0") == "runpod/flash:py3.12-v2.0"