explicit-python-dlq 1.0.0__tar.gz → 2.0.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.
- {explicit_python_dlq-1.0.0/src/explicit_python_dlq.egg-info → explicit_python_dlq-2.0.0}/PKG-INFO +43 -5
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/README.md +37 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/pyproject.toml +15 -39
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/config.py +0 -6
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/django/README.md +4 -4
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/django/adapters/db.py +4 -3
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/django/models.py +16 -6
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/drf/README.md +0 -1
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/domain/commands.py +7 -7
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/domain/factories.py +7 -13
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/domain/model.py +18 -11
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/domain/services.py +1 -5
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/infrastructure/handlers.py +1 -4
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0/src/explicit_python_dlq.egg-info}/PKG-INFO +43 -5
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit_python_dlq.egg-info/requires.txt +2 -2
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/MANIFEST.in +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/setup.cfg +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/__init__.py +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/adapters/__init__.py +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/adapters/db.py +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/__init__.py +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/django/__init__.py +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/django/adapters/__init__.py +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/django/apps.py +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/django/migrations/0001_initial.py +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/django/migrations/__init__.py +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/drf/__init__.py +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/drf/serializers.py +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/drf/views.py +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/domain/__init__.py +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/infrastructure/__init__.py +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/py.typed +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/services/__init__.py +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/services/handlers.py +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/types.py +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit_python_dlq.egg-info/SOURCES.txt +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit_python_dlq.egg-info/dependency_links.txt +0 -0
- {explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit_python_dlq.egg-info/top_level.txt +0 -0
{explicit_python_dlq-1.0.0/src/explicit_python_dlq.egg-info → explicit_python_dlq-2.0.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: explicit-python-dlq
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Набор компонентов реализующих DQL для систем на базе explicit
|
|
5
5
|
Author-email: "АО \"БАРС Груп\"" <education_dev@bars-open.ru>
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -8,17 +8,18 @@ Classifier: Environment :: Web Environment
|
|
|
8
8
|
Classifier: Natural Language :: Russian
|
|
9
9
|
Classifier: Operating System :: OS Independent
|
|
10
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
-
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Requires-Python: >=3.10
|
|
16
17
|
Description-Content-Type: text/markdown
|
|
17
|
-
Requires-Dist: explicit-python<3
|
|
18
|
+
Requires-Dist: explicit-python<4,>=3
|
|
18
19
|
Provides-Extra: dev
|
|
19
20
|
Requires-Dist: freezegun; extra == "dev"
|
|
20
21
|
Provides-Extra: django
|
|
21
|
-
Requires-Dist: explicit-python-django<
|
|
22
|
+
Requires-Dist: explicit-python-django<3,>=2; extra == "django"
|
|
22
23
|
Provides-Extra: rest
|
|
23
24
|
Requires-Dist: djangorestframework>=3.13.0; extra == "rest"
|
|
24
25
|
Requires-Dist: django_filter>=23.3; extra == "rest"
|
|
@@ -103,3 +104,40 @@ def bootstrap() -> None:
|
|
|
103
104
|
В пакете реализованы готовые к использованию компоненты:
|
|
104
105
|
* Реализация абстрактного [хранилища сообщений](./src/explicit/dlq/contrib/django/README.md) на базе Django ORM
|
|
105
106
|
* [REST API](./src/explicit/dlq/contrib/drf/README.md) для работы с хранилищем сообщений. Предназначен для совместной работы с реализацией хранилища сообщений django ORM.
|
|
107
|
+
|
|
108
|
+
## Запуск тестов
|
|
109
|
+
|
|
110
|
+
Тесты запускаются через `tox`. В системе должен быть установлен [uv](https://docs.astral.sh/uv/).
|
|
111
|
+
|
|
112
|
+
Для корректного запуска тестов необходимы следующие значения переменных окружения в системе. Вместо `<login>` и `<password>` необходимо подставить данные от УЗ БАРС:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
UV_INDEX="internal=http://<login>:<password>@nexus.py.bars.group/repository/pypi-all/simple"
|
|
116
|
+
UV_DEFAULT_INDEX="https://pypi.org/simple"
|
|
117
|
+
UV_INDEX_STRATEGY=unsafe-best-match
|
|
118
|
+
# Для docker 29+
|
|
119
|
+
TOX_DOCKER_GATEWAY=localhost
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Чтобы не менять значения системных переменных, можно создать `.env` файл c переменными окружения и запускать команды через [python-dotenv](https://github.com/theskumar/python-dotenv/).
|
|
123
|
+
|
|
124
|
+
Установка `tox` с необходимыми зависимостями:
|
|
125
|
+
```bash
|
|
126
|
+
uv tool install tox --with tox-uv --with m3-tox-docker
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Или с использованием `python-dotenv`:
|
|
130
|
+
```bash
|
|
131
|
+
dotenv run uv tool install tox --with tox-uv --with m3-tox-docker
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Запуск тестов:
|
|
135
|
+
```bash
|
|
136
|
+
tox
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Через `python-dotenv`:
|
|
140
|
+
```bash
|
|
141
|
+
dotenv run tox
|
|
142
|
+
```
|
|
143
|
+
|
|
@@ -78,3 +78,40 @@ def bootstrap() -> None:
|
|
|
78
78
|
В пакете реализованы готовые к использованию компоненты:
|
|
79
79
|
* Реализация абстрактного [хранилища сообщений](./src/explicit/dlq/contrib/django/README.md) на базе Django ORM
|
|
80
80
|
* [REST API](./src/explicit/dlq/contrib/drf/README.md) для работы с хранилищем сообщений. Предназначен для совместной работы с реализацией хранилища сообщений django ORM.
|
|
81
|
+
|
|
82
|
+
## Запуск тестов
|
|
83
|
+
|
|
84
|
+
Тесты запускаются через `tox`. В системе должен быть установлен [uv](https://docs.astral.sh/uv/).
|
|
85
|
+
|
|
86
|
+
Для корректного запуска тестов необходимы следующие значения переменных окружения в системе. Вместо `<login>` и `<password>` необходимо подставить данные от УЗ БАРС:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
UV_INDEX="internal=http://<login>:<password>@nexus.py.bars.group/repository/pypi-all/simple"
|
|
90
|
+
UV_DEFAULT_INDEX="https://pypi.org/simple"
|
|
91
|
+
UV_INDEX_STRATEGY=unsafe-best-match
|
|
92
|
+
# Для docker 29+
|
|
93
|
+
TOX_DOCKER_GATEWAY=localhost
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Чтобы не менять значения системных переменных, можно создать `.env` файл c переменными окружения и запускать команды через [python-dotenv](https://github.com/theskumar/python-dotenv/).
|
|
97
|
+
|
|
98
|
+
Установка `tox` с необходимыми зависимостями:
|
|
99
|
+
```bash
|
|
100
|
+
uv tool install tox --with tox-uv --with m3-tox-docker
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Или с использованием `python-dotenv`:
|
|
104
|
+
```bash
|
|
105
|
+
dotenv run uv tool install tox --with tox-uv --with m3-tox-docker
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Запуск тестов:
|
|
109
|
+
```bash
|
|
110
|
+
tox
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Через `python-dotenv`:
|
|
114
|
+
```bash
|
|
115
|
+
dotenv run tox
|
|
116
|
+
```
|
|
117
|
+
|
|
@@ -27,16 +27,17 @@ classifiers=[
|
|
|
27
27
|
"Natural Language :: Russian",
|
|
28
28
|
"Operating System :: OS Independent",
|
|
29
29
|
"Development Status :: 5 - Production/Stable",
|
|
30
|
-
"Programming Language :: Python :: 3.9",
|
|
31
30
|
"Programming Language :: Python :: 3.10",
|
|
32
31
|
"Programming Language :: Python :: 3.11",
|
|
33
32
|
"Programming Language :: Python :: 3.12",
|
|
33
|
+
"Programming Language :: Python :: 3.13",
|
|
34
|
+
"Programming Language :: Python :: 3.14",
|
|
34
35
|
]
|
|
35
36
|
|
|
36
|
-
requires-python = ">=3.
|
|
37
|
+
requires-python = ">=3.10"
|
|
37
38
|
|
|
38
39
|
dependencies = [
|
|
39
|
-
"explicit-python>=
|
|
40
|
+
"explicit-python>=3,<4",
|
|
40
41
|
]
|
|
41
42
|
|
|
42
43
|
[project.optional-dependencies]
|
|
@@ -44,7 +45,7 @@ dev = [
|
|
|
44
45
|
"freezegun"
|
|
45
46
|
]
|
|
46
47
|
django = [
|
|
47
|
-
"explicit-python-django>=
|
|
48
|
+
"explicit-python-django>=2,<3"
|
|
48
49
|
]
|
|
49
50
|
rest = [
|
|
50
51
|
"djangorestframework>=3.13.0",
|
|
@@ -62,19 +63,21 @@ include_trailing_comma = true
|
|
|
62
63
|
line_length = 120
|
|
63
64
|
combine_as_imports = true
|
|
64
65
|
lines_after_imports = 2
|
|
65
|
-
extra_standard_library = ["dataclasses"]
|
|
66
66
|
known_project = ["explicit", "testapp"]
|
|
67
67
|
sections = ["FUTURE","STDLIB","THIRDPARTY","FIRSTPARTY","PROJECT","LOCALFOLDER"]
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
[tool.ruff]
|
|
71
|
-
target-version = "
|
|
71
|
+
target-version = "py310"
|
|
72
72
|
line-length = 120
|
|
73
73
|
indent-width = 4
|
|
74
|
+
exclude = [
|
|
75
|
+
"**/migrations/*.py",
|
|
76
|
+
]
|
|
74
77
|
|
|
75
78
|
|
|
76
79
|
[tool.ruff.lint]
|
|
77
|
-
select = ["F", "E", "D", "W", "S", "N",]
|
|
80
|
+
select = ["F", "E", "D", "W", "S", "N", "UP007", "UP045"]
|
|
78
81
|
ignore = [
|
|
79
82
|
# Missing docstring in public module
|
|
80
83
|
"D100",
|
|
@@ -85,36 +88,9 @@ ignore = [
|
|
|
85
88
|
# use of assert detected (useless with pytest)
|
|
86
89
|
"S101",
|
|
87
90
|
]
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
".eggs",
|
|
92
|
-
".git",
|
|
93
|
-
".git-rewrite",
|
|
94
|
-
".hg",
|
|
95
|
-
".idea",
|
|
96
|
-
".ipynb_checkpoints",
|
|
97
|
-
".mypy_cache",
|
|
98
|
-
".nox",
|
|
99
|
-
".pants.d",
|
|
100
|
-
".pyenv",
|
|
101
|
-
".pytest_cache",
|
|
102
|
-
".pytype",
|
|
103
|
-
".ruff_cache",
|
|
104
|
-
".svn",
|
|
105
|
-
".tox",
|
|
106
|
-
".venv",
|
|
107
|
-
".vscode",
|
|
108
|
-
"__pypackages__",
|
|
109
|
-
"_build",
|
|
110
|
-
"buck-out",
|
|
111
|
-
"build",
|
|
112
|
-
"dist",
|
|
113
|
-
"node_modules",
|
|
114
|
-
"site-packages",
|
|
115
|
-
"venv",
|
|
116
|
-
"py-libs/**",
|
|
117
|
-
]
|
|
91
|
+
|
|
92
|
+
[tool.ruff.lint.pydocstyle]
|
|
93
|
+
convention = "google"
|
|
118
94
|
|
|
119
95
|
[tool.ruff.format]
|
|
120
96
|
quote-style = "single"
|
|
@@ -122,8 +98,8 @@ indent-style = "space"
|
|
|
122
98
|
docstring-code-format = true
|
|
123
99
|
|
|
124
100
|
[tool.ruff.lint.per-file-ignores]
|
|
125
|
-
"
|
|
126
|
-
"
|
|
101
|
+
"**/testapp/**" = ["D"]
|
|
102
|
+
"**/testapp/settings.py" = ["S105"]
|
|
127
103
|
|
|
128
104
|
[tool.mypy]
|
|
129
105
|
plugins = ["pydantic.mypy"]
|
|
@@ -7,10 +7,6 @@ from typing import (
|
|
|
7
7
|
TYPE_CHECKING,
|
|
8
8
|
)
|
|
9
9
|
|
|
10
|
-
from pydantic import (
|
|
11
|
-
ConfigDict,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
10
|
from explicit.dlq.domain import (
|
|
15
11
|
commands,
|
|
16
12
|
)
|
|
@@ -35,8 +31,6 @@ if TYPE_CHECKING:
|
|
|
35
31
|
class DLQConfig:
|
|
36
32
|
"""Конфигурация DLQ."""
|
|
37
33
|
|
|
38
|
-
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
39
|
-
|
|
40
34
|
bus: 'MessageBus'
|
|
41
35
|
repository: 'AbstractDLQRepository'
|
|
42
36
|
message_dispatcher: 'DLQMessageDispatcher'
|
{explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/django/README.md
RENAMED
|
@@ -7,13 +7,12 @@ testapp/settings.py:
|
|
|
7
7
|
INSTALLED_APPS = [
|
|
8
8
|
# другие приложения
|
|
9
9
|
'explicit.dlq.contrib.django', # подключение приложения с моделью DeadLetter
|
|
10
|
-
'testapp.core',
|
|
10
|
+
'testapp.core', # настройка компонентов из explicit.dlq
|
|
11
11
|
]
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
testapp/dlq/apps.py:
|
|
15
15
|
```python
|
|
16
|
-
|
|
17
16
|
from datetime import timedelta
|
|
18
17
|
|
|
19
18
|
from django.apps import AppConfig as AppConfigBase
|
|
@@ -27,9 +26,10 @@ class AppConfig(AppConfigBase):
|
|
|
27
26
|
|
|
28
27
|
def _configure_dlq(self):
|
|
29
28
|
from explicit.dlq.config import DLQConfig, configure_dlq
|
|
30
|
-
|
|
31
|
-
# реализация репозитория на базе Django ORM
|
|
29
|
+
|
|
30
|
+
# реализация репозитория на базе Django ORM
|
|
32
31
|
from explicit.dlq.contrib.django.adapters.db import Repository
|
|
32
|
+
|
|
33
33
|
config = DLQConfig(
|
|
34
34
|
# ...
|
|
35
35
|
repository=Repository(),
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""Адаптеры для работы с Dead Letter Queue (DLQ) в базе данных."""
|
|
2
2
|
|
|
3
|
+
from collections.abc import (
|
|
4
|
+
Iterator,
|
|
5
|
+
)
|
|
3
6
|
from typing import (
|
|
4
7
|
TYPE_CHECKING,
|
|
5
|
-
Iterator,
|
|
6
|
-
Union,
|
|
7
8
|
)
|
|
8
9
|
|
|
9
10
|
from django.core.exceptions import (
|
|
@@ -95,7 +96,7 @@ class Repository(AbstractDLQRepository):
|
|
|
95
96
|
for db_instance in self._base_qs.iterator():
|
|
96
97
|
yield self._to_domain(db_instance)
|
|
97
98
|
|
|
98
|
-
def get_object_by_id(self, identifier: '
|
|
99
|
+
def get_object_by_id(self, identifier: 'UUID | str') -> DeadLetter:
|
|
99
100
|
"""Получить сообщение по идентификатору."""
|
|
100
101
|
try:
|
|
101
102
|
db_instance = self._base_qs.get(pk=identifier)
|
{explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/django/models.py
RENAMED
|
@@ -12,8 +12,12 @@ from django.db import (
|
|
|
12
12
|
)
|
|
13
13
|
|
|
14
14
|
from explicit.dlq.domain.model import (
|
|
15
|
+
TOPIC_MAX_LENGTH,
|
|
15
16
|
DeadLetter as DomainDeadLetter,
|
|
16
17
|
)
|
|
18
|
+
from explicit.domain import (
|
|
19
|
+
get_field_title,
|
|
20
|
+
)
|
|
17
21
|
|
|
18
22
|
|
|
19
23
|
if DJANGO_VERSION >= (4, 0):
|
|
@@ -27,12 +31,18 @@ else:
|
|
|
27
31
|
class DeadLetter(models.Model):
|
|
28
32
|
"""Недоставленное/необработанное сообщение."""
|
|
29
33
|
|
|
30
|
-
id = models.UUIDField(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
id = models.UUIDField(
|
|
35
|
+
primary_key=True, default=uuid4, editable=False, verbose_name=get_field_title(DomainDeadLetter, 'id')
|
|
36
|
+
)
|
|
37
|
+
raw_message_value = models.BinaryField(verbose_name=get_field_title(DomainDeadLetter, 'raw_message_value'))
|
|
38
|
+
raw_message_key = models.BinaryField(
|
|
39
|
+
verbose_name=get_field_title(DomainDeadLetter, 'raw_message_key'), null=True, blank=True
|
|
40
|
+
)
|
|
41
|
+
topic = models.CharField(verbose_name=get_field_title(DomainDeadLetter, 'topic'), max_length=TOPIC_MAX_LENGTH)
|
|
42
|
+
attempts = JSONField(verbose_name=get_field_title(DomainDeadLetter, 'attempts'))
|
|
43
|
+
processed_at = models.DateTimeField(
|
|
44
|
+
verbose_name=get_field_title(DomainDeadLetter, 'processed_at'), null=True, blank=True
|
|
45
|
+
)
|
|
36
46
|
|
|
37
47
|
# служебные поля используемые для фильтрации и сортировки, чтобы не лезть в json
|
|
38
48
|
_first_attempted_at = models.DateTimeField(db_index=True)
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"""Команды DLQ."""
|
|
2
2
|
|
|
3
|
-
from typing import (
|
|
4
|
-
Optional,
|
|
5
|
-
)
|
|
6
3
|
from uuid import (
|
|
7
4
|
UUID,
|
|
8
5
|
)
|
|
9
6
|
|
|
7
|
+
from pydantic import (
|
|
8
|
+
ConfigDict,
|
|
9
|
+
)
|
|
10
|
+
|
|
10
11
|
from explicit.messagebus.commands import (
|
|
11
12
|
Command,
|
|
12
13
|
)
|
|
@@ -15,14 +16,13 @@ from explicit.messagebus.commands import (
|
|
|
15
16
|
class RegisterDeadLetter(Command):
|
|
16
17
|
"""Зарегистрировать сообщение, которое не удалось обработать."""
|
|
17
18
|
|
|
19
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
20
|
+
|
|
18
21
|
topic: str
|
|
19
22
|
raw_message_value: bytes
|
|
20
|
-
raw_message_key:
|
|
23
|
+
raw_message_key: bytes | None = None
|
|
21
24
|
exception: Exception
|
|
22
25
|
|
|
23
|
-
class Config: # noqa: D106
|
|
24
|
-
arbitrary_types_allowed = True
|
|
25
|
-
|
|
26
26
|
|
|
27
27
|
class ProcessDeadLetter(Command):
|
|
28
28
|
"""Выполнить попытку обработки сообщения."""
|
{explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/domain/factories.py
RENAMED
|
@@ -5,9 +5,6 @@ from datetime import (
|
|
|
5
5
|
datetime,
|
|
6
6
|
timezone,
|
|
7
7
|
)
|
|
8
|
-
from typing import (
|
|
9
|
-
Union,
|
|
10
|
-
)
|
|
11
8
|
|
|
12
9
|
from explicit.domain import (
|
|
13
10
|
Str,
|
|
@@ -21,9 +18,6 @@ from explicit.domain.model import (
|
|
|
21
18
|
Unset,
|
|
22
19
|
unset,
|
|
23
20
|
)
|
|
24
|
-
from explicit.domain.types import (
|
|
25
|
-
NoneStr,
|
|
26
|
-
)
|
|
27
21
|
|
|
28
22
|
from .model import (
|
|
29
23
|
Attempt,
|
|
@@ -34,7 +28,7 @@ from .model import (
|
|
|
34
28
|
class AttemptDTO(DTOBase):
|
|
35
29
|
"""Объект передачи данных о попытке обработки сообщения."""
|
|
36
30
|
|
|
37
|
-
failed_at:
|
|
31
|
+
failed_at: datetime | Unset = unset
|
|
38
32
|
error_message: Str = unset
|
|
39
33
|
traceback: Str = unset
|
|
40
34
|
|
|
@@ -43,11 +37,11 @@ class DeadLetterDTO(DTOBase):
|
|
|
43
37
|
"""Объект передачи данных о необработанном сообщении."""
|
|
44
38
|
|
|
45
39
|
id: UnsetUUID = unset
|
|
46
|
-
raw_message_value:
|
|
47
|
-
raw_message_key:
|
|
40
|
+
raw_message_value: bytes | Unset = unset
|
|
41
|
+
raw_message_key: bytes | None | Unset = unset
|
|
48
42
|
topic: Str = unset
|
|
49
|
-
attempts:
|
|
50
|
-
processed_at:
|
|
43
|
+
attempts: list[AttemptDTO] | Unset = unset
|
|
44
|
+
processed_at: datetime | None | Unset = unset
|
|
51
45
|
|
|
52
46
|
|
|
53
47
|
class Factory(AbstractDomainFactory):
|
|
@@ -55,7 +49,7 @@ class Factory(AbstractDomainFactory):
|
|
|
55
49
|
|
|
56
50
|
def create(self, data: DeadLetterDTO) -> DeadLetter:
|
|
57
51
|
"""Создать DLQ из DTO."""
|
|
58
|
-
params = data.
|
|
52
|
+
params = data.model_dump()
|
|
59
53
|
|
|
60
54
|
return DeadLetter(**params)
|
|
61
55
|
|
|
@@ -68,7 +62,7 @@ class Factory(AbstractDomainFactory):
|
|
|
68
62
|
)
|
|
69
63
|
|
|
70
64
|
def create_from_exception(
|
|
71
|
-
self, raw_message_value: bytes, raw_message_key:
|
|
65
|
+
self, raw_message_value: bytes, raw_message_key: bytes | None, topic: str, exception: Exception
|
|
72
66
|
) -> DeadLetter:
|
|
73
67
|
"""Создать DLQ из исключения."""
|
|
74
68
|
attempt = self.create_attempt_from_exception(exception)
|
|
@@ -7,13 +7,11 @@ from datetime import (
|
|
|
7
7
|
)
|
|
8
8
|
from typing import (
|
|
9
9
|
TYPE_CHECKING,
|
|
10
|
-
Optional,
|
|
11
|
-
Union,
|
|
12
10
|
)
|
|
13
11
|
|
|
14
12
|
from pydantic import (
|
|
15
13
|
Field,
|
|
16
|
-
|
|
14
|
+
field_validator,
|
|
17
15
|
)
|
|
18
16
|
|
|
19
17
|
from explicit.contrib.domain.model import (
|
|
@@ -22,9 +20,16 @@ from explicit.contrib.domain.model import (
|
|
|
22
20
|
|
|
23
21
|
|
|
24
22
|
if TYPE_CHECKING:
|
|
25
|
-
from dataclasses import
|
|
23
|
+
from dataclasses import (
|
|
24
|
+
dataclass,
|
|
25
|
+
)
|
|
26
26
|
else:
|
|
27
|
-
from pydantic.dataclasses import
|
|
27
|
+
from pydantic.dataclasses import (
|
|
28
|
+
dataclass,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
TOPIC_MAX_LENGTH = 256
|
|
28
33
|
|
|
29
34
|
|
|
30
35
|
class DeadLetterNotFound(Exception): # noqa: N818
|
|
@@ -49,12 +54,14 @@ class DeadLetter:
|
|
|
49
54
|
|
|
50
55
|
id: uuid.UUID = uuid_identifier()
|
|
51
56
|
raw_message_value: bytes = Field(title='Содержимое сообщения')
|
|
52
|
-
raw_message_key:
|
|
53
|
-
topic: str = Field(title='Топик сообщения', max_length=
|
|
54
|
-
attempts: tuple[Attempt, ...] = Field(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
raw_message_key: bytes | None = Field(title='Ключ сообщения', default=None)
|
|
58
|
+
topic: str = Field(title='Топик сообщения', max_length=TOPIC_MAX_LENGTH)
|
|
59
|
+
attempts: tuple[Attempt, ...] = Field(
|
|
60
|
+
default_factory=tuple, validate_default=True, title='Попытки обработки сообщения'
|
|
61
|
+
)
|
|
62
|
+
processed_at: datetime | None = Field(default=None, title='Время успешной обработки сообщения')
|
|
63
|
+
|
|
64
|
+
@field_validator('attempts')
|
|
58
65
|
@classmethod
|
|
59
66
|
def _validate_attempts(cls, value):
|
|
60
67
|
if not value:
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
"""Сервисы предметной области для работы с DLQ."""
|
|
2
2
|
|
|
3
|
-
from typing import (
|
|
4
|
-
Optional,
|
|
5
|
-
)
|
|
6
|
-
|
|
7
3
|
from explicit.unit_of_work import (
|
|
8
4
|
AbstractUnitOfWork,
|
|
9
5
|
)
|
|
@@ -19,7 +15,7 @@ from .model import (
|
|
|
19
15
|
|
|
20
16
|
def register_dead_letter(
|
|
21
17
|
raw_message_value: bytes,
|
|
22
|
-
raw_message_key:
|
|
18
|
+
raw_message_key: bytes | None,
|
|
23
19
|
topic: str,
|
|
24
20
|
exception: Exception,
|
|
25
21
|
uow: 'AbstractUnitOfWork',
|
{explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/infrastructure/handlers.py
RENAMED
|
@@ -3,9 +3,6 @@
|
|
|
3
3
|
from contextlib import (
|
|
4
4
|
AbstractContextManager,
|
|
5
5
|
)
|
|
6
|
-
from typing import (
|
|
7
|
-
Optional,
|
|
8
|
-
)
|
|
9
6
|
|
|
10
7
|
from explicit.dlq.domain.commands import (
|
|
11
8
|
RegisterDeadLetter,
|
|
@@ -40,7 +37,7 @@ class RegisterInDLQOnFailure(AbstractContextManager):
|
|
|
40
37
|
bus: 'MessageBus',
|
|
41
38
|
topic: str,
|
|
42
39
|
raw_message_value: bytes,
|
|
43
|
-
raw_message_key:
|
|
40
|
+
raw_message_key: bytes | None = None,
|
|
44
41
|
):
|
|
45
42
|
self._bus = bus
|
|
46
43
|
self._topic = topic
|
{explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0/src/explicit_python_dlq.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: explicit-python-dlq
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Набор компонентов реализующих DQL для систем на базе explicit
|
|
5
5
|
Author-email: "АО \"БАРС Груп\"" <education_dev@bars-open.ru>
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -8,17 +8,18 @@ Classifier: Environment :: Web Environment
|
|
|
8
8
|
Classifier: Natural Language :: Russian
|
|
9
9
|
Classifier: Operating System :: OS Independent
|
|
10
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
-
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Requires-Python: >=3.10
|
|
16
17
|
Description-Content-Type: text/markdown
|
|
17
|
-
Requires-Dist: explicit-python<3
|
|
18
|
+
Requires-Dist: explicit-python<4,>=3
|
|
18
19
|
Provides-Extra: dev
|
|
19
20
|
Requires-Dist: freezegun; extra == "dev"
|
|
20
21
|
Provides-Extra: django
|
|
21
|
-
Requires-Dist: explicit-python-django<
|
|
22
|
+
Requires-Dist: explicit-python-django<3,>=2; extra == "django"
|
|
22
23
|
Provides-Extra: rest
|
|
23
24
|
Requires-Dist: djangorestframework>=3.13.0; extra == "rest"
|
|
24
25
|
Requires-Dist: django_filter>=23.3; extra == "rest"
|
|
@@ -103,3 +104,40 @@ def bootstrap() -> None:
|
|
|
103
104
|
В пакете реализованы готовые к использованию компоненты:
|
|
104
105
|
* Реализация абстрактного [хранилища сообщений](./src/explicit/dlq/contrib/django/README.md) на базе Django ORM
|
|
105
106
|
* [REST API](./src/explicit/dlq/contrib/drf/README.md) для работы с хранилищем сообщений. Предназначен для совместной работы с реализацией хранилища сообщений django ORM.
|
|
107
|
+
|
|
108
|
+
## Запуск тестов
|
|
109
|
+
|
|
110
|
+
Тесты запускаются через `tox`. В системе должен быть установлен [uv](https://docs.astral.sh/uv/).
|
|
111
|
+
|
|
112
|
+
Для корректного запуска тестов необходимы следующие значения переменных окружения в системе. Вместо `<login>` и `<password>` необходимо подставить данные от УЗ БАРС:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
UV_INDEX="internal=http://<login>:<password>@nexus.py.bars.group/repository/pypi-all/simple"
|
|
116
|
+
UV_DEFAULT_INDEX="https://pypi.org/simple"
|
|
117
|
+
UV_INDEX_STRATEGY=unsafe-best-match
|
|
118
|
+
# Для docker 29+
|
|
119
|
+
TOX_DOCKER_GATEWAY=localhost
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Чтобы не менять значения системных переменных, можно создать `.env` файл c переменными окружения и запускать команды через [python-dotenv](https://github.com/theskumar/python-dotenv/).
|
|
123
|
+
|
|
124
|
+
Установка `tox` с необходимыми зависимостями:
|
|
125
|
+
```bash
|
|
126
|
+
uv tool install tox --with tox-uv --with m3-tox-docker
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Или с использованием `python-dotenv`:
|
|
130
|
+
```bash
|
|
131
|
+
dotenv run uv tool install tox --with tox-uv --with m3-tox-docker
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Запуск тестов:
|
|
135
|
+
```bash
|
|
136
|
+
tox
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Через `python-dotenv`:
|
|
140
|
+
```bash
|
|
141
|
+
dotenv run tox
|
|
142
|
+
```
|
|
143
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/adapters/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/__init__.py
RENAMED
|
File without changes
|
{explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/django/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/django/apps.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/drf/__init__.py
RENAMED
|
File without changes
|
{explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/drf/serializers.py
RENAMED
|
File without changes
|
{explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/contrib/drf/views.py
RENAMED
|
File without changes
|
|
File without changes
|
{explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/infrastructure/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/services/__init__.py
RENAMED
|
File without changes
|
{explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit/dlq/services/handlers.py
RENAMED
|
File without changes
|
|
File without changes
|
{explicit_python_dlq-1.0.0 → explicit_python_dlq-2.0.0}/src/explicit_python_dlq.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|