Skip to content

Commit cc0d1d0

Browse files
committed
Disallow running dstack on Python 3.9
Python 3.9.0 reached end-of-life on 2025-10-31.
1 parent 2a936a2 commit cc0d1d0

12 files changed

Lines changed: 25 additions & 51 deletions

File tree

.github/workflows/build-artifacts.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ jobs:
6464
strategy:
6565
matrix:
6666
os: [macos-latest, ubuntu-latest, windows-latest]
67-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
67+
python-version: ["3.10", "3.11", "3.12", "3.13"]
6868
steps:
6969
- uses: actions/checkout@v4
7070
- name: Set up Python ${{ matrix.python-version }}

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
- Frontend: from `frontend/` run `npm install`, `npm run build`, then copy `frontend/build` into `src/dstack/_internal/server/statics/`; for dev, `npm run start` with API on port 8000.
1616

1717
## Coding Style & Naming Conventions
18-
- Python targets 3.9+ with 4-space indentation and max line length of 99 (see `ruff.toml`; `E501` is ignored but keep lines readable).
18+
- Python targets 3.10+ with 4-space indentation and max line length of 99 (see `pyproject.toml`; `E501` is ignored but keep lines readable).
1919
- Imports are sorted via Ruff’s isort settings (`dstack` treated as first-party).
2020
- Keep primary/public functions before local helper functions in a module section.
2121
- Roughly keep function definitions in the order they are referenced within a file so call flow stays easy to follow.

docs/docs/concepts/backends.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -741,9 +741,6 @@ projects:
741741

742742
</div>
743743

744-
!!! info "Python version"
745-
Nebius is only supported if `dstack server` is running on Python 3.10 or higher.
746-
747744

748745
### Crusoe
749746

examples/plugins/example_plugin/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ readme = "README.md"
66
authors = [
77
{ name = "Victor Skvortsov", email = "victor@dstack.ai" }
88
]
9-
requires-python = ">=3.9"
9+
requires-python = ">=3.10"
1010
dependencies = []
1111

1212
[build-system]

examples/plugins/example_plugin_server/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "dstack-plugin-server"
33
version = "0.1.0"
44
description = "Example plugin server"
55
readme = "README.md"
6-
requires-python = ">=3.9"
6+
requires-python = ">=3.10"
77
dependencies = [
88
"fastapi",
99
"uvicorn",

pyproject.toml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "dstack"
33
dynamic = ["version", "readme"]
44
authors = [{ name = "Andrey Cheptsov", email = "andrey@dstack.ai" }]
55
description = "dstack is an open-source orchestration engine for running AI workloads on any cloud or on-premises."
6-
requires-python = ">=3.9"
6+
requires-python = ">=3.10"
77
classifiers = [
88
"Development Status :: 4 - Beta",
99
"Topic :: Scientific/Engineering :: Artificial Intelligence",
@@ -82,7 +82,7 @@ ignore-case = true
8282
dstack-plugin-server = { path = "examples/plugins/example_plugin_server", editable = true }
8383

8484
[tool.ruff]
85-
target-version = "py39"
85+
target-version = "py310"
8686
line-length = 99
8787

8888
[tool.ruff.lint]
@@ -198,7 +198,6 @@ server = [
198198
"python-json-logger>=3.1.0",
199199
"prometheus-client",
200200
"grpcio>=1.50",
201-
"backports.entry-points-selectable",
202201
]
203202
aws = [
204203
"boto3>=1.38.13",
@@ -226,11 +225,11 @@ gcp = [
226225
"dstack[server]",
227226
]
228227
datacrunch = [
229-
"verda>=1.23.0; python_version >= '3.10'",
228+
"verda>=1.23.0",
230229
"dstack[server]",
231230
]
232231
verda = [
233-
"verda>=1.23.0; python_version >= '3.10'",
232+
"verda>=1.23.0",
234233
"dstack[server]",
235234
]
236235
kubernetes = [
@@ -251,7 +250,7 @@ oci = [
251250
"dstack[server]",
252251
]
253252
nebius = [
254-
"nebius>=0.3.4,<0.4; python_version >= '3.10'",
253+
"nebius>=0.3.4,<0.4",
255254
"dstack[server]",
256255
]
257256
fluentbit = [

src/dstack/_internal/server/services/plugins.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import itertools
22
from importlib import import_module
3+
from importlib.metadata import entry_points
34
from typing import Dict
45

5-
from backports.entry_points_selectable import entry_points # backport for Python 3.9
6-
76
from dstack._internal.core.errors import ServerClientError
87
from dstack._internal.utils.common import run_async
98
from dstack._internal.utils.logging import get_logger
@@ -60,7 +59,7 @@ def load_plugins(enabled_plugins: list[str]):
6059
_PLUGINS.clear()
6160
entrypoints: dict[str, PluginEntrypoint] = {}
6261
plugins_to_load = enabled_plugins.copy()
63-
for entrypoint in entry_points(group="dstack.plugins"): # type: ignore[call-arg]
62+
for entrypoint in entry_points(group="dstack.plugins"):
6463
if entrypoint.name not in enabled_plugins:
6564
logger.info(
6665
("Found not enabled plugin %s. Plugin will not be loaded."),

src/tests/_internal/cli/utils/test_offer.py

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import asyncio
1+
import pytest
22

33
from dstack._internal.cli.utils.common import console
44
from dstack._internal.cli.utils.run import print_run_plan
@@ -34,18 +34,11 @@ def _get_offer(index: int) -> InstanceOfferWithAvailability:
3434
)
3535

3636

37-
def _get_run_plan(*, offers: list[InstanceOfferWithAvailability], total_offers: int) -> RunPlan:
37+
async def _get_run_plan(
38+
*, offers: list[InstanceOfferWithAvailability], total_offers: int
39+
) -> RunPlan:
3840
run_spec = get_run_spec(repo_id="test-repo")
39-
# Keep this helper's asyncio state isolated. `asyncio.run()` clears the current event loop,
40-
# which breaks later Python 3.9 tests that still construct asyncio primitives via
41-
# `get_event_loop()` on the main thread.
42-
loop = asyncio.new_event_loop()
43-
try:
44-
job = loop.run_until_complete(
45-
get_jobs_from_run_spec(run_spec=run_spec, secrets={}, replica_num=0)
46-
)[0]
47-
finally:
48-
loop.close()
41+
job = (await get_jobs_from_run_spec(run_spec=run_spec, secrets={}, replica_num=0))[0]
4942
return RunPlan(
5043
project_name="test-project",
5144
user="test-user",
@@ -64,8 +57,9 @@ def _get_run_plan(*, offers: list[InstanceOfferWithAvailability], total_offers:
6457

6558

6659
class TestPrintRunPlanOfferHint:
67-
def test_prints_hint_before_short_offer_table(self):
68-
run_plan = _get_run_plan(offers=[_get_offer(1), _get_offer(2)], total_offers=2)
60+
@pytest.mark.asyncio
61+
async def test_prints_hint_before_short_offer_table(self):
62+
run_plan = await _get_run_plan(offers=[_get_offer(1), _get_offer(2)], total_offers=2)
6963

7064
with console.capture() as capture:
7165
print_run_plan(
@@ -78,9 +72,10 @@ def test_prints_hint_before_short_offer_table(self):
7872
assert " ".join(_OFFER_FLEET_HINT.split()) in " ".join(output.split())
7973
assert output.index(_OFFER_FLEET_HINT_START) < output.index("1 aws (us-east-1)")
8074

81-
def test_prints_hint_after_truncated_offer_table(self):
75+
@pytest.mark.asyncio
76+
async def test_prints_hint_after_truncated_offer_table(self):
8277
offers = [_get_offer(index) for index in range(1, 4)]
83-
run_plan = _get_run_plan(offers=offers, total_offers=10)
78+
run_plan = await _get_run_plan(offers=offers, total_offers=10)
8479

8580
with console.capture() as capture:
8681
print_run_plan(

src/tests/_internal/core/backends/verda/test_compute.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
import sys
21
from types import SimpleNamespace
32
from unittest.mock import MagicMock, patch
43

54
import pytest
6-
7-
if sys.version_info < (3, 10):
8-
pytest.skip("Verda requires Python 3.10", allow_module_level=True)
9-
105
from verda.exceptions import APIException
116

127
from dstack._internal.core.backends.verda.compute import (

src/tests/_internal/core/backends/verda/test_configurator.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
1-
import sys
21
from unittest.mock import patch
32

4-
import pytest
5-
6-
if sys.version_info < (3, 10):
7-
pytest.skip("Verda requires Python 3.10", allow_module_level=True)
8-
93
from dstack._internal.core.backends.verda.configurator import (
104
VerdaConfigurator,
115
)

0 commit comments

Comments
 (0)