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.
Files changed (80) hide show
  1. {microagent-1.8.1 → microagent-1.8.2}/CHANGELOG.rst +7 -0
  2. {microagent-1.8.1 → microagent-1.8.2}/PKG-INFO +1 -1
  3. {microagent-1.8.1 → microagent-1.8.2}/docs/conf.py +1 -2
  4. {microagent-1.8.1 → microagent-1.8.2}/examples/comment_agent.py +3 -2
  5. {microagent-1.8.1 → microagent-1.8.2}/examples/email_agent.py +2 -1
  6. {microagent-1.8.1 → microagent-1.8.2}/examples/redis_server.py +4 -3
  7. {microagent-1.8.1 → microagent-1.8.2}/examples/user_agent.py +2 -1
  8. {microagent-1.8.1 → microagent-1.8.2}/microagent/__init__.py +2 -2
  9. {microagent-1.8.1 → microagent-1.8.2}/microagent/agent.py +2 -0
  10. {microagent-1.8.1 → microagent-1.8.2}/microagent/bus.py +23 -8
  11. {microagent-1.8.1 → microagent-1.8.2}/microagent/timer.py +17 -10
  12. {microagent-1.8.1 → microagent-1.8.2}/microagent/tools/mocks.py +3 -2
  13. {microagent-1.8.1 → microagent-1.8.2}/microagent/tools/redis.py +3 -1
  14. {microagent-1.8.1 → microagent-1.8.2}/microagent.egg-info/PKG-INFO +1 -1
  15. {microagent-1.8.1 → microagent-1.8.2}/tests/test_bus.py +105 -0
  16. {microagent-1.8.1 → microagent-1.8.2}/.github/workflows/python-app.yml +0 -0
  17. {microagent-1.8.1 → microagent-1.8.2}/.gitignore +0 -0
  18. {microagent-1.8.1 → microagent-1.8.2}/.readthedocs.yml +0 -0
  19. {microagent-1.8.1 → microagent-1.8.2}/.travis.yml +0 -0
  20. {microagent-1.8.1 → microagent-1.8.2}/LICENSE.md +0 -0
  21. {microagent-1.8.1 → microagent-1.8.2}/Makefile +0 -0
  22. {microagent-1.8.1 → microagent-1.8.2}/README.rst +0 -0
  23. {microagent-1.8.1 → microagent-1.8.2}/docs/Makefile +0 -0
  24. {microagent-1.8.1 → microagent-1.8.2}/docs/agent.rst +0 -0
  25. {microagent-1.8.1 → microagent-1.8.2}/docs/amqp.rst +0 -0
  26. {microagent-1.8.1 → microagent-1.8.2}/docs/broker.rst +0 -0
  27. {microagent-1.8.1 → microagent-1.8.2}/docs/bus.rst +0 -0
  28. {microagent-1.8.1 → microagent-1.8.2}/docs/example_agents.rst +0 -0
  29. {microagent-1.8.1 → microagent-1.8.2}/docs/example_run.rst +0 -0
  30. {microagent-1.8.1 → microagent-1.8.2}/docs/hooks.rst +0 -0
  31. {microagent-1.8.1 → microagent-1.8.2}/docs/index.rst +0 -0
  32. {microagent-1.8.1 → microagent-1.8.2}/docs/kafka.rst +0 -0
  33. {microagent-1.8.1 → microagent-1.8.2}/docs/launcher.rst +0 -0
  34. {microagent-1.8.1 → microagent-1.8.2}/docs/ma-bus.dia +0 -0
  35. {microagent-1.8.1 → microagent-1.8.2}/docs/ma-bus.svg +0 -0
  36. {microagent-1.8.1 → microagent-1.8.2}/docs/ma-logo.dia +0 -0
  37. {microagent-1.8.1 → microagent-1.8.2}/docs/ma-logo.svg +0 -0
  38. {microagent-1.8.1 → microagent-1.8.2}/docs/ma-periodic.dia +0 -0
  39. {microagent-1.8.1 → microagent-1.8.2}/docs/ma-periodic.svg +0 -0
  40. {microagent-1.8.1 → microagent-1.8.2}/docs/ma-queue.dia +0 -0
  41. {microagent-1.8.1 → microagent-1.8.2}/docs/ma-queue.svg +0 -0
  42. {microagent-1.8.1 → microagent-1.8.2}/docs/mocks.rst +0 -0
  43. {microagent-1.8.1 → microagent-1.8.2}/docs/periodic.rst +0 -0
  44. {microagent-1.8.1 → microagent-1.8.2}/docs/redis.rst +0 -0
  45. {microagent-1.8.1 → microagent-1.8.2}/docs/requirements.txt +0 -0
  46. {microagent-1.8.1 → microagent-1.8.2}/examples/__init__.py +0 -0
  47. {microagent-1.8.1 → microagent-1.8.2}/examples/push_agent.py +0 -0
  48. {microagent-1.8.1 → microagent-1.8.2}/examples/requirements.txt +0 -0
  49. {microagent-1.8.1 → microagent-1.8.2}/examples/settings.py +1 -1
  50. {microagent-1.8.1 → microagent-1.8.2}/examples/signals.json +0 -0
  51. {microagent-1.8.1 → microagent-1.8.2}/microagent/abc.py +0 -0
  52. {microagent-1.8.1 → microagent-1.8.2}/microagent/broker.py +0 -0
  53. {microagent-1.8.1 → microagent-1.8.2}/microagent/hooks.py +0 -0
  54. {microagent-1.8.1 → microagent-1.8.2}/microagent/launcher.py +0 -0
  55. {microagent-1.8.1 → microagent-1.8.2}/microagent/py.typed +0 -0
  56. {microagent-1.8.1 → microagent-1.8.2}/microagent/queue.py +0 -0
  57. {microagent-1.8.1 → microagent-1.8.2}/microagent/signal.py +0 -0
  58. {microagent-1.8.1 → microagent-1.8.2}/microagent/tools/__init__.py +0 -0
  59. {microagent-1.8.1 → microagent-1.8.2}/microagent/tools/amqp.py +0 -0
  60. {microagent-1.8.1 → microagent-1.8.2}/microagent/tools/kafka.py +0 -0
  61. {microagent-1.8.1 → microagent-1.8.2}/microagent/utils.py +0 -0
  62. {microagent-1.8.1 → microagent-1.8.2}/microagent.egg-info/SOURCES.txt +0 -0
  63. {microagent-1.8.1 → microagent-1.8.2}/microagent.egg-info/dependency_links.txt +0 -0
  64. {microagent-1.8.1 → microagent-1.8.2}/microagent.egg-info/entry_points.txt +0 -0
  65. {microagent-1.8.1 → microagent-1.8.2}/microagent.egg-info/requires.txt +0 -0
  66. {microagent-1.8.1 → microagent-1.8.2}/microagent.egg-info/top_level.txt +0 -0
  67. {microagent-1.8.1 → microagent-1.8.2}/pyproject.toml +0 -0
  68. {microagent-1.8.1 → microagent-1.8.2}/setup.cfg +0 -0
  69. {microagent-1.8.1 → microagent-1.8.2}/tests/__init__.py +0 -0
  70. {microagent-1.8.1 → microagent-1.8.2}/tests/conftest.py +0 -0
  71. {microagent-1.8.1 → microagent-1.8.2}/tests/stuff.json +0 -0
  72. {microagent-1.8.1 → microagent-1.8.2}/tests/test_agent.py +0 -0
  73. {microagent-1.8.1 → microagent-1.8.2}/tests/test_api.py +0 -0
  74. {microagent-1.8.1 → microagent-1.8.2}/tests/test_broker.py +0 -0
  75. {microagent-1.8.1 → microagent-1.8.2}/tests/test_hook.py +0 -0
  76. {microagent-1.8.1 → microagent-1.8.2}/tests/test_launcher.py +0 -0
  77. {microagent-1.8.1 → microagent-1.8.2}/tests/test_timer.py +0 -0
  78. {microagent-1.8.1 → microagent-1.8.2}/tests/test_tools_amqp.py +0 -0
  79. {microagent-1.8.1 → microagent-1.8.2}/tests/test_tools_kafka.py +0 -0
  80. {microagent-1.8.1 → microagent-1.8.2}/tests/test_tools_redis.py +0 -0
@@ -3,6 +3,13 @@ Changelog
3
3
  =========
4
4
 
5
5
 
6
+ 1.8.2 (2026-06-11)
7
+ ==================
8
+
9
+ - Receiver/bus graceful cancellation
10
+ - Fix exception handling
11
+
12
+
6
13
  1.8.1 (2026-06-06)
7
14
  ==================
8
15
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: microagent
3
- Version: 1.8.1
3
+ Version: 1.8.2
4
4
  Summary: Tool for agent ecosystem
5
5
  Author-email: Dmitriy Vlasov <scailer@yandex.ru>
6
6
  License: MIT License
@@ -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
- from microagent import MicroAgent, on, receiver, load_stuff
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
- from microagent import MicroAgent, on, consumer, load_stuff
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
- from microagent.tools.redis import RedisSignalBus, RedisBroker
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
- from microagent import MicroAgent, on, periodic, receiver, cron, load_stuff
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'
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.loads(f.read().replace('\n', '')))
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()))
@@ -208,6 +208,8 @@ class MicroAgent:
208
208
  periodic_task.cancel()
209
209
  for cron_task in self.cron_tasks:
210
210
  cron_task.cancel()
211
+ if self.bus:
212
+ await self.bus.close()
211
213
  await self.hook.pre_stop()
212
214
 
213
215
  @staticmethod
@@ -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(self.bind(response_signal.make_channel_name(self.prefix)))
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 __repr__(self) -> str:
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(self.handle_signal(signal, sender, signal_id, data))
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
- _timer_handle: asyncio.TimerHandle | None = None
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._timer_handle = asyncio.get_running_loop().call_later(start_after, _periodic, self) # type: ignore[arg-type]
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._timer_handle is not None:
82
- self._timer_handle.cancel()
83
- if self._running_task is not None and not self._running_task.done():
84
- self._running_task.cancel()
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._timer_handle = asyncio.get_running_loop().call_later(task.period, _periodic, 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._running_task = running
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: microagent
3
- Version: 1.8.1
3
+ Version: 1.8.2
4
4
  Summary: Tool for agent ecosystem
5
5
  Author-email: Dmitriy Vlasov <scailer@yandex.ru>
6
6
  License: MIT License
@@ -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
@@ -1,5 +1,5 @@
1
- import sys
2
1
  import logging
2
+ import sys
3
3
 
4
4
  logging.basicConfig(format=(
5
5
  '%(levelname)-8s [pid#%(process)d] %(asctime)s %(name)s '
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes