Skip to content

Commit 51b9b99

Browse files
committed
chore: pull main
2 parents 6e2ed25 + dc732f8 commit 51b9b99

File tree

9 files changed

+164
-89
lines changed

9 files changed

+164
-89
lines changed

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ jobs:
1616
GITHUB_CONTEXT: ${{ toJson(github) }}
1717
run: echo "$GITHUB_CONTEXT"
1818

19-
- uses: actions/checkout@v4
19+
- uses: actions/checkout@v5
2020

2121
- name: Set up Python
22-
uses: actions/setup-python@v5
22+
uses: actions/setup-python@v6
2323
with:
2424
python-version: 3.13
2525

.github/workflows/test.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ jobs:
1515
if: github.event.pull_request.draft == false
1616
runs-on: ubuntu-latest
1717
steps:
18-
- uses: actions/checkout@v4
19-
- uses: actions/setup-python@v5
18+
- uses: actions/checkout@v5
19+
- uses: actions/setup-python@v6
2020
with:
2121
python-version: 3.13
2222

@@ -45,9 +45,9 @@ jobs:
4545
fail-fast: false
4646

4747
steps:
48-
- uses: actions/checkout@v4
48+
- uses: actions/checkout@v5
4949
- name: Set up Python
50-
uses: actions/setup-python@v5
50+
uses: actions/setup-python@v6
5151
with:
5252
python-version: ${{ matrix.python-version }}
5353

@@ -76,9 +76,9 @@ jobs:
7676
runs-on: ubuntu-latest
7777

7878
steps:
79-
- uses: actions/checkout@v4
79+
- uses: actions/checkout@v5
8080

81-
- uses: actions/setup-python@v5
81+
- uses: actions/setup-python@v6
8282
with:
8383
python-version: 3.13
8484

@@ -87,7 +87,7 @@ jobs:
8787
version: "latest"
8888

8989
- name: Get coverage files
90-
uses: actions/download-artifact@v4
90+
uses: actions/download-artifact@v5
9191
with:
9292
pattern: .coverage*
9393
path: coverage

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,13 @@ test = [
7373

7474
"coverage[toml]>=7.2.0,<8.0.0",
7575
"pytest>=7.4.0,<9",
76+
"freezegun>=1.2.2"
7677
]
7778

7879
dev = [
7980
{include-group = "test"},
80-
"mypy==1.11.2",
81-
"ruff==0.11.8",
81+
"mypy==1.17.1",
82+
"ruff==0.12.8",
8283
"pre-commit >=3.6.0,<5.0.0",
8384
]
8485

taskiq_faststream/broker.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from taskiq.decor import AsyncTaskiqDecoratedTask
1111
from typing_extensions import TypeAlias
1212

13-
from taskiq_faststream.formatter import PatchedFormatter, PathcedMessage
13+
from taskiq_faststream.formatter import PatchedFormatter, PatchedMessage
1414
from taskiq_faststream.types import ScheduledTask
1515
from taskiq_faststream.utils import resolve_msg
1616

@@ -46,7 +46,7 @@ async def shutdown(self) -> None:
4646
await self.broker.close()
4747
await super().shutdown()
4848

49-
async def kick(self, message: PathcedMessage) -> None: # type: ignore[override]
49+
async def kick(self, message: PatchedMessage) -> None: # type: ignore[override]
5050
"""Call wrapped FastStream broker `publish` method."""
5151
await _broker_publish(self.broker, message)
5252

@@ -73,6 +73,8 @@ def task( # type: ignore[override]
7373
SendableMessage,
7474
typing.Callable[[], SendableMessage],
7575
typing.Callable[[], typing.Awaitable[SendableMessage]],
76+
typing.Callable[[], typing.Generator[SendableMessage, None, None]],
77+
typing.Callable[[], typing.AsyncGenerator[SendableMessage, None]],
7678
] = None,
7779
*,
7880
schedule: list[ScheduledTask],
@@ -121,7 +123,7 @@ async def shutdown(self) -> None:
121123
await self.app._shutdown() # noqa: SLF001
122124
await super(BrokerWrapper, self).shutdown()
123125

124-
async def kick(self, message: PathcedMessage) -> None: # type: ignore[override]
126+
async def kick(self, message: PatchedMessage) -> None: # type: ignore[override]
125127
"""Call wrapped FastStream broker `publish` method."""
126128
assert ( # noqa: S101
127129
self.app.broker
@@ -131,7 +133,7 @@ async def kick(self, message: PathcedMessage) -> None: # type: ignore[override]
131133

132134
async def _broker_publish(
133135
broker: Any,
134-
message: PathcedMessage,
136+
message: PatchedMessage,
135137
) -> None:
136138
async for msg in resolve_msg(message.body):
137139
await broker.publish(msg, **message.labels)

taskiq_faststream/formatter.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77

88
@dataclass
9-
class PathcedMessage:
9+
class PatchedMessage:
1010
"""DTO to transfer data to `broker.kick`."""
1111

1212
body: Any
@@ -19,18 +19,18 @@ class PatchedFormatter(TaskiqFormatter):
1919
def dumps( # type: ignore[override]
2020
self,
2121
message: TaskiqMessage,
22-
) -> PathcedMessage:
22+
) -> PatchedMessage:
2323
"""
2424
Dumps taskiq message to some broker message format.
2525
2626
:param message: message to send.
2727
:return: Dumped message.
2828
"""
29-
labels = message.labels
29+
labels = message.labels.copy()
3030
labels.pop("schedule", None)
3131
labels.pop("schedule_id", None)
3232

33-
return PathcedMessage(
33+
return PatchedMessage(
3434
body=labels.pop("message", None),
3535
labels=labels,
3636
)

taskiq_faststream/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
try:
77
from faststream.utils.functions import to_async
88
except ImportError:
9-
from faststream._internal.utils import to_async
9+
from faststream._internal.utils import to_async # type: ignore[no-redef]
1010

1111

1212
async def resolve_msg(

tests/messages.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from collections.abc import AsyncIterator, Iterator
2+
3+
message = "Hi!"
4+
5+
6+
def sync_callable_msg() -> str:
7+
return message
8+
9+
10+
async def async_callable_msg() -> str:
11+
return message
12+
13+
14+
async def async_generator_msg() -> AsyncIterator[str]:
15+
yield message
16+
17+
18+
def sync_generator_msg() -> Iterator[str]:
19+
yield message
20+
21+
22+
class _C:
23+
def __call__(self) -> str:
24+
return message
25+
26+
27+
class _AC:
28+
async def __call__(self) -> str:
29+
return message
30+
31+
32+
sync_callable_class_message = _C()
33+
async_callable_class_message = _AC()

tests/test_resolve_message.py

Lines changed: 29 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,34 @@
1-
from collections.abc import AsyncIterator, Iterator
1+
import typing
22

33
import pytest
4+
from faststream.types import SendableMessage
45

56
from taskiq_faststream.utils import resolve_msg
6-
7-
8-
@pytest.mark.anyio
9-
async def test_regular() -> None:
10-
async for m in resolve_msg("msg"):
11-
assert m == "msg"
12-
13-
14-
@pytest.mark.anyio
15-
async def test_sync_callable() -> None:
16-
async for m in resolve_msg(lambda: "msg"):
17-
assert m == "msg"
18-
19-
7+
from tests import messages
8+
9+
10+
@pytest.mark.parametrize(
11+
"msg",
12+
[
13+
messages.message, # regular msg
14+
messages.sync_callable_msg, # sync callable
15+
messages.async_callable_msg, # async callable
16+
messages.sync_generator_msg, # sync generator
17+
messages.async_generator_msg, # async generator
18+
messages.sync_callable_class_message, # sync callable class
19+
messages.async_callable_class_message, # async callable class
20+
],
21+
)
2022
@pytest.mark.anyio
21-
async def test_async_callable() -> None:
22-
async def gen_msg() -> str:
23-
return "msg"
24-
25-
async for m in resolve_msg(gen_msg):
26-
assert m == "msg"
27-
28-
29-
@pytest.mark.anyio
30-
async def test_sync_callable_class() -> None:
31-
class C:
32-
def __init__(self) -> None:
33-
pass
34-
35-
def __call__(self) -> str:
36-
return "msg"
37-
38-
async for m in resolve_msg(C()):
39-
assert m == "msg"
40-
41-
42-
@pytest.mark.anyio
43-
async def test_async_callable_class() -> None:
44-
class C:
45-
def __init__(self) -> None:
46-
pass
47-
48-
async def __call__(self) -> str:
49-
return "msg"
50-
51-
async for m in resolve_msg(C()):
52-
assert m == "msg"
53-
54-
55-
@pytest.mark.anyio
56-
async def test_async_generator() -> None:
57-
async def get_msg() -> AsyncIterator[str]:
58-
yield "msg"
59-
60-
async for m in resolve_msg(get_msg):
61-
assert m == "msg"
62-
63-
64-
@pytest.mark.anyio
65-
async def test_sync_generator() -> None:
66-
def get_msg() -> Iterator[str]:
67-
yield "msg"
68-
69-
async for m in resolve_msg(get_msg):
70-
assert m == "msg"
23+
async def test_resolve_msg(
24+
msg: typing.Union[
25+
None,
26+
SendableMessage,
27+
typing.Callable[[], SendableMessage],
28+
typing.Callable[[], typing.Awaitable[SendableMessage]],
29+
typing.Callable[[], typing.Generator[SendableMessage, None, None]],
30+
typing.Callable[[], typing.AsyncGenerator[SendableMessage, None]],
31+
],
32+
) -> None:
33+
async for m in resolve_msg(msg):
34+
assert m == messages.message

tests/testcase.py

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import asyncio
2-
from datetime import datetime, timezone
2+
import typing
3+
from datetime import datetime, timedelta, timezone
34
from typing import Any
45
from unittest.mock import MagicMock
56

67
import anyio
78
import pytest
8-
from taskiq import AsyncBroker, TaskiqScheduler
9+
from faststream.types import SendableMessage
10+
from freezegun import freeze_time
11+
from taskiq import AsyncBroker
912
from taskiq.cli.scheduler.args import SchedulerArgs
1013
from taskiq.cli.scheduler.run import run_scheduler
1114
from taskiq.schedule_sources import LabelScheduleSource
1215

13-
from taskiq_faststream import BrokerWrapper
16+
from taskiq_faststream import BrokerWrapper, StreamScheduler
17+
from tests import messages
1418

1519

1620
@pytest.mark.anyio
@@ -53,7 +57,7 @@ async def handler(msg: str) -> None:
5357
task = asyncio.create_task(
5458
run_scheduler(
5559
SchedulerArgs(
56-
scheduler=TaskiqScheduler(
60+
scheduler=StreamScheduler(
5761
broker=taskiq_broker,
5862
sources=[LabelScheduleSource(taskiq_broker)],
5963
),
@@ -67,3 +71,74 @@ async def handler(msg: str) -> None:
6771

6872
mock.assert_called_once_with("Hi!")
6973
task.cancel()
74+
75+
@pytest.mark.parametrize(
76+
"msg",
77+
[
78+
messages.message, # regular msg
79+
messages.sync_callable_msg, # sync callable
80+
messages.async_callable_msg, # async callable
81+
messages.sync_generator_msg, # sync generator
82+
messages.async_generator_msg, # async generator
83+
messages.sync_callable_class_message, # sync callable class
84+
messages.async_callable_class_message, # async callable class
85+
],
86+
)
87+
async def test_task_multiple_schedules_by_cron(
88+
self,
89+
subject: str,
90+
broker: Any,
91+
event: asyncio.Event,
92+
msg: typing.Union[
93+
None,
94+
SendableMessage,
95+
typing.Callable[[], SendableMessage],
96+
typing.Callable[[], typing.Awaitable[SendableMessage]],
97+
typing.Callable[[], typing.Generator[SendableMessage, None, None]],
98+
typing.Callable[[], typing.AsyncGenerator[SendableMessage, None]],
99+
],
100+
) -> None:
101+
"""Test cron runs twice via StreamScheduler."""
102+
received_message = []
103+
104+
@broker.subscriber(subject)
105+
async def handler(message: str) -> None:
106+
received_message.append(message)
107+
event.set()
108+
109+
taskiq_broker = self.build_taskiq_broker(broker)
110+
111+
taskiq_broker.task(
112+
msg,
113+
**{self.subj_name: subject},
114+
schedule=[
115+
{
116+
"cron": "* * * * *",
117+
},
118+
],
119+
)
120+
121+
async with self.test_class(broker):
122+
with freeze_time("00:00:00", tick=True) as frozen_datetime:
123+
task = asyncio.create_task(
124+
run_scheduler(
125+
SchedulerArgs(
126+
scheduler=StreamScheduler(
127+
broker=taskiq_broker,
128+
sources=[LabelScheduleSource(taskiq_broker)],
129+
),
130+
modules=[],
131+
),
132+
),
133+
)
134+
135+
await asyncio.wait_for(event.wait(), 2.0)
136+
event.clear()
137+
frozen_datetime.tick(timedelta(minutes=2))
138+
await asyncio.wait_for(event.wait(), 2.0)
139+
140+
task.cancel()
141+
142+
assert received_message == [messages.message, messages.message], (
143+
received_message
144+
)

0 commit comments

Comments
 (0)