Skip to content

Commit aff8c46

Browse files
committed
extract interval validation to separate module
1 parent 3545765 commit aff8c46

5 files changed

Lines changed: 75 additions & 122 deletions

File tree

taskiq/scheduler/scheduled_task/v1.py

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
from pydantic import BaseModel, Field, root_validator
66

7+
from taskiq.scheduler.scheduled_task.validators import validate_interval_value
8+
79

810
class ScheduledTask(BaseModel):
911
"""Abstraction over task schedule."""
@@ -34,26 +36,5 @@ def __check(cls, values: Dict[str, Any]) -> Dict[str, Any]:
3436
if all(values.get(key) is None for key in ("cron", "interval", "time")):
3537
raise ValueError("Either cron, interval, or datetime must be present.")
3638

37-
# Validate interval constraints
38-
if (interval := values.get("interval")) is not None:
39-
if isinstance(interval, int):
40-
if interval < 1:
41-
raise ValueError(
42-
f"Interval must be at least 1 second, got {interval} seconds",
43-
)
44-
else:
45-
# For timedelta, check that it's at least 1 second
46-
# and has no fractional seconds
47-
total_seconds = interval.total_seconds()
48-
if total_seconds != int(total_seconds):
49-
raise ValueError(
50-
f"Fractional intervals are not supported, "
51-
f"got {total_seconds} seconds",
52-
)
53-
if total_seconds < 1:
54-
raise ValueError(
55-
f"Interval must be at least 1 second, "
56-
f"got {total_seconds} seconds",
57-
)
58-
39+
validate_interval_value(values.get("interval"))
5940
return values

taskiq/scheduler/scheduled_task/v2.py

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
from pydantic import BaseModel, Field, model_validator
77

8+
from taskiq.scheduler.scheduled_task.validators import validate_interval_value
9+
810
if sys.version_info >= (3, 11):
911
from typing import Self
1012
else:
@@ -39,27 +41,5 @@ def __check(self) -> Self:
3941
if self.cron is None and self.time is None and self.interval is None:
4042
raise ValueError("Either cron, interval, or datetime must be present.")
4143

42-
# Validate interval constraints
43-
if self.interval is not None:
44-
if isinstance(self.interval, int):
45-
if self.interval < 1:
46-
raise ValueError(
47-
f"Interval must be at least 1 second, "
48-
f"got {self.interval} seconds",
49-
)
50-
else:
51-
# For timedelta, check that it's at least 1 second
52-
# and has no fractional seconds
53-
total_seconds = self.interval.total_seconds()
54-
if total_seconds != int(total_seconds):
55-
raise ValueError(
56-
f"Fractional intervals are not supported, "
57-
f"got {total_seconds} seconds",
58-
)
59-
if total_seconds < 1:
60-
raise ValueError(
61-
f"Interval must be at least 1 second, "
62-
f"got {total_seconds} seconds",
63-
)
64-
44+
validate_interval_value(self.interval)
6545
return self
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from datetime import timedelta
2+
from typing import Union
3+
4+
5+
def validate_interval_value(
6+
value: Union[int, timedelta, None],
7+
) -> None:
8+
"""Validate that the given interval value meets required constraints.
9+
10+
:param value: The interval value to validate.
11+
:raises ValueError: ValueError: if value is int < 1,
12+
or timedelta with fractional seconds, or < 1 second.
13+
"""
14+
if value is None:
15+
return
16+
if isinstance(value, int):
17+
if value < 1:
18+
raise ValueError(
19+
f"Interval must be at least 1 second, got {value} seconds",
20+
)
21+
else:
22+
# For timedelta, check that it's at least 1 second
23+
# and has no fractional seconds
24+
total_seconds = value.total_seconds()
25+
if total_seconds != int(total_seconds):
26+
raise ValueError(
27+
f"Fractional intervals are not supported, got {total_seconds} seconds",
28+
)
29+
if total_seconds < 1:
30+
raise ValueError(
31+
f"Interval must be at least 1 second, got {total_seconds} seconds",
32+
)
Lines changed: 25 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,40 @@
1-
from datetime import datetime, timedelta
1+
from datetime import timedelta
22
from typing import Union
33

44
import pytest
55

6-
from taskiq.scheduler.scheduled_task import ScheduledTask
6+
from taskiq.scheduler.scheduled_task.validators import validate_interval_value
77

88

9-
def test_valid_interval_tasks() -> None:
10-
"""Test that valid interval tasks are created successfully."""
11-
task1 = ScheduledTask(
12-
task_name="test_task",
13-
labels={},
14-
args=[],
15-
kwargs={},
16-
interval=30,
17-
)
18-
assert task1.interval == 30
19-
20-
task2 = ScheduledTask(
21-
task_name="test_task",
22-
labels={},
23-
args=[],
24-
kwargs={},
25-
interval=timedelta(minutes=5),
26-
)
27-
assert task2.interval == timedelta(minutes=5)
28-
29-
task3 = ScheduledTask(
30-
task_name="test_task",
31-
labels={},
32-
args=[],
33-
kwargs={},
34-
interval=1,
35-
)
36-
assert task3.interval == 1
9+
@pytest.mark.parametrize(
10+
"value",
11+
[
12+
None,
13+
1,
14+
5,
15+
3600,
16+
timedelta(seconds=1),
17+
timedelta(seconds=5),
18+
timedelta(hours=1, minutes=30),
19+
timedelta(seconds=1, microseconds=0),
20+
],
21+
)
22+
def test_validate_interval_value_success(value: Union[int, timedelta, None]) -> None:
23+
validate_interval_value(value)
3724

3825

3926
@pytest.mark.parametrize(
40-
"interval",
27+
"value",
4128
[
4229
0,
43-
-5,
30+
-1,
4431
timedelta(seconds=0),
45-
timedelta(seconds=0.5),
46-
timedelta(microseconds=500000),
47-
timedelta(seconds=-1),
32+
timedelta(milliseconds=999),
33+
timedelta(seconds=1.5),
34+
timedelta(seconds=0.999),
35+
timedelta(seconds=1, microseconds=1),
4836
],
4937
)
50-
def test_invalid_interval_tasks(interval: Union[int, timedelta]) -> None:
51-
"""Test that invalid interval tasks raise ValueError."""
38+
def test_validate_interval_value_fail(value: Union[int, timedelta, None]) -> None:
5239
with pytest.raises(ValueError):
53-
ScheduledTask(
54-
task_name="test_task",
55-
labels={},
56-
args=[],
57-
kwargs={},
58-
interval=interval,
59-
)
60-
61-
62-
def test_interval_validation_with_other_schedule_types() -> None:
63-
"""Test that interval validation works with other schedule types."""
64-
task1 = ScheduledTask(
65-
task_name="test_task",
66-
labels={},
67-
args=[],
68-
kwargs={},
69-
cron="* * * * *",
70-
interval=30,
71-
)
72-
assert task1.interval == 30
73-
74-
task2 = ScheduledTask(
75-
task_name="test_task",
76-
labels={},
77-
args=[],
78-
kwargs={},
79-
time=datetime.now(),
80-
interval=timedelta(minutes=5),
81-
)
82-
assert task2.interval == timedelta(minutes=5)
83-
84-
with pytest.raises(ValueError, match="Interval must be at least 1 second"):
85-
ScheduledTask(
86-
task_name="test_task",
87-
labels={},
88-
args=[],
89-
kwargs={},
90-
cron="* * * * *",
91-
interval=0,
92-
)
40+
validate_interval_value(value)

tests/scheduler/test_scheduled_task.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,15 @@ def test_scheduled_task_parameters() -> None:
1212
kwargs={},
1313
schedule_id="b",
1414
)
15+
16+
17+
def test_scheduled_task_interval() -> None:
18+
with pytest.raises(ValueError):
19+
ScheduledTask(
20+
task_name="a",
21+
labels={},
22+
args=[],
23+
kwargs={},
24+
schedule_id="b",
25+
interval=-1,
26+
)

0 commit comments

Comments
 (0)