microagent 1.8.1__tar.gz → 1.8.2__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.1 → microagent-1.8.2}/CHANGELOG.rst +7 -0
- {microagent-1.8.1 → microagent-1.8.2}/PKG-INFO +1 -1
- {microagent-1.8.1 → microagent-1.8.2}/docs/conf.py +1 -2
- {microagent-1.8.1 → microagent-1.8.2}/examples/comment_agent.py +3 -2
- {microagent-1.8.1 → microagent-1.8.2}/examples/email_agent.py +2 -1
- {microagent-1.8.1 → microagent-1.8.2}/examples/redis_server.py +4 -3
- {microagent-1.8.1 → microagent-1.8.2}/examples/user_agent.py +2 -1
- {microagent-1.8.1 → microagent-1.8.2}/microagent/__init__.py +2 -2
- {microagent-1.8.1 → microagent-1.8.2}/microagent/agent.py +2 -0
- {microagent-1.8.1 → microagent-1.8.2}/microagent/bus.py +23 -8
- {microagent-1.8.1 → microagent-1.8.2}/microagent/timer.py +17 -10
- {microagent-1.8.1 → microagent-1.8.2}/microagent/tools/mocks.py +3 -2
- {microagent-1.8.1 → microagent-1.8.2}/microagent/tools/redis.py +3 -1
- {microagent-1.8.1 → microagent-1.8.2}/microagent.egg-info/PKG-INFO +1 -1
- {microagent-1.8.1 → microagent-1.8.2}/tests/test_bus.py +105 -0
- {microagent-1.8.1 → microagent-1.8.2}/.github/workflows/python-app.yml +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/.gitignore +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/.readthedocs.yml +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/.travis.yml +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/LICENSE.md +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/Makefile +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/README.rst +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/Makefile +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/agent.rst +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/amqp.rst +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/broker.rst +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/bus.rst +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/example_agents.rst +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/example_run.rst +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/hooks.rst +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/index.rst +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/kafka.rst +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/launcher.rst +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/ma-bus.dia +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/ma-bus.svg +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/ma-logo.dia +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/ma-logo.svg +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/ma-periodic.dia +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/ma-periodic.svg +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/ma-queue.dia +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/ma-queue.svg +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/mocks.rst +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/periodic.rst +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/redis.rst +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/docs/requirements.txt +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/examples/__init__.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/examples/push_agent.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/examples/requirements.txt +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/examples/settings.py +1 -1
- {microagent-1.8.1 → microagent-1.8.2}/examples/signals.json +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/microagent/abc.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/microagent/broker.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/microagent/hooks.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/microagent/launcher.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/microagent/py.typed +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/microagent/queue.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/microagent/signal.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/microagent/tools/__init__.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/microagent/tools/amqp.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/microagent/tools/kafka.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/microagent/utils.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/microagent.egg-info/SOURCES.txt +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/microagent.egg-info/dependency_links.txt +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/microagent.egg-info/entry_points.txt +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/microagent.egg-info/requires.txt +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/microagent.egg-info/top_level.txt +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/pyproject.toml +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/setup.cfg +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/tests/__init__.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/tests/conftest.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/tests/stuff.json +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/tests/test_agent.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/tests/test_api.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/tests/test_broker.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/tests/test_hook.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/tests/test_launcher.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/tests/test_timer.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/tests/test_tools_amqp.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/tests/test_tools_kafka.py +0 -0
- {microagent-1.8.1 → microagent-1.8.2}/tests/test_tools_redis.py +0 -0
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
# -- Path setup --------------------------------------------------------------
|
|
10
10
|
|
|
11
|
+
import pathlib
|
|
11
12
|
# If extensions (or modules to document with autodoc) are in another directory,
|
|
12
13
|
# add these directories to sys.path here. If the directory is relative to the
|
|
13
14
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
@@ -17,8 +18,6 @@
|
|
|
17
18
|
# sys.path.insert(0, os.path.abspath('.'))
|
|
18
19
|
# mypy: ignore-errors
|
|
19
20
|
import re
|
|
20
|
-
import pathlib
|
|
21
|
-
|
|
22
21
|
|
|
23
22
|
try:
|
|
24
23
|
version = re.findall(r"^__version__ = '([^']+)'\r?$",
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# mypy: ignore-errors
|
|
2
|
-
import os
|
|
3
2
|
import asyncio
|
|
3
|
+
import os
|
|
4
4
|
from collections import defaultdict
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
from microagent import MicroAgent, load_stuff, on, receiver
|
|
6
7
|
|
|
7
8
|
cur_dir = os.path.dirname(os.path.realpath(__file__))
|
|
8
9
|
signals, queues = load_stuff('file://' + os.path.join(cur_dir, 'signals.json'))
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# mypy: ignore-errors
|
|
2
2
|
import os
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
from microagent import MicroAgent, consumer, load_stuff, on
|
|
4
5
|
|
|
5
6
|
cur_dir = os.path.dirname(os.path.realpath(__file__))
|
|
6
7
|
signals, queues = load_stuff('file://' + os.path.join(cur_dir, 'signals.json'))
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# mypy: ignore-errors
|
|
2
|
-
import sys
|
|
3
2
|
import asyncio
|
|
4
3
|
import logging
|
|
5
|
-
|
|
4
|
+
import sys
|
|
6
5
|
|
|
7
|
-
from user_agent import UserAgent
|
|
8
6
|
from comment_agent import CommentAgent
|
|
9
7
|
from email_agent import EmailAgent
|
|
8
|
+
from user_agent import UserAgent
|
|
9
|
+
|
|
10
|
+
from microagent.tools.redis import RedisBroker, RedisSignalBus
|
|
10
11
|
|
|
11
12
|
logging.basicConfig(format=(
|
|
12
13
|
'%(levelname)-8s [pid#%(process)d] %(asctime)s %(name)s '
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# mypy: ignore-errors
|
|
2
2
|
import os
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
from microagent import MicroAgent, cron, load_stuff, on, periodic, receiver
|
|
4
5
|
|
|
5
6
|
cur_dir = os.path.dirname(os.path.realpath(__file__))
|
|
6
7
|
signals, queues = load_stuff('file://' + os.path.join(cur_dir, 'signals.json'))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
__version__ = '1.8.
|
|
1
|
+
__version__ = '1.8.2'
|
|
2
2
|
|
|
3
3
|
import importlib
|
|
4
4
|
import json
|
|
@@ -44,7 +44,7 @@ def load_stuff(source: str) -> tuple[Any, Any]:
|
|
|
44
44
|
|
|
45
45
|
if source.startswith('file://'):
|
|
46
46
|
with open(source.replace('file://', ''), encoding='utf8') as f:
|
|
47
|
-
data.update(json.
|
|
47
|
+
data.update(json.load(f))
|
|
48
48
|
else:
|
|
49
49
|
with urllib.request.urlopen(source) as response:
|
|
50
50
|
data.update(json.loads(response.read()))
|
|
@@ -110,13 +110,18 @@ class AbstractSignalBus(BusProtocol):
|
|
|
110
110
|
|
|
111
111
|
receivers: dict[str, list[Receiver]] = field(default_factory=lambda: defaultdict(list))
|
|
112
112
|
_responses: dict[str, IterQueue] = field(default_factory=dict)
|
|
113
|
+
_tasks: set[asyncio.Task] = field(default_factory=set)
|
|
113
114
|
|
|
114
115
|
def __post_init__(self) -> None:
|
|
115
116
|
response_signal = Signal(name='response', providing_args=[])
|
|
116
|
-
asyncio.create_task(
|
|
117
|
+
task = asyncio.create_task(
|
|
118
|
+
self.bind(response_signal.make_channel_name(self.prefix))
|
|
119
|
+
)
|
|
120
|
+
self._tasks.add(task)
|
|
121
|
+
task.add_done_callback(self._tasks.discard)
|
|
117
122
|
self.log.debug('%s initialized', self)
|
|
118
123
|
|
|
119
|
-
def
|
|
124
|
+
def __str__(self) -> str:
|
|
120
125
|
return f'<Bus {self.__class__.__name__} {self.dsn} {self.prefix}#{self.uid}>'
|
|
121
126
|
|
|
122
127
|
def __getattr__(self, name: str) -> 'BoundSignal':
|
|
@@ -230,10 +235,20 @@ class AbstractSignalBus(BusProtocol):
|
|
|
230
235
|
if diff_args:
|
|
231
236
|
self.log.warning('Pubsub mismatch arguments %s %s', channel, diff_args)
|
|
232
237
|
|
|
233
|
-
asyncio.create_task(
|
|
238
|
+
task = asyncio.create_task(
|
|
239
|
+
self.handle_signal(signal, sender, signal_id, data)
|
|
240
|
+
)
|
|
241
|
+
self._tasks.add(task)
|
|
242
|
+
task.add_done_callback(self._tasks.discard)
|
|
234
243
|
|
|
235
244
|
return None
|
|
236
245
|
|
|
246
|
+
async def close(self) -> None:
|
|
247
|
+
for task in self._tasks:
|
|
248
|
+
task.cancel()
|
|
249
|
+
if self._tasks:
|
|
250
|
+
await asyncio.wait(self._tasks)
|
|
251
|
+
|
|
237
252
|
def handle_response(self, signal_id: str, message: dict[str, int | str | None]) -> None:
|
|
238
253
|
if queue := self._responses.get(signal_id):
|
|
239
254
|
queue.put_nowait(message)
|
|
@@ -272,13 +287,13 @@ class AbstractSignalBus(BusProtocol):
|
|
|
272
287
|
if isinstance(response, int | str):
|
|
273
288
|
return response
|
|
274
289
|
|
|
275
|
-
except TypeError:
|
|
276
|
-
self.log.exception('Call %s failed', signal.name)
|
|
277
|
-
|
|
278
290
|
except asyncio.TimeoutError:
|
|
279
291
|
self.log.error(
|
|
280
292
|
'TimeoutError: %s %.2f', receiver.handler, time.monotonic() - timer)
|
|
281
293
|
|
|
294
|
+
except Exception:
|
|
295
|
+
self.log.exception('Call %s failed', signal.name)
|
|
296
|
+
|
|
282
297
|
return None
|
|
283
298
|
|
|
284
299
|
|
|
@@ -295,7 +310,7 @@ class BoundSignal(SignalProtocol):
|
|
|
295
310
|
self.signal.make_channel_name(self.bus.prefix, sender),
|
|
296
311
|
self.signal.serialize(kwargs))
|
|
297
312
|
|
|
298
|
-
async def call(self, sender: str, timeout: int = 60, **kwargs: Any
|
|
313
|
+
async def call(self, sender: str, *, timeout: int = 60, **kwargs: Any
|
|
299
314
|
) -> dict[str, int | str | None]:
|
|
300
315
|
|
|
301
316
|
if self.signal.type_map:
|
|
@@ -313,7 +328,7 @@ class BoundSignal(SignalProtocol):
|
|
|
313
328
|
|
|
314
329
|
return {}
|
|
315
330
|
|
|
316
|
-
def waiter(self, sender: str, timeout: int = 60, **kwargs: Any
|
|
331
|
+
def waiter(self, sender: str, *, timeout: int = 60, **kwargs: Any
|
|
317
332
|
) -> contextlib.AbstractAsyncContextManager:
|
|
318
333
|
'''
|
|
319
334
|
async with bus.iter(sender='name', a=1, timeout=10) as queue:
|
|
@@ -24,7 +24,7 @@ import inspect
|
|
|
24
24
|
import re
|
|
25
25
|
import time
|
|
26
26
|
from collections.abc import Callable
|
|
27
|
-
from dataclasses import dataclass
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
28
|
from datetime import datetime, timedelta, timezone
|
|
29
29
|
from typing import TYPE_CHECKING, ClassVar, NamedTuple, TypedDict
|
|
30
30
|
|
|
@@ -54,12 +54,16 @@ class CRON(NamedTuple):
|
|
|
54
54
|
return f'[{self.spec}]'
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
class PeriodicState(TypedDict, total=False):
|
|
58
|
+
timer_handle: asyncio.TimerHandle
|
|
59
|
+
running_task: asyncio.Task
|
|
60
|
+
|
|
61
|
+
|
|
57
62
|
class PeriodicMixin:
|
|
58
63
|
agent: 'MicroAgent'
|
|
59
64
|
handler: Callable
|
|
60
65
|
timeout: float
|
|
61
|
-
|
|
62
|
-
_running_task: asyncio.Task | None = None
|
|
66
|
+
_state: PeriodicState
|
|
63
67
|
|
|
64
68
|
async def call(self) -> None:
|
|
65
69
|
try:
|
|
@@ -75,13 +79,14 @@ class PeriodicMixin:
|
|
|
75
79
|
self.agent.log.exception(f'Periodic Exception: {exc}')
|
|
76
80
|
|
|
77
81
|
def start(self, start_after: float) -> None:
|
|
78
|
-
self.
|
|
82
|
+
self._state['timer_handle'] = asyncio.get_running_loop().call_later(
|
|
83
|
+
start_after, _periodic, self) # type: ignore[arg-type]
|
|
79
84
|
|
|
80
85
|
def cancel(self) -> None:
|
|
81
|
-
if self.
|
|
82
|
-
|
|
83
|
-
if self.
|
|
84
|
-
|
|
86
|
+
if (timer_handle := self._state.get('timer_handle')) is not None:
|
|
87
|
+
timer_handle.cancel()
|
|
88
|
+
if (running_task := self._state.get('running_task')) is not None and not running_task.done():
|
|
89
|
+
running_task.cancel()
|
|
85
90
|
|
|
86
91
|
|
|
87
92
|
class PeriodicArgs(TypedDict):
|
|
@@ -97,6 +102,7 @@ class PeriodicTask(PeriodicMixin):
|
|
|
97
102
|
period: float
|
|
98
103
|
timeout: float
|
|
99
104
|
start_after: float
|
|
105
|
+
_state: PeriodicState = field(default_factory=PeriodicState)
|
|
100
106
|
|
|
101
107
|
_register: ClassVar[dict[BoundKey, PeriodicArgs]] = {}
|
|
102
108
|
|
|
@@ -115,6 +121,7 @@ class CRONTask(PeriodicMixin):
|
|
|
115
121
|
handler: PeriodicFunc
|
|
116
122
|
cron: CRON
|
|
117
123
|
timeout: float
|
|
124
|
+
_state: PeriodicState = field(default_factory=PeriodicState)
|
|
118
125
|
|
|
119
126
|
_register: ClassVar[dict[BoundKey, CRONArgs]] = {}
|
|
120
127
|
|
|
@@ -135,9 +142,9 @@ class CRONTask(PeriodicMixin):
|
|
|
135
142
|
|
|
136
143
|
|
|
137
144
|
def _periodic(task: PeriodicTask | CRONTask) -> asyncio.Task:
|
|
138
|
-
task.
|
|
145
|
+
task._state['timer_handle'] = asyncio.get_running_loop().call_later(task.period, _periodic, task)
|
|
139
146
|
running = asyncio.create_task(task.call())
|
|
140
|
-
task.
|
|
147
|
+
task._state['running_task'] = running
|
|
141
148
|
return running
|
|
142
149
|
|
|
143
150
|
|
|
@@ -39,12 +39,13 @@ class BusMock(MagicMock):
|
|
|
39
39
|
self.bind_signal = AsyncMock()
|
|
40
40
|
self.send = AsyncMock()
|
|
41
41
|
self.call = AsyncMock()
|
|
42
|
+
self.close = AsyncMock()
|
|
42
43
|
|
|
43
44
|
def __str__(self) -> str:
|
|
44
|
-
return f'<BusMock id={id(self)}'
|
|
45
|
+
return f'<BusMock id={id(self)}>'
|
|
45
46
|
|
|
46
47
|
def __getattr__(self, name: str) -> BoundSignalMock:
|
|
47
|
-
if name.startswith('_') or name in {'bind_signal', 'send', 'call'}:
|
|
48
|
+
if name.startswith('_') or name in {'bind_receiver', 'bind_signal', 'send', 'call', 'close'}:
|
|
48
49
|
return super().__getattr__(name)
|
|
49
50
|
self._stuff[name] = self._stuff.get(name, BoundSignalMock())
|
|
50
51
|
return self._stuff[name]
|
|
@@ -49,7 +49,9 @@ class RedisSignalBus(AbstractSignalBus):
|
|
|
49
49
|
async def bind(self, channel: str) -> None:
|
|
50
50
|
async with self._pubsub_lock:
|
|
51
51
|
pubsub = self.connection.pubsub()
|
|
52
|
-
asyncio.create_task(self._receiver(pubsub, channel))
|
|
52
|
+
task = asyncio.create_task(self._receiver(pubsub, channel))
|
|
53
|
+
self._tasks.add(task)
|
|
54
|
+
task.add_done_callback(self._tasks.discard)
|
|
53
55
|
|
|
54
56
|
async def _receiver(self, pubsub: client.PubSub, channel: str) -> None:
|
|
55
57
|
async with pubsub as psub:
|
|
@@ -348,3 +348,108 @@ async def test_Bus_broadcast_fail_timeout(bus, test_signal):
|
|
|
348
348
|
|
|
349
349
|
assert ret is None
|
|
350
350
|
bus.log.error.assert_called()
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
async def test_Bus_broadcast_fail_broad_exception(bus, test_signal):
|
|
354
|
+
handler = MagicMock(return_value=1, **{'__qualname__': 'qname'})
|
|
355
|
+
receiver = Receiver(agent=None, handler=handler, signal=test_signal, timeout=60)
|
|
356
|
+
receiver.handler.side_effect = ValueError('something went wrong')
|
|
357
|
+
|
|
358
|
+
ret = await bus.broadcast(receiver, test_signal, 'test', {'uuid': 1})
|
|
359
|
+
|
|
360
|
+
assert ret is None
|
|
361
|
+
bus.log.exception.assert_called()
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
async def test_Bus_broadcast_fail_cancelled_propagates(bus, test_signal):
|
|
365
|
+
handler = MagicMock(return_value=1, **{'__qualname__': 'qname'})
|
|
366
|
+
receiver = Receiver(agent=None, handler=handler, signal=test_signal, timeout=60)
|
|
367
|
+
|
|
368
|
+
async def cancelled(*args, **kwargs):
|
|
369
|
+
raise asyncio.CancelledError()
|
|
370
|
+
|
|
371
|
+
receiver.handler.side_effect = cancelled
|
|
372
|
+
|
|
373
|
+
with pytest.raises(asyncio.CancelledError):
|
|
374
|
+
await bus.broadcast(receiver, test_signal, 'test', {'uuid': 1})
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
async def test_Bus_broadcast_fail_runtime_error(bus, test_signal):
|
|
378
|
+
handler = MagicMock(return_value=1, **{'__qualname__': 'qname'})
|
|
379
|
+
receiver = Receiver(agent=None, handler=handler, signal=test_signal, timeout=60)
|
|
380
|
+
|
|
381
|
+
async def runtime_error(*args, **kwargs):
|
|
382
|
+
raise RuntimeError('unexpected')
|
|
383
|
+
|
|
384
|
+
receiver.handler.side_effect = runtime_error
|
|
385
|
+
|
|
386
|
+
ret = await bus.broadcast(receiver, test_signal, 'test', {'uuid': 1})
|
|
387
|
+
|
|
388
|
+
assert ret is None
|
|
389
|
+
bus.log.exception.assert_called()
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
async def test_Bus_close_ok():
|
|
393
|
+
class SlowBindBus(AbstractSignalBus):
|
|
394
|
+
started = False
|
|
395
|
+
|
|
396
|
+
async def send(self, channel: str, message: str) -> None:
|
|
397
|
+
pass
|
|
398
|
+
|
|
399
|
+
async def bind(self, channel: str) -> None:
|
|
400
|
+
self.started = True
|
|
401
|
+
await asyncio.Event().wait()
|
|
402
|
+
|
|
403
|
+
bus = SlowBindBus(dsn=DSN)
|
|
404
|
+
await asyncio.sleep(0.01)
|
|
405
|
+
assert bus.started
|
|
406
|
+
assert bus._tasks
|
|
407
|
+
|
|
408
|
+
await bus.close()
|
|
409
|
+
|
|
410
|
+
assert not bus._tasks
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
async def test_Bus_close_idempotent():
|
|
414
|
+
bus = Bus(dsn=DSN)
|
|
415
|
+
await bus.close()
|
|
416
|
+
await bus.close()
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
async def test_Bus_close_ok_all_tasks_cancelled():
|
|
420
|
+
bus = Bus(dsn=DSN, prefix='TEST')
|
|
421
|
+
bus.bind = AsyncMock()
|
|
422
|
+
bus.send = AsyncMock()
|
|
423
|
+
|
|
424
|
+
async def slow_handler(*args, **kwargs):
|
|
425
|
+
await asyncio.sleep(10)
|
|
426
|
+
|
|
427
|
+
bus.handle_signal = slow_handler
|
|
428
|
+
|
|
429
|
+
signal = Signal(name='test_signal', providing_args=['a'])
|
|
430
|
+
bus.receiver('TEST:test_signal:sender', signal.serialize({'a': 1}))
|
|
431
|
+
await asyncio.sleep(0.01)
|
|
432
|
+
|
|
433
|
+
assert bus._tasks
|
|
434
|
+
|
|
435
|
+
await bus.close()
|
|
436
|
+
|
|
437
|
+
assert not bus._tasks
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
async def test_Bus_close_cancels_receiver_task(bus, test_signal):
|
|
441
|
+
async def slow_handler(*args, **kwargs):
|
|
442
|
+
await asyncio.sleep(10)
|
|
443
|
+
|
|
444
|
+
receiver = Receiver(agent=None, handler=slow_handler, signal=test_signal, timeout=60)
|
|
445
|
+
await bus.bind_receiver(receiver)
|
|
446
|
+
|
|
447
|
+
bus.receiver('TEST:test_signal:test', test_signal.serialize({'uuid': 1}))
|
|
448
|
+
await asyncio.sleep(0.01)
|
|
449
|
+
|
|
450
|
+
task_count = len(bus._tasks)
|
|
451
|
+
assert task_count > 0
|
|
452
|
+
|
|
453
|
+
await bus.close()
|
|
454
|
+
|
|
455
|
+
assert not bus._tasks
|
|
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
|