mesh-sandbox 1.0.10__py3-none-any.whl → 1.0.12__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/api.py +0 -1
- mesh_sandbox/common/__init__.py +1 -3
- mesh_sandbox/common/messaging.py +20 -32
- mesh_sandbox/common/mex_headers.py +0 -1
- mesh_sandbox/conftest.py +2 -6
- mesh_sandbox/dependencies.py +5 -6
- mesh_sandbox/handlers/admin.py +0 -2
- mesh_sandbox/handlers/handshake.py +0 -2
- mesh_sandbox/handlers/inbox.py +1 -10
- mesh_sandbox/handlers/lookup.py +0 -2
- mesh_sandbox/handlers/outbox.py +4 -8
- mesh_sandbox/handlers/tracking.py +0 -2
- mesh_sandbox/models/mailbox.py +0 -2
- mesh_sandbox/models/message.py +2 -5
- mesh_sandbox/models/workflow.py +0 -2
- mesh_sandbox/routers/inbox.py +5 -5
- mesh_sandbox/routers/inbox_count.py +2 -2
- mesh_sandbox/routers/lookup.py +3 -4
- mesh_sandbox/routers/outbox.py +3 -3
- mesh_sandbox/routers/tracking.py +2 -2
- mesh_sandbox/store/base.py +0 -1
- mesh_sandbox/store/canned_store.py +9 -15
- mesh_sandbox/store/file_store.py +0 -1
- mesh_sandbox/store/serialisation.py +2 -3
- mesh_sandbox/test_plugin/example_plugin.py +3 -4
- mesh_sandbox/tests/admin.py +0 -26
- mesh_sandbox/tests/docker_tests.py +0 -2
- mesh_sandbox/tests/exceptions.py +0 -1
- mesh_sandbox/tests/handshake.py +3 -11
- mesh_sandbox/tests/helpers.py +0 -3
- mesh_sandbox/tests/inbox.py +0 -13
- mesh_sandbox/tests/java_client_tests.py +1 -6
- mesh_sandbox/tests/lookup.py +2 -4
- mesh_sandbox/tests/mesh_api_helpers.py +1 -8
- mesh_sandbox/tests/mesh_client_tests.py +7 -12
- mesh_sandbox/tests/messaging_tests.py +13 -23
- mesh_sandbox/tests/outbox.py +1 -15
- mesh_sandbox/tests/serialisation.py +0 -1
- mesh_sandbox/views/error.py +3 -7
- mesh_sandbox/views/inbox.py +4 -7
- mesh_sandbox/views/lookup.py +6 -6
- mesh_sandbox/views/outbox.py +21 -26
- mesh_sandbox/views/tracking.py +0 -4
- {mesh_sandbox-1.0.10.dist-info → mesh_sandbox-1.0.12.dist-info}/METADATA +16 -41
- mesh_sandbox-1.0.12.dist-info/RECORD +76 -0
- mesh_sandbox-1.0.10.dist-info/RECORD +0 -76
- {mesh_sandbox-1.0.10.dist-info → mesh_sandbox-1.0.12.dist-info}/LICENSE +0 -0
- {mesh_sandbox-1.0.10.dist-info → mesh_sandbox-1.0.12.dist-info}/WHEEL +0 -0
mesh_sandbox/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.0.
|
|
1
|
+
__version__ = "1.0.12"
|
mesh_sandbox/api.py
CHANGED
mesh_sandbox/common/__init__.py
CHANGED
|
@@ -4,7 +4,7 @@ import os
|
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
5
|
from functools import partial
|
|
6
6
|
from hashlib import sha256
|
|
7
|
-
from typing import Any, Callable, Final, Optional, TypeVar
|
|
7
|
+
from typing import Any, Callable, Final, Optional, TypeVar
|
|
8
8
|
|
|
9
9
|
from fastapi.encoders import jsonable_encoder
|
|
10
10
|
from pydantic import BaseModel # pylint: disable=no-name-in-module
|
|
@@ -47,7 +47,6 @@ def strtobool(val: Any) -> Optional[bool]:
|
|
|
47
47
|
|
|
48
48
|
@dataclass
|
|
49
49
|
class EnvConfig:
|
|
50
|
-
|
|
51
50
|
env: str = field(default="local")
|
|
52
51
|
build_label: str = field(default="latest")
|
|
53
52
|
auth_mode: str = field(default="no_auth")
|
|
@@ -72,7 +71,6 @@ T = TypeVar("T")
|
|
|
72
71
|
|
|
73
72
|
|
|
74
73
|
def index_of(items: list[T], find: Callable[[T], bool]) -> int:
|
|
75
|
-
|
|
76
74
|
for index, elem in enumerate(items):
|
|
77
75
|
if find(elem):
|
|
78
76
|
return index
|
mesh_sandbox/common/messaging.py
CHANGED
|
@@ -6,7 +6,7 @@ from abc import ABC, abstractmethod
|
|
|
6
6
|
from collections import defaultdict
|
|
7
7
|
from functools import wraps
|
|
8
8
|
from types import ModuleType
|
|
9
|
-
from typing import Any, Callable, Literal, NamedTuple, Optional, TypeVar, cast
|
|
9
|
+
from typing import Any, Callable, ClassVar, Literal, NamedTuple, Optional, TypeVar, cast
|
|
10
10
|
|
|
11
11
|
from fastapi import HTTPException, status
|
|
12
12
|
from starlette.background import BackgroundTasks
|
|
@@ -19,24 +19,25 @@ from . import constants, generate_cipher_text
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class _SandboxPlugin(ABC):
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
22
|
+
triggers: ClassVar[
|
|
23
|
+
list[
|
|
24
|
+
Literal[
|
|
25
|
+
"before_accept_message",
|
|
26
|
+
"after_accept_message",
|
|
27
|
+
"accept_message_error",
|
|
28
|
+
"before_save_message",
|
|
29
|
+
"after_save_message",
|
|
30
|
+
"save_message_error",
|
|
31
|
+
"before_send_message",
|
|
32
|
+
"after_send_message",
|
|
33
|
+
"send_message_error",
|
|
34
|
+
"before_acknowledge_message",
|
|
35
|
+
"after_acknowledge_message",
|
|
36
|
+
"acknowledge_message_error",
|
|
37
|
+
"before_save_chunk",
|
|
38
|
+
"after_save_chunk",
|
|
39
|
+
"save_chunk_error",
|
|
40
|
+
]
|
|
40
41
|
]
|
|
41
42
|
] = []
|
|
42
43
|
|
|
@@ -83,7 +84,6 @@ MESH_AUTH_SCHEME = "NHSMESH"
|
|
|
83
84
|
|
|
84
85
|
|
|
85
86
|
def try_parse_authorisation_token(auth_token: str) -> Optional[AuthoriseHeaderParts]:
|
|
86
|
-
|
|
87
87
|
if not auth_token:
|
|
88
88
|
return None
|
|
89
89
|
|
|
@@ -126,7 +126,6 @@ class Messaging:
|
|
|
126
126
|
self.event_name = event_name
|
|
127
127
|
|
|
128
128
|
def __call__(self, func):
|
|
129
|
-
|
|
130
129
|
if not inspect.iscoroutinefunction(func):
|
|
131
130
|
raise ValueError(f"wrapped function is not awaitable: {func}")
|
|
132
131
|
|
|
@@ -155,7 +154,6 @@ class Messaging:
|
|
|
155
154
|
|
|
156
155
|
class _IfNotReadonly:
|
|
157
156
|
def __call__(self, func):
|
|
158
|
-
|
|
159
157
|
if not inspect.iscoroutinefunction(func):
|
|
160
158
|
raise ValueError(f"wrapped function is not awaitable: {func}")
|
|
161
159
|
|
|
@@ -170,18 +168,15 @@ class Messaging:
|
|
|
170
168
|
return _async_inner
|
|
171
169
|
|
|
172
170
|
def _find_plugins(self, package: ModuleType):
|
|
173
|
-
|
|
174
171
|
for _, name, _ in pkgutil.iter_modules(package.__path__, package.__name__ + "."):
|
|
175
172
|
module = importlib.import_module(name)
|
|
176
173
|
for _, plugin_type in inspect.getmembers(module):
|
|
177
|
-
|
|
178
174
|
if not inspect.isclass(plugin_type) or not plugin_type.__name__.endswith("Plugin"):
|
|
179
175
|
continue
|
|
180
176
|
|
|
181
177
|
self.register_plugin(plugin_type)
|
|
182
178
|
|
|
183
179
|
def register_plugin(self, plugin_type: type):
|
|
184
|
-
|
|
185
180
|
self.logger.info(f"potential plugin: {plugin_type.__name__} found")
|
|
186
181
|
if not hasattr(plugin_type, "triggers"):
|
|
187
182
|
self.logger.warning(f"plugin: {plugin_type.__name__} has no class attr triggers .. not loading")
|
|
@@ -224,7 +219,6 @@ class Messaging:
|
|
|
224
219
|
return created
|
|
225
220
|
|
|
226
221
|
async def on_event(self, event: str, event_args: dict[str, Any], exception: Optional[Exception] = None):
|
|
227
|
-
|
|
228
222
|
instances = self._plugin_instances.get(event, [])
|
|
229
223
|
if not instances:
|
|
230
224
|
registered = self._plugin_registry.get(event, [])
|
|
@@ -245,7 +239,6 @@ class Messaging:
|
|
|
245
239
|
|
|
246
240
|
@_TriggersEvent(event_name="send_message")
|
|
247
241
|
async def send_message(self, message: Message, body: bytes, background_tasks: BackgroundTasks) -> Message:
|
|
248
|
-
|
|
249
242
|
if message.total_chunks > 0:
|
|
250
243
|
await self.save_chunk(message=message, chunk_number=1, chunk=body, background_tasks=background_tasks)
|
|
251
244
|
|
|
@@ -261,7 +254,6 @@ class Messaging:
|
|
|
261
254
|
@_TriggersEvent(event_name="accept_message")
|
|
262
255
|
@_IfNotReadonly()
|
|
263
256
|
async def accept_message(self, message: Message, file_size: int, background_tasks: BackgroundTasks):
|
|
264
|
-
|
|
265
257
|
if message.status != MessageStatus.ACCEPTED:
|
|
266
258
|
message.events.insert(0, MessageEvent(status=MessageStatus.ACCEPTED))
|
|
267
259
|
|
|
@@ -284,7 +276,6 @@ class Messaging:
|
|
|
284
276
|
async def add_message_event(
|
|
285
277
|
self, message: Message, event: MessageEvent, background_tasks: BackgroundTasks
|
|
286
278
|
) -> Message:
|
|
287
|
-
|
|
288
279
|
message.events.insert(0, event)
|
|
289
280
|
await self.save_message(message=message, background_tasks=background_tasks)
|
|
290
281
|
|
|
@@ -342,7 +333,6 @@ class Messaging:
|
|
|
342
333
|
return await self.get_inbox_messages(mailbox_id, _accepted_messages)
|
|
343
334
|
|
|
344
335
|
async def _validate_auth_token(self, mailbox_id: str, authorization: str) -> Optional[Mailbox]:
|
|
345
|
-
|
|
346
336
|
if self.config.auth_mode == "none":
|
|
347
337
|
return await self.get_mailbox(mailbox_id, accessed=True)
|
|
348
338
|
|
|
@@ -358,7 +348,6 @@ class Messaging:
|
|
|
358
348
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=constants.ERROR_MAILBOX_TOKEN_MISMATCH)
|
|
359
349
|
|
|
360
350
|
if self.config.auth_mode == "canned":
|
|
361
|
-
|
|
362
351
|
if header_parts.nonce.upper() != "VALID":
|
|
363
352
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=constants.ERROR_INVALID_AUTH_TOKEN)
|
|
364
353
|
return await self.get_mailbox(mailbox_id, accessed=True)
|
|
@@ -386,7 +375,6 @@ class Messaging:
|
|
|
386
375
|
return mailbox
|
|
387
376
|
|
|
388
377
|
async def authorise_mailbox(self, mailbox_id: str, authorization: str) -> Optional[Mailbox]:
|
|
389
|
-
|
|
390
378
|
mailbox = await self._validate_auth_token(mailbox_id, authorization)
|
|
391
379
|
|
|
392
380
|
if not mailbox:
|
mesh_sandbox/conftest.py
CHANGED
|
@@ -15,9 +15,8 @@ from .dependencies import get_env_config, get_messaging, get_store
|
|
|
15
15
|
from .tests.helpers import temp_env_vars
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
@pytest.fixture(
|
|
18
|
+
@pytest.fixture(autouse=True)
|
|
19
19
|
def setup():
|
|
20
|
-
|
|
21
20
|
get_store.cache_clear()
|
|
22
21
|
get_env_config.cache_clear()
|
|
23
22
|
get_messaging.cache_clear()
|
|
@@ -32,9 +31,8 @@ def setup():
|
|
|
32
31
|
yield
|
|
33
32
|
|
|
34
33
|
|
|
35
|
-
@pytest.fixture(
|
|
34
|
+
@pytest.fixture(name="app")
|
|
36
35
|
def test_app() -> TestClient:
|
|
37
|
-
|
|
38
36
|
return TestClient(app)
|
|
39
37
|
|
|
40
38
|
|
|
@@ -58,7 +56,6 @@ class StoppableServer:
|
|
|
58
56
|
|
|
59
57
|
@pytest.fixture(scope="session", name="base_uri")
|
|
60
58
|
def create_server(unused_tcp_port_factory: Callable[[], int]):
|
|
61
|
-
|
|
62
59
|
port = unused_tcp_port_factory()
|
|
63
60
|
|
|
64
61
|
server = StoppableServer(port)
|
|
@@ -66,7 +63,6 @@ def create_server(unused_tcp_port_factory: Callable[[], int]):
|
|
|
66
63
|
server_thread = Thread(target=server.run)
|
|
67
64
|
server_thread.start()
|
|
68
65
|
try:
|
|
69
|
-
|
|
70
66
|
base_uri = f"http://localhost:{port}"
|
|
71
67
|
timeout = time() + 1
|
|
72
68
|
with httpx.Client(base_url=base_uri) as client:
|
mesh_sandbox/dependencies.py
CHANGED
|
@@ -18,7 +18,6 @@ _ACCEPTABLE_ACCEPTS = re.compile(r"^application/vnd\.mesh\.v(\d+)\+json$")
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def parse_accept_header(accept: Optional[str]) -> Optional[int]:
|
|
21
|
-
|
|
22
21
|
if not accept:
|
|
23
22
|
return 1
|
|
24
23
|
|
|
@@ -107,17 +106,17 @@ async def normalise_content_type(
|
|
|
107
106
|
return (content_type or "").strip().lower()
|
|
108
107
|
|
|
109
108
|
|
|
110
|
-
@lru_cache
|
|
109
|
+
@lru_cache
|
|
111
110
|
def get_env_config() -> EnvConfig:
|
|
112
111
|
return EnvConfig()
|
|
113
112
|
|
|
114
113
|
|
|
115
|
-
@lru_cache
|
|
114
|
+
@lru_cache
|
|
116
115
|
def get_logger() -> logging.Logger:
|
|
117
116
|
return logging.getLogger("mesh-sandbox")
|
|
118
117
|
|
|
119
118
|
|
|
120
|
-
@lru_cache
|
|
119
|
+
@lru_cache
|
|
121
120
|
def get_store() -> Store:
|
|
122
121
|
logger = get_logger()
|
|
123
122
|
config = get_env_config()
|
|
@@ -133,12 +132,12 @@ def get_store() -> Store:
|
|
|
133
132
|
raise ValueError(f"unrecognised store mode {config.store_mode}")
|
|
134
133
|
|
|
135
134
|
|
|
136
|
-
@lru_cache
|
|
135
|
+
@lru_cache
|
|
137
136
|
def get_messaging() -> Messaging:
|
|
138
137
|
return Messaging(store=get_store())
|
|
139
138
|
|
|
140
139
|
|
|
141
|
-
@lru_cache
|
|
140
|
+
@lru_cache
|
|
142
141
|
def get_fernet() -> FernetHelper:
|
|
143
142
|
return FernetHelper()
|
|
144
143
|
|
mesh_sandbox/handlers/admin.py
CHANGED
|
@@ -22,7 +22,6 @@ class AdminHandler:
|
|
|
22
22
|
self.messaging = messaging
|
|
23
23
|
|
|
24
24
|
async def reset(self, mailbox_id: Optional[str] = None):
|
|
25
|
-
|
|
26
25
|
if self.messaging.readonly:
|
|
27
26
|
raise HTTPException(
|
|
28
27
|
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
|
@@ -91,7 +90,6 @@ class AdminHandler:
|
|
|
91
90
|
async def add_message_event(
|
|
92
91
|
self, message_id: str, new_event: AddMessageEventRequest, background_tasks: BackgroundTasks
|
|
93
92
|
):
|
|
94
|
-
|
|
95
93
|
message = await self.messaging.get_message(message_id)
|
|
96
94
|
if not message:
|
|
97
95
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
|
@@ -6,7 +6,6 @@ from ..models.mailbox import Mailbox
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class HandshakeHandler:
|
|
9
|
-
|
|
10
9
|
# pylint: disable=too-many-arguments
|
|
11
10
|
async def handshake(
|
|
12
11
|
self,
|
|
@@ -20,7 +19,6 @@ class HandshakeHandler:
|
|
|
20
19
|
mex_osarchitecture: str,
|
|
21
20
|
accepts_api_version: int = 1,
|
|
22
21
|
):
|
|
23
|
-
|
|
24
22
|
if accepts_api_version < 2:
|
|
25
23
|
return {"mailboxId": mailbox.mailbox_id}
|
|
26
24
|
|
mesh_sandbox/handlers/inbox.py
CHANGED
|
@@ -42,7 +42,6 @@ class InboxHandler:
|
|
|
42
42
|
|
|
43
43
|
@staticmethod
|
|
44
44
|
def _get_status_headers(message: Message) -> dict[str, Optional[str]]:
|
|
45
|
-
|
|
46
45
|
status_timestamp = (
|
|
47
46
|
message.status_timestamp(MessageStatus.ACCEPTED, MessageStatus.ERROR) or datetime.utcnow()
|
|
48
47
|
).strftime("%Y%m%d%H%M%S")
|
|
@@ -50,7 +49,6 @@ class InboxHandler:
|
|
|
50
49
|
error_event = message.error_event
|
|
51
50
|
|
|
52
51
|
if message.message_type == MessageType.DATA and not error_event:
|
|
53
|
-
|
|
54
52
|
return {
|
|
55
53
|
Headers.Mex_StatusCode: "00",
|
|
56
54
|
Headers.Mex_StatusEvent: "TRANSFER",
|
|
@@ -76,7 +74,6 @@ class InboxHandler:
|
|
|
76
74
|
|
|
77
75
|
@staticmethod
|
|
78
76
|
def _get_response_headers(message: Message, chunk_number: int):
|
|
79
|
-
|
|
80
77
|
headers = {
|
|
81
78
|
Headers.Mex_From: message.sender.mailbox_id,
|
|
82
79
|
Headers.Mex_To: message.recipient.mailbox_id,
|
|
@@ -108,7 +105,6 @@ class InboxHandler:
|
|
|
108
105
|
return {h: v for h, v in headers.items() if v}
|
|
109
106
|
|
|
110
107
|
async def head_message(self, mailbox: Mailbox, message_id: str):
|
|
111
|
-
|
|
112
108
|
message = await self.messaging.get_message(message_id)
|
|
113
109
|
|
|
114
110
|
if not message:
|
|
@@ -211,7 +207,6 @@ class InboxHandler:
|
|
|
211
207
|
async def acknowledge_message(
|
|
212
208
|
self, background_tasks: BackgroundTasks, mailbox: Mailbox, message_id: str, accepts_api_version: int = 1
|
|
213
209
|
):
|
|
214
|
-
|
|
215
210
|
message = await self.messaging.get_message(message_id)
|
|
216
211
|
if not message:
|
|
217
212
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=constants.ERROR_MESSAGE_DOES_NOT_EXIST)
|
|
@@ -240,7 +235,6 @@ class InboxHandler:
|
|
|
240
235
|
message_filter: Optional[Callable[[Message], bool]] = None,
|
|
241
236
|
rich: bool = False,
|
|
242
237
|
) -> tuple[list[Message], Optional[dict]]:
|
|
243
|
-
|
|
244
238
|
messages = (
|
|
245
239
|
sorted(
|
|
246
240
|
await self.messaging.get_inbox_messages(mailbox.mailbox_id),
|
|
@@ -270,8 +264,7 @@ class InboxHandler:
|
|
|
270
264
|
return messages, last_key
|
|
271
265
|
|
|
272
266
|
@staticmethod
|
|
273
|
-
def _get_workflow_filter(workflow_filter: Optional[str]) -> Optional[Callable[[Message], bool]]:
|
|
274
|
-
|
|
267
|
+
def _get_workflow_filter(workflow_filter: Optional[str]) -> Optional[Callable[[Message], bool]]: # noqa: C901
|
|
275
268
|
workflow_id_filter = (workflow_filter or "").strip()
|
|
276
269
|
if not workflow_id_filter:
|
|
277
270
|
return None
|
|
@@ -332,7 +325,6 @@ class InboxHandler:
|
|
|
332
325
|
continue_from: Optional[str] = None,
|
|
333
326
|
workflow_filter: Optional[str] = None,
|
|
334
327
|
) -> Response:
|
|
335
|
-
|
|
336
328
|
last_key: Optional[dict] = None
|
|
337
329
|
|
|
338
330
|
if continue_from:
|
|
@@ -379,7 +371,6 @@ class InboxHandler:
|
|
|
379
371
|
continue_from: Optional[str],
|
|
380
372
|
max_results: int = 100,
|
|
381
373
|
) -> JSONResponse:
|
|
382
|
-
|
|
383
374
|
last_key: Optional[dict] = None
|
|
384
375
|
if continue_from:
|
|
385
376
|
last_key = self.fernet.decode_dict(continue_from)
|
mesh_sandbox/handlers/lookup.py
CHANGED
|
@@ -10,7 +10,6 @@ class LookupHandler:
|
|
|
10
10
|
self.messaging = messaging
|
|
11
11
|
|
|
12
12
|
async def lookup_by_ods_code_and_workflow(self, ods_code: str, workflow_id: str, accepts_api_version: int = 1):
|
|
13
|
-
|
|
14
13
|
if not ods_code or (ods_code and not ods_code.strip()):
|
|
15
14
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="ods code missing")
|
|
16
15
|
|
|
@@ -22,7 +21,6 @@ class LookupHandler:
|
|
|
22
21
|
return endpoint_lookup_response(mailboxes, accepts_api_version)
|
|
23
22
|
|
|
24
23
|
async def lookup_by_workflow_id(self, workflow_id: str, accepts_api_version: int = 1):
|
|
25
|
-
|
|
26
24
|
if not workflow_id or (workflow_id and not workflow_id.strip()):
|
|
27
25
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="workflow id missing")
|
|
28
26
|
|
mesh_sandbox/handlers/outbox.py
CHANGED
|
@@ -43,7 +43,7 @@ def get_chunk_range(chunk_range: str, request_chunk_no: int) -> tuple[Optional[s
|
|
|
43
43
|
return "bad headers", 0, 0
|
|
44
44
|
|
|
45
45
|
try:
|
|
46
|
-
chunk_no, total_chunks =
|
|
46
|
+
chunk_no, total_chunks = (int(val.strip()) for val in parts)
|
|
47
47
|
except ValueError:
|
|
48
48
|
return "bad header value - chunk values should be numeric", 0, 0
|
|
49
49
|
|
|
@@ -78,7 +78,6 @@ class OutboxHandler:
|
|
|
78
78
|
content_type: str,
|
|
79
79
|
accepts_api_version: int = 1,
|
|
80
80
|
): # pylint: disable=too-many-locals
|
|
81
|
-
|
|
82
81
|
if not mex_headers.mex_to:
|
|
83
82
|
raise HTTPException(status_code=http_status.HTTP_400_BAD_REQUEST, detail=constants.ERROR_MISSING_TO_ADDRESS)
|
|
84
83
|
|
|
@@ -146,11 +145,9 @@ class OutboxHandler:
|
|
|
146
145
|
await self.messaging.send_message(message=message, body=body, background_tasks=background_tasks)
|
|
147
146
|
|
|
148
147
|
self.logger.info(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
f"workflow={message.workflow_id or ''}"
|
|
153
|
-
)
|
|
148
|
+
f"created message: message_id={message.message_id} "
|
|
149
|
+
f"from={message.sender.mailbox_id} to={message.recipient.mailbox_id} "
|
|
150
|
+
f"workflow={message.workflow_id or ''}"
|
|
154
151
|
)
|
|
155
152
|
|
|
156
153
|
return send_message_response(message, accepts_api_version)
|
|
@@ -166,7 +163,6 @@ class OutboxHandler:
|
|
|
166
163
|
content_encoding: str,
|
|
167
164
|
accepts_api_version: int = 1,
|
|
168
165
|
):
|
|
169
|
-
|
|
170
166
|
chunk_range = (mex_chunk_range or "").strip()
|
|
171
167
|
if not chunk_range:
|
|
172
168
|
raise MessagingException(
|
|
@@ -17,7 +17,6 @@ class TrackingHandler:
|
|
|
17
17
|
self.messaging = messaging
|
|
18
18
|
|
|
19
19
|
async def tracking_by_message_id(self, sender_mailbox: Mailbox, message_id: str, accepts_api_version: int = 1):
|
|
20
|
-
|
|
21
20
|
message: Optional[Message] = await self.messaging.get_message(message_id)
|
|
22
21
|
|
|
23
22
|
if not message:
|
|
@@ -35,7 +34,6 @@ class TrackingHandler:
|
|
|
35
34
|
return JSONResponse(content=exclude_none_json_encoder(model), media_type=MESH_MEDIA_TYPES[accepts_api_version])
|
|
36
35
|
|
|
37
36
|
async def tracking_by_local_id(self, sender_mailbox: Mailbox, local_id: str):
|
|
38
|
-
|
|
39
37
|
messages: list[Message] = await self.messaging.get_by_local_id(sender_mailbox.mailbox_id, local_id)
|
|
40
38
|
|
|
41
39
|
if len(messages) == 0:
|
mesh_sandbox/models/mailbox.py
CHANGED
|
@@ -5,7 +5,6 @@ from typing import Optional
|
|
|
5
5
|
|
|
6
6
|
@dataclass
|
|
7
7
|
class Mailbox:
|
|
8
|
-
|
|
9
8
|
mailbox_id: str
|
|
10
9
|
mailbox_name: str
|
|
11
10
|
billing_entity: Optional[str] = field(default=None)
|
|
@@ -35,5 +34,4 @@ class Mailbox:
|
|
|
35
34
|
self._inbox_count = inbox_count
|
|
36
35
|
|
|
37
36
|
def __post_init__(self):
|
|
38
|
-
|
|
39
37
|
self.mailbox_id = self.mailbox_id.upper()
|
mesh_sandbox/models/message.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
from collections.abc import Generator
|
|
1
2
|
from dataclasses import dataclass, field
|
|
2
3
|
from datetime import datetime
|
|
3
|
-
from typing import Callable, Final,
|
|
4
|
+
from typing import Callable, Final, Optional
|
|
4
5
|
|
|
5
6
|
from dateutil.relativedelta import relativedelta
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class MessageStatus:
|
|
9
|
-
|
|
10
10
|
UPLOADING: Final[str] = "uploading" # still uploading chunks
|
|
11
11
|
ACCEPTED: Final[str] = "accepted" # finished uploading
|
|
12
12
|
ACKNOWLEDGED: Final[str] = "acknowledged" # client has acknowledged the message
|
|
@@ -17,7 +17,6 @@ class MessageStatus:
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class MessageType:
|
|
20
|
-
|
|
21
20
|
REPORT: Final[str] = "REPORT"
|
|
22
21
|
DATA: Final[str] = "DATA"
|
|
23
22
|
|
|
@@ -31,7 +30,6 @@ class MessageDeliveryStatus:
|
|
|
31
30
|
|
|
32
31
|
@dataclass
|
|
33
32
|
class MessageMetadata:
|
|
34
|
-
|
|
35
33
|
subject: Optional[str] = field(default=None)
|
|
36
34
|
content_type: Optional[str] = field(default=None)
|
|
37
35
|
content_encoding: Optional[str] = field(default=None)
|
|
@@ -133,6 +131,5 @@ class Message: # pylint: disable=too-many-public-methods,too-many-instance-attr
|
|
|
133
131
|
yield event
|
|
134
132
|
|
|
135
133
|
def __post_init__(self):
|
|
136
|
-
|
|
137
134
|
self.message_id = self.message_id.upper()
|
|
138
135
|
self.events = self.events or [MessageEvent(status=MessageStatus.ACCEPTED, timestamp=self.created_timestamp)]
|
mesh_sandbox/models/workflow.py
CHANGED
|
@@ -3,13 +3,11 @@ from dataclasses import dataclass, field
|
|
|
3
3
|
|
|
4
4
|
@dataclass
|
|
5
5
|
class Workflow:
|
|
6
|
-
|
|
7
6
|
workflow_id: str
|
|
8
7
|
senders: list[str] = field(default_factory=list)
|
|
9
8
|
receivers: list[str] = field(default_factory=list)
|
|
10
9
|
|
|
11
10
|
def __post_init__(self):
|
|
12
|
-
|
|
13
11
|
self.workflow_id = (self.workflow_id or "").strip()
|
|
14
12
|
|
|
15
13
|
self.senders = [mailbox for mailbox in ((mb or "").strip().upper() for mb in self.senders) if mailbox]
|
mesh_sandbox/routers/inbox.py
CHANGED
|
@@ -35,8 +35,8 @@ router = APIRouter(
|
|
|
35
35
|
responses={
|
|
36
36
|
status.HTTP_200_OK: {
|
|
37
37
|
"content": {
|
|
38
|
-
MESH_MEDIA_TYPES[2]: {"schema": InboxV2.
|
|
39
|
-
MESH_MEDIA_TYPES[1]: {"schema": InboxV1.
|
|
38
|
+
MESH_MEDIA_TYPES[2]: {"schema": InboxV2.model_json_schema()},
|
|
39
|
+
MESH_MEDIA_TYPES[1]: {"schema": InboxV1.model_json_schema()},
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
status.HTTP_403_FORBIDDEN: {"description": "Authentication failed", "content": None},
|
|
@@ -149,7 +149,7 @@ async def head_message(
|
|
|
149
149
|
status.HTTP_200_OK: {
|
|
150
150
|
"content": {
|
|
151
151
|
MESH_MEDIA_TYPES[2]: {
|
|
152
|
-
"schema": RichInboxView.
|
|
152
|
+
"schema": RichInboxView.model_json_schema(),
|
|
153
153
|
}
|
|
154
154
|
},
|
|
155
155
|
}
|
|
@@ -205,7 +205,7 @@ async def rich_inbox(
|
|
|
205
205
|
},
|
|
206
206
|
status.HTTP_206_PARTIAL_CONTENT: {
|
|
207
207
|
"description": (
|
|
208
|
-
"Partial Content
|
|
208
|
+
"Partial Content - Indicates that chunk has been downloaded "
|
|
209
209
|
"successfully and that there are further chunks."
|
|
210
210
|
),
|
|
211
211
|
"content": {"application/octet-stream": None},
|
|
@@ -244,7 +244,7 @@ async def retrieve_message(
|
|
|
244
244
|
},
|
|
245
245
|
status.HTTP_206_PARTIAL_CONTENT: {
|
|
246
246
|
"description": (
|
|
247
|
-
"Partial Content
|
|
247
|
+
"Partial Content - Indicates that chunk has been downloaded "
|
|
248
248
|
"successfully and that there are further chunks."
|
|
249
249
|
),
|
|
250
250
|
"content": {"application/octet-stream": None},
|
|
@@ -24,10 +24,10 @@ router = APIRouter(
|
|
|
24
24
|
200: {
|
|
25
25
|
"content": {
|
|
26
26
|
MESH_MEDIA_TYPES[2]: {
|
|
27
|
-
"schema": InboxCountV2.
|
|
27
|
+
"schema": InboxCountV2.model_json_schema(),
|
|
28
28
|
},
|
|
29
29
|
MESH_MEDIA_TYPES[1]: {
|
|
30
|
-
"schema": InboxCountV1.
|
|
30
|
+
"schema": InboxCountV1.model_json_schema(),
|
|
31
31
|
},
|
|
32
32
|
}
|
|
33
33
|
}
|
mesh_sandbox/routers/lookup.py
CHANGED
|
@@ -19,10 +19,10 @@ router = APIRouter(
|
|
|
19
19
|
status.HTTP_200_OK: {
|
|
20
20
|
"content": {
|
|
21
21
|
MESH_MEDIA_TYPES[2]: {
|
|
22
|
-
"schema": MailboxLookupV2.
|
|
22
|
+
"schema": MailboxLookupV2.model_json_schema(),
|
|
23
23
|
},
|
|
24
24
|
MESH_MEDIA_TYPES[1]: {
|
|
25
|
-
"schema": EndpointLookupV1.
|
|
25
|
+
"schema": EndpointLookupV1.model_json_schema(),
|
|
26
26
|
},
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -35,7 +35,6 @@ async def lookup_by_ods_code_and_workflow_id(
|
|
|
35
35
|
accepts_api_version: int = Depends(get_accepts_api_version),
|
|
36
36
|
handler: LookupHandler = Depends(LookupHandler),
|
|
37
37
|
):
|
|
38
|
-
|
|
39
38
|
return await handler.lookup_by_ods_code_and_workflow(ods_code, workflow_id, accepts_api_version)
|
|
40
39
|
|
|
41
40
|
|
|
@@ -46,7 +45,7 @@ async def lookup_by_ods_code_and_workflow_id(
|
|
|
46
45
|
status.HTTP_200_OK: {
|
|
47
46
|
"content": {
|
|
48
47
|
MESH_MEDIA_TYPES[2]: {
|
|
49
|
-
"schema": MailboxLookupV2.
|
|
48
|
+
"schema": MailboxLookupV2.model_json_schema(),
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
}
|
mesh_sandbox/routers/outbox.py
CHANGED
|
@@ -40,10 +40,10 @@ router = APIRouter(
|
|
|
40
40
|
status.HTTP_202_ACCEPTED: {
|
|
41
41
|
"content": {
|
|
42
42
|
MESH_MEDIA_TYPES[2]: {
|
|
43
|
-
"schema": SendMessageV2.
|
|
43
|
+
"schema": SendMessageV2.model_json_schema(),
|
|
44
44
|
},
|
|
45
45
|
MESH_MEDIA_TYPES[1]: {
|
|
46
|
-
"schema": SendMessageV1.
|
|
46
|
+
"schema": SendMessageV1.model_json_schema(),
|
|
47
47
|
},
|
|
48
48
|
}
|
|
49
49
|
},
|
|
@@ -111,7 +111,7 @@ async def send_chunk(
|
|
|
111
111
|
200: {
|
|
112
112
|
"content": {
|
|
113
113
|
MESH_MEDIA_TYPES[2]: {
|
|
114
|
-
"schema": RichOutboxView.
|
|
114
|
+
"schema": RichOutboxView.model_json_schema(),
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
}
|
mesh_sandbox/routers/tracking.py
CHANGED
|
@@ -27,10 +27,10 @@ router = APIRouter(
|
|
|
27
27
|
200: {
|
|
28
28
|
"content": {
|
|
29
29
|
MESH_MEDIA_TYPES[2]: {
|
|
30
|
-
"schema": TrackingV2.
|
|
30
|
+
"schema": TrackingV2.model_json_schema(),
|
|
31
31
|
},
|
|
32
32
|
MESH_MEDIA_TYPES[1]: {
|
|
33
|
-
"schema": TrackingV1.
|
|
33
|
+
"schema": TrackingV1.model_json_schema(),
|
|
34
34
|
},
|
|
35
35
|
}
|
|
36
36
|
}
|