Skip to content

Commit fb27ddf

Browse files
authored
Merge pull request #234 from taskiq-python/feature/scheduled-task
2 parents 9aae397 + bd0417f commit fb27ddf

File tree

11 files changed

+119
-52
lines changed

11 files changed

+119
-52
lines changed

.github/workflows/test.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ jobs:
3737
strategy:
3838
matrix:
3939
py_version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
40+
pydantic_ver: ["<2", ">=2,<3"]
4041
os: [ubuntu-latest, windows-latest]
4142
runs-on: "${{ matrix.os }}"
4243
steps:
@@ -50,6 +51,8 @@ jobs:
5051
cache: "poetry"
5152
- name: Install deps
5253
run: poetry install --all-extras
54+
- name: Setup pydantic version
55+
run: poetry run pip install "pydantic ${{ matrix.pydantic_ver }}"
5356
- name: Run pytest check
5457
run: poetry run pytest -vv -n auto --cov="taskiq" .
5558
- name: Generate report

docs/examples/extending/schedule_source.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Coroutine, List
1+
from typing import List
22

33
from taskiq import ScheduledTask, ScheduleSource
44

taskiq/result/__init__.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
# flake8: noqa
2-
from packaging.version import Version
1+
from taskiq.compat import IS_PYDANTIC2
32

4-
from taskiq.compat import PYDANTIC_VER
5-
6-
if PYDANTIC_VER >= Version("2.0"):
3+
if IS_PYDANTIC2:
74
from .v2 import TaskiqResult
85
else:
9-
from .v1 import TaskiqResult
6+
from .v1 import TaskiqResult # type: ignore
107

118

129
__all__ = [

taskiq/scheduler/scheduled_task.py

Lines changed: 0 additions & 44 deletions
This file was deleted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from taskiq.compat import IS_PYDANTIC2
2+
3+
from .cron_spec import CronSpec
4+
5+
if IS_PYDANTIC2:
6+
from .v2 import ScheduledTask
7+
else:
8+
from .v1 import ScheduledTask # type: ignore
9+
10+
11+
__all__ = ["CronSpec", "ScheduledTask"]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from datetime import timedelta
2+
from typing import Optional, Union
3+
4+
from pydantic import BaseModel
5+
6+
7+
class CronSpec(BaseModel):
8+
"""Cron specification for running tasks."""
9+
10+
minutes: Optional[Union[str, int]] = "*"
11+
hours: Optional[Union[str, int]] = "*"
12+
days: Optional[Union[str, int]] = "*"
13+
months: Optional[Union[str, int]] = "*"
14+
weekdays: Optional[Union[str, int]] = "*"
15+
16+
offset: Optional[Union[str, timedelta]] = None
17+
18+
def to_cron(self) -> str: # pragma: no cover
19+
"""Converts cron spec to cron string."""
20+
return f"{self.minutes} {self.hours} {self.days} {self.months} {self.weekdays}"

taskiq/scheduler/scheduled_task/v1.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import uuid
2+
from datetime import datetime, timedelta
3+
from typing import Any, Dict, List, Optional, Union
4+
5+
from pydantic import BaseModel, Field, root_validator
6+
7+
8+
class ScheduledTask(BaseModel):
9+
"""Abstraction over task schedule."""
10+
11+
task_name: str
12+
labels: Dict[str, Any]
13+
args: List[Any]
14+
kwargs: Dict[str, Any]
15+
schedule_id: str = Field(default_factory=lambda: uuid.uuid4().hex)
16+
cron: Optional[str] = None
17+
cron_offset: Optional[Union[str, timedelta]] = None
18+
time: Optional[datetime] = None
19+
20+
@root_validator(pre=False) # type: ignore
21+
@classmethod
22+
def __check(cls, values: Dict[str, Any]) -> Dict[str, Any]:
23+
"""
24+
This method validates, that either `cron` or `time` field is present.
25+
26+
:raises ValueError: if cron and time are none.
27+
"""
28+
if values.get("cron") is None and values.get("time") is None:
29+
raise ValueError("Either cron or datetime must be present.")
30+
return values

taskiq/scheduler/scheduled_task/v2.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import uuid
2+
from datetime import datetime, timedelta
3+
from typing import Any, Dict, List, Optional, Union
4+
5+
from pydantic import BaseModel, Field, model_validator
6+
from typing_extensions import Self
7+
8+
9+
class ScheduledTask(BaseModel):
10+
"""Abstraction over task schedule."""
11+
12+
task_name: str
13+
labels: Dict[str, Any]
14+
args: List[Any]
15+
kwargs: Dict[str, Any]
16+
schedule_id: str = Field(default_factory=lambda: uuid.uuid4().hex)
17+
cron: Optional[str] = None
18+
cron_offset: Optional[Union[str, timedelta]] = None
19+
time: Optional[datetime] = None
20+
21+
@model_validator(mode="after")
22+
def __check(self) -> Self:
23+
"""
24+
This method validates, that either `cron` or `time` field is present.
25+
26+
:raises ValueError: if cron and time are none.
27+
"""
28+
if self.cron is None and self.time is None:
29+
raise ValueError("Either cron or datetime must be present.")
30+
return self

tests/formatters/test_json_formatter.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import json
2+
13
import pytest
24

35
from taskiq.formatters.json_formatter import JSONFormatter
@@ -24,7 +26,11 @@ async def test_json_dumps() -> None:
2426
),
2527
labels={"label1": 1, "label2": "text"},
2628
)
27-
assert fmt.dumps(msg) == expected
29+
dumped = fmt.dumps(msg)
30+
assert dumped.task_id == expected.task_id
31+
assert dumped.task_name == expected.task_name
32+
assert dumped.labels == expected.labels
33+
assert json.loads(dumped.message) == json.loads(expected.message)
2834

2935

3036
@pytest.mark.anyio

tests/scheduler/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)