Skip to content

Commit 2d81c81

Browse files
committed
add: stream data normalizer
1 parent 71868f9 commit 2d81c81

5 files changed

Lines changed: 95 additions & 11 deletions

File tree

README.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,33 @@ broker.subscribe(
117117
)
118118
```
119119

120-
The decoded value is passed as the first task argument. Stream messages also
121-
receive the `taskiq-stream` label with the source topic name.
120+
The decoded value is passed as the first task argument. A decoder can also
121+
return `StreamMessage` to map one Kafka message into the exact `args` and
122+
`kwargs` expected by the task.
123+
124+
```python
125+
import json
126+
127+
from taskiq_aio_kafka import StreamMessage
128+
129+
130+
@broker.task
131+
async def process_user(user_id: int, email: str) -> None:
132+
print(user_id, email)
133+
134+
135+
def decode_user_event(message: bytes) -> StreamMessage:
136+
event = json.loads(message)
137+
return StreamMessage(
138+
args=(event["user"]["id"],),
139+
kwargs={"email": event["user"]["email"]},
140+
)
141+
142+
143+
broker.subscribe("users.created", process_user, decoder=decode_user_event)
144+
```
145+
146+
Stream messages also receive the `taskiq-stream` label with the source topic name.
122147

123148
## Configuration
124149

taskiq_aio_kafka/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Taskiq integration with aiokafka."""
22

3-
__all__ = ("AioKafkaBroker",)
3+
__all__ = ("AioKafkaBroker", "StreamMessage")
44

55
from taskiq_aio_kafka.broker import AioKafkaBroker
6+
from taskiq_aio_kafka.subscriber import StreamMessage

taskiq_aio_kafka/broker.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from .decorated_task import AioKafkaDecoratedTask
2121
from .exceptions import WrongAioKafkaBrokerParametersError
2222
from .models import KafkaConsumerParameters, KafkaProducerParameters
23-
from .subscriber import StreamDecoder, StreamSubscriber
23+
from .subscriber import StreamDecoder, StreamMessage, StreamSubscriber
2424
from .topic import Topic
2525
from .types import TopicType
2626
from .utils import get_topic_name
@@ -171,8 +171,8 @@ def configure_consumer(self, **consumer_parameters: Any) -> None:
171171
)
172172

173173
@staticmethod
174-
def _default_stream_decoder(message: bytes) -> bytes:
175-
return message
174+
def _default_stream_decoder(message: bytes) -> StreamMessage:
175+
return StreamMessage(args=(message,))
176176

177177
def subscribe(
178178
self,
@@ -390,6 +390,7 @@ def _build_stream_message(
390390
) -> bytes:
391391
raw_value = raw_kafka_message.value
392392
decoded_value = subscriber.decoder(raw_value)
393+
stream_message = self._normalize_stream_message(decoded_value)
393394
labels = {
394395
TASK_STREAM_LABEL: raw_kafka_message.topic,
395396
**subscriber.labels,
@@ -399,7 +400,13 @@ def _build_stream_message(
399400
task_name=subscriber.task_name,
400401
labels=labels,
401402
labels_types={},
402-
args=[decoded_value],
403-
kwargs={},
403+
args=list(stream_message.args),
404+
kwargs=stream_message.kwargs,
404405
)
405406
return self.formatter.dumps(message).message
407+
408+
@staticmethod
409+
def _normalize_stream_message(message: Any | StreamMessage) -> StreamMessage:
410+
if isinstance(message, StreamMessage):
411+
return message
412+
return StreamMessage(args=(message,))

taskiq_aio_kafka/subscriber.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1-
__all__ = ("StreamDecoder", "StreamSubscriber")
1+
__all__ = ("StreamDecoder", "StreamMessage", "StreamSubscriber")
22

3-
from collections.abc import Callable
3+
from collections.abc import Callable, Sequence
44
from dataclasses import dataclass, field
55
from typing import Any
66

7-
StreamDecoder = Callable[[bytes], Any]
7+
8+
@dataclass(frozen=True)
9+
class StreamMessage:
10+
"""Decoded stream message arguments for a taskiq task."""
11+
12+
args: Sequence[Any] = ()
13+
kwargs: dict[str, Any] = field(default_factory=dict)
14+
15+
16+
StreamDecoder = Callable[[bytes], Any | StreamMessage]
817

918

1019
@dataclass(frozen=True)

tests/test_broker_multi_topic.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from taskiq_aio_kafka.broker import AioKafkaBroker
1010
from taskiq_aio_kafka.constants import TASK_STREAM_LABEL
11+
from taskiq_aio_kafka.subscriber import StreamMessage
1112
from taskiq_aio_kafka.topic import Topic
1213

1314

@@ -357,6 +358,47 @@ async def test_task(message: bytes) -> None:
357358
}
358359

359360

361+
async def test_subscribe_decoder_can_return_task_args_and_kwargs() -> None:
362+
"""Test that stream messages can be mapped to task args and kwargs."""
363+
broker = AioKafkaBroker(
364+
bootstrap_servers="localhost",
365+
kafka_topic="default-topic",
366+
kafka_admin_client=get_admin_client_mock(),
367+
)
368+
369+
@broker.task
370+
async def test_task(user_id: int, email: str, active: bool) -> None:
371+
assert user_id
372+
assert email
373+
assert active
374+
375+
def decode_message(message: bytes) -> StreamMessage:
376+
user_id, email, active = message.decode().split(":")
377+
return StreamMessage(
378+
args=(int(user_id),),
379+
kwargs={
380+
"email": email,
381+
"active": active == "true",
382+
},
383+
)
384+
385+
broker.subscribe("stream-topic", test_task, decoder=decode_message)
386+
broker._aiokafka_consumer = _ConsumerMock(
387+
[build_consumer_record("stream-topic", b"1:user@example.com:true")],
388+
)
389+
broker._is_consumer_started = True
390+
391+
received_message = await get_first_task(broker)
392+
taskiq_message = broker.formatter.loads(received_message)
393+
394+
assert taskiq_message.task_name == test_task.task_name
395+
assert taskiq_message.args == [1]
396+
assert taskiq_message.kwargs == {
397+
"email": "user@example.com",
398+
"active": True,
399+
}
400+
401+
360402
def test_subscribe_adds_topic_to_broker_topics() -> None:
361403
"""Test that subscribed topic is included in listened topics."""
362404
broker = AioKafkaBroker(

0 commit comments

Comments
 (0)