Skip to content

Commit 8805f3d

Browse files
committed
experiment
1 parent 0bd0f17 commit 8805f3d

9 files changed

Lines changed: 202 additions & 111 deletions

File tree

‎docs/examples/router/multiple_brokers.py‎

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
router = TaskiqRouter()
88

9-
default_email_flow = Flow.queue("emails.default")
10-
priority_email_flow = Flow.queue("emails.priority")
11-
bulk_email_flow = Flow.queue("emails.bulk")
9+
default_email_flow = Flow("emails.default")
10+
priority_email_flow = Flow("emails.priority")
11+
bulk_email_flow = Flow("emails.bulk")
1212

1313
default_broker = InMemoryBroker(
1414
router=router,
@@ -30,9 +30,9 @@ async def send_email(user_id: int, template: str) -> str:
3030
return f"{template} email sent to user {user_id}"
3131

3232

33-
router.route_task(
34-
send_email.task_name,
35-
broker="priority",
33+
priority_route = router.route_task(
34+
send_email,
35+
broker=priority_broker,
3636
flow=priority_email_flow,
3737
)
3838

@@ -46,14 +46,18 @@ async def _main() -> None:
4646
routed_task = await send_email.kiq(7, "welcome")
4747
routed_result = await routed_task.wait_result(timeout=2)
4848

49-
bulk_task = await send_email.kicker().with_route(
50-
"default",
51-
bulk_email_flow,
52-
).kiq(8, "digest")
49+
bulk_task = (
50+
await send_email.kicker()
51+
.with_route(
52+
default_broker,
53+
bulk_email_flow,
54+
)
55+
.kiq(8, "digest")
56+
)
5357
bulk_result = await bulk_task.wait_result(timeout=2)
5458

5559
print(f"Direct call: {direct_result}")
56-
print(f"Default route: {router.resolve_route(send_email.task_name)}")
60+
print(f"Declared route: {priority_route.broker_name}")
5761
print(f"Routed call: {routed_result.return_value}")
5862
print(f"Route override: {bulk_result.return_value}")
5963
finally:

‎docs/examples/router/shared_task_package.py‎

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,36 @@
11
"""Declare shared task definitions and bind them in the final application."""
22

33
import asyncio
4+
from collections.abc import Mapping
5+
from dataclasses import dataclass
46

57
from taskiq import Flow, InMemoryBroker, TaskiqRouter, task_builder
68

79

10+
@dataclass(frozen=True, slots=True)
11+
class BillingQueue:
12+
"""Broker-specific flow that follows the shared flow protocol."""
13+
14+
name: str
15+
priority: int
16+
17+
def broker_options(self, broker_name: str) -> Mapping[str, object]:
18+
"""Return options that a billing broker adapter can understand."""
19+
return {
20+
"broker": broker_name,
21+
"priority": self.priority,
22+
}
23+
24+
825
@task_builder("billing.calculate_total", domain="billing")
926
async def calculate_total(price: int, quantity: int) -> int:
1027
"""Package-level task definition that is not bound to any broker."""
1128
return price * quantity
1229

1330

1431
router = TaskiqRouter()
15-
billing_flow = Flow.queue("billing.tasks")
16-
priority_billing_flow = Flow.queue("billing.priority")
32+
billing_flow = Flow("billing.tasks")
33+
priority_billing_flow = BillingQueue(name="billing.priority", priority=10)
1734

1835
billing_broker = InMemoryBroker(
1936
router=router,
@@ -22,17 +39,26 @@ async def calculate_total(price: int, quantity: int) -> int:
2239
await_inplace=True,
2340
)
2441

25-
registered_calculate_total = billing_broker.register_task(calculate_total)
42+
registered_calculate_total = router.register_task(
43+
calculate_total,
44+
broker=billing_broker,
45+
flow=billing_flow,
46+
)
2647

2748

2849
async def _main() -> None:
2950
await billing_broker.startup()
3051
try:
3152
direct_result = await calculate_total.call(19, 3)
3253

33-
prepared_task = registered_calculate_total.kicker().with_flow(
34-
priority_billing_flow,
35-
).prepare(19, 3)
54+
prepared_task = (
55+
registered_calculate_total.kicker()
56+
.with_route(
57+
billing_broker,
58+
priority_billing_flow,
59+
)
60+
.prepare(19, 3)
61+
)
3662

3763
queued_task = await prepared_task.kiq()
3864
queued_result = await queued_task.wait_result(timeout=2)

‎taskiq/__init__.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
TaskiqError,
2525
TaskiqResultTimeoutError,
2626
)
27-
from taskiq.flow import Flow, FlowKind
27+
from taskiq.flow import Flow, FlowProtocol
2828
from taskiq.funcs import gather
2929
from taskiq.kicker import PreparedKiq
3030
from taskiq.message import BrokerMessage, TaskiqMessage
@@ -52,7 +52,7 @@
5252
"BrokerMessage",
5353
"Context",
5454
"Flow",
55-
"FlowKind",
55+
"FlowProtocol",
5656
"InMemoryBroker",
5757
"NoResultError",
5858
"PreparedKiq",

‎taskiq/abc/broker.py‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from taskiq.decor import AsyncTaskiqDecoratedTask
2525
from taskiq.events import TaskiqEvents
2626
from taskiq.exceptions import TaskBrokerMismatchError
27-
from taskiq.flow import Flow
27+
from taskiq.flow import FlowProtocol
2828
from taskiq.formatters.proxy_formatter import ProxyFormatter
2929
from taskiq.message import BrokerMessage
3030
from taskiq.result_backends.dummy import DummyResultBackend
@@ -84,7 +84,7 @@ def __init__(
8484
*,
8585
router: TaskiqRouter | None = None,
8686
broker_name: str | None = None,
87-
default_flow: Flow | None = None,
87+
default_flow: FlowProtocol | None = None,
8888
) -> None:
8989
if result_backend is None:
9090
result_backend = DummyResultBackend()
@@ -262,7 +262,7 @@ async def kick(
262262
async def kick_to_flow(
263263
self,
264264
message: BrokerMessage,
265-
flow: Flow | None = None,
265+
flow: FlowProtocol | None = None,
266266
) -> None:
267267
"""
268268
Send message to a flow-aware broker.
@@ -401,8 +401,10 @@ def inner(
401401

402402
def register_task(
403403
self,
404-
func: Callable[_FuncParams, _ReturnType]
405-
| TaskDefinition[_FuncParams, _ReturnType],
404+
func: (
405+
Callable[_FuncParams, _ReturnType]
406+
| TaskDefinition[_FuncParams, _ReturnType]
407+
),
406408
task_name: str | None = None,
407409
**labels: Any,
408410
) -> AsyncTaskiqDecoratedTask[_FuncParams, _ReturnType]:

‎taskiq/brokers/inmemory_broker.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from taskiq.depends.progress_tracker import TaskProgress
1010
from taskiq.events import TaskiqEvents
1111
from taskiq.exceptions import UnknownTaskError
12-
from taskiq.flow import Flow
12+
from taskiq.flow import FlowProtocol
1313
from taskiq.message import BrokerMessage
1414
from taskiq.receiver import Receiver
1515
from taskiq.router import TaskiqRouter
@@ -135,7 +135,7 @@ def __init__(
135135
*,
136136
router: TaskiqRouter | None = None,
137137
broker_name: str | None = None,
138-
default_flow: Flow | None = None,
138+
default_flow: FlowProtocol | None = None,
139139
) -> None:
140140
super().__init__(
141141
router=router,

‎taskiq/flow.py‎

Lines changed: 19 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,42 @@
1-
import enum
1+
from collections.abc import Mapping
22
from dataclasses import dataclass, field, replace
3-
from typing import Any
3+
from typing import Protocol, runtime_checkable
44

5-
__all__ = ("Flow", "FlowKind")
5+
__all__ = ("Flow", "FlowProtocol")
66

77

8-
@enum.unique
9-
class FlowKind(str, enum.Enum):
10-
"""Transport-neutral flow shape."""
8+
@runtime_checkable
9+
class FlowProtocol(Protocol):
10+
"""Transport-neutral flow contract accepted by routers and brokers."""
1111

12-
QUEUE = "queue"
13-
TOPIC = "topic"
14-
STREAM = "stream"
12+
name: str
13+
14+
def broker_options(self, broker_name: str) -> Mapping[str, object]:
15+
"""Return options relevant for a concrete broker implementation."""
16+
...
1517

1618

1719
@dataclass(frozen=True, slots=True)
1820
class Flow:
19-
"""Transport-neutral publish or subscribe address.
20-
21-
Plain flows are intentionally generic. Every broker may interpret a flow
22-
using its own defaults: queue name, topic, stream, channel, list key, or any
23-
other transport address.
21+
"""Generic transport-neutral flow address.
2422
25-
Broker packages can subclass this value object to expose transport-specific
26-
details while still accepting plain Flow instances.
23+
Broker packages can provide their own flow objects that implement
24+
FlowProtocol, for example RabbitQueue, KafkaTopic, NatsSubject or RedisQueue.
25+
This generic value object is intentionally small and works as a common
26+
fallback for brokers that only need a named address with optional metadata.
2727
"""
2828

2929
name: str
30-
kind: FlowKind = FlowKind.QUEUE
31-
options: dict[str, Any] = field(
30+
options: Mapping[str, object] = field(
3231
default_factory=dict,
3332
compare=False,
3433
hash=False,
3534
)
3635

37-
@classmethod
38-
def queue(cls, name: str, **options: Any) -> "Flow":
39-
"""Create a queue-like flow."""
40-
return cls(name=name, kind=FlowKind.QUEUE, options=options)
41-
42-
@classmethod
43-
def topic(cls, name: str, **options: Any) -> "Flow":
44-
"""Create a topic-like flow."""
45-
return cls(name=name, kind=FlowKind.TOPIC, options=options)
46-
47-
@classmethod
48-
def stream(cls, name: str, **options: Any) -> "Flow":
49-
"""Create a stream-like flow."""
50-
return cls(name=name, kind=FlowKind.STREAM, options=options)
51-
52-
def with_options(self, **options: Any) -> "Flow":
36+
def with_options(self, **options: object) -> "Flow":
5337
"""Return the same flow with additional generic options."""
5438
return replace(self, options={**self.options, **options})
5539

56-
def broker_options(self, broker_name: str) -> dict[str, Any]:
40+
def broker_options(self, broker_name: str) -> Mapping[str, object]:
5741
"""Return transport options for broker-specific implementations."""
5842
return dict(self.options)

‎taskiq/kicker.py‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from taskiq.abc.result_backend import AsyncResultBackend
2222
from taskiq.compat import model_dump
2323
from taskiq.exceptions import SendTaskError
24-
from taskiq.flow import Flow
24+
from taskiq.flow import FlowProtocol
2525
from taskiq.labels import prepare_label
2626
from taskiq.message import TaskiqMessage
2727
from taskiq.router import TaskiqRouter
@@ -74,7 +74,7 @@ def __init__(
7474
self.custom_schedule_id: str | None = None
7575
self.return_type = return_type
7676
self.route_broker: AsyncBroker | str | None = None
77-
self.route_flow: Flow | None = None
77+
self.route_flow: FlowProtocol | None = None
7878

7979
def with_labels(
8080
self,
@@ -137,7 +137,7 @@ def with_broker(
137137

138138
def with_flow(
139139
self,
140-
flow: Flow | None,
140+
flow: FlowProtocol | None,
141141
) -> AsyncKicker[_FuncParams, _ReturnType]:
142142
"""
143143
Replace flow for the current invocation.
@@ -151,12 +151,14 @@ def with_flow(
151151
def with_route(
152152
self,
153153
broker: AsyncBroker | str,
154-
flow: Flow | None,
154+
flow: FlowProtocol | None,
155155
) -> AsyncKicker[_FuncParams, _ReturnType]:
156156
"""
157157
Replace broker and flow for the current invocation.
158158
159159
:param broker: broker instance or broker name.
160+
Broker instances are preferred; names are kept for configuration
161+
and backward-compatible lookup.
160162
:param flow: flow to send message to.
161163
:return: Kicker with a route override.
162164
"""

0 commit comments

Comments
 (0)