mesh-sandbox 0.1.34__py3-none-any.whl → 1.0.1__py3-none-any.whl
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.
- mesh_sandbox/__init__.py +1 -1
- mesh_sandbox/common/messaging.py +398 -0
- mesh_sandbox/conftest.py +2 -1
- mesh_sandbox/dependencies.py +8 -3
- mesh_sandbox/handlers/admin.py +33 -15
- mesh_sandbox/handlers/inbox.py +13 -20
- mesh_sandbox/handlers/lookup.py +6 -8
- mesh_sandbox/handlers/outbox.py +17 -13
- mesh_sandbox/handlers/tracking.py +9 -10
- mesh_sandbox/routers/admin.py +33 -6
- mesh_sandbox/store/base.py +21 -139
- mesh_sandbox/store/canned_store.py +30 -34
- mesh_sandbox/store/file_store.py +22 -22
- mesh_sandbox/store/memory_store.py +22 -148
- mesh_sandbox/test_plugin/example_plugin.py +12 -2
- mesh_sandbox/tests/admin.py +63 -2
- mesh_sandbox/tests/messaging_tests.py +165 -0
- mesh_sandbox/views/admin.py +9 -1
- {mesh_sandbox-0.1.34.dist-info → mesh_sandbox-1.0.1.dist-info}/METADATA +1 -1
- {mesh_sandbox-0.1.34.dist-info → mesh_sandbox-1.0.1.dist-info}/RECORD +22 -21
- mesh_sandbox/tests/plugin_manager_tests.py +0 -41
- {mesh_sandbox-0.1.34.dist-info → mesh_sandbox-1.0.1.dist-info}/LICENSE +0 -0
- {mesh_sandbox-0.1.34.dist-info → mesh_sandbox-1.0.1.dist-info}/WHEEL +0 -0
|
@@ -1,175 +1,49 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import importlib
|
|
3
|
-
import inspect
|
|
4
1
|
import logging
|
|
5
|
-
import pkgutil
|
|
6
|
-
from abc import ABC, abstractmethod
|
|
7
2
|
from collections import defaultdict
|
|
8
|
-
from
|
|
9
|
-
from typing import Literal, Optional, cast
|
|
3
|
+
from typing import Optional
|
|
10
4
|
|
|
11
|
-
from fastapi import BackgroundTasks
|
|
12
|
-
|
|
13
|
-
from .. import plugins as plugins_ns
|
|
14
5
|
from ..common import EnvConfig
|
|
15
|
-
from ..models.message import Message
|
|
6
|
+
from ..models.message import Message
|
|
16
7
|
from .canned_store import CannedStore
|
|
17
8
|
|
|
18
9
|
|
|
19
|
-
class _SandboxPlugin(ABC):
|
|
20
|
-
|
|
21
|
-
triggers: list[Literal["message_accepted", "message_acknowledged"]] = []
|
|
22
|
-
|
|
23
|
-
@abstractmethod
|
|
24
|
-
async def on_event(self, event: str, message: Message):
|
|
25
|
-
pass
|
|
26
|
-
|
|
27
|
-
|
|
28
10
|
class MemoryStore(CannedStore):
|
|
29
11
|
"""
|
|
30
12
|
in memory store, good for 'in-process' testing or small messages
|
|
31
13
|
"""
|
|
32
14
|
|
|
33
|
-
|
|
15
|
+
readonly = False
|
|
34
16
|
load_messages = False
|
|
35
17
|
|
|
36
|
-
def __init__(self, config: EnvConfig, logger: logging.Logger
|
|
18
|
+
def __init__(self, config: EnvConfig, logger: logging.Logger):
|
|
37
19
|
super().__init__(config, logger, filter_expired=True)
|
|
38
20
|
|
|
39
|
-
self._plugin_registry = self._find_plugins(plugins_module)
|
|
40
|
-
self._plugin_instances: dict[str, list[_SandboxPlugin]] = {}
|
|
41
|
-
|
|
42
|
-
def _find_plugins(self, package: ModuleType) -> dict[str, list[type[_SandboxPlugin]]]:
|
|
43
|
-
plugin_classes = defaultdict(list)
|
|
44
|
-
for _, name, _ in pkgutil.iter_modules(package.__path__, package.__name__ + "."):
|
|
45
|
-
module = importlib.import_module(name)
|
|
46
|
-
for _, class_type in inspect.getmembers(module):
|
|
47
|
-
|
|
48
|
-
if not inspect.isclass(class_type) or not class_type.__name__.endswith("Plugin"):
|
|
49
|
-
continue
|
|
50
|
-
|
|
51
|
-
self.logger.info(f"potential plugin: {class_type.__name__} found")
|
|
52
|
-
if not hasattr(class_type, "triggers"):
|
|
53
|
-
self.logger.warning(f"plugin: {class_type.__name__} has no class attr triggers .. not loading")
|
|
54
|
-
continue
|
|
55
|
-
|
|
56
|
-
if not hasattr(class_type, "on_event"):
|
|
57
|
-
self.logger.warning(f"plugin: {class_type.__name__} has no class attr on_event .. not loading")
|
|
58
|
-
continue
|
|
59
|
-
|
|
60
|
-
if not inspect.iscoroutinefunction(class_type.on_event):
|
|
61
|
-
self.logger.warning(f"plugin: {class_type.__name__} on_event os not a coroutine.. not loading")
|
|
62
|
-
continue
|
|
63
|
-
|
|
64
|
-
on_event_args = cast(list[str], inspect.getfullargspec(class_type.on_event)[0])
|
|
65
|
-
|
|
66
|
-
if not on_event_args:
|
|
67
|
-
self.logger.warning(
|
|
68
|
-
f"plugin: {class_type.__name__} on_event expected args event, message .. not loading"
|
|
69
|
-
)
|
|
70
|
-
continue
|
|
71
|
-
|
|
72
|
-
if not isinstance(inspect.getattr_static(class_type, "on_event"), staticmethod):
|
|
73
|
-
on_event_args.pop(0)
|
|
74
|
-
|
|
75
|
-
if not len(on_event_args) == 2:
|
|
76
|
-
self.logger.warning(
|
|
77
|
-
f"plugin: {class_type.__name__} on_event expected args event, message .. not loading"
|
|
78
|
-
)
|
|
79
|
-
continue
|
|
80
|
-
|
|
81
|
-
for trigger in class_type.triggers:
|
|
82
|
-
plugin_classes[trigger].append(class_type)
|
|
83
|
-
|
|
84
|
-
return cast(dict[str, list[type[_SandboxPlugin]]], plugin_classes)
|
|
85
|
-
|
|
86
|
-
@staticmethod
|
|
87
|
-
async def _construct(plugin_type: type[_SandboxPlugin]) -> _SandboxPlugin:
|
|
88
|
-
created = plugin_type()
|
|
89
|
-
return created
|
|
90
|
-
|
|
91
|
-
async def on_event(self, event: str, message: Message):
|
|
92
|
-
|
|
93
|
-
instances = self._plugin_instances.get(event, [])
|
|
94
|
-
if not instances:
|
|
95
|
-
registered = self._plugin_registry.get(event, [])
|
|
96
|
-
if not registered:
|
|
97
|
-
return
|
|
98
|
-
|
|
99
|
-
instances = await asyncio.gather(*[self._construct(plugin_type) for plugin_type in registered])
|
|
100
|
-
self._plugin_instances[event] = instances
|
|
101
|
-
|
|
102
|
-
await asyncio.gather(*[plugin.on_event(event, message) for plugin in instances])
|
|
103
|
-
|
|
104
21
|
async def reset(self):
|
|
105
|
-
|
|
106
|
-
super().initialise()
|
|
22
|
+
super().initialise()
|
|
107
23
|
|
|
108
24
|
async def reset_mailbox(self, mailbox_id: str):
|
|
25
|
+
self.inboxes[mailbox_id] = []
|
|
26
|
+
self.outboxes[mailbox_id] = []
|
|
27
|
+
self.local_ids[mailbox_id] = defaultdict(list)
|
|
28
|
+
self.mailboxes[mailbox_id].inbox_count = 0
|
|
109
29
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
self.local_ids[mailbox_id] = defaultdict(list)
|
|
114
|
-
self.mailboxes[mailbox_id].inbox_count = 0
|
|
115
|
-
|
|
116
|
-
async def save_message(self, message: Message):
|
|
117
|
-
self.messages[message.message_id] = message
|
|
118
|
-
|
|
119
|
-
async def send_message(self, message: Message, body: bytes, background_tasks: BackgroundTasks) -> Message:
|
|
120
|
-
|
|
121
|
-
async with self.lock:
|
|
122
|
-
parts = cast(list[Optional[bytes]], [None for _ in range(message.total_chunks)])
|
|
123
|
-
|
|
124
|
-
self.chunks[message.message_id] = parts
|
|
30
|
+
async def add_to_outbox(self, message: Message):
|
|
31
|
+
if not message.sender.mailbox_id:
|
|
32
|
+
return
|
|
125
33
|
|
|
126
|
-
|
|
127
|
-
|
|
34
|
+
self.outboxes[message.sender.mailbox_id].insert(0, message)
|
|
35
|
+
if not message.metadata.local_id:
|
|
36
|
+
return
|
|
128
37
|
|
|
129
|
-
|
|
130
|
-
self.outboxes[message.sender.mailbox_id].insert(0, message)
|
|
131
|
-
if message.metadata.local_id:
|
|
132
|
-
self.local_ids[message.sender.mailbox_id][message.metadata.local_id].insert(0, message)
|
|
38
|
+
self.local_ids[message.sender.mailbox_id][message.metadata.local_id].insert(0, message)
|
|
133
39
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
else:
|
|
137
|
-
await self._accept_message_no_lock(message, background_tasks)
|
|
40
|
+
async def add_to_inbox(self, message: Message):
|
|
41
|
+
self.inboxes[message.recipient.mailbox_id].append(message)
|
|
138
42
|
|
|
139
|
-
|
|
140
|
-
|
|
43
|
+
async def save_message(self, message: Message):
|
|
44
|
+
self.messages[message.message_id] = message
|
|
141
45
|
|
|
142
46
|
async def save_chunk(self, message: Message, chunk_number: int, chunk: Optional[bytes]):
|
|
47
|
+
if message.message_id not in self.chunks:
|
|
48
|
+
self.chunks[message.message_id] = [None for _ in range(message.total_chunks)]
|
|
143
49
|
self.chunks[message.message_id][chunk_number - 1] = chunk
|
|
144
|
-
|
|
145
|
-
async def receive_chunk(
|
|
146
|
-
self, message: Message, chunk_number: int, chunk: Optional[bytes], background_tasks: BackgroundTasks
|
|
147
|
-
):
|
|
148
|
-
await self.save_chunk(message, chunk_number, chunk)
|
|
149
|
-
background_tasks.add_task(self.on_event, "chunk_received", message)
|
|
150
|
-
|
|
151
|
-
async def _get_file_size(self, message: Message) -> int:
|
|
152
|
-
return sum(len(chunk or b"") for chunk in self.chunks.get(message.message_id, []))
|
|
153
|
-
|
|
154
|
-
async def _accept_message_no_lock(self, message: Message, background_tasks: BackgroundTasks) -> Message:
|
|
155
|
-
|
|
156
|
-
if message.status != MessageStatus.ACCEPTED:
|
|
157
|
-
message.events.insert(0, MessageEvent(status=MessageStatus.ACCEPTED))
|
|
158
|
-
message.file_size = await self._get_file_size(message)
|
|
159
|
-
self.inboxes[message.recipient.mailbox_id].append(message)
|
|
160
|
-
await self.save_message(message)
|
|
161
|
-
|
|
162
|
-
background_tasks.add_task(self.on_event, "message_accepted", message)
|
|
163
|
-
return message
|
|
164
|
-
|
|
165
|
-
async def accept_message(self, message: Message, background_tasks: BackgroundTasks) -> Message:
|
|
166
|
-
async with self.lock:
|
|
167
|
-
return await self._accept_message_no_lock(message, background_tasks)
|
|
168
|
-
|
|
169
|
-
async def acknowledge_message(self, message: Message, background_tasks: BackgroundTasks) -> Message:
|
|
170
|
-
async with self.lock:
|
|
171
|
-
message.events.insert(0, MessageEvent(status=MessageStatus.ACKNOWLEDGED))
|
|
172
|
-
await self.save_message(message)
|
|
173
|
-
|
|
174
|
-
background_tasks.add_task(self.on_event, "message_acknowledged", message)
|
|
175
|
-
return message
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os.path
|
|
3
|
+
from typing import Any, Optional
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class OnMessageAcceptedPlugin:
|
|
@@ -8,9 +9,18 @@ class OnMessageAcceptedPlugin:
|
|
|
8
9
|
with open(os.path.join(os.path.dirname(__file__), "example_plugin.txt"), encoding="utf-8", mode="r") as f:
|
|
9
10
|
self.config_message = f.read().strip()
|
|
10
11
|
|
|
11
|
-
triggers = ["
|
|
12
|
+
triggers = ["before_accept_message"]
|
|
12
13
|
|
|
13
|
-
async def on_event(self, event: str,
|
|
14
|
+
async def on_event(self, event: str, kwargs: dict[str, Any], err: Optional[Exception] = None):
|
|
15
|
+
|
|
16
|
+
if err:
|
|
17
|
+
print(f"plugin received error {event}\n{kwargs}\n{err}")
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
print(f"plugin received event {event}")
|
|
21
|
+
|
|
22
|
+
message = kwargs.get("message")
|
|
23
|
+
assert message
|
|
14
24
|
self.logger.info(f"message: {message.message_id} accepted - {self.config_message}")
|
|
15
25
|
|
|
16
26
|
if not message.metadata.subject:
|
mesh_sandbox/tests/admin.py
CHANGED
|
@@ -17,7 +17,7 @@ from mesh_sandbox.tests.mesh_api_helpers import (
|
|
|
17
17
|
|
|
18
18
|
from ..common.constants import Headers
|
|
19
19
|
from ..models.message import MessageStatus, MessageType
|
|
20
|
-
from ..views.admin import
|
|
20
|
+
from ..views.admin import AddMessageEventRequest, CreateReportRequest
|
|
21
21
|
from .helpers import generate_auth_token, temp_env_vars
|
|
22
22
|
|
|
23
23
|
|
|
@@ -241,7 +241,7 @@ def test_put_report_in_inbox(app: TestClient, tmp_path: str):
|
|
|
241
241
|
|
|
242
242
|
with temp_env_vars(STORE_MODE="file", MAILBOXES_DATA_DIR=tmp_path):
|
|
243
243
|
|
|
244
|
-
request =
|
|
244
|
+
request = CreateReportRequest(
|
|
245
245
|
mailbox_id=recipient,
|
|
246
246
|
status=MessageStatus.ERROR,
|
|
247
247
|
workflow_id=uuid4().hex,
|
|
@@ -298,3 +298,64 @@ def test_put_report_in_inbox(app: TestClient, tmp_path: str):
|
|
|
298
298
|
)
|
|
299
299
|
messages = res.json().get("messages", [])
|
|
300
300
|
assert not messages
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def test_add_message_event(app: TestClient, tmp_path: str):
|
|
304
|
+
|
|
305
|
+
recipient = _CANNED_MAILBOX1
|
|
306
|
+
|
|
307
|
+
with temp_env_vars(STORE_MODE="file", MAILBOXES_DATA_DIR=tmp_path):
|
|
308
|
+
|
|
309
|
+
create_report_request = CreateReportRequest(
|
|
310
|
+
mailbox_id=recipient,
|
|
311
|
+
status=MessageStatus.ERROR,
|
|
312
|
+
workflow_id=uuid4().hex,
|
|
313
|
+
code="21",
|
|
314
|
+
description="my error",
|
|
315
|
+
subject=f"my subject {uuid4().hex}",
|
|
316
|
+
local_id=f"my local id {uuid4().hex}",
|
|
317
|
+
file_name=f"my filename {uuid4().hex}",
|
|
318
|
+
linked_message_id=uuid4().hex,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
res = app.post("/messageexchange/report", json=create_report_request.dict())
|
|
322
|
+
assert res.status_code == status.HTTP_200_OK
|
|
323
|
+
|
|
324
|
+
result = res.json()
|
|
325
|
+
assert result
|
|
326
|
+
message_id = result["message_id"]
|
|
327
|
+
assert message_id
|
|
328
|
+
|
|
329
|
+
res = app.get(
|
|
330
|
+
f"/messageexchange/{recipient}/inbox",
|
|
331
|
+
headers={Headers.Authorization: generate_auth_token(recipient)},
|
|
332
|
+
)
|
|
333
|
+
messages = res.json().get("messages", [])
|
|
334
|
+
assert messages
|
|
335
|
+
assert messages[0] == message_id
|
|
336
|
+
|
|
337
|
+
res = app.put(
|
|
338
|
+
f"/messageexchange/{recipient}/inbox/{message_id}/status/acknowledged",
|
|
339
|
+
headers={Headers.Authorization: generate_auth_token(recipient)},
|
|
340
|
+
)
|
|
341
|
+
assert res.status_code == status.HTTP_200_OK
|
|
342
|
+
|
|
343
|
+
res = app.get(
|
|
344
|
+
f"/messageexchange/{recipient}/inbox",
|
|
345
|
+
headers={Headers.Authorization: generate_auth_token(recipient)},
|
|
346
|
+
)
|
|
347
|
+
messages = res.json().get("messages", [])
|
|
348
|
+
assert not messages
|
|
349
|
+
|
|
350
|
+
# move the message to accepted again
|
|
351
|
+
add_event_request = AddMessageEventRequest(status=MessageStatus.ACCEPTED)
|
|
352
|
+
|
|
353
|
+
res = app.post(f"/messageexchange/message/{message_id}/event", json=add_event_request.dict())
|
|
354
|
+
assert res.status_code == status.HTTP_200_OK
|
|
355
|
+
|
|
356
|
+
res = app.get(
|
|
357
|
+
f"/messageexchange/{recipient}/inbox",
|
|
358
|
+
headers={Headers.Authorization: generate_auth_token(recipient)},
|
|
359
|
+
)
|
|
360
|
+
messages = res.json().get("messages", [])
|
|
361
|
+
assert messages == [message_id]
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
from uuid import uuid4
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from fastapi import BackgroundTasks
|
|
6
|
+
|
|
7
|
+
from .. import tests as tests_module
|
|
8
|
+
from ..common.messaging import Messaging
|
|
9
|
+
from ..dependencies import get_messaging, get_store
|
|
10
|
+
from ..models.message import (
|
|
11
|
+
Message,
|
|
12
|
+
MessageEvent,
|
|
13
|
+
MessageMetadata,
|
|
14
|
+
MessageParty,
|
|
15
|
+
MessageStatus,
|
|
16
|
+
)
|
|
17
|
+
from .helpers import temp_env_vars
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture(name="message", scope="function")
|
|
21
|
+
def _create_message() -> Message:
|
|
22
|
+
message = Message(
|
|
23
|
+
message_id=uuid4().hex.upper(),
|
|
24
|
+
workflow_id=uuid4().hex.upper(),
|
|
25
|
+
sender=MessageParty(mailbox_id=uuid4().hex.upper()),
|
|
26
|
+
recipient=MessageParty(mailbox_id=uuid4().hex.upper()),
|
|
27
|
+
metadata=MessageMetadata(),
|
|
28
|
+
events=[MessageEvent(status=MessageStatus.ACCEPTED)],
|
|
29
|
+
)
|
|
30
|
+
return message
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.fixture(name="background_tasks", scope="function")
|
|
34
|
+
def _background_tasks() -> BackgroundTasks:
|
|
35
|
+
return BackgroundTasks()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def test_canned_store_raises_for_save_message(message: Message):
|
|
39
|
+
|
|
40
|
+
with pytest.raises(NotImplementedError):
|
|
41
|
+
with temp_env_vars(STORE_MODE="canned"):
|
|
42
|
+
store = get_store()
|
|
43
|
+
await store.save_message(message)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def test_canned_messaging_does_not_raise_no_bgt(message: Message):
|
|
47
|
+
|
|
48
|
+
calls = []
|
|
49
|
+
|
|
50
|
+
class TestPlugin:
|
|
51
|
+
|
|
52
|
+
triggers = ["before_save_message", "after_save_message", "save_message_error"]
|
|
53
|
+
|
|
54
|
+
async def on_event(self, event: str, args: dict[str, Any], exception: Optional[Exception] = None):
|
|
55
|
+
calls.append((event, args, exception))
|
|
56
|
+
|
|
57
|
+
with temp_env_vars(STORE_MODE="canned"):
|
|
58
|
+
messaging = get_messaging()
|
|
59
|
+
messaging.register_plugin(TestPlugin)
|
|
60
|
+
await messaging.save_message(message=message)
|
|
61
|
+
|
|
62
|
+
assert len(calls) == 1
|
|
63
|
+
assert calls[0][0] == "before_save_message"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
async def test_canned_messaging_does_not_raise_with_bgt(message: Message, background_tasks: BackgroundTasks):
|
|
67
|
+
|
|
68
|
+
calls = []
|
|
69
|
+
|
|
70
|
+
class Test2Plugin:
|
|
71
|
+
|
|
72
|
+
triggers = ["before_save_message", "after_save_message", "save_message_error"]
|
|
73
|
+
|
|
74
|
+
async def on_event(self, event: str, args: dict[str, Any], exception: Optional[Exception] = None):
|
|
75
|
+
calls.append((event, args, exception))
|
|
76
|
+
|
|
77
|
+
with temp_env_vars(STORE_MODE="canned"):
|
|
78
|
+
messaging = get_messaging()
|
|
79
|
+
messaging.register_plugin(Test2Plugin)
|
|
80
|
+
await messaging.save_message(message=message, background_tasks=background_tasks)
|
|
81
|
+
|
|
82
|
+
await background_tasks()
|
|
83
|
+
|
|
84
|
+
assert len(calls) == 2
|
|
85
|
+
assert calls[0][0] == "before_save_message"
|
|
86
|
+
assert calls[1][0] == "after_save_message"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
async def test_messaging_does_not_raise_with_bgt(message: Message, background_tasks: BackgroundTasks):
|
|
90
|
+
|
|
91
|
+
calls = []
|
|
92
|
+
|
|
93
|
+
class Test2Plugin:
|
|
94
|
+
|
|
95
|
+
triggers = ["before_save_message", "after_save_message", "save_message_error"]
|
|
96
|
+
|
|
97
|
+
async def on_event(self, event: str, args: dict[str, Any], exception: Optional[Exception] = None):
|
|
98
|
+
calls.append((event, args, exception))
|
|
99
|
+
|
|
100
|
+
with temp_env_vars(STORE_MODE="memory"):
|
|
101
|
+
messaging = get_messaging()
|
|
102
|
+
messaging.register_plugin(Test2Plugin)
|
|
103
|
+
await messaging.save_message(message=message, background_tasks=background_tasks)
|
|
104
|
+
|
|
105
|
+
await background_tasks()
|
|
106
|
+
|
|
107
|
+
assert len(calls) == 2
|
|
108
|
+
assert calls[0][0] == "before_save_message"
|
|
109
|
+
assert calls[1][0] == "after_save_message"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
async def test_error_events_raised_with_bgt(message: Message, background_tasks: BackgroundTasks):
|
|
113
|
+
|
|
114
|
+
calls = []
|
|
115
|
+
|
|
116
|
+
class Test2Plugin:
|
|
117
|
+
|
|
118
|
+
triggers = ["before_save_message", "after_save_message", "save_message_error"]
|
|
119
|
+
|
|
120
|
+
async def on_event(self, event: str, args: dict[str, Any], exception: Optional[Exception] = None):
|
|
121
|
+
calls.append((event, args, exception))
|
|
122
|
+
|
|
123
|
+
with temp_env_vars(STORE_MODE="memory"):
|
|
124
|
+
messaging = get_messaging()
|
|
125
|
+
messaging.register_plugin(Test2Plugin)
|
|
126
|
+
await messaging.save_message(message=message, background_tasks=background_tasks)
|
|
127
|
+
|
|
128
|
+
await background_tasks()
|
|
129
|
+
|
|
130
|
+
assert len(calls) == 2
|
|
131
|
+
assert calls[0][0] == "before_save_message"
|
|
132
|
+
assert calls[1][0] == "after_save_message"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class TestDiscoveredPlugin:
|
|
136
|
+
|
|
137
|
+
triggers = ["before_accept_message"]
|
|
138
|
+
|
|
139
|
+
async def on_event(self, event: str, args: dict[str, Any], err: Optional[Exception] = None):
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
async def test_plugin_discovery(monkeypatch):
|
|
144
|
+
|
|
145
|
+
messaging = Messaging(store=get_store(), plugins_module=tests_module)
|
|
146
|
+
|
|
147
|
+
calls = []
|
|
148
|
+
|
|
149
|
+
async def on_event(_, event: str, args: dict[str, Any], err: Optional[Exception] = None):
|
|
150
|
+
calls.append((event, args, err))
|
|
151
|
+
|
|
152
|
+
monkeypatch.setattr(TestDiscoveredPlugin, "on_event", on_event)
|
|
153
|
+
|
|
154
|
+
assert messaging._plugin_registry # pylint: disable=protected-access
|
|
155
|
+
|
|
156
|
+
message = Message(message_id=uuid4().hex)
|
|
157
|
+
args = {"message": message}
|
|
158
|
+
|
|
159
|
+
await messaging.on_event("before_accept_message", args)
|
|
160
|
+
|
|
161
|
+
assert len(calls) == 1
|
|
162
|
+
|
|
163
|
+
assert calls[0][0] == "before_accept_message"
|
|
164
|
+
assert calls[0][1] == args
|
|
165
|
+
assert calls[0][2] is None
|
mesh_sandbox/views/admin.py
CHANGED
|
@@ -5,7 +5,7 @@ from pydantic import BaseModel, Field # pylint: disable=no-name-in-module
|
|
|
5
5
|
from mesh_sandbox.models.message import MessageStatus
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class
|
|
8
|
+
class CreateReportRequest(BaseModel):
|
|
9
9
|
mailbox_id: str = Field(description="mailbox id to put the report in")
|
|
10
10
|
code: str = Field(description="error code")
|
|
11
11
|
description: str = Field(description="error description")
|
|
@@ -15,3 +15,11 @@ class PutReportRequest(BaseModel):
|
|
|
15
15
|
status: str = Field(description="report status (error/undeliverable)", default=MessageStatus.UNDELIVERABLE)
|
|
16
16
|
file_name: Optional[str] = Field(description="file name", default=None)
|
|
17
17
|
linked_message_id: Optional[str] = Field(description="linked message id", default=None)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AddMessageEventRequest(BaseModel):
|
|
21
|
+
status: str = Field(description="new message status")
|
|
22
|
+
code: str = Field(description="error code", default=None)
|
|
23
|
+
event: str = Field(description="error event (SEND/TRANSFER) etc)", default=None)
|
|
24
|
+
description: str = Field(description="error description", default=None)
|
|
25
|
+
linked_message_id: Optional[str] = Field(description="linked message id", default=None)
|
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
mesh_sandbox/__init__.py,sha256=
|
|
1
|
+
mesh_sandbox/__init__.py,sha256=d4QHYmS_30j0hPN8NmNPnQ_Z0TphDRbu4MtQj9cT9e8,22
|
|
2
2
|
mesh_sandbox/api.py,sha256=F2KUKENAsSe6NAGG0wzHA2jebGF6mWFgta1q55oqacU,3925
|
|
3
3
|
mesh_sandbox/common/__init__.py,sha256=MwKS4FixADUGgN49MQJI7vW0Wx6mnf9SBOPbgE4RddM,3403
|
|
4
4
|
mesh_sandbox/common/constants.py,sha256=_hnaHDkAQGHWLF7n_WfC5ZHIY5D-fUbOdpSqLusUMNY,6504
|
|
5
5
|
mesh_sandbox/common/exceptions.py,sha256=YQII8w6DQQoKuW0cukEr6PIE9j0rwrqDpCn5lapgmkQ,1481
|
|
6
6
|
mesh_sandbox/common/fernet.py,sha256=f8yygkVnK1cmQLBgcPifoB4PwGpgiOrG8Yk-6OMDy8o,551
|
|
7
7
|
mesh_sandbox/common/handler_helpers.py,sha256=Eg00Tide2mL87EoMFw83fDuxiTL5DgIwxHs2RaJasgE,392
|
|
8
|
+
mesh_sandbox/common/messaging.py,sha256=xZbNpWQLhfjI4wiBG3PpIkEy2WbvwxqaWSR90sSb4hk,14664
|
|
8
9
|
mesh_sandbox/common/mex_headers.py,sha256=Eu-ixdVml2obROXggYyUyiS4QZHUA1JUgF7gO4YYkyg,6088
|
|
9
|
-
mesh_sandbox/conftest.py,sha256=
|
|
10
|
-
mesh_sandbox/dependencies.py,sha256=
|
|
10
|
+
mesh_sandbox/conftest.py,sha256=IXrb8Y8Hi4-0ZxDNfTDFI0L1uG1Iz9FBQdNBZtPMsEw,2233
|
|
11
|
+
mesh_sandbox/dependencies.py,sha256=Crl7yEUED-XYl_odMenpvMJoYeIVDROWfvcrfvV85fc,4089
|
|
11
12
|
mesh_sandbox/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
mesh_sandbox/handlers/admin.py,sha256=
|
|
13
|
+
mesh_sandbox/handlers/admin.py,sha256=JwPqeN3LHpau0pk7QOEFVrKZ9DV_896j0L_eSZxHIX4,3783
|
|
13
14
|
mesh_sandbox/handlers/handshake.py,sha256=p3_NveSscNBgCdIIJhBza34b0WreqgBxZwI2sw7ZMTk,605
|
|
14
|
-
mesh_sandbox/handlers/inbox.py,sha256=
|
|
15
|
-
mesh_sandbox/handlers/lookup.py,sha256=
|
|
16
|
-
mesh_sandbox/handlers/outbox.py,sha256=
|
|
17
|
-
mesh_sandbox/handlers/tracking.py,sha256=
|
|
15
|
+
mesh_sandbox/handlers/inbox.py,sha256=J20JUX1dHlS2We2UIDQa1MMoVN_QsRoP0tC-3zKAlD8,15450
|
|
16
|
+
mesh_sandbox/handlers/lookup.py,sha256=p_xmnCJi6BoNqRaQjRAGeQthL5EohQfZ_LA1h0QU36Q,1392
|
|
17
|
+
mesh_sandbox/handlers/outbox.py,sha256=XrLOGhw0QqMTfMFevrh-_Z-EyFSEAQLO1BcmbL1bR0Y,10045
|
|
18
|
+
mesh_sandbox/handlers/tracking.py,sha256=nmn5JexFhAvzFmj9C5l0w4sq8sNz3x-mMd3CsGVrEM4,2356
|
|
18
19
|
mesh_sandbox/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
20
|
mesh_sandbox/models/mailbox.py,sha256=W1b1R1XjhyiCp30LvTLtQVlxagfxXH8eb0XnNj3D4Dw,993
|
|
20
21
|
mesh_sandbox/models/message.py,sha256=ze-IPbCg6XlKZtOL1RWQvMnu04AzrGppwdk2mTyVAfU,4926
|
|
21
22
|
mesh_sandbox/models/workflow.py,sha256=T8A1Q729TOUaz1MOa1Ly8oZs_G4769xMZpTYGF0TlO8,518
|
|
22
23
|
mesh_sandbox/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
24
|
mesh_sandbox/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
-
mesh_sandbox/routers/admin.py,sha256=
|
|
25
|
+
mesh_sandbox/routers/admin.py,sha256=pJ9rPF-JDWs1dbVbQVqW8uKQrX3yh4oUxswBOnO5zR8,3852
|
|
25
26
|
mesh_sandbox/routers/handshake.py,sha256=5TVyQ5OrHIe6W3UVpbap-hc5ia669Of6w6F1vgcYjm4,2693
|
|
26
27
|
mesh_sandbox/routers/inbox.py,sha256=EkOmL9pxE6pYPBTzGU1CZJLu80ZR2EnKg9fe7DDMLLQ,8898
|
|
27
28
|
mesh_sandbox/routers/inbox_count.py,sha256=hY88h7dBa6asnvYEP9mmx6CEBw5ZyRO7gz6lHg4Yj3c,1521
|
|
@@ -31,8 +32,8 @@ mesh_sandbox/routers/request_logging.py,sha256=gZaOJ_Mp3QPsmrpxXQ0pOzVGNPKd_RAEj
|
|
|
31
32
|
mesh_sandbox/routers/tracking.py,sha256=UVIRkMBGD5pI7vgp8l6cnP5VX8OFRDPNb2JHs9q5w58,2210
|
|
32
33
|
mesh_sandbox/routers/update.py,sha256=a9ttmk3levdDcu-ZF7a5EniR35zRMhty8pG2EyzB1po,409
|
|
33
34
|
mesh_sandbox/store/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
-
mesh_sandbox/store/base.py,sha256=
|
|
35
|
-
mesh_sandbox/store/canned_store.py,sha256=
|
|
35
|
+
mesh_sandbox/store/base.py,sha256=FV93oTeg40ikOaIqcRmDGnigbOEpYexYNqcLyvCaLnA,1983
|
|
36
|
+
mesh_sandbox/store/canned_store.py,sha256=DG5v7Aqy29mCCF9nupqoLIW4m2puLl6iGs2JiNtYnyw,9862
|
|
36
37
|
mesh_sandbox/store/data/mailboxes/X26ABC1/in/SIMPLE_MESSAGE/1,sha256=aqaFLD9HL0hqn0sLIsv-qQqMVyaBmnHx17fu-lgVR9A,662
|
|
37
38
|
mesh_sandbox/store/data/mailboxes/X26ABC1/in/SIMPLE_MESSAGE.json,sha256=lb_vaAXr9hJefOdQ1WXvrBXlIRtzPKzUXt3NT_-b_qA,714
|
|
38
39
|
mesh_sandbox/store/data/mailboxes/X26ABC1/in/UNDELIVERED_MESSAGE/1,sha256=aqaFLD9HL0hqn0sLIsv-qQqMVyaBmnHx17fu-lgVR9A,662
|
|
@@ -42,14 +43,14 @@ mesh_sandbox/store/data/mailboxes/X26ABC2/in/CHUNKED_MESSAGE_GZ/2,sha256=uelYjD2
|
|
|
42
43
|
mesh_sandbox/store/data/mailboxes/X26ABC2/in/CHUNKED_MESSAGE_GZ.json,sha256=N9T-V-nTrwnJeSefL5aIjXLLlBkjdO86iUmKyxjee0k,817
|
|
43
44
|
mesh_sandbox/store/data/mailboxes.jsonl,sha256=ADXpImNo9UH6rY6T9bqgI-BvnbziFaGwcf_XeJW1bW4,432
|
|
44
45
|
mesh_sandbox/store/data/workflows.jsonl,sha256=RFvycuqVmEkImeXlFD2wjuJFjt6dw581D_raPWstxpU,292
|
|
45
|
-
mesh_sandbox/store/file_store.py,sha256=
|
|
46
|
-
mesh_sandbox/store/memory_store.py,sha256=
|
|
46
|
+
mesh_sandbox/store/file_store.py,sha256=a4GAhDLjEj2S-zvQxpbMkyDKhC69POCFZln0abH6sUw,2400
|
|
47
|
+
mesh_sandbox/store/memory_store.py,sha256=VJTsPAnwvPAVFTEo-mO2A__lcc7jMnyqiq0JerYmrKY,1643
|
|
47
48
|
mesh_sandbox/store/serialisation.py,sha256=pMYZ704GP74aCDRX9Nu1r4B1XA4BPJXYY-RKLFJJfUg,3615
|
|
48
49
|
mesh_sandbox/test_plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
|
-
mesh_sandbox/test_plugin/example_plugin.py,sha256=
|
|
50
|
+
mesh_sandbox/test_plugin/example_plugin.py,sha256=MnNpyyWKziqvOr89LeIxOwqskwHcrSsh_te67SeNJr4,967
|
|
50
51
|
mesh_sandbox/test_plugin/example_plugin.txt,sha256=aUePU6UUVhpxeP37Pdz5Zxyn_nxI6HCY3qtQBo1iUEI,24
|
|
51
52
|
mesh_sandbox/tests/__init__.py,sha256=wPjH0Ka1334a0OR9VMUfeYxsr03JqWjHTBnAH_1SB7I,106
|
|
52
|
-
mesh_sandbox/tests/admin.py,sha256=
|
|
53
|
+
mesh_sandbox/tests/admin.py,sha256=Y_nzBhqSGPCTjG3EczSHClA-mVALGNBd-iOShoYh8WU,14549
|
|
53
54
|
mesh_sandbox/tests/docker_tests.py,sha256=SJuuquoQA7_fP49--89mrIZmY_4KRO7fiFkOcZeR4AU,1584
|
|
54
55
|
mesh_sandbox/tests/exceptions.py,sha256=j_jKskVzLAYpyu4qNAEY5ahkbk8Uh2Nc_CGpnpdXjnI,740
|
|
55
56
|
mesh_sandbox/tests/handshake.py,sha256=X-fCbwaEAc8cAvvnniCvt4W4DAV9ItPq7tpIpZkfj6M,6259
|
|
@@ -59,17 +60,17 @@ mesh_sandbox/tests/java_client_tests.py,sha256=NYvNH1LxJc0SmHQ3q6mSvEd6Wuv2r_xMq
|
|
|
59
60
|
mesh_sandbox/tests/lookup.py,sha256=wY8x0F415y2um48Lw5kyD4ugMo1vBGKB7cqPdHEaVO4,2768
|
|
60
61
|
mesh_sandbox/tests/mesh_api_helpers.py,sha256=ZedDWMHO88j4Zxw3hsK5vmvivy3UhdzbzHle0g9GJXA,3901
|
|
61
62
|
mesh_sandbox/tests/mesh_client_tests.py,sha256=WL7ht-tT4QLMw73pIbcFMZg7Ehpn5Mi4jcZg5qgEgyw,3947
|
|
63
|
+
mesh_sandbox/tests/messaging_tests.py,sha256=Bup6aJsiJ0xdtg0Ab37ME0VHI6HFJA-0ATmfS88giBI,5006
|
|
62
64
|
mesh_sandbox/tests/outbox.py,sha256=AbQR8vgRy8KAG0RMCU0jYFUXSCPQMrLRJMJTU3PGQhI,20061
|
|
63
|
-
mesh_sandbox/tests/plugin_manager_tests.py,sha256=wTmyDtg1Up2jzqZgiRIVjkD35USZw-tTEoS4LtdFwCo,1070
|
|
64
65
|
mesh_sandbox/tests/serialisation.py,sha256=kRSMZLDHlX6oRK6c2OCZL0XQoUCKQxqcYXrJWbc9VkA,663
|
|
65
66
|
mesh_sandbox/views/__init__.py,sha256=nZkb6_1S8jz8Xl_AayfwjgEZG0JD2dfulfGxjJ5W9Ec,1237
|
|
66
|
-
mesh_sandbox/views/admin.py,sha256=
|
|
67
|
+
mesh_sandbox/views/admin.py,sha256=2YGslfDyC0QKoq3WTGXBicqrOMR9UmQYocP3R0qTbAY,1315
|
|
67
68
|
mesh_sandbox/views/error.py,sha256=9lnUr3P93Vm-nOrBTEuAD6nrSBUvpI6-XqXzILWjgGk,3885
|
|
68
69
|
mesh_sandbox/views/inbox.py,sha256=gnaD9Csx5BqilVRefQQ_tXmeq80lwcLJfepW005GrkU,5662
|
|
69
70
|
mesh_sandbox/views/lookup.py,sha256=HHUqZ-Iy22ysC3qaO8Bl5GBQqf_7IiBbe5acyxqS78M,2775
|
|
70
71
|
mesh_sandbox/views/outbox.py,sha256=jxYiHylEdpljZ6Wl45Ke3aaH5rEKb-kzUuCNEKDS3So,4932
|
|
71
72
|
mesh_sandbox/views/tracking.py,sha256=1H7Ghcvqkmx__KS1Y-lm105EVx_Z1eJo3oMDh8pzRMQ,8918
|
|
72
|
-
mesh_sandbox-0.1.
|
|
73
|
-
mesh_sandbox-0.1.
|
|
74
|
-
mesh_sandbox-0.1.
|
|
75
|
-
mesh_sandbox-0.1.
|
|
73
|
+
mesh_sandbox-1.0.1.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
|
|
74
|
+
mesh_sandbox-1.0.1.dist-info/LICENSE,sha256=usgzIvDUpVX5pYZepJTRXQJqIaz0mdd32GuS5a3PFlY,1051
|
|
75
|
+
mesh_sandbox-1.0.1.dist-info/METADATA,sha256=UUnhdGz3llub6OOLmTV7d2zGBkawfaUyyUzhSR1zMuE,2432
|
|
76
|
+
mesh_sandbox-1.0.1.dist-info/RECORD,,
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from uuid import uuid4
|
|
3
|
-
|
|
4
|
-
from .. import tests as tests_module
|
|
5
|
-
from ..common import EnvConfig
|
|
6
|
-
from ..models.message import Message
|
|
7
|
-
from ..store.memory_store import MemoryStore
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class TestPlugin:
|
|
11
|
-
|
|
12
|
-
triggers = ["message_accepted"]
|
|
13
|
-
|
|
14
|
-
def real_handler(self, event: str, message: Message):
|
|
15
|
-
pass
|
|
16
|
-
|
|
17
|
-
async def on_event(self, event: str, message: Message):
|
|
18
|
-
self.real_handler(event, message)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
async def test_plugin_provider():
|
|
22
|
-
|
|
23
|
-
manager = MemoryStore(config=EnvConfig(), logger=logging.getLogger("mesh-sandbox"), plugins_module=tests_module)
|
|
24
|
-
|
|
25
|
-
calls = []
|
|
26
|
-
|
|
27
|
-
def test_handler(_: TestPlugin, event: str, message: Message):
|
|
28
|
-
calls.append((event, message))
|
|
29
|
-
|
|
30
|
-
TestPlugin.real_handler = test_handler # type: ignore[method-assign, assignment]
|
|
31
|
-
|
|
32
|
-
assert manager._plugin_registry # pylint: disable=protected-access
|
|
33
|
-
|
|
34
|
-
message = Message(message_id=uuid4().hex)
|
|
35
|
-
|
|
36
|
-
await manager.on_event("message_accepted", message)
|
|
37
|
-
|
|
38
|
-
assert len(calls) == 1
|
|
39
|
-
|
|
40
|
-
assert calls[0][0] == "message_accepted"
|
|
41
|
-
assert calls[0][1] == message
|
|
File without changes
|
|
File without changes
|