Skip to content

Commit e585946

Browse files
Reject negative retry durations
1 parent 117195d commit e585946

4 files changed

Lines changed: 35 additions & 3 deletions

File tree

src/dstack/_internal/cli/services/profile.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
Profile,
99
ProfileRetry,
1010
SpotPolicy,
11-
parse_duration,
1211
parse_max_duration,
12+
parse_retry_duration,
1313
)
1414

1515

@@ -179,4 +179,6 @@ def max_duration(v: str) -> int:
179179

180180

181181
def retry_duration(v: str) -> int:
182-
return parse_duration(v)
182+
duration = parse_retry_duration(v)
183+
assert duration is not None
184+
return duration

src/dstack/_internal/core/models/profiles.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ def parse_duration(v: Optional[Union[int, str]]) -> Optional[int]:
6868
return Duration.parse(v)
6969

7070

71+
def parse_retry_duration(v: Optional[Union[int, str]]) -> Optional[int]:
72+
duration = parse_duration(v)
73+
if duration is not None and duration < 0:
74+
raise ValueError("Duration cannot be negative")
75+
return duration
76+
77+
7178
def parse_max_duration(v: Optional[Union[int, str, bool]]) -> Optional[Union[Literal["off"], int]]:
7279
return parse_off_duration(v)
7380

@@ -136,7 +143,7 @@ class ProfileRetry(generate_dual_core_model(ProfileRetryConfig)):
136143
),
137144
] = None
138145

139-
_validate_duration = validator("duration", pre=True, allow_reuse=True)(parse_duration)
146+
_validate_duration = validator("duration", pre=True, allow_reuse=True)(parse_retry_duration)
140147

141148
@root_validator
142149
def _validate_fields(cls, values):

src/tests/_internal/cli/services/configurators/test_profile.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import argparse
22
from typing import List, Tuple
33

4+
import pytest
5+
46
from dstack._internal.cli.services.profile import (
57
apply_profile_args,
68
register_profile_args,
@@ -68,6 +70,10 @@ def test_retry_duration(self):
6870
profile.retry = ProfileRetry(on_events=None, duration="1h")
6971
assert profile.dict() == modified.dict()
7072

73+
def test_retry_duration_rejects_negative(self):
74+
with pytest.raises(SystemExit):
75+
apply_args(Profile(name="test"), ["--retry-duration", "-1"])
76+
7177

7278
def apply_args(profile: Profile, args: List[str]) -> Tuple[Profile, argparse.Namespace]:
7379
parser = argparse.ArgumentParser()
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import pytest
2+
from pydantic import ValidationError
3+
4+
from dstack._internal.core.models.profiles import ProfileRetry, RetryEvent
5+
6+
7+
class TestProfileRetry:
8+
@pytest.mark.parametrize("duration", [-1, "-1"])
9+
def test_rejects_negative_duration(self, duration):
10+
with pytest.raises(ValidationError, match="Duration cannot be negative"):
11+
ProfileRetry(duration=duration)
12+
13+
def test_parses_duration(self):
14+
retry = ProfileRetry(duration="1h", on_events=[RetryEvent.ERROR])
15+
16+
assert retry.duration == 3600
17+
assert retry.on_events == [RetryEvent.ERROR]

0 commit comments

Comments
 (0)