Skip to content

Commit 359214f

Browse files
committed
Merge branch 'release/0.3.1'
2 parents 14611af + 93750e7 commit 359214f

File tree

12 files changed

+614
-361
lines changed

12 files changed

+614
-361
lines changed

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: Testing taskiq-redis
22

3-
on: push
3+
on: pull_request
44

55
jobs:
66
black:

.pre-commit-config.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,15 @@ repos:
4141
language: system
4242
pass_filenames: false
4343
types: [python]
44-
args: [--count, taskiq_nats]
44+
args: [--count, taskiq_nats, tests]
4545

4646
- id: mypy
4747
name: Validate types with MyPy
4848
entry: poetry run mypy
4949
language: system
50+
pass_filenames: false
5051
types: [python]
52+
args: [taskiq_nats, tests]
5153

5254
- id: yesqa
5355
name: Remove usless noqa

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Taskiq NATS
22

33
Taskiq-nats is a plugin for taskiq that adds NATS broker.
4+
This package has support for NATS JetStream.
45

56
## Installation
67

@@ -16,7 +17,7 @@ Here's a minimal setup example with a broker and one task.
1617

1718
```python
1819
import asyncio
19-
from taskiq_nats import NatsBroker
20+
from taskiq_nats import NatsBroker, JetStreamBroker
2021

2122
broker = NatsBroker(
2223
[
@@ -26,6 +27,17 @@ broker = NatsBroker(
2627
queue="random_queue_name",
2728
)
2829

30+
# Or alternatively you can use a JetStream broker:
31+
broker = JetStreamBroker(
32+
[
33+
"nats://nats1:4222",
34+
"nats://nats2:4222",
35+
],
36+
queue="random_queue_name",
37+
subject="my-subj",
38+
stream_name="my-stream"
39+
)
40+
2941

3042
@broker.task
3143
async def my_lovely_task():

docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ services:
1010
- "-m"
1111
- "8222"
1212
- "--debug"
13+
- "--jetstream"
1314
healthcheck:
1415
test:
1516
- "CMD"

poetry.lock

Lines changed: 443 additions & 339 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "taskiq-nats"
3-
version = "0.3.0"
3+
version = "0.3.1"
44
description = "NATS integration for taskiq"
55
authors = ["taskiq-team <taskiq@norely.com>"]
66
readme = "README.md"
@@ -21,7 +21,7 @@ keywords = ["taskiq", "tasks", "distributed", "async", "nats", "result_backend"]
2121
[tool.poetry.dependencies]
2222
python = "^3.8.1"
2323
nats-py = "^2.2.0"
24-
taskiq = "^0"
24+
taskiq = ">=0.8,<1"
2525

2626

2727
[tool.poetry.group.dev.dependencies]

taskiq_nats/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"""
22
NATS integration for taskiq.
33
4-
This package contains broker that
4+
This package contains brokers that
55
uses NATS as a message queue.
66
"""
77

8-
from taskiq_nats.broker import NatsBroker
8+
from taskiq_nats.broker import JetStreamBroker, NatsBroker
99

10-
__all__ = ["NatsBroker"]
10+
__all__ = ["NatsBroker", "JetStreamBroker"]

taskiq_nats/broker.py

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
from typing import Any, AsyncGenerator, Callable, List, Optional, TypeVar, Union
33

44
from nats.aio.client import Client
5-
from taskiq import AsyncBroker, AsyncResultBackend, BrokerMessage
5+
from nats.js import JetStreamContext
6+
from nats.js.api import StreamConfig
7+
from taskiq import AckableMessage, AsyncBroker, AsyncResultBackend, BrokerMessage
68

79
_T = TypeVar("_T") # noqa: WPS111 (Too short)
810

@@ -28,7 +30,7 @@ class NatsBroker(AsyncBroker):
2830
def __init__( # noqa: WPS211 (too many args)
2931
self,
3032
servers: Union[str, List[str]],
31-
subject: str = "tasiq_tasks",
33+
subject: str = "taskiq_tasks",
3234
queue: Optional[str] = None,
3335
result_backend: "Optional[AsyncResultBackend[_T]]" = None,
3436
task_id_generator: Optional[Callable[[], str]] = None,
@@ -76,3 +78,82 @@ async def shutdown(self) -> None:
7678
"""Close connections to NATS."""
7779
await self.client.close()
7880
await super().shutdown()
81+
82+
83+
class JetStreamBroker(AsyncBroker): # noqa: WPS230
84+
"""
85+
JetStream broker for taskiq.
86+
87+
This broker creates a JetStream context
88+
and uses it to send and receive messages.
89+
90+
This is useful for systems where you need to
91+
be sure that messages are delivered to the workers.
92+
"""
93+
94+
def __init__( # noqa: WPS211 (too many args)
95+
self,
96+
servers: Union[str, List[str]],
97+
subject: str = "tasiq_tasks",
98+
stream_name: str = "taskiq_jstream",
99+
queue: Optional[str] = None,
100+
result_backend: "Optional[AsyncResultBackend[_T]]" = None,
101+
task_id_generator: Optional[Callable[[], str]] = None,
102+
stream_config: Optional[StreamConfig] = None,
103+
**connection_kwargs: Any,
104+
) -> None:
105+
super().__init__(result_backend, task_id_generator)
106+
self.servers = servers
107+
self.client: Client = Client()
108+
self.connection_kwargs = connection_kwargs
109+
self.queue = queue
110+
self.subject = subject
111+
self.stream_name = stream_name
112+
self.js: JetStreamContext
113+
self.stream_config = stream_config or StreamConfig()
114+
115+
async def startup(self) -> None:
116+
"""
117+
Startup event handler.
118+
119+
It simply connects to NATS cluster, and
120+
setup JetStream.
121+
"""
122+
await super().startup()
123+
await self.client.connect(self.servers, **self.connection_kwargs)
124+
self.js = self.client.jetstream()
125+
if self.stream_config.name is None:
126+
self.stream_config.name = self.stream_name
127+
if not self.stream_config.subjects:
128+
self.stream_config.subjects = [self.subject]
129+
await self.js.add_stream(config=self.stream_config)
130+
131+
async def kick(self, message: BrokerMessage) -> None:
132+
"""
133+
Send a message using NATS.
134+
135+
:param message: message to send.
136+
"""
137+
await self.js.publish(
138+
self.subject,
139+
payload=message.message,
140+
headers=message.labels,
141+
)
142+
143+
async def listen(self) -> AsyncGenerator[AckableMessage, None]:
144+
"""
145+
Start listen to new messages.
146+
147+
:yield: incoming messages.
148+
"""
149+
subscribe = await self.js.subscribe(self.subject, queue=self.queue or "")
150+
async for message in subscribe.messages:
151+
yield AckableMessage(
152+
data=message.data,
153+
ack=message.ack,
154+
)
155+
156+
async def shutdown(self) -> None:
157+
"""Close connections to NATS."""
158+
await self.client.close()
159+
await super().shutdown()

tests/conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import uuid
23
from typing import List
34

45
import pytest
@@ -22,7 +23,7 @@ def nats_subject() -> str:
2223
2324
:return: random string.
2425
"""
25-
return "12345"
26+
return uuid.uuid4().hex
2627

2728

2829
@pytest.fixture

tests/test_broker.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,7 @@
66
from taskiq import BrokerMessage
77

88
from taskiq_nats import NatsBroker
9-
10-
11-
async def read_message(broker: NatsBroker) -> bytes: # type: ignore
12-
"""
13-
Read signle message from the broker's listen method.
14-
15-
:param broker: current broker.
16-
:return: firs message.
17-
"""
18-
async for message in broker.listen(): # noqa: WPS328
19-
return message
9+
from tests.utils import read_message
2010

2111

2212
@pytest.mark.anyio
@@ -40,6 +30,8 @@ async def test_success_broadcast(nats_urls: List[str], nats_subject: str) -> Non
4030
for received_message in await asyncio.wait_for(asyncio.gather(*tasks), timeout=1):
4131
assert received_message == sent_message.message
4232

33+
await broker.shutdown()
34+
4335

4436
@pytest.mark.anyio
4537
async def test_success_queued(nats_urls: List[str], nats_subject: str) -> None:
@@ -58,3 +50,4 @@ async def test_success_queued(nats_urls: List[str], nats_subject: str) -> None:
5850
)
5951
asyncio.create_task(broker.kick(sent_message))
6052
assert await asyncio.wait_for(reading_task, timeout=1) == sent_message.message
53+
await broker.shutdown()

tests/test_jstream.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import asyncio
2+
import uuid
3+
from typing import List
4+
5+
import pytest
6+
from taskiq import AckableMessage, BrokerMessage
7+
8+
from taskiq_nats import JetStreamBroker
9+
from tests.utils import read_message
10+
11+
12+
@pytest.mark.anyio
13+
async def test_success(nats_urls: List[str], nats_subject: str) -> None:
14+
"""
15+
Tests that JetStream works.
16+
17+
This function sends a message to JetStream
18+
before starting to listen to it.
19+
20+
It epexts to receive the same message.
21+
"""
22+
broker = JetStreamBroker(
23+
servers=nats_urls,
24+
subject=nats_subject,
25+
queue=uuid.uuid4().hex,
26+
stream_name=uuid.uuid4().hex,
27+
)
28+
await broker.startup()
29+
sent_message = BrokerMessage(
30+
task_id=uuid.uuid4().hex,
31+
task_name="meme",
32+
message=b"some",
33+
labels={},
34+
)
35+
await broker.kick(sent_message)
36+
ackable_msg = await asyncio.wait_for(read_message(broker), 0.5)
37+
assert isinstance(ackable_msg, AckableMessage)
38+
assert ackable_msg.data == sent_message.message
39+
ack = ackable_msg.ack()
40+
if ack is not None:
41+
await ack
42+
await broker.shutdown()

tests/utils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from typing import Union
2+
3+
from taskiq import AckableMessage, AsyncBroker
4+
5+
6+
async def read_message(broker: AsyncBroker) -> Union[bytes, AckableMessage]:
7+
"""
8+
Read signle message from the broker's listen method.
9+
10+
:param broker: current broker.
11+
:return: firs message.
12+
"""
13+
msg: Union[bytes, AckableMessage] = b"error"
14+
async for message in broker.listen():
15+
msg = message
16+
break
17+
return msg

0 commit comments

Comments
 (0)