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: 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 5579cc1b..fbf0714f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,10 +8,15 @@ 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", + "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", diff --git a/src/runpod_flash/core/resources/constants.py b/src/runpod_flash/core/resources/constants.py index 188a2567..48cd2986 100644 --- a/src/runpod_flash/core/resources/constants.py +++ b/src/runpod_flash/core/resources/constants.py @@ -1,20 +1,21 @@ 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 and 3.11 are available via side-by-side -# install (~7 GB alt-Python overhead) 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") -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. +# 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" # 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/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" diff --git a/tests/unit/core/resources/test_constants.py b/tests/unit/core/resources/test_constants.py index 6384c6a4..19d27d6a 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.99") 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.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 366d29f3..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.13" + name="test", imageName="test:latest", python_version="3.99" ) def test_python_version_rejects_3_9(self):