Skip to content

Commit 7edb04d

Browse files
authored
Support Python 3.13, drop Python 3.8 (#1910)
This commit includes: - Updated version constraints - Running tests on 3.9-3.13 - Changes by `ruff format` using new 3.9 syntax
1 parent 99011c6 commit 7edb04d

File tree

12 files changed

+249
-194
lines changed

12 files changed

+249
-194
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
strategy:
6262
matrix:
6363
os: [ macos-latest, ubuntu-latest, windows-latest ]
64-
python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ]
64+
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ]
6565
steps:
6666
- uses: actions/checkout@v4
6767
- name: Set up Python ${{ matrix.python-version }}
@@ -78,10 +78,9 @@ jobs:
7878
- name: Run pytest on POSIX
7979
if: matrix.os != 'windows-latest'
8080
# Skip Postgres tests on macos since macos runner doesn't have Docker.
81-
# Skip Postgres tests for Python 3.8 since testcontainers<4 doesn't support asyncpg correctly.
8281
run: |
8382
RUNPOSTGRES=""
84-
if [ "${{ matrix.os }}" != "macos-latest" ] && [ "${{ matrix.python-version }}" != "3.8" ]; then
83+
if [ "${{ matrix.os }}" != "macos-latest" ]; then
8584
RUNPOSTGRES="--runpostgres"
8685
fi
8786
pytest src/tests --runui $RUNPOSTGRES

.github/workflows/release.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
strategy:
5353
matrix:
5454
os: [ macos-latest, ubuntu-latest, windows-latest ]
55-
python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ]
55+
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ]
5656
steps:
5757
- uses: actions/checkout@v4
5858
- name: Set up Python ${{ matrix.python-version }}
@@ -69,10 +69,9 @@ jobs:
6969
- name: Run pytest on POSIX
7070
if: matrix.os != 'windows-latest'
7171
# Skip Postgres tests on macos since macos runner doesn't have Docker.
72-
# Skip Postgres tests for Python 3.8 since testcontainers<4 doesn't support asyncpg correctly.
7372
run: |
7473
RUNPOSTGRES=""
75-
if [ "${{ matrix.os }}" != "macos-latest" ] && [ "${{ matrix.python-version }}" != "3.8" ]; then
74+
if [ "${{ matrix.os }}" != "macos-latest" ]; then
7675
RUNPOSTGRES="--runpostgres"
7776
fi
7877
pytest src/tests --runui $RUNPOSTGRES

ruff.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
target-version = "py38"
1+
target-version = "py39"
22
line-length = 99
33

44
[lint]

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def get_long_description():
128128
description="dstack is an open-source orchestration engine for running AI workloads on any cloud or on-premises.",
129129
long_description=get_long_description(),
130130
long_description_content_type="text/markdown",
131-
python_requires=">=3.8",
131+
python_requires=">=3.9",
132132
install_requires=BASE_DEPS,
133133
extras_require={
134134
"all": ALL_DEPS,

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,8 +361,9 @@ async def delete_fleets(
361361
instances_ids = sorted([i.id for f in fleet_models for i in f.instances])
362362
await session.commit()
363363
logger.info("Deleting fleets: %s", [v.name for v in fleet_models])
364-
async with get_locker().lock_ctx(FleetModel.__tablename__, fleets_ids), get_locker().lock_ctx(
365-
InstanceModel.__tablename__, instances_ids
364+
async with (
365+
get_locker().lock_ctx(FleetModel.__tablename__, fleets_ids),
366+
get_locker().lock_ctx(InstanceModel.__tablename__, instances_ids),
366367
):
367368
# Refetch after lock
368369
# TODO lock instances with FOR UPDATE?

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -423,8 +423,9 @@ async def stop_runs(
423423
job_models = res.scalars().all()
424424
job_ids = sorted([j.id for j in job_models])
425425
await session.commit()
426-
async with get_locker().lock_ctx(RunModel.__tablename__, run_ids), get_locker().lock_ctx(
427-
JobModel.__tablename__, job_ids
426+
async with (
427+
get_locker().lock_ctx(RunModel.__tablename__, run_ids),
428+
get_locker().lock_ctx(JobModel.__tablename__, job_ids),
428429
):
429430
for run_model in run_models:
430431
await stop_run(session=session, run_model=run_model, abort=abort)

src/tests/_internal/server/background/tasks/test_process_gateways.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@ async def test_provisions_gateway(self, test_db, session: AsyncSession):
2525
project_id=project.id,
2626
backend_id=backend.id,
2727
)
28-
with patch(
29-
"dstack._internal.server.services.backends.get_project_backend_with_model_by_type_or_error"
30-
) as m, patch(
31-
"dstack._internal.server.services.gateways.gateway_connections_pool.get_or_add"
32-
) as pool_add:
28+
with (
29+
patch(
30+
"dstack._internal.server.services.backends.get_project_backend_with_model_by_type_or_error"
31+
) as m,
32+
patch(
33+
"dstack._internal.server.services.gateways.gateway_connections_pool.get_or_add"
34+
) as pool_add,
35+
):
3336
aws = Mock()
3437
m.return_value = (backend, aws)
3538
pool_add.return_value = MagicMock()
@@ -85,11 +88,14 @@ async def test_marks_gateway_as_failed_if_fails_to_connect(
8588
project_id=project.id,
8689
backend_id=backend.id,
8790
)
88-
with patch(
89-
"dstack._internal.server.services.backends.get_project_backend_with_model_by_type_or_error"
90-
) as m, patch(
91-
"dstack._internal.server.services.gateways.connect_to_gateway_with_retry"
92-
) as connect_to_gateway_with_retry_mock:
91+
with (
92+
patch(
93+
"dstack._internal.server.services.backends.get_project_backend_with_model_by_type_or_error"
94+
) as m,
95+
patch(
96+
"dstack._internal.server.services.gateways.connect_to_gateway_with_retry"
97+
) as connect_to_gateway_with_retry_mock,
98+
):
9399
aws = Mock()
94100
m.return_value = (backend, aws)
95101
connect_to_gateway_with_retry_mock.return_value = None

src/tests/_internal/server/background/tasks/test_process_metrics.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,12 @@ async def test_collects_metrics(self, test_db, session: AsyncSession):
5252
status=JobStatus.RUNNING,
5353
job_provisioning_data=get_job_provisioning_data(),
5454
)
55-
with patch(
56-
"dstack._internal.server.services.runner.ssh.SSHTunnel"
57-
) as SSHTunnelMock, patch(
58-
"dstack._internal.server.services.runner.client.RunnerClient"
59-
) as RunnerClientMock:
55+
with (
56+
patch("dstack._internal.server.services.runner.ssh.SSHTunnel") as SSHTunnelMock,
57+
patch(
58+
"dstack._internal.server.services.runner.client.RunnerClient"
59+
) as RunnerClientMock,
60+
):
6061
runner_client_mock = RunnerClientMock.return_value
6162
runner_client_mock.get_metrics.return_value = MetricsResponse(
6263
timestamp_micro=1,

src/tests/_internal/server/background/tasks/test_process_running_jobs.py

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,13 @@ async def test_leaves_provisioning_job_unchanged_if_runner_not_alive(
8383
submitted_at=datetime(2023, 1, 2, 5, 12, 30, 5, tzinfo=timezone.utc),
8484
job_provisioning_data=job_provisioning_data,
8585
)
86-
with patch(
87-
"dstack._internal.server.services.runner.ssh.SSHTunnel"
88-
) as SSHTunnelMock, patch(
89-
"dstack._internal.server.services.runner.client.RunnerClient"
90-
) as RunnerClientMock, patch(
91-
"dstack._internal.utils.common.get_current_datetime"
92-
) as datetime_mock:
86+
with (
87+
patch("dstack._internal.server.services.runner.ssh.SSHTunnel") as SSHTunnelMock,
88+
patch(
89+
"dstack._internal.server.services.runner.client.RunnerClient"
90+
) as RunnerClientMock,
91+
patch("dstack._internal.utils.common.get_current_datetime") as datetime_mock,
92+
):
9393
datetime_mock.return_value = datetime(2023, 1, 2, 5, 12, 30, 10, tzinfo=timezone.utc)
9494
runner_client_mock = RunnerClientMock.return_value
9595
runner_client_mock.healthcheck = Mock()
@@ -123,11 +123,12 @@ async def test_runs_provisioning_job(self, test_db, session: AsyncSession):
123123
status=JobStatus.PROVISIONING,
124124
job_provisioning_data=job_provisioning_data,
125125
)
126-
with patch(
127-
"dstack._internal.server.services.runner.ssh.SSHTunnel"
128-
) as SSHTunnelMock, patch(
129-
"dstack._internal.server.services.runner.client.RunnerClient"
130-
) as RunnerClientMock:
126+
with (
127+
patch("dstack._internal.server.services.runner.ssh.SSHTunnel") as SSHTunnelMock,
128+
patch(
129+
"dstack._internal.server.services.runner.client.RunnerClient"
130+
) as RunnerClientMock,
131+
):
131132
runner_client_mock = RunnerClientMock.return_value
132133
runner_client_mock.healthcheck.return_value = HealthcheckResponse(
133134
service="dstack-runner", version="0.0.1.dev2"
@@ -164,11 +165,13 @@ async def test_updates_running_job(self, test_db, session: AsyncSession, tmp_pat
164165
status=JobStatus.RUNNING,
165166
job_provisioning_data=job_provisioning_data,
166167
)
167-
with patch(
168-
"dstack._internal.server.services.runner.ssh.SSHTunnel"
169-
) as SSHTunnelMock, patch(
170-
"dstack._internal.server.services.runner.client.RunnerClient"
171-
) as RunnerClientMock, patch.object(settings, "SERVER_DIR_PATH", tmp_path):
168+
with (
169+
patch("dstack._internal.server.services.runner.ssh.SSHTunnel") as SSHTunnelMock,
170+
patch(
171+
"dstack._internal.server.services.runner.client.RunnerClient"
172+
) as RunnerClientMock,
173+
patch.object(settings, "SERVER_DIR_PATH", tmp_path),
174+
):
172175
runner_client_mock = RunnerClientMock.return_value
173176
runner_client_mock.pull.return_value = PullResponse(
174177
job_states=[JobStateEvent(timestamp=1, state=JobStatus.RUNNING)],
@@ -182,11 +185,12 @@ async def test_updates_running_job(self, test_db, session: AsyncSession, tmp_pat
182185
assert job is not None
183186
assert job.status == JobStatus.RUNNING
184187
assert job.runner_timestamp == 1
185-
with patch(
186-
"dstack._internal.server.services.runner.ssh.SSHTunnel"
187-
) as SSHTunnelMock, patch(
188-
"dstack._internal.server.services.runner.client.RunnerClient"
189-
) as RunnerClientMock:
188+
with (
189+
patch("dstack._internal.server.services.runner.ssh.SSHTunnel") as SSHTunnelMock,
190+
patch(
191+
"dstack._internal.server.services.runner.client.RunnerClient"
192+
) as RunnerClientMock,
193+
):
190194
runner_client_mock = RunnerClientMock.return_value
191195
runner_client_mock.pull.return_value = PullResponse(
192196
job_states=[JobStateEvent(timestamp=1, state=JobStatus.DONE)],
@@ -251,11 +255,10 @@ async def test_provisioning_shim_with_volumes(
251255
status=JobStatus.PROVISIONING,
252256
job_provisioning_data=job_provisioning_data,
253257
)
254-
with patch(
255-
"dstack._internal.server.services.runner.ssh.SSHTunnel"
256-
) as SSHTunnelMock, patch(
257-
"dstack._internal.server.services.runner.client.ShimClient"
258-
) as ShimClientMock:
258+
with (
259+
patch("dstack._internal.server.services.runner.ssh.SSHTunnel") as SSHTunnelMock,
260+
patch("dstack._internal.server.services.runner.client.ShimClient") as ShimClientMock,
261+
):
259262
ShimClientMock.return_value.healthcheck.return_value = HealthcheckResponse(
260263
service="dstack-shim", version="0.0.1.dev2"
261264
)
@@ -303,13 +306,13 @@ async def test_pulling_shim(self, test_db, session: AsyncSession):
303306
status=JobStatus.PULLING,
304307
job_provisioning_data=job_provisioning_data,
305308
)
306-
with patch(
307-
"dstack._internal.server.services.runner.ssh.SSHTunnel"
308-
) as SSHTunnelMock, patch(
309-
"dstack._internal.server.services.runner.client.RunnerClient"
310-
) as RunnerClientMock, patch(
311-
"dstack._internal.server.services.runner.client.ShimClient"
312-
) as ShimClientMock:
309+
with (
310+
patch("dstack._internal.server.services.runner.ssh.SSHTunnel") as SSHTunnelMock,
311+
patch(
312+
"dstack._internal.server.services.runner.client.RunnerClient"
313+
) as RunnerClientMock,
314+
patch("dstack._internal.server.services.runner.client.ShimClient") as ShimClientMock,
315+
):
313316
RunnerClientMock.return_value.healthcheck.return_value = HealthcheckResponse(
314317
service="dstack-runner", version="0.0.1.dev2"
315318
)
@@ -355,9 +358,10 @@ async def test_pulling_shim_failed(self, test_db, session: AsyncSession):
355358
job_provisioning_data=job_provisioning_data,
356359
instance=instance,
357360
)
358-
with patch(
359-
"dstack._internal.server.services.runner.ssh.SSHTunnel"
360-
) as SSHTunnelMock, patch("dstack._internal.server.services.runner.ssh.time.sleep"):
361+
with (
362+
patch("dstack._internal.server.services.runner.ssh.SSHTunnel") as SSHTunnelMock,
363+
patch("dstack._internal.server.services.runner.ssh.time.sleep"),
364+
):
361365
SSHTunnelMock.side_effect = SSHError
362366
await process_running_jobs()
363367
assert SSHTunnelMock.call_count == 3

0 commit comments

Comments
 (0)