python-cq 0.2.2__tar.gz → 0.3.1__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.
- python_cq-0.3.1/.gitignore +322 -0
- {python_cq-0.2.2 → python_cq-0.3.1}/PKG-INFO +6 -11
- {python_cq-0.2.2 → python_cq-0.3.1}/cq/__init__.py +6 -6
- {python_cq-0.2.2 → python_cq-0.3.1}/cq/_core/dispatcher/bus.py +47 -21
- {python_cq-0.2.2 → python_cq-0.3.1}/cq/_core/message.py +9 -6
- {python_cq-0.2.2 → python_cq-0.3.1}/pyproject.toml +42 -26
- {python_cq-0.2.2 → python_cq-0.3.1}/README.md +0 -0
- {python_cq-0.2.2 → python_cq-0.3.1}/cq/_core/__init__.py +0 -0
- {python_cq-0.2.2 → python_cq-0.3.1}/cq/_core/dispatcher/__init__.py +0 -0
- {python_cq-0.2.2 → python_cq-0.3.1}/cq/_core/dispatcher/base.py +0 -0
- {python_cq-0.2.2 → python_cq-0.3.1}/cq/_core/dispatcher/pipe.py +0 -0
- {python_cq-0.2.2 → python_cq-0.3.1}/cq/_core/dto.py +0 -0
- {python_cq-0.2.2 → python_cq-0.3.1}/cq/_core/middleware.py +0 -0
- {python_cq-0.2.2 → python_cq-0.3.1}/cq/exceptions.py +0 -0
- {python_cq-0.2.2 → python_cq-0.3.1}/cq/middlewares/__init__.py +0 -0
- {python_cq-0.2.2 → python_cq-0.3.1}/cq/middlewares/retry.py +0 -0
- {python_cq-0.2.2 → python_cq-0.3.1}/cq/py.typed +0 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# Created by https://www.toptal.com/developers/gitignore/api/python,pycharm,dotenv,macos
|
|
2
|
+
# Edit at https://www.toptal.com/developers/gitignore?templates=python,pycharm,dotenv,macos
|
|
3
|
+
|
|
4
|
+
### dotenv ###
|
|
5
|
+
.env
|
|
6
|
+
|
|
7
|
+
### macOS ###
|
|
8
|
+
# General
|
|
9
|
+
.DS_Store
|
|
10
|
+
.AppleDouble
|
|
11
|
+
.LSOverride
|
|
12
|
+
|
|
13
|
+
# Icon must end with two \r
|
|
14
|
+
Icon
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Thumbnails
|
|
18
|
+
._*
|
|
19
|
+
|
|
20
|
+
# Files that might appear in the root of a volume
|
|
21
|
+
.DocumentRevisions-V100
|
|
22
|
+
.fseventsd
|
|
23
|
+
.Spotlight-V100
|
|
24
|
+
.TemporaryItems
|
|
25
|
+
.Trashes
|
|
26
|
+
.VolumeIcon.icns
|
|
27
|
+
.com.apple.timemachine.donotpresent
|
|
28
|
+
|
|
29
|
+
# Directories potentially created on remote AFP share
|
|
30
|
+
.AppleDB
|
|
31
|
+
.AppleDesktop
|
|
32
|
+
Network Trash Folder
|
|
33
|
+
Temporary Items
|
|
34
|
+
.apdisk
|
|
35
|
+
|
|
36
|
+
### macOS Patch ###
|
|
37
|
+
# iCloud generated files
|
|
38
|
+
*.icloud
|
|
39
|
+
|
|
40
|
+
### PyCharm ###
|
|
41
|
+
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
42
|
+
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
43
|
+
|
|
44
|
+
# User-specific stuff
|
|
45
|
+
.idea/**/workspace.xml
|
|
46
|
+
.idea/**/tasks.xml
|
|
47
|
+
.idea/**/usage.statistics.xml
|
|
48
|
+
.idea/**/dictionaries
|
|
49
|
+
.idea/**/shelf
|
|
50
|
+
|
|
51
|
+
# AWS User-specific
|
|
52
|
+
.idea/**/aws.xml
|
|
53
|
+
|
|
54
|
+
# Generated files
|
|
55
|
+
.idea/**/contentModel.xml
|
|
56
|
+
|
|
57
|
+
# Sensitive or high-churn files
|
|
58
|
+
.idea/**/dataSources/
|
|
59
|
+
.idea/**/dataSources.ids
|
|
60
|
+
.idea/**/dataSources.local.xml
|
|
61
|
+
.idea/**/sqlDataSources.xml
|
|
62
|
+
.idea/**/dynamic.xml
|
|
63
|
+
.idea/**/uiDesigner.xml
|
|
64
|
+
.idea/**/dbnavigator.xml
|
|
65
|
+
|
|
66
|
+
# Gradle
|
|
67
|
+
.idea/**/gradle.xml
|
|
68
|
+
.idea/**/libraries
|
|
69
|
+
|
|
70
|
+
# Gradle and Maven with auto-import
|
|
71
|
+
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
72
|
+
# since they will be recreated, and may cause churn. Uncomment if using
|
|
73
|
+
# auto-import.
|
|
74
|
+
# .idea/artifacts
|
|
75
|
+
# .idea/compiler.xml
|
|
76
|
+
# .idea/jarRepositories.xml
|
|
77
|
+
# .idea/modules.xml
|
|
78
|
+
# .idea/*.iml
|
|
79
|
+
# .idea/modules
|
|
80
|
+
# *.iml
|
|
81
|
+
# *.ipr
|
|
82
|
+
|
|
83
|
+
# CMake
|
|
84
|
+
cmake-build-*/
|
|
85
|
+
|
|
86
|
+
# Mongo Explorer plugin
|
|
87
|
+
.idea/**/mongoSettings.xml
|
|
88
|
+
|
|
89
|
+
# File-based project format
|
|
90
|
+
*.iws
|
|
91
|
+
|
|
92
|
+
# IntelliJ
|
|
93
|
+
out/
|
|
94
|
+
|
|
95
|
+
# mpeltonen/sbt-idea plugin
|
|
96
|
+
.idea_modules/
|
|
97
|
+
|
|
98
|
+
# JIRA plugin
|
|
99
|
+
atlassian-ide-plugin.xml
|
|
100
|
+
|
|
101
|
+
# Cursive Clojure plugin
|
|
102
|
+
.idea/replstate.xml
|
|
103
|
+
|
|
104
|
+
# SonarLint plugin
|
|
105
|
+
.idea/sonarlint/
|
|
106
|
+
|
|
107
|
+
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
108
|
+
com_crashlytics_export_strings.xml
|
|
109
|
+
crashlytics.properties
|
|
110
|
+
crashlytics-build.properties
|
|
111
|
+
fabric.properties
|
|
112
|
+
|
|
113
|
+
# Editor-based Rest Client
|
|
114
|
+
.idea/httpRequests
|
|
115
|
+
|
|
116
|
+
# Android studio 3.1+ serialized cache file
|
|
117
|
+
.idea/caches/build_file_checksums.ser
|
|
118
|
+
|
|
119
|
+
### PyCharm Patch ###
|
|
120
|
+
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
|
121
|
+
|
|
122
|
+
# *.iml
|
|
123
|
+
# modules.xml
|
|
124
|
+
# .idea/misc.xml
|
|
125
|
+
# *.ipr
|
|
126
|
+
|
|
127
|
+
# Sonarlint plugin
|
|
128
|
+
# https://plugins.jetbrains.com/plugin/7973-sonarlint
|
|
129
|
+
.idea/**/sonarlint/
|
|
130
|
+
|
|
131
|
+
# SonarQube Plugin
|
|
132
|
+
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
|
|
133
|
+
.idea/**/sonarIssues.xml
|
|
134
|
+
|
|
135
|
+
# Markdown Navigator plugin
|
|
136
|
+
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
|
|
137
|
+
.idea/**/markdown-navigator.xml
|
|
138
|
+
.idea/**/markdown-navigator-enh.xml
|
|
139
|
+
.idea/**/markdown-navigator/
|
|
140
|
+
|
|
141
|
+
# Cache file creation bug
|
|
142
|
+
# See https://youtrack.jetbrains.com/issue/JBR-2257
|
|
143
|
+
.idea/$CACHE_FILE$
|
|
144
|
+
|
|
145
|
+
# CodeStream plugin
|
|
146
|
+
# https://plugins.jetbrains.com/plugin/12206-codestream
|
|
147
|
+
.idea/codestream.xml
|
|
148
|
+
|
|
149
|
+
# Azure Toolkit for IntelliJ plugin
|
|
150
|
+
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
|
|
151
|
+
.idea/**/azureSettings.xml
|
|
152
|
+
|
|
153
|
+
### Python ###
|
|
154
|
+
# Byte-compiled / optimized / DLL files
|
|
155
|
+
__pycache__/
|
|
156
|
+
*.py[cod]
|
|
157
|
+
*$py.class
|
|
158
|
+
|
|
159
|
+
# C extensions
|
|
160
|
+
*.so
|
|
161
|
+
|
|
162
|
+
# Distribution / packaging
|
|
163
|
+
.Python
|
|
164
|
+
build/
|
|
165
|
+
develop-eggs/
|
|
166
|
+
dist/
|
|
167
|
+
downloads/
|
|
168
|
+
eggs/
|
|
169
|
+
.eggs/
|
|
170
|
+
lib/
|
|
171
|
+
lib64/
|
|
172
|
+
parts/
|
|
173
|
+
sdist/
|
|
174
|
+
var/
|
|
175
|
+
wheels/
|
|
176
|
+
share/python-wheels/
|
|
177
|
+
*.egg-info/
|
|
178
|
+
.installed.cfg
|
|
179
|
+
*.egg
|
|
180
|
+
MANIFEST
|
|
181
|
+
|
|
182
|
+
# PyInstaller
|
|
183
|
+
# Usually these files are written by a python script from a template
|
|
184
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
185
|
+
*.manifest
|
|
186
|
+
*.spec
|
|
187
|
+
|
|
188
|
+
# Installer logs
|
|
189
|
+
pip-log.txt
|
|
190
|
+
pip-delete-this-directory.txt
|
|
191
|
+
|
|
192
|
+
# Unit test / coverage reports
|
|
193
|
+
htmlcov/
|
|
194
|
+
.tox/
|
|
195
|
+
.nox/
|
|
196
|
+
.coverage
|
|
197
|
+
.coverage.*
|
|
198
|
+
.cache
|
|
199
|
+
nosetests.xml
|
|
200
|
+
coverage.xml
|
|
201
|
+
*.cover
|
|
202
|
+
*.py,cover
|
|
203
|
+
.hypothesis/
|
|
204
|
+
.pytest_cache/
|
|
205
|
+
cover/
|
|
206
|
+
|
|
207
|
+
# Translations
|
|
208
|
+
*.mo
|
|
209
|
+
*.pot
|
|
210
|
+
|
|
211
|
+
# Django stuff:
|
|
212
|
+
*.log
|
|
213
|
+
local_settings.py
|
|
214
|
+
db.sqlite3
|
|
215
|
+
db.sqlite3-journal
|
|
216
|
+
|
|
217
|
+
# Flask stuff:
|
|
218
|
+
instance/
|
|
219
|
+
.webassets-cache
|
|
220
|
+
|
|
221
|
+
# Scrapy stuff:
|
|
222
|
+
.scrapy
|
|
223
|
+
|
|
224
|
+
# Sphinx documentation
|
|
225
|
+
docs/_build/
|
|
226
|
+
|
|
227
|
+
# PyBuilder
|
|
228
|
+
.pybuilder/
|
|
229
|
+
target/
|
|
230
|
+
|
|
231
|
+
# Jupyter Notebook
|
|
232
|
+
.ipynb_checkpoints
|
|
233
|
+
|
|
234
|
+
# IPython
|
|
235
|
+
profile_default/
|
|
236
|
+
ipython_config.py
|
|
237
|
+
|
|
238
|
+
# pyenv
|
|
239
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
240
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
241
|
+
# .python-version
|
|
242
|
+
|
|
243
|
+
# pipenv
|
|
244
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
245
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
246
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
247
|
+
# install all needed dependencies.
|
|
248
|
+
#Pipfile.lock
|
|
249
|
+
|
|
250
|
+
# poetry
|
|
251
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
252
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
253
|
+
# commonly ignored for libraries.
|
|
254
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
255
|
+
#poetry.lock
|
|
256
|
+
|
|
257
|
+
# pdm
|
|
258
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
259
|
+
#pdm.lock
|
|
260
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
261
|
+
# in version control.
|
|
262
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
263
|
+
.pdm.toml
|
|
264
|
+
|
|
265
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
266
|
+
__pypackages__/
|
|
267
|
+
|
|
268
|
+
# Celery stuff
|
|
269
|
+
celerybeat-schedule
|
|
270
|
+
celerybeat.pid
|
|
271
|
+
|
|
272
|
+
# SageMath parsed files
|
|
273
|
+
*.sage.py
|
|
274
|
+
|
|
275
|
+
# Environments
|
|
276
|
+
.venv
|
|
277
|
+
env/
|
|
278
|
+
venv/
|
|
279
|
+
ENV/
|
|
280
|
+
env.bak/
|
|
281
|
+
venv.bak/
|
|
282
|
+
|
|
283
|
+
# Spyder project settings
|
|
284
|
+
.spyderproject
|
|
285
|
+
.spyproject
|
|
286
|
+
|
|
287
|
+
# Rope project settings
|
|
288
|
+
.ropeproject
|
|
289
|
+
|
|
290
|
+
# mkdocs documentation
|
|
291
|
+
/site
|
|
292
|
+
|
|
293
|
+
# mypy
|
|
294
|
+
.mypy_cache/
|
|
295
|
+
.dmypy.json
|
|
296
|
+
dmypy.json
|
|
297
|
+
|
|
298
|
+
# Pyre type checker
|
|
299
|
+
.pyre/
|
|
300
|
+
|
|
301
|
+
# pytype static type analyzer
|
|
302
|
+
.pytype/
|
|
303
|
+
|
|
304
|
+
# Cython debug symbols
|
|
305
|
+
cython_debug/
|
|
306
|
+
|
|
307
|
+
# PyCharm
|
|
308
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
309
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
310
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
311
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
312
|
+
#.idea/
|
|
313
|
+
|
|
314
|
+
# End of https://www.toptal.com/developers/gitignore/api/python,pycharm,dotenv,macos
|
|
315
|
+
|
|
316
|
+
# Pyenv
|
|
317
|
+
.python-version
|
|
318
|
+
|
|
319
|
+
# Code editors
|
|
320
|
+
.fleet/
|
|
321
|
+
.idea/
|
|
322
|
+
.vscode/
|
|
@@ -1,29 +1,25 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-cq
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Lightweight CQRS library.
|
|
5
|
-
|
|
5
|
+
Project-URL: Repository, https://github.com/100nm/python-cq
|
|
6
|
+
Author: remimd
|
|
6
7
|
License: MIT
|
|
7
8
|
Keywords: cqrs
|
|
8
|
-
Author: remimd
|
|
9
|
-
Requires-Python: >=3.12,<4
|
|
10
9
|
Classifier: Development Status :: 4 - Beta
|
|
11
10
|
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
13
11
|
Classifier: Natural Language :: English
|
|
14
12
|
Classifier: Operating System :: OS Independent
|
|
15
13
|
Classifier: Programming Language :: Python
|
|
16
14
|
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
19
15
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
16
|
Classifier: Topic :: Software Development :: Libraries
|
|
21
17
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
22
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
19
|
Classifier: Typing :: Typed
|
|
24
|
-
Requires-
|
|
20
|
+
Requires-Python: <4,>=3.12
|
|
21
|
+
Requires-Dist: pydantic<3,>=2
|
|
25
22
|
Requires-Dist: python-injection
|
|
26
|
-
Project-URL: Repository, https://github.com/100nm/python-cq
|
|
27
23
|
Description-Content-Type: text/markdown
|
|
28
24
|
|
|
29
25
|
# python-cq
|
|
@@ -51,4 +47,3 @@ pip install python-cq
|
|
|
51
47
|
* [**Writing Application Layer**](https://github.com/100nm/python-cq/tree/prod/documentation/writing-application-layer.md)
|
|
52
48
|
* [**Pipeline**](https://github.com/100nm/python-cq/tree/prod/documentation/pipeline.md)
|
|
53
49
|
* [**FastAPI Example**](https://github.com/100nm/python-cq/tree/prod/documentation/fastapi-example.md)
|
|
54
|
-
|
|
@@ -12,9 +12,9 @@ from ._core.message import (
|
|
|
12
12
|
QueryBus,
|
|
13
13
|
command_handler,
|
|
14
14
|
event_handler,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
get_command_bus,
|
|
16
|
+
get_event_bus,
|
|
17
|
+
get_query_bus,
|
|
18
18
|
query_handler,
|
|
19
19
|
)
|
|
20
20
|
from ._core.middleware import Middleware, MiddlewareResult
|
|
@@ -35,8 +35,8 @@ __all__ = (
|
|
|
35
35
|
"QueryBus",
|
|
36
36
|
"command_handler",
|
|
37
37
|
"event_handler",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
38
|
+
"get_command_bus",
|
|
39
|
+
"get_event_bus",
|
|
40
|
+
"get_query_bus",
|
|
41
41
|
"query_handler",
|
|
42
42
|
)
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from abc import abstractmethod
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
3
|
from collections import defaultdict
|
|
4
|
-
from collections.abc import Callable
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
6
|
from inspect import isclass
|
|
7
7
|
from types import GenericAlias
|
|
8
|
-
from typing import Protocol, Self, TypeAliasType, runtime_checkable
|
|
8
|
+
from typing import Any, Protocol, Self, TypeAliasType, runtime_checkable
|
|
9
9
|
|
|
10
10
|
import injection
|
|
11
11
|
|
|
12
12
|
from cq._core.dispatcher.base import BaseDispatcher, Dispatcher
|
|
13
13
|
|
|
14
14
|
type HandlerType[**P, T] = type[Handler[P, T]]
|
|
15
|
-
type HandlerFactory[**P, T] = Callable[..., Handler[P, T]]
|
|
15
|
+
type HandlerFactory[**P, T] = Callable[..., Awaitable[Handler[P, T]]]
|
|
16
|
+
|
|
17
|
+
type Listener[T] = Callable[[T], Awaitable[Any]]
|
|
16
18
|
|
|
17
19
|
type BusType[I, O] = type[Bus[I, O]]
|
|
18
20
|
|
|
@@ -34,32 +36,56 @@ class Bus[I, O](Dispatcher[I, O], Protocol):
|
|
|
34
36
|
def subscribe(self, input_type: type[I], factory: HandlerFactory[[I], O]) -> Self:
|
|
35
37
|
raise NotImplementedError
|
|
36
38
|
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def add_listeners(self, *listeners: Listener[I]) -> Self:
|
|
41
|
+
raise NotImplementedError
|
|
42
|
+
|
|
37
43
|
|
|
38
44
|
@dataclass(eq=False, frozen=True, slots=True)
|
|
39
45
|
class SubscriberDecorator[I, O]:
|
|
40
46
|
bus_type: BusType[I, O] | TypeAliasType | GenericAlias
|
|
41
47
|
injection_module: injection.Module = field(default_factory=injection.mod)
|
|
42
48
|
|
|
43
|
-
def __call__(self, first_input_type: type[I], /, *input_types: type[I])
|
|
44
|
-
def decorator(wrapped
|
|
49
|
+
def __call__(self, first_input_type: type[I], /, *input_types: type[I]) -> Any:
|
|
50
|
+
def decorator(wrapped: type[Handler[[I], O]]) -> type[Handler[[I], O]]:
|
|
45
51
|
if not isclass(wrapped) or not issubclass(wrapped, Handler):
|
|
46
52
|
raise TypeError(f"`{wrapped}` isn't a valid handler.")
|
|
47
53
|
|
|
48
|
-
bus = self.
|
|
49
|
-
|
|
54
|
+
bus = self.injection_module.find_instance(self.bus_type)
|
|
55
|
+
lazy_instance = self.injection_module.aget_lazy_instance(
|
|
56
|
+
wrapped,
|
|
57
|
+
default=NotImplemented,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
async def getter() -> Handler[[I], O]:
|
|
61
|
+
return await lazy_instance
|
|
50
62
|
|
|
51
63
|
for input_type in (first_input_type, *input_types):
|
|
52
|
-
bus.subscribe(input_type,
|
|
64
|
+
bus.subscribe(input_type, getter)
|
|
53
65
|
|
|
54
|
-
return wrapped
|
|
66
|
+
return self.injection_module.injectable(wrapped)
|
|
55
67
|
|
|
56
68
|
return decorator
|
|
57
69
|
|
|
58
|
-
def __find_bus(self) -> Bus[I, O]:
|
|
59
|
-
return self.injection_module.find_instance(self.bus_type)
|
|
60
70
|
|
|
71
|
+
class BaseBus[I, O](BaseDispatcher[I, O], Bus[I, O], ABC):
|
|
72
|
+
__slots__ = ("__listeners",)
|
|
73
|
+
|
|
74
|
+
__listeners: list[Listener[I]]
|
|
75
|
+
|
|
76
|
+
def __init__(self) -> None:
|
|
77
|
+
super().__init__()
|
|
78
|
+
self.__listeners = []
|
|
79
|
+
|
|
80
|
+
def add_listeners(self, *listeners: Listener[I]) -> Self:
|
|
81
|
+
self.__listeners.extend(listeners)
|
|
82
|
+
return self
|
|
61
83
|
|
|
62
|
-
|
|
84
|
+
async def _trigger_listeners(self, input_value: I, /) -> None:
|
|
85
|
+
await asyncio.gather(*(listener(input_value) for listener in self.__listeners))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class SimpleBus[I, O](BaseBus[I, O]):
|
|
63
89
|
__slots__ = ("__handlers",)
|
|
64
90
|
|
|
65
91
|
__handlers: dict[type[I], HandlerFactory[[I], O]]
|
|
@@ -69,6 +95,7 @@ class SimpleBus[I, O](BaseDispatcher[I, O], Bus[I, O]):
|
|
|
69
95
|
self.__handlers = {}
|
|
70
96
|
|
|
71
97
|
async def dispatch(self, input_value: I, /) -> O:
|
|
98
|
+
await self._trigger_listeners(input_value)
|
|
72
99
|
input_type = type(input_value)
|
|
73
100
|
|
|
74
101
|
try:
|
|
@@ -76,10 +103,8 @@ class SimpleBus[I, O](BaseDispatcher[I, O], Bus[I, O]):
|
|
|
76
103
|
except KeyError:
|
|
77
104
|
return NotImplemented
|
|
78
105
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
input_value,
|
|
82
|
-
)
|
|
106
|
+
handler = await handler_factory()
|
|
107
|
+
return await self._invoke_with_middlewares(handler.handle, input_value)
|
|
83
108
|
|
|
84
109
|
def subscribe(self, input_type: type[I], factory: HandlerFactory[[I], O]) -> Self:
|
|
85
110
|
if input_type in self.__handlers:
|
|
@@ -91,7 +116,7 @@ class SimpleBus[I, O](BaseDispatcher[I, O], Bus[I, O]):
|
|
|
91
116
|
return self
|
|
92
117
|
|
|
93
118
|
|
|
94
|
-
class TaskBus[I](
|
|
119
|
+
class TaskBus[I](BaseBus[I, None]):
|
|
95
120
|
__slots__ = ("__handlers",)
|
|
96
121
|
|
|
97
122
|
__handlers: dict[type[I], list[HandlerFactory[[I], None]]]
|
|
@@ -101,19 +126,20 @@ class TaskBus[I](BaseDispatcher[I, None], Bus[I, None]):
|
|
|
101
126
|
self.__handlers = defaultdict(list)
|
|
102
127
|
|
|
103
128
|
async def dispatch(self, input_value: I, /) -> None:
|
|
129
|
+
await self._trigger_listeners(input_value)
|
|
104
130
|
handler_factories = self.__handlers.get(type(input_value))
|
|
105
131
|
|
|
106
132
|
if not handler_factories:
|
|
107
133
|
return
|
|
108
134
|
|
|
109
135
|
await asyncio.gather(
|
|
110
|
-
*
|
|
136
|
+
*[
|
|
111
137
|
self._invoke_with_middlewares(
|
|
112
|
-
handler_factory().handle,
|
|
138
|
+
(await handler_factory()).handle,
|
|
113
139
|
input_value,
|
|
114
140
|
)
|
|
115
141
|
for handler_factory in handler_factories
|
|
116
|
-
|
|
142
|
+
]
|
|
117
143
|
)
|
|
118
144
|
|
|
119
145
|
def subscribe(
|
|
@@ -39,13 +39,16 @@ injection.set_constant(TaskBus(), EventBus, alias=True)
|
|
|
39
39
|
injection.set_constant(SimpleBus(), QueryBus, alias=True)
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
@injection.inject
|
|
43
|
+
def get_command_bus[T](bus: CommandBus[T] = NotImplemented, /) -> CommandBus[T]:
|
|
44
|
+
return bus
|
|
44
45
|
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
@injection.inject
|
|
48
|
+
def get_event_bus(bus: EventBus = NotImplemented, /) -> EventBus:
|
|
49
|
+
return bus
|
|
48
50
|
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
@injection.inject
|
|
53
|
+
def get_query_bus[T](bus: QueryBus[T] = NotImplemented, /) -> QueryBus[T]:
|
|
54
|
+
return bus
|
|
@@ -1,11 +1,30 @@
|
|
|
1
|
-
[
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[dependency-groups]
|
|
6
|
+
dev = [
|
|
7
|
+
"hatch",
|
|
8
|
+
"mypy",
|
|
9
|
+
"ruff",
|
|
10
|
+
]
|
|
11
|
+
example = [
|
|
12
|
+
"fastapi",
|
|
13
|
+
]
|
|
14
|
+
test = [
|
|
15
|
+
"pytest",
|
|
16
|
+
"pytest-asyncio",
|
|
17
|
+
"pytest-cov",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project]
|
|
2
21
|
name = "python-cq"
|
|
3
|
-
version = "0.
|
|
22
|
+
version = "0.3.1"
|
|
4
23
|
description = "Lightweight CQRS library."
|
|
5
|
-
license = "MIT"
|
|
6
|
-
authors = ["remimd"]
|
|
24
|
+
license = { text = "MIT" }
|
|
7
25
|
readme = "README.md"
|
|
8
|
-
|
|
26
|
+
requires-python = ">=3.12, <4"
|
|
27
|
+
authors = [{ name = "remimd" }]
|
|
9
28
|
keywords = ["cqrs"]
|
|
10
29
|
classifiers = [
|
|
11
30
|
"Development Status :: 4 - Beta",
|
|
@@ -20,24 +39,13 @@ classifiers = [
|
|
|
20
39
|
"Natural Language :: English",
|
|
21
40
|
"Typing :: Typed",
|
|
22
41
|
]
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
pydantic = ">=2, <3"
|
|
28
|
-
python-injection = "*"
|
|
29
|
-
|
|
30
|
-
[tool.poetry.group.dev.dependencies]
|
|
31
|
-
mypy = "*"
|
|
32
|
-
ruff = "*"
|
|
33
|
-
|
|
34
|
-
[tool.poetry.group.example.dependencies]
|
|
35
|
-
fastapi = "*"
|
|
42
|
+
dependencies = [
|
|
43
|
+
"pydantic (>=2, <3)",
|
|
44
|
+
"python-injection",
|
|
45
|
+
]
|
|
36
46
|
|
|
37
|
-
[
|
|
38
|
-
|
|
39
|
-
pytest-asyncio = "*"
|
|
40
|
-
pytest-cov = "*"
|
|
47
|
+
[project.urls]
|
|
48
|
+
Repository = "https://github.com/100nm/python-cq"
|
|
41
49
|
|
|
42
50
|
[tool.coverage.report]
|
|
43
51
|
exclude_lines = [
|
|
@@ -46,6 +54,15 @@ exclude_lines = [
|
|
|
46
54
|
"raise NotImplementedError",
|
|
47
55
|
]
|
|
48
56
|
|
|
57
|
+
[tool.hatch.build]
|
|
58
|
+
skip-excluded-dirs = true
|
|
59
|
+
|
|
60
|
+
[tool.hatch.build.targets.sdist]
|
|
61
|
+
include = ["cq"]
|
|
62
|
+
|
|
63
|
+
[tool.hatch.build.targets.wheel]
|
|
64
|
+
packages = ["cq"]
|
|
65
|
+
|
|
49
66
|
[tool.mypy]
|
|
50
67
|
check_untyped_defs = true
|
|
51
68
|
disallow_any_generics = true
|
|
@@ -70,7 +87,6 @@ asyncio_mode = "auto"
|
|
|
70
87
|
testpaths = "**/tests/"
|
|
71
88
|
|
|
72
89
|
[tool.ruff]
|
|
73
|
-
target-version = "py312"
|
|
74
90
|
line-length = 88
|
|
75
91
|
indent-width = 4
|
|
76
92
|
|
|
@@ -84,6 +100,6 @@ line-ending = "auto"
|
|
|
84
100
|
extend-select = ["F", "I", "N"]
|
|
85
101
|
fixable = ["ALL"]
|
|
86
102
|
|
|
87
|
-
[
|
|
88
|
-
|
|
89
|
-
|
|
103
|
+
[tool.uv]
|
|
104
|
+
default-groups = ["dev", "test"]
|
|
105
|
+
package = true
|
|
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
|