Skip to content
Merged
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/Deployment_Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 6 additions & 3 deletions docs/Flash_Deploy_Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand All @@ -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

Expand Down
7 changes: 6 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Comment thread
deanq marked this conversation as resolved.

dependencies = [
"cloudpickle>=3.1.1",
Expand Down
17 changes: 9 additions & 8 deletions src/runpod_flash/core/resources/constants.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
6 changes: 6 additions & 0 deletions tests/unit/cli/commands/build_utils/test_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
23 changes: 14 additions & 9 deletions tests/unit/core/resources/test_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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")
Expand All @@ -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"
Expand Down Expand Up @@ -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"):
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/resources/test_serverless.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Loading