microagent 1.7.2__tar.gz → 1.8.0__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 (81) hide show
  1. microagent-1.8.0/.github/workflows/python-app.yml +43 -0
  2. {microagent-1.7.2 → microagent-1.8.0}/CHANGELOG.rst +20 -0
  3. {microagent-1.7.2 → microagent-1.8.0}/Makefile +1 -1
  4. {microagent-1.7.2 → microagent-1.8.0}/PKG-INFO +18 -18
  5. {microagent-1.7.2 → microagent-1.8.0}/README.rst +7 -10
  6. {microagent-1.7.2 → microagent-1.8.0}/docs/conf.py +2 -2
  7. {microagent-1.7.2 → microagent-1.8.0}/docs/example_agents.rst +9 -3
  8. microagent-1.8.0/examples/push_agent.py +59 -0
  9. {microagent-1.7.2 → microagent-1.8.0}/examples/signals.json +5 -1
  10. {microagent-1.7.2 → microagent-1.8.0}/microagent/__init__.py +15 -6
  11. {microagent-1.7.2 → microagent-1.8.0}/microagent/abc.py +0 -2
  12. {microagent-1.7.2 → microagent-1.8.0}/microagent/agent.py +0 -2
  13. {microagent-1.7.2 → microagent-1.8.0}/microagent/broker.py +16 -4
  14. {microagent-1.7.2 → microagent-1.8.0}/microagent/bus.py +2 -3
  15. {microagent-1.7.2 → microagent-1.8.0}/microagent/hooks.py +0 -2
  16. {microagent-1.7.2 → microagent-1.8.0}/microagent/launcher.py +0 -2
  17. {microagent-1.7.2 → microagent-1.8.0}/microagent/queue.py +22 -6
  18. {microagent-1.7.2 → microagent-1.8.0}/microagent/signal.py +0 -2
  19. {microagent-1.7.2 → microagent-1.8.0}/microagent/timer.py +1 -3
  20. {microagent-1.7.2 → microagent-1.8.0}/microagent/tools/amqp.py +35 -28
  21. {microagent-1.7.2 → microagent-1.8.0}/microagent/tools/kafka.py +0 -1
  22. {microagent-1.7.2 → microagent-1.8.0}/microagent/tools/mocks.py +2 -1
  23. {microagent-1.7.2 → microagent-1.8.0}/microagent/tools/redis.py +2 -2
  24. {microagent-1.7.2 → microagent-1.8.0}/microagent/utils.py +1 -2
  25. {microagent-1.7.2 → microagent-1.8.0}/microagent.egg-info/PKG-INFO +18 -18
  26. {microagent-1.7.2 → microagent-1.8.0}/microagent.egg-info/SOURCES.txt +1 -0
  27. {microagent-1.7.2 → microagent-1.8.0}/microagent.egg-info/requires.txt +6 -6
  28. {microagent-1.7.2 → microagent-1.8.0}/pyproject.toml +16 -15
  29. {microagent-1.7.2 → microagent-1.8.0}/tests/stuff.json +3 -1
  30. {microagent-1.7.2 → microagent-1.8.0}/tests/test_agent.py +2 -2
  31. {microagent-1.7.2 → microagent-1.8.0}/tests/test_api.py +9 -15
  32. {microagent-1.7.2 → microagent-1.8.0}/tests/test_broker.py +6 -3
  33. {microagent-1.7.2 → microagent-1.8.0}/tests/test_bus.py +2 -3
  34. {microagent-1.7.2 → microagent-1.8.0}/tests/test_launcher.py +0 -1
  35. {microagent-1.7.2 → microagent-1.8.0}/tests/test_timer.py +2 -3
  36. {microagent-1.7.2 → microagent-1.8.0}/tests/test_tools_amqp.py +52 -17
  37. {microagent-1.7.2 → microagent-1.8.0}/tests/test_tools_kafka.py +0 -1
  38. {microagent-1.7.2 → microagent-1.8.0}/tests/test_tools_redis.py +2 -2
  39. microagent-1.7.2/.github/workflows/python-app.yml +0 -34
  40. {microagent-1.7.2 → microagent-1.8.0}/.gitignore +0 -0
  41. {microagent-1.7.2 → microagent-1.8.0}/.readthedocs.yml +0 -0
  42. {microagent-1.7.2 → microagent-1.8.0}/.travis.yml +0 -0
  43. {microagent-1.7.2 → microagent-1.8.0}/LICENSE.md +0 -0
  44. {microagent-1.7.2 → microagent-1.8.0}/docs/Makefile +0 -0
  45. {microagent-1.7.2 → microagent-1.8.0}/docs/agent.rst +0 -0
  46. {microagent-1.7.2 → microagent-1.8.0}/docs/amqp.rst +0 -0
  47. {microagent-1.7.2 → microagent-1.8.0}/docs/broker.rst +0 -0
  48. {microagent-1.7.2 → microagent-1.8.0}/docs/bus.rst +0 -0
  49. {microagent-1.7.2 → microagent-1.8.0}/docs/example_run.rst +0 -0
  50. {microagent-1.7.2 → microagent-1.8.0}/docs/hooks.rst +0 -0
  51. {microagent-1.7.2 → microagent-1.8.0}/docs/index.rst +0 -0
  52. {microagent-1.7.2 → microagent-1.8.0}/docs/kafka.rst +0 -0
  53. {microagent-1.7.2 → microagent-1.8.0}/docs/launcher.rst +0 -0
  54. {microagent-1.7.2 → microagent-1.8.0}/docs/ma-bus.dia +0 -0
  55. {microagent-1.7.2 → microagent-1.8.0}/docs/ma-bus.svg +0 -0
  56. {microagent-1.7.2 → microagent-1.8.0}/docs/ma-logo.dia +0 -0
  57. {microagent-1.7.2 → microagent-1.8.0}/docs/ma-logo.svg +0 -0
  58. {microagent-1.7.2 → microagent-1.8.0}/docs/ma-periodic.dia +0 -0
  59. {microagent-1.7.2 → microagent-1.8.0}/docs/ma-periodic.svg +0 -0
  60. {microagent-1.7.2 → microagent-1.8.0}/docs/ma-queue.dia +0 -0
  61. {microagent-1.7.2 → microagent-1.8.0}/docs/ma-queue.svg +0 -0
  62. {microagent-1.7.2 → microagent-1.8.0}/docs/mocks.rst +0 -0
  63. {microagent-1.7.2 → microagent-1.8.0}/docs/periodic.rst +0 -0
  64. {microagent-1.7.2 → microagent-1.8.0}/docs/redis.rst +0 -0
  65. {microagent-1.7.2 → microagent-1.8.0}/docs/requirements.txt +0 -0
  66. {microagent-1.7.2 → microagent-1.8.0}/examples/__init__.py +0 -0
  67. {microagent-1.7.2 → microagent-1.8.0}/examples/comment_agent.py +0 -0
  68. {microagent-1.7.2 → microagent-1.8.0}/examples/email_agent.py +0 -0
  69. {microagent-1.7.2 → microagent-1.8.0}/examples/redis_server.py +0 -0
  70. {microagent-1.7.2 → microagent-1.8.0}/examples/requirements.txt +0 -0
  71. {microagent-1.7.2 → microagent-1.8.0}/examples/settings.py +0 -0
  72. {microagent-1.7.2 → microagent-1.8.0}/examples/user_agent.py +0 -0
  73. {microagent-1.7.2 → microagent-1.8.0}/microagent/py.typed +0 -0
  74. {microagent-1.7.2 → microagent-1.8.0}/microagent/tools/__init__.py +0 -0
  75. {microagent-1.7.2 → microagent-1.8.0}/microagent.egg-info/dependency_links.txt +0 -0
  76. {microagent-1.7.2 → microagent-1.8.0}/microagent.egg-info/entry_points.txt +0 -0
  77. {microagent-1.7.2 → microagent-1.8.0}/microagent.egg-info/top_level.txt +0 -0
  78. {microagent-1.7.2 → microagent-1.8.0}/setup.cfg +0 -0
  79. {microagent-1.7.2 → microagent-1.8.0}/tests/__init__.py +0 -0
  80. {microagent-1.7.2 → microagent-1.8.0}/tests/conftest.py +0 -0
  81. {microagent-1.7.2 → microagent-1.8.0}/tests/test_hook.py +0 -0
@@ -0,0 +1,43 @@
1
+ name: Tests
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ tests:
7
+
8
+ runs-on: ubuntu-latest
9
+ strategy:
10
+ matrix:
11
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
12
+
13
+ steps:
14
+ - uses: actions/checkout@v5
15
+
16
+ - name: Install uv
17
+ uses: astral-sh/setup-uv@v6
18
+ with:
19
+ python-version: ${{ matrix.python-version }}
20
+ enable-cache: true
21
+
22
+ - name: Lint with ruff
23
+ run: uvx ruff check microagent
24
+
25
+ - name: Lint with mypy
26
+ run: uvx mypy microagent
27
+
28
+ - name: Testing
29
+ run: uvx --with .[dev] pytest tests
30
+
31
+ check:
32
+ if: always()
33
+
34
+ needs:
35
+ - tests
36
+
37
+ runs-on: ubuntu-latest
38
+
39
+ steps:
40
+ - name: Decide whether the needed jobs succeeded or failed
41
+ uses: re-actors/alls-green@release/v1
42
+ with:
43
+ jobs: ${{ toJSON(needs) }}
@@ -2,6 +2,26 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+
6
+ 1.8.0 (2026-02-26)
7
+ ==================
8
+
9
+ - The broker now supports the use of exchanges and topic-based routing
10
+ - Drop support python 3.9, add python 3.13, 3.14
11
+
12
+
13
+ 1.7.3 (2024-03-12)
14
+ ================
15
+
16
+ - RPC bugfix
17
+
18
+
19
+ 1.7.2 (2024-03-05)
20
+ ================
21
+
22
+ - Typing fixes
23
+
24
+
5
25
  1.7 (2023-10-01)
6
26
  ================
7
27
 
@@ -4,7 +4,7 @@ clean:
4
4
  find . -name '*.pyc' | xargs rm -rf
5
5
 
6
6
  test:
7
- python -m ruff microagent tests
7
+ python -m ruff check microagent tests
8
8
  python -m mypy microagent
9
9
  python -m pytest tests
10
10
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: microagent
3
- Version: 1.7.2
3
+ Version: 1.8.0
4
4
  Summary: Tool for agent ecosystem
5
5
  Author-email: Dmitriy Vlasov <scailer@yandex.ru>
6
6
  License: MIT License
@@ -39,17 +39,19 @@ Classifier: Programming Language :: Python :: 3
39
39
  Classifier: Programming Language :: Python :: 3.10
40
40
  Classifier: Programming Language :: Python :: 3.11
41
41
  Classifier: Programming Language :: Python :: 3.12
42
+ Classifier: Programming Language :: Python :: 3.13
43
+ Classifier: Programming Language :: Python :: 3.14
42
44
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
43
45
  Classifier: Framework :: AsyncIO
44
46
  Requires-Python: >=3.10
45
47
  Description-Content-Type: text/x-rst
46
48
  License-File: LICENSE.md
47
49
  Provides-Extra: redis
48
- Requires-Dist: redis[hiredis]==5.*; extra == "redis"
50
+ Requires-Dist: redis[hiredis]>5; extra == "redis"
49
51
  Provides-Extra: amqp
50
- Requires-Dist: aiormq==6.8.*; extra == "amqp"
52
+ Requires-Dist: aiormq==6.9.*; extra == "amqp"
51
53
  Provides-Extra: kafka
52
- Requires-Dist: aiokafka==0.10.*; extra == "kafka"
54
+ Requires-Dist: aiokafka==0.13.*; extra == "kafka"
53
55
  Provides-Extra: dev
54
56
  Requires-Dist: pytest; extra == "dev"
55
57
  Requires-Dist: pytest-runner; extra == "dev"
@@ -58,9 +60,10 @@ Requires-Dist: pytest-cov; extra == "dev"
58
60
  Requires-Dist: coverage; extra == "dev"
59
61
  Requires-Dist: mypy; extra == "dev"
60
62
  Requires-Dist: ruff; extra == "dev"
61
- Requires-Dist: redis==5.*; extra == "dev"
62
- Requires-Dist: aiormq==6.8.*; extra == "dev"
63
- Requires-Dist: aiokafka==0.10.*; extra == "dev"
63
+ Requires-Dist: redis[hiredis]>5; extra == "dev"
64
+ Requires-Dist: aiormq==6.9.*; extra == "dev"
65
+ Requires-Dist: aiokafka==0.13.*; extra == "dev"
66
+ Dynamic: license-file
64
67
 
65
68
  MicroAgent
66
69
  ==========
@@ -77,18 +80,15 @@ MicroAgent
77
80
  .. image:: https://img.shields.io/pypi/status/microagent.svg
78
81
  :target: https://pypi.python.org/pypi/microagent
79
82
 
80
- .. image:: https://img.shields.io/pypi/dd/microagent.svg
83
+ .. image:: https://img.shields.io/pypi/dm/microagent.svg
81
84
  :target: https://pypi.python.org/pypi/microagent
82
85
 
83
- .. image:: https://codecov.io/gh/scailer/microagent/branch/master/graph/badge.svg
84
- :target: https://codecov.io/gh/scailer/microagent
85
-
86
- .. image:: https://travis-ci.com/scailer/microagent.svg?branch=master
87
- :target: https://travis-ci.con/scailer/microagent
88
-
89
86
  .. image:: https://readthedocs.org/projects/microagent/badge/?version=latest&style=flat
90
87
  :target: https://microagent.readthedocs.io/
91
88
 
89
+ .. image:: https://github.com/scailer/microagent/workflows/Tests/badge.svg
90
+ :target: https://github.com/scailer/microagent/actions
91
+
92
92
 
93
93
  The goal of this project is to facilitate the creation of **microservices**
94
94
  interacting via a **signal bus** and/or **queue broker**.
@@ -107,7 +107,7 @@ Tool provide features:
107
107
 
108
108
  * running a **periodical tasks** (interval or as CRON)
109
109
  * specification of signals (events), their sending and receiving via the bus (redis_)
110
- * description of queues, sending and receiving messages via the queue broker (aioamqp_, kafka_, redis_)
110
+ * description of queues, sending and receiving messages via the queue broker (aiormq_, kafka_, redis_)
111
111
  * limited **RPC** via signal bus
112
112
  * launching sub-services (in the same process)
113
113
  * launching a group of microagents (each in a separate process)
@@ -164,7 +164,7 @@ With redis_ backend provide signal bus and list-based queues::
164
164
 
165
165
  pip install 'microagent[redis]'
166
166
 
167
- With aioamqp_ backend provide queues over AMQP (RabbitMQ)::
167
+ With aiormq_ backend provide queues over AMQP (RabbitMQ)::
168
168
 
169
169
  pip install 'microagent[amqp]'
170
170
 
@@ -174,6 +174,6 @@ With kafka_ backend provide queues over Kafka (experemental)::
174
174
 
175
175
 
176
176
  .. _redis: https://pypi.org/project/redis/
177
- .. _aioamqp: https://pypi.org/project/aioamqp/
177
+ .. _aiormq: https://pypi.org/project/aiormq/
178
178
  .. _kafka: https://pypi.org/project/aiokafka/
179
179
  .. _documentation: https://microagent.readthedocs.io/
@@ -13,18 +13,15 @@ MicroAgent
13
13
  .. image:: https://img.shields.io/pypi/status/microagent.svg
14
14
  :target: https://pypi.python.org/pypi/microagent
15
15
 
16
- .. image:: https://img.shields.io/pypi/dd/microagent.svg
16
+ .. image:: https://img.shields.io/pypi/dm/microagent.svg
17
17
  :target: https://pypi.python.org/pypi/microagent
18
18
 
19
- .. image:: https://codecov.io/gh/scailer/microagent/branch/master/graph/badge.svg
20
- :target: https://codecov.io/gh/scailer/microagent
21
-
22
- .. image:: https://travis-ci.com/scailer/microagent.svg?branch=master
23
- :target: https://travis-ci.con/scailer/microagent
24
-
25
19
  .. image:: https://readthedocs.org/projects/microagent/badge/?version=latest&style=flat
26
20
  :target: https://microagent.readthedocs.io/
27
21
 
22
+ .. image:: https://github.com/scailer/microagent/workflows/Tests/badge.svg
23
+ :target: https://github.com/scailer/microagent/actions
24
+
28
25
 
29
26
  The goal of this project is to facilitate the creation of **microservices**
30
27
  interacting via a **signal bus** and/or **queue broker**.
@@ -43,7 +40,7 @@ Tool provide features:
43
40
 
44
41
  * running a **periodical tasks** (interval or as CRON)
45
42
  * specification of signals (events), their sending and receiving via the bus (redis_)
46
- * description of queues, sending and receiving messages via the queue broker (aioamqp_, kafka_, redis_)
43
+ * description of queues, sending and receiving messages via the queue broker (aiormq_, kafka_, redis_)
47
44
  * limited **RPC** via signal bus
48
45
  * launching sub-services (in the same process)
49
46
  * launching a group of microagents (each in a separate process)
@@ -100,7 +97,7 @@ With redis_ backend provide signal bus and list-based queues::
100
97
 
101
98
  pip install 'microagent[redis]'
102
99
 
103
- With aioamqp_ backend provide queues over AMQP (RabbitMQ)::
100
+ With aiormq_ backend provide queues over AMQP (RabbitMQ)::
104
101
 
105
102
  pip install 'microagent[amqp]'
106
103
 
@@ -110,6 +107,6 @@ With kafka_ backend provide queues over Kafka (experemental)::
110
107
 
111
108
 
112
109
  .. _redis: https://pypi.org/project/redis/
113
- .. _aioamqp: https://pypi.org/project/aioamqp/
110
+ .. _aiormq: https://pypi.org/project/aiormq/
114
111
  .. _kafka: https://pypi.org/project/aiokafka/
115
112
  .. _documentation: https://microagent.readthedocs.io/
@@ -220,7 +220,7 @@ epub_exclude_files = ['search.html']
220
220
  # -- Options for intersphinx extension ---------------------------------------
221
221
 
222
222
  # Example configuration for intersphinx: refer to the Python standard library.
223
- intersphinx_mapping = {'https://docs.python.org/': None}
223
+ # intersphinx_mapping = {'https://docs.python.org/': None}
224
224
 
225
225
  # -- Options for todo extension ----------------------------------------------
226
226
 
@@ -229,6 +229,6 @@ todo_include_todos = True
229
229
 
230
230
  extlinks = {
231
231
  'redis': ('https://redis.readthedocs.io/%s', ''),
232
- 'aioamqp': ('https://aioamqp.readthedocs.io/%s', ''),
232
+ 'aiormq': ('https://github.com/mosquito/aiormq/blob/master/README.rst%s', ''),
233
233
  'kafka': ('https://aiokafka.readthedocs.io/%s', ''),
234
234
  }
@@ -4,6 +4,12 @@ Agent examples
4
4
  ======================================
5
5
 
6
6
 
7
+ signals.json
8
+
9
+ .. include:: ../examples/signals.json
10
+ :code: json
11
+
12
+
7
13
  user_agent.py
8
14
 
9
15
  .. include:: ../examples/user_agent.py
@@ -22,7 +28,7 @@ email_agent.py
22
28
  :code: python
23
29
 
24
30
 
25
- signals.json
31
+ push_agent.py
26
32
 
27
- .. include:: ../examples/signals.json
28
- :code: json
33
+ .. include:: ../examples/push_agent.py
34
+ :code: python
@@ -0,0 +1,59 @@
1
+ # mypy: ignore-errors
2
+ import asyncio
3
+ import logging
4
+ import os
5
+ import sys
6
+
7
+ from microagent import MicroAgent, consumer, load_stuff, on, periodic
8
+ from microagent.tools import amqp, redis
9
+
10
+ logging.basicConfig(format=(
11
+ '%(levelname)-8s [pid#%(process)d] %(asctime)s %(name)s '
12
+ '%(filename)s:%(lineno)d %(message)s'
13
+ ), stream=sys.stdout, level=logging.INFO)
14
+
15
+ cur_dir = os.path.dirname(os.path.realpath(__file__))
16
+ signals, queues = load_stuff('file://' + os.path.join(cur_dir, 'signals.json'))
17
+
18
+
19
+ class UserAgent(MicroAgent):
20
+ @on('pre_start')
21
+ def setup(self):
22
+ self.log.info('Run ...\n %s', self.info())
23
+
24
+ @periodic(period=15, timeout=10, start_after=3)
25
+ async def example_periodic_task_send_push(self):
26
+ self.log.info('Run periodic task')
27
+ await self.broker.push3.send({'text': 'informer text'}, topic='msg.ios')
28
+ await self.broker.push.send({'text': 'informer 1'})
29
+
30
+ @consumer(queues.android)
31
+ async def example_read_queue_android(self, **kwargs):
32
+ self.log.info('Catch android %s', kwargs)
33
+
34
+ @consumer(queues.ios)
35
+ async def example_read_queue_ios(self, **kwargs):
36
+ self.log.info('Catch ios %s', kwargs)
37
+
38
+ @consumer(queues.android_a)
39
+ async def example_read_queue_android_a(self, **kwargs):
40
+ self.log.info('Catch android_a %s', kwargs)
41
+
42
+ @consumer(queues.ios_a)
43
+ async def example_read_queue_ios_a(self, **kwargs):
44
+ self.log.info('Catch ios_a %s', kwargs)
45
+
46
+
47
+ async def main():
48
+ bus = redis.RedisSignalBus('redis://localhost/7')
49
+ broker = amqp.AMQPBroker('amqp://guest:guest@localhost:5672/')
50
+ await broker.push3.send({'q':1}, topic='msg.android')
51
+
52
+ agent = UserAgent(bus=bus, broker=broker)
53
+ await agent.start()
54
+
55
+ while True:
56
+ await asyncio.sleep(1)
57
+
58
+
59
+ asyncio.run(main())
@@ -11,6 +11,10 @@
11
11
  }}
12
12
  ],
13
13
  "queues": [
14
- {"name": "mailer"}
14
+ {"name": "mailer"},
15
+ {"name": "android", "exchange": "push3", "topics": ["*.android"]},
16
+ {"name": "ios", "exchange": "push3", "topics": ["*.ios"]},
17
+ {"name": "android_a", "exchange": "push"},
18
+ {"name": "ios_a", "exchange": "push"}
15
19
  ]
16
20
  }
@@ -1,9 +1,8 @@
1
- __version__ = '1.7.2'
1
+ __version__ = '1.8.0'
2
2
 
3
3
  import importlib
4
4
  import json
5
5
  import urllib.request
6
-
7
6
  from collections import abc
8
7
  from typing import Any, NamedTuple
9
8
 
@@ -16,7 +15,6 @@ from .signal import Receiver, ReceiverArgs, Signal
16
15
  from .timer import CRONArgs, CRONTask, PeriodicArgs, PeriodicTask, cron_parser
17
16
  from .utils import make_bound_key
18
17
 
19
-
20
18
  __all__ = ['Signal', 'Queue', 'MicroAgent', 'ServerInterrupt', 'receiver', 'consumer',
21
19
  'periodic', 'cron', 'on', 'load_stuff', 'load_signals', 'load_queues']
22
20
 
@@ -58,10 +56,18 @@ def load_stuff(source: str) -> tuple[Any, Any]:
58
56
  type_map = {name: get_types(_type) for name, _type in providing_args.items()}
59
57
  providing_args = list(providing_args)
60
58
 
61
- Signal(name=_data['name'], providing_args=providing_args, type_map=type_map)
59
+ Signal(
60
+ name=_data['name'],
61
+ providing_args=providing_args,
62
+ type_map=type_map
63
+ )
62
64
 
63
65
  for _data in data.get('queues', []):
64
- Queue(name=_data['name'])
66
+ Queue(
67
+ name=_data['name'],
68
+ exchange=_data.get('exchange', ''),
69
+ topics=_data.get('topics', [])
70
+ )
65
71
 
66
72
  if data.get('jsonlib'):
67
73
  jsonlib = importlib.import_module(data['jsonlib']) # type: ignore
@@ -129,7 +135,10 @@ def load_queues(source: str) -> NamedTuple:
129
135
  {
130
136
  "queues": [
131
137
  {"name": "mailer"},
132
- {"name": "pusher"},
138
+ {"name": "pusher1", "exchange": "push"},
139
+ {"name": "pusher2", "exchange": "push"},
140
+ {"name": "create_event", "exchange": "logs", "topics": ["*created"]},
141
+ {"name": "update_event", "exchange": "logs", "topics": ["*updated"]},
133
142
  ]
134
143
  }
135
144
  '''
@@ -1,9 +1,7 @@
1
1
  import logging
2
-
3
2
  from collections.abc import Awaitable, Callable
4
3
  from typing import Any, Literal, Protocol
5
4
 
6
-
7
5
  HookLabel = Literal['server', 'pre_start', 'post_start', 'pre_stop']
8
6
  HookFunc = Callable[[Any], Awaitable[None]]
9
7
  PeriodicFunc = Callable[[Any], Awaitable[None]]
@@ -77,7 +77,6 @@ Using MicroAgent resources.
77
77
 
78
78
  import asyncio
79
79
  import logging
80
-
81
80
  from collections.abc import Callable, Iterable
82
81
  from dataclasses import dataclass, field
83
82
  from datetime import datetime, timedelta, timezone
@@ -90,7 +89,6 @@ from .queue import Consumer
90
89
  from .signal import Receiver
91
90
  from .timer import CRONTask, PeriodicTask
92
91
 
93
-
94
92
  HandlerTypes = TypeVar('HandlerTypes', Receiver, Consumer, PeriodicTask, CRONTask, Hook)
95
93
 
96
94
 
@@ -7,10 +7,17 @@ The broker performs the functions of guaranteed and consistent transmission of m
7
7
  from the product to the consumer, many to one (or according to the broker's own logic).
8
8
  The message has a free structure, fully defined in the user area.
9
9
 
10
+ The broker supports three types of architecture:
11
+
12
+ - **Simple:** messages are delivered to a single queue.
13
+ - **Branching:** a message is sent to an exchange, from which it is copied to multiple queues.
14
+ - **Topics:** a message is sent to an exchange and, based on the topic, \
15
+ is routed to queues subscribed to that topic.
16
+
10
17
  Implementations:
11
18
 
12
19
  * :ref:`redis <tools-redis>`
13
- * :ref:`aioamqp <tools-amqp>`
20
+ * :ref:`aiormq <tools-amqp>`
14
21
  * :ref:`kafka <tools-kafka>`
15
22
 
16
23
 
@@ -22,9 +29,13 @@ Using QueueBroker separately (sending only)
22
29
  from microagent.tools.redis import RedisBroker
23
30
 
24
31
  queues = load_queues('file://queues.json')
25
-
26
32
  broker = RedisBroker('redis://localhost/7')
27
- await broker.user_created.send({'user_id': 1})
33
+
34
+ await broker.user_created.send({'user_id': 1}) # send to queue
35
+
36
+ await broker.push.send({'user_id': 1, 'text': 'hi!'}) # send to exchange
37
+
38
+ await broker.logs.send({'text': 'success'}, topic='user.message') # send with topic
28
39
 
29
40
 
30
41
  Using with MicroAgent
@@ -47,7 +58,6 @@ Using with MicroAgent
47
58
  '''
48
59
  import logging
49
60
  import uuid
50
-
51
61
  from abc import abstractmethod
52
62
  from dataclasses import dataclass, field
53
63
  from typing import Any
@@ -160,6 +170,8 @@ class BoundQueue(QueueProtocol):
160
170
  queue: Queue
161
171
 
162
172
  async def send(self, message: dict, **options: Any) -> None:
173
+ if 'exchange' not in options and self.queue.exchange:
174
+ options['exchange'] = self.queue.exchange
163
175
  await self.broker.send(self.queue.name, self.queue.serialize(message), **options)
164
176
 
165
177
  async def length(self) -> int:
@@ -49,7 +49,6 @@ import contextlib
49
49
  import logging
50
50
  import time
51
51
  import uuid
52
-
53
52
  from abc import abstractmethod
54
53
  from collections import abc, defaultdict
55
54
  from dataclasses import dataclass, field
@@ -333,6 +332,6 @@ def check_types(signal: Signal, data: dict, log: logging.Logger) -> None:
333
332
  if signal.type_map:
334
333
  for key, value in data.items():
335
334
  if key not in signal.type_map:
336
- log.warning('Receiver get unknown arg "%s" %s', key, value)
335
+ log.warning('Receiver "%s" get unknown arg "%s" %s', signal.name, key, value)
337
336
  elif not isinstance(value, signal.type_map[key]):
338
- log.warning('Receiver get wrong type for "%s" %s', key, value)
337
+ log.warning('Receiver "%s" get wrong type for "%s" %s', signal.name, key, value)
@@ -24,14 +24,12 @@ allow provide endpoints for microagent, such as http, websocket, smtp or other.
24
24
  '''
25
25
 
26
26
  import inspect
27
-
28
27
  from collections import abc, defaultdict
29
28
  from dataclasses import dataclass
30
29
  from typing import TYPE_CHECKING, ClassVar, TypedDict
31
30
 
32
31
  from .abc import BoundKey, HookFunc
33
32
 
34
-
35
33
  if TYPE_CHECKING:
36
34
  from .agent import MicroAgent
37
35
 
@@ -23,13 +23,11 @@ import multiprocessing
23
23
  import os
24
24
  import signal
25
25
  import time
26
-
27
26
  from collections.abc import Iterator
28
27
  from functools import partial
29
28
  from itertools import chain
30
29
  from typing import TYPE_CHECKING, Any
31
30
 
32
-
33
31
  if TYPE_CHECKING:
34
32
  from .agent import MicroAgent
35
33
  from .broker import AbstractQueueBroker
@@ -1,12 +1,10 @@
1
1
  import json
2
-
3
- from dataclasses import dataclass
2
+ from dataclasses import dataclass, field
4
3
  from types import ModuleType
5
4
  from typing import TYPE_CHECKING, ClassVar, TypedDict
6
5
 
7
6
  from .abc import BoundKey, ConsumerFunc
8
7
 
9
-
10
8
  if TYPE_CHECKING:
11
9
  from .agent import MicroAgent
12
10
 
@@ -41,8 +39,10 @@ class Queue:
41
39
 
42
40
  {
43
41
  "queues": [
44
- {"name": "mailer"},
45
- {"name": "pusher"},
42
+ {"name": "mailer"}, // simple
43
+ {"name": "pusher", "exchange": "events"}, // fanout
44
+ {"name": "logger", "exchange": "events"},
45
+ {"name": "save", "exchange": "msg", "topics": "m.*"}, // topics
46
46
  ]
47
47
  }
48
48
 
@@ -53,14 +53,30 @@ class Queue:
53
53
  some_queue = Queue(
54
54
  name='some_queue'
55
55
  )
56
+
57
+ else_queue = Queue(
58
+ name='else_queue',
59
+ exchange='fanout_exchange'
60
+ )
61
+
62
+ topic_queue = Queue(
63
+ name='topic_queue',
64
+ exchange='topic_exchange',
65
+ topics=['message.*']
66
+ )
56
67
  '''
57
68
  name: str
69
+ exchange: str = ''
70
+ topics: list[str] = field(default_factory=list)
58
71
 
59
72
  _queues: ClassVar[dict[str, 'Queue']] = {}
73
+ _exchanges: ClassVar[dict[str, 'Queue']] = {}
60
74
  _jsonlib: ClassVar[ModuleType] = json
61
75
 
62
76
  def __post_init__(self) -> None:
63
77
  self._queues[self.name] = self
78
+ if self.exchange:
79
+ self._exchanges[self.exchange] = self
64
80
 
65
81
  def __repr__(self) -> str:
66
82
  return f'<Queue {self.name}>'
@@ -81,7 +97,7 @@ class Queue:
81
97
  def get(cls, name: str) -> 'Queue':
82
98
  ''' Get the queue instance by name '''
83
99
  try:
84
- return cls._queues[name]
100
+ return cls._exchanges.get(name) or cls._queues[name]
85
101
  except KeyError as exc:
86
102
  raise QueueNotFound(f'No such queue {name}') from exc
87
103
 
@@ -1,12 +1,10 @@
1
1
  import json
2
-
3
2
  from dataclasses import dataclass
4
3
  from types import ModuleType
5
4
  from typing import TYPE_CHECKING, ClassVar, TypedDict
6
5
 
7
6
  from .abc import BoundKey, ReceiverFunc
8
7
 
9
-
10
8
  if TYPE_CHECKING:
11
9
  from .agent import MicroAgent
12
10
 
@@ -23,7 +23,6 @@ import asyncio
23
23
  import inspect
24
24
  import re
25
25
  import time
26
-
27
26
  from collections.abc import Callable
28
27
  from dataclasses import dataclass
29
28
  from datetime import datetime, timedelta, timezone
@@ -31,7 +30,6 @@ from typing import TYPE_CHECKING, ClassVar, NamedTuple, TypedDict
31
30
 
32
31
  from .abc import BoundKey, PeriodicFunc
33
32
 
34
-
35
33
  if TYPE_CHECKING:
36
34
  from .agent import MicroAgent
37
35
 
@@ -75,7 +73,7 @@ class PeriodicMixin:
75
73
  self.agent.log.exception(f'Periodic Exception: {exc}')
76
74
 
77
75
  def start(self, start_after: float) -> None:
78
- asyncio.get_running_loop().call_later(start_after, _periodic, self)
76
+ asyncio.get_running_loop().call_later(start_after, _periodic, self) # type: ignore[arg-type]
79
77
 
80
78
 
81
79
  class PeriodicArgs(TypedDict):