microagent 1.8.2__tar.gz → 1.8.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {microagent-1.8.2 → microagent-1.8.3}/CHANGELOG.rst +7 -0
- {microagent-1.8.2 → microagent-1.8.3}/PKG-INFO +1 -1
- {microagent-1.8.2 → microagent-1.8.3}/microagent/__init__.py +1 -1
- {microagent-1.8.2 → microagent-1.8.3}/microagent/abc.py +3 -0
- {microagent-1.8.2 → microagent-1.8.3}/microagent/agent.py +2 -0
- {microagent-1.8.2 → microagent-1.8.3}/microagent/broker.py +12 -0
- {microagent-1.8.2 → microagent-1.8.3}/microagent/queue.py +1 -1
- {microagent-1.8.2 → microagent-1.8.3}/microagent/signal.py +1 -1
- {microagent-1.8.2 → microagent-1.8.3}/microagent/tools/amqp.py +44 -18
- {microagent-1.8.2 → microagent-1.8.3}/microagent/tools/kafka.py +15 -12
- {microagent-1.8.2 → microagent-1.8.3}/microagent/tools/mocks.py +3 -2
- {microagent-1.8.2 → microagent-1.8.3}/microagent/tools/redis.py +22 -24
- {microagent-1.8.2 → microagent-1.8.3}/microagent.egg-info/PKG-INFO +1 -1
- {microagent-1.8.2 → microagent-1.8.3}/tests/test_tools_amqp.py +5 -2
- {microagent-1.8.2 → microagent-1.8.3}/tests/test_tools_kafka.py +11 -4
- {microagent-1.8.2 → microagent-1.8.3}/tests/test_tools_redis.py +1 -1
- {microagent-1.8.2 → microagent-1.8.3}/.github/workflows/python-app.yml +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/.gitignore +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/.readthedocs.yml +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/.travis.yml +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/LICENSE.md +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/Makefile +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/README.rst +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/Makefile +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/agent.rst +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/amqp.rst +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/broker.rst +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/bus.rst +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/conf.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/example_agents.rst +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/example_run.rst +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/hooks.rst +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/index.rst +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/kafka.rst +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/launcher.rst +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/ma-bus.dia +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/ma-bus.svg +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/ma-logo.dia +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/ma-logo.svg +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/ma-periodic.dia +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/ma-periodic.svg +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/ma-queue.dia +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/ma-queue.svg +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/mocks.rst +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/periodic.rst +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/redis.rst +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/docs/requirements.txt +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/examples/__init__.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/examples/comment_agent.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/examples/email_agent.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/examples/push_agent.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/examples/redis_server.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/examples/requirements.txt +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/examples/settings.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/examples/signals.json +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/examples/user_agent.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/microagent/bus.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/microagent/hooks.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/microagent/launcher.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/microagent/py.typed +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/microagent/timer.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/microagent/tools/__init__.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/microagent/utils.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/microagent.egg-info/SOURCES.txt +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/microagent.egg-info/dependency_links.txt +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/microagent.egg-info/entry_points.txt +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/microagent.egg-info/requires.txt +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/microagent.egg-info/top_level.txt +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/pyproject.toml +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/setup.cfg +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/tests/__init__.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/tests/conftest.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/tests/stuff.json +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/tests/test_agent.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/tests/test_api.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/tests/test_broker.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/tests/test_bus.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/tests/test_hook.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/tests/test_launcher.py +0 -0
- {microagent-1.8.2 → microagent-1.8.3}/tests/test_timer.py +0 -0
|
@@ -56,6 +56,7 @@ Using with MicroAgent
|
|
|
56
56
|
email_agent = EmailAgent(broker=broker)
|
|
57
57
|
await email_agent.start()
|
|
58
58
|
'''
|
|
59
|
+
import asyncio
|
|
59
60
|
import logging
|
|
60
61
|
import uuid
|
|
61
62
|
from abc import abstractmethod
|
|
@@ -108,6 +109,17 @@ class AbstractQueueBroker(BrokerProtocol):
|
|
|
108
109
|
log: logging.Logger = logging.getLogger('microagent.broker')
|
|
109
110
|
|
|
110
111
|
_bindings: dict[str, Consumer] = field(default_factory=dict)
|
|
112
|
+
_background_tasks: set[asyncio.Task] = field(default_factory=set)
|
|
113
|
+
|
|
114
|
+
async def close(self) -> None:
|
|
115
|
+
'''
|
|
116
|
+
Graceful shutdown. Cancel all background tasks.
|
|
117
|
+
'''
|
|
118
|
+
tasks = list(self._background_tasks)
|
|
119
|
+
for task in tasks:
|
|
120
|
+
task.cancel()
|
|
121
|
+
if tasks:
|
|
122
|
+
await asyncio.wait(tasks)
|
|
111
123
|
|
|
112
124
|
def __getattr__(self, name: str) -> 'BoundQueue':
|
|
113
125
|
return BoundQueue(self, Queue.get(name))
|
|
@@ -60,15 +60,30 @@ class AMQPBroker(AbstractQueueBroker):
|
|
|
60
60
|
'''
|
|
61
61
|
connection: AbstractConnection = field(init=False)
|
|
62
62
|
sending_channel: AbstractChannel | None = None
|
|
63
|
+
_managed_connections: list['ManagedConnection'] = field(default_factory=list, init=False)
|
|
64
|
+
_closing: bool = field(default=False, init=False)
|
|
63
65
|
|
|
64
66
|
def __post_init__(self) -> None:
|
|
65
67
|
self.connection = ReConnection(self.reconnect, self.dsn)
|
|
66
68
|
|
|
67
69
|
async def reconnect(self) -> None:
|
|
70
|
+
if self._closing:
|
|
71
|
+
return
|
|
68
72
|
self.connection = ReConnection(self.reconnect, self.dsn)
|
|
69
73
|
await self.connection.connect()
|
|
70
74
|
log.info('Reconnect "%s"', self.connection)
|
|
71
75
|
|
|
76
|
+
async def close(self) -> None:
|
|
77
|
+
self._closing = True
|
|
78
|
+
for mc in self._managed_connections:
|
|
79
|
+
mc.closed = True
|
|
80
|
+
await mc.close()
|
|
81
|
+
if self.sending_channel and not self.sending_channel.is_closed:
|
|
82
|
+
await self.sending_channel.close()
|
|
83
|
+
if self.connection.is_opened:
|
|
84
|
+
await self.connection.close()
|
|
85
|
+
await super().close()
|
|
86
|
+
|
|
72
87
|
async def get_channel(self) -> AbstractChannel:
|
|
73
88
|
'''
|
|
74
89
|
Takes a channel from the pool or a new one, performs a lazy connection if required.
|
|
@@ -103,17 +118,17 @@ class AMQPBroker(AbstractQueueBroker):
|
|
|
103
118
|
|
|
104
119
|
async def bind(self, name: str) -> None:
|
|
105
120
|
consumer = self._bindings[name]
|
|
106
|
-
|
|
121
|
+
mc = ManagedConnection(
|
|
107
122
|
dsn=self.dsn,
|
|
108
123
|
consumer=consumer,
|
|
109
124
|
handler=self._amqp_wrapper(consumer)
|
|
110
|
-
)
|
|
125
|
+
)
|
|
126
|
+
self._managed_connections.append(mc)
|
|
127
|
+
await mc.bind()
|
|
111
128
|
|
|
112
129
|
def _amqp_wrapper(self, consumer: Consumer) -> Callable[[DeliveredMessage], Awaitable[None]]:
|
|
113
130
|
async def _wrapper(message: DeliveredMessage) -> None:
|
|
114
|
-
|
|
115
|
-
log.debug('Calling %s by %s without data', consumer, consumer.queue.name)
|
|
116
|
-
return
|
|
131
|
+
data = self.prepared_data(consumer, message.body)
|
|
117
132
|
|
|
118
133
|
log.debug('Calling %s by %s with %s', consumer,
|
|
119
134
|
consumer.queue.name, str(data).encode('utf-8'))
|
|
@@ -126,7 +141,7 @@ class AMQPBroker(AbstractQueueBroker):
|
|
|
126
141
|
if consumer.options.get('autoack', True) and message.delivery_tag:
|
|
127
142
|
await message.channel.basic_ack(delivery_tag=message.delivery_tag)
|
|
128
143
|
|
|
129
|
-
except TypeError:
|
|
144
|
+
except TypeError: # when data mismatch handler args (stay data in queue)
|
|
130
145
|
log.exception('Call %s failed', consumer)
|
|
131
146
|
|
|
132
147
|
except asyncio.TimeoutError:
|
|
@@ -185,12 +200,15 @@ class ManagedConnection:
|
|
|
185
200
|
handler: Callable
|
|
186
201
|
bind_attempts: int = 0
|
|
187
202
|
bind_running: bool = False
|
|
203
|
+
closed: bool = False
|
|
204
|
+
_connection: AbstractConnection | None = None
|
|
205
|
+
_channel: AbstractChannel | None = None
|
|
188
206
|
|
|
189
207
|
async def bind(self) -> None:
|
|
190
208
|
''' Start connection and bind consumer '''
|
|
191
|
-
|
|
192
|
-
await
|
|
193
|
-
|
|
209
|
+
self._connection = ReConnection(self.rebind, self.dsn)
|
|
210
|
+
await self._connection.connect()
|
|
211
|
+
self._channel = await self._connection.channel()
|
|
194
212
|
|
|
195
213
|
queue_name = self.consumer.queue.name
|
|
196
214
|
exchange_name = self.consumer.queue.exchange
|
|
@@ -199,27 +217,35 @@ class ManagedConnection:
|
|
|
199
217
|
log.warning('Declare queue "%s" with exchange_name "%s" topics "%s"',
|
|
200
218
|
queue_name, exchange_name, topics)
|
|
201
219
|
|
|
202
|
-
await
|
|
220
|
+
await self._channel.queue_declare(queue_name)
|
|
203
221
|
|
|
204
222
|
if topics: # topics
|
|
205
|
-
await
|
|
223
|
+
await self._channel.exchange_declare(exchange=exchange_name, exchange_type='topic')
|
|
206
224
|
for topic in topics:
|
|
207
|
-
await
|
|
225
|
+
await self._channel.queue_bind(queue=queue_name, exchange=exchange_name, routing_key=topic)
|
|
208
226
|
elif exchange_name: # fanout
|
|
209
|
-
await
|
|
210
|
-
await
|
|
227
|
+
await self._channel.exchange_declare(exchange=exchange_name, exchange_type='fanout')
|
|
228
|
+
await self._channel.queue_bind(queue=queue_name, exchange=exchange_name)
|
|
229
|
+
|
|
230
|
+
await self._channel.basic_consume(queue_name, self.handler)
|
|
211
231
|
|
|
212
|
-
|
|
232
|
+
async def close(self) -> None:
|
|
233
|
+
if self._channel and not self._channel.is_closed:
|
|
234
|
+
await self._channel.close()
|
|
235
|
+
if self._connection and self._connection.is_opened:
|
|
236
|
+
await self._connection.close()
|
|
213
237
|
|
|
214
238
|
async def rebind(self) -> bool:
|
|
215
|
-
if self.bind_running:
|
|
216
|
-
log.
|
|
239
|
+
if self.closed or self.bind_running:
|
|
240
|
+
log.warning('Already rebinding queue "%s"', self.consumer.queue.name)
|
|
217
241
|
return False
|
|
218
242
|
|
|
219
243
|
if self.bind_attempts > REBIND_ATTEMPTS:
|
|
220
|
-
log.
|
|
244
|
+
log.error('Failed all attempts to rebind queue "%s"', self.consumer.queue.name)
|
|
221
245
|
return False
|
|
222
246
|
|
|
247
|
+
self.bind_running = True
|
|
248
|
+
|
|
223
249
|
await asyncio.sleep((self.bind_attempts ** 2) * REBIND_BASE_DELAY)
|
|
224
250
|
self.bind_attempts += 1
|
|
225
251
|
|
|
@@ -44,26 +44,27 @@ class KafkaBroker(AbstractQueueBroker):
|
|
|
44
44
|
'''
|
|
45
45
|
addr: str = field(init=False)
|
|
46
46
|
producer: aiokafka.AIOKafkaProducer = field(init=False)
|
|
47
|
+
_producer_started: bool = field(default=False, init=False)
|
|
47
48
|
|
|
48
49
|
def __post_init__(self) -> None:
|
|
49
50
|
self.addr = parse.urlparse(self.dsn).netloc
|
|
50
51
|
self.producer = aiokafka.AIOKafkaProducer(bootstrap_servers=self.addr)
|
|
51
52
|
|
|
52
53
|
async def send(self, name: str, message: str, **kwargs: Any) -> None:
|
|
53
|
-
|
|
54
|
+
if not self._producer_started:
|
|
55
|
+
await self.producer.start()
|
|
56
|
+
self._producer_started = True
|
|
57
|
+
await self.producer.send_and_wait(name, bytes(message, 'utf8'), **kwargs)
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
finally:
|
|
59
|
-
await self.producer.stop()
|
|
60
|
-
_loop = asyncio.get_running_loop()
|
|
61
|
-
self.producer = aiokafka.AIOKafkaProducer(loop=_loop, bootstrap_servers=self.addr)
|
|
59
|
+
async def close(self) -> None:
|
|
60
|
+
await self.producer.stop()
|
|
61
|
+
await super().close()
|
|
62
62
|
|
|
63
63
|
async def bind(self, name: str) -> None:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
kafka_consumer = aiokafka.AIOKafkaConsumer(name, bootstrap_servers=self.addr)
|
|
65
|
+
task = asyncio.create_task(self._kafka_wrapper(kafka_consumer, name))
|
|
66
|
+
self._background_tasks.add(task)
|
|
67
|
+
task.add_done_callback(self._background_tasks.discard)
|
|
67
68
|
|
|
68
69
|
async def _kafka_wrapper(self, kafka_consumer: aiokafka.AIOKafkaConsumer, name: str) -> None:
|
|
69
70
|
consumer = self._bindings[name]
|
|
@@ -73,7 +74,9 @@ class KafkaBroker(AbstractQueueBroker):
|
|
|
73
74
|
async for msg in kafka_consumer:
|
|
74
75
|
data = self.prepared_data(consumer, msg.value)
|
|
75
76
|
data['kafka'] = msg
|
|
76
|
-
asyncio.create_task(self._handle(consumer, data))
|
|
77
|
+
htask = asyncio.create_task(self._handle(consumer, data))
|
|
78
|
+
self._background_tasks.add(htask)
|
|
79
|
+
htask.add_done_callback(self._background_tasks.discard)
|
|
77
80
|
|
|
78
81
|
finally:
|
|
79
82
|
await kafka_consumer.stop()
|
|
@@ -68,14 +68,15 @@ class BrokerMock(MagicMock):
|
|
|
68
68
|
self._stuff: dict[str, BoundQueueMock] = {}
|
|
69
69
|
self.bind_consumer = AsyncMock()
|
|
70
70
|
self.send = AsyncMock()
|
|
71
|
+
self.close = AsyncMock()
|
|
71
72
|
self.dsn = ''
|
|
72
73
|
self.uid = ''
|
|
73
74
|
|
|
74
75
|
def __str__(self) -> str:
|
|
75
|
-
return f'<BrokerMock id={id(self)}'
|
|
76
|
+
return f'<BrokerMock id={id(self)}>'
|
|
76
77
|
|
|
77
78
|
def __getattr__(self, name: str) -> BoundQueueMock:
|
|
78
|
-
if name.startswith('_') or name in {'bind_consumer', 'send'}:
|
|
79
|
+
if name.startswith('_') or name in {'bind_consumer', 'send', 'close'}:
|
|
79
80
|
return super().__getattr__(name)
|
|
80
81
|
self._stuff[name] = self._stuff.get(name, BoundQueueMock())
|
|
81
82
|
return self._stuff[name]
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
:ref:`Signal Bus <bus>` and :ref:`Queue Broker <broker>` based on :redis:`redis <>`.
|
|
3
3
|
'''
|
|
4
4
|
import asyncio
|
|
5
|
-
import inspect
|
|
6
5
|
import time
|
|
7
6
|
from collections import defaultdict
|
|
8
7
|
from dataclasses import dataclass, field
|
|
@@ -110,33 +109,32 @@ class RedisBroker(AbstractQueueBroker):
|
|
|
110
109
|
return Redis.from_url(self.dsn, decode_responses=True)
|
|
111
110
|
|
|
112
111
|
async def send(self, name: str, message: str, **kwargs: Any) -> None:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if inspect.isawaitable(ret):
|
|
116
|
-
await ret
|
|
112
|
+
await self.connection.rpush(name, message)
|
|
117
113
|
|
|
118
114
|
async def queue_length(self, name: str, **options: Any) -> int:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if inspect.isawaitable(ret):
|
|
122
|
-
return await ret
|
|
123
|
-
|
|
124
|
-
return ret
|
|
115
|
+
return await self.connection.llen(name)
|
|
125
116
|
|
|
126
117
|
async def bind(self, name: str) -> None:
|
|
127
|
-
|
|
128
|
-
|
|
118
|
+
task = asyncio.create_task(self._wait(name))
|
|
119
|
+
self._background_tasks.add(task)
|
|
120
|
+
task.add_done_callback(self._background_tasks.discard)
|
|
129
121
|
|
|
130
122
|
async def _wait(self, name: str) -> None:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
123
|
+
await asyncio.sleep(self.BIND_TIME)
|
|
124
|
+
conn = self.new_connection()
|
|
125
|
+
try:
|
|
126
|
+
while True:
|
|
127
|
+
if data := await conn.blpop(name, self.WAIT_TIME):
|
|
128
|
+
_, data = data
|
|
129
|
+
htask = asyncio.create_task(self._handler(name, data))
|
|
130
|
+
self._background_tasks.add(htask)
|
|
131
|
+
htask.add_done_callback(self._background_tasks.discard)
|
|
132
|
+
finally:
|
|
133
|
+
await conn.aclose()
|
|
136
134
|
|
|
137
135
|
async def rollback(self, name: str, data: str) -> None:
|
|
138
|
-
|
|
139
|
-
attempt = self._rollbacks[
|
|
136
|
+
_key = (name, data)
|
|
137
|
+
attempt = self._rollbacks[_key]
|
|
140
138
|
|
|
141
139
|
if attempt > self.ROLLBACK_ATTEMPTS:
|
|
142
140
|
self.log.error('Rollback limit exceeded on queue "%s" with data: %s', name, data)
|
|
@@ -147,7 +145,7 @@ class RedisBroker(AbstractQueueBroker):
|
|
|
147
145
|
_loop = asyncio.get_running_loop()
|
|
148
146
|
_loop.call_later(attempt ** 2, lambda: asyncio.create_task(self.send(name, data)))
|
|
149
147
|
|
|
150
|
-
self._rollbacks[
|
|
148
|
+
self._rollbacks[_key] += 1
|
|
151
149
|
|
|
152
150
|
async def _handler(self, name: str, data: str) -> None:
|
|
153
151
|
consumer = self._bindings[name]
|
|
@@ -156,9 +154,9 @@ class RedisBroker(AbstractQueueBroker):
|
|
|
156
154
|
|
|
157
155
|
try:
|
|
158
156
|
await asyncio.wait_for(consumer.handler(**_data), consumer.timeout)
|
|
159
|
-
except Exception:
|
|
160
|
-
self.log.exception('Call %s failed', consumer.queue.name)
|
|
161
|
-
await self.rollback(consumer.queue.name, data)
|
|
162
157
|
except asyncio.TimeoutError:
|
|
163
158
|
self.log.error('TimeoutError: %s %.2f', consumer, time.monotonic() - timer)
|
|
164
159
|
await self.rollback(name, data)
|
|
160
|
+
except Exception:
|
|
161
|
+
self.log.exception('Call %s failed', consumer.queue.name)
|
|
162
|
+
await self.rollback(consumer.queue.name, data)
|
|
@@ -136,6 +136,9 @@ async def test_broker_rebind_ok_already(monkeypatch, channel):
|
|
|
136
136
|
|
|
137
137
|
assert not await conn.rebind()
|
|
138
138
|
|
|
139
|
+
conn.bind_running = False
|
|
140
|
+
assert await conn.rebind()
|
|
141
|
+
|
|
139
142
|
|
|
140
143
|
async def test_broker_rebind_ok_attempts(monkeypatch, channel):
|
|
141
144
|
queue = Queue(name='test_queue')
|
|
@@ -204,8 +207,8 @@ async def test_broker_wrapper_ok_nodata(channel):
|
|
|
204
207
|
|
|
205
208
|
await broker._amqp_wrapper(consumer)(message)
|
|
206
209
|
|
|
207
|
-
channel.basic_ack.
|
|
208
|
-
handler.
|
|
210
|
+
channel.basic_ack.assert_called()
|
|
211
|
+
handler.assert_called_once()
|
|
209
212
|
|
|
210
213
|
|
|
211
214
|
async def test_broker_wrapper_ok_type_err(channel):
|
|
@@ -71,10 +71,17 @@ async def test_broker_send_ok(kafka_producer):
|
|
|
71
71
|
broker = KafkaBroker('kafka://localhost')
|
|
72
72
|
await broker.send(queue.name, '{}')
|
|
73
73
|
|
|
74
|
-
broker.producer.start.
|
|
74
|
+
broker.producer.start.assert_called_once()
|
|
75
75
|
broker.producer.send_and_wait.assert_called_once_with(queue.name, b'{}')
|
|
76
|
-
broker.producer.stop.
|
|
76
|
+
broker.producer.stop.assert_not_called()
|
|
77
77
|
|
|
78
|
-
broker.producer._closed = False
|
|
79
78
|
await broker.send(queue.name, '{}')
|
|
80
|
-
broker.producer.
|
|
79
|
+
assert broker.producer.send_and_wait.call_count == 2
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def test_broker_close_stops_producer_ok(kafka_producer):
|
|
83
|
+
broker = KafkaBroker('kafka://localhost')
|
|
84
|
+
await broker.send('test_queue', '{}')
|
|
85
|
+
|
|
86
|
+
await broker.close()
|
|
87
|
+
broker.producer.stop.assert_called_once()
|
|
@@ -142,7 +142,7 @@ async def test_broker_rollback_many_attempts_ok():
|
|
|
142
142
|
queue = Queue(name='test_queue')
|
|
143
143
|
broker = RedisBroker('redis://localhost')
|
|
144
144
|
broker.send = AsyncMock()
|
|
145
|
-
broker._rollbacks[
|
|
145
|
+
broker._rollbacks[(queue.name, '{}')] = 4
|
|
146
146
|
await broker.rollback(queue.name, '{}')
|
|
147
147
|
broker.send.assert_not_called()
|
|
148
148
|
await asyncio.sleep(.01)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|