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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.34"
1
+ __version__ = "1.0.1"
@@ -0,0 +1,398 @@
1
+ import asyncio
2
+ import importlib
3
+ import inspect
4
+ import pkgutil
5
+ from abc import ABC, abstractmethod
6
+ from collections import defaultdict
7
+ from functools import wraps
8
+ from types import ModuleType
9
+ from typing import Any, Callable, Literal, NamedTuple, Optional, TypeVar, cast
10
+
11
+ from fastapi import HTTPException, status
12
+ from starlette.background import BackgroundTasks
13
+
14
+ from .. import plugins as plugins_ns
15
+ from ..models.mailbox import Mailbox
16
+ from ..models.message import Message, MessageEvent, MessageStatus, MessageType
17
+ from ..store.base import Store
18
+ from . import constants, generate_cipher_text
19
+
20
+
21
+ class _SandboxPlugin(ABC):
22
+
23
+ triggers: 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
+ ]
41
+ ] = []
42
+
43
+ @abstractmethod
44
+ async def on_event(self, event: str, event_args: dict[str, Any], exception: Optional[Exception] = None):
45
+ pass
46
+
47
+
48
+ def _accepted_messages(msg: Message) -> bool:
49
+ return msg.status == MessageStatus.ACCEPTED
50
+
51
+
52
+ T_co = TypeVar("T_co", covariant=True)
53
+
54
+
55
+ class AuthoriseHeaderParts(NamedTuple):
56
+ scheme: str
57
+ mailbox_id: str
58
+ nonce: str
59
+ nonce_count: str
60
+ timestamp: str
61
+ cipher_text: str
62
+ parts: int
63
+
64
+ def get_reasons_invalid(self) -> list[str]:
65
+ reasons = []
66
+ if self.parts != 5:
67
+ reasons.append(f"invalid num header parts: {self.parts}")
68
+
69
+ if not self.nonce_count.isdigit():
70
+ reasons.append("nonce count is not digits")
71
+
72
+ if self.scheme not in (MESH_AUTH_SCHEME, ""):
73
+ reasons.append("invalid auth scheme or mailbox_id contains a space")
74
+
75
+ if " " in self.mailbox_id:
76
+ reasons.append("mailbox_id contains a space")
77
+
78
+ return reasons
79
+
80
+
81
+ _DEFAULT_PARTS_IF_MISSING = ["" for _ in range(5)]
82
+ MESH_AUTH_SCHEME = "NHSMESH"
83
+
84
+
85
+ def try_parse_authorisation_token(auth_token: str) -> Optional[AuthoriseHeaderParts]:
86
+
87
+ if not auth_token:
88
+ return None
89
+
90
+ auth_token = auth_token.strip()
91
+
92
+ scheme = MESH_AUTH_SCHEME if auth_token.upper().startswith(MESH_AUTH_SCHEME) else ""
93
+
94
+ if scheme:
95
+ auth_token = auth_token[len(MESH_AUTH_SCHEME) + 1 :]
96
+
97
+ auth_token_parts = auth_token.split(":")
98
+
99
+ num_parts = len(auth_token_parts)
100
+ auth_token_parts = auth_token_parts + _DEFAULT_PARTS_IF_MISSING
101
+
102
+ header_parts = AuthoriseHeaderParts(
103
+ scheme=scheme,
104
+ mailbox_id=auth_token_parts[0],
105
+ nonce=auth_token_parts[1],
106
+ nonce_count=auth_token_parts[2],
107
+ timestamp=auth_token_parts[3],
108
+ cipher_text=auth_token_parts[4],
109
+ parts=num_parts,
110
+ )
111
+
112
+ return header_parts
113
+
114
+
115
+ class Messaging:
116
+ def __init__(self, store: Store, plugins_module: ModuleType = plugins_ns):
117
+ self.store = store
118
+ self.logger = store.logger
119
+ self.config = store.config
120
+ self._plugin_registry: dict[str, list[type[_SandboxPlugin]]] = defaultdict(list)
121
+ self._plugin_instances: dict[str, list[_SandboxPlugin]] = {}
122
+ self._find_plugins(plugins_module)
123
+
124
+ class _TriggersEvent:
125
+ def __init__(self, event_name: str):
126
+ self.event_name = event_name
127
+
128
+ def __call__(self, func):
129
+
130
+ if not inspect.iscoroutinefunction(func):
131
+ raise ValueError(f"wrapped function is not awaitable: {func}")
132
+
133
+ @wraps(func)
134
+ async def _async_inner(*args, **kwargs):
135
+ if len(args) > 1:
136
+ raise ValueError(f"only call {func} with kwargs")
137
+ messaging = cast(Messaging, args[0])
138
+
139
+ kwargs_for_event = kwargs.copy()
140
+ background_tasks = kwargs_for_event.pop("background_tasks", None)
141
+
142
+ await messaging.on_event(f"before_{self.event_name}", kwargs_for_event)
143
+
144
+ try:
145
+ result = await func(*args, **kwargs)
146
+ if background_tasks:
147
+ background_tasks.add_task(messaging.on_event, f"after_{self.event_name}", kwargs_for_event)
148
+ return result
149
+ except Exception as err:
150
+ if background_tasks:
151
+ background_tasks.add_task(messaging.on_event, f"{self.event_name}_error", kwargs_for_event, err)
152
+ raise
153
+
154
+ return _async_inner
155
+
156
+ class _IfNotReadonly:
157
+ def __call__(self, func):
158
+
159
+ if not inspect.iscoroutinefunction(func):
160
+ raise ValueError(f"wrapped function is not awaitable: {func}")
161
+
162
+ @wraps(func)
163
+ async def _async_inner(*args, **kwargs):
164
+ messaging = cast(Messaging, args[0])
165
+ if messaging.readonly:
166
+ return
167
+
168
+ return await func(*args, **kwargs)
169
+
170
+ return _async_inner
171
+
172
+ def _find_plugins(self, package: ModuleType):
173
+
174
+ for _, name, _ in pkgutil.iter_modules(package.__path__, package.__name__ + "."):
175
+ module = importlib.import_module(name)
176
+ for _, plugin_type in inspect.getmembers(module):
177
+
178
+ if not inspect.isclass(plugin_type) or not plugin_type.__name__.endswith("Plugin"):
179
+ continue
180
+
181
+ self.register_plugin(plugin_type)
182
+
183
+ def register_plugin(self, plugin_type: type):
184
+
185
+ self.logger.info(f"potential plugin: {plugin_type.__name__} found")
186
+ if not hasattr(plugin_type, "triggers"):
187
+ self.logger.warning(f"plugin: {plugin_type.__name__} has no class attr triggers .. not loading")
188
+ return
189
+
190
+ if not hasattr(plugin_type, "on_event"):
191
+ self.logger.warning(f"plugin: {plugin_type.__name__} has no class attr on_event .. not loading")
192
+ return
193
+
194
+ plugin_type = cast(type[_SandboxPlugin], plugin_type)
195
+
196
+ if not inspect.iscoroutinefunction(plugin_type.on_event):
197
+ self.logger.warning(f"plugin: {plugin_type.__name__} on_event is not a coroutine.. not loading")
198
+ return
199
+
200
+ on_event_args = cast(list[str], inspect.getfullargspec(plugin_type.on_event)[0])
201
+
202
+ msg_args = (
203
+ f"plugin: {plugin_type.__name__} on_event expected args "
204
+ f"(event: str, event_args: dict[str, Any], error: Exception = None) .. not loading"
205
+ )
206
+
207
+ if not on_event_args:
208
+ self.logger.warning(msg_args)
209
+ return
210
+
211
+ if not isinstance(inspect.getattr_static(plugin_type, "on_event"), staticmethod):
212
+ on_event_args.pop(0)
213
+
214
+ if len(on_event_args) not in (2, 3):
215
+ self.logger.warning(msg_args)
216
+ return
217
+
218
+ for trigger in plugin_type.triggers:
219
+ self._plugin_registry[trigger].append(plugin_type)
220
+
221
+ @staticmethod
222
+ async def _construct(plugin_type: type[_SandboxPlugin]) -> _SandboxPlugin:
223
+ created = plugin_type()
224
+ return created
225
+
226
+ async def on_event(self, event: str, event_args: dict[str, Any], exception: Optional[Exception] = None):
227
+
228
+ instances = self._plugin_instances.get(event, [])
229
+ if not instances:
230
+ registered = self._plugin_registry.get(event, [])
231
+ if not registered:
232
+ return
233
+
234
+ instances = await asyncio.gather(*[self._construct(plugin_type) for plugin_type in registered])
235
+ self._plugin_instances[event] = instances
236
+
237
+ if exception:
238
+ await asyncio.gather(*[plugin.on_event(event, event_args, exception) for plugin in instances])
239
+ else:
240
+ await asyncio.gather(*[plugin.on_event(event, event_args) for plugin in instances])
241
+
242
+ @property
243
+ def readonly(self) -> bool:
244
+ return self.store.readonly
245
+
246
+ @_TriggersEvent(event_name="send_message")
247
+ async def send_message(self, message: Message, body: bytes, background_tasks: BackgroundTasks) -> Message:
248
+
249
+ if message.total_chunks > 0:
250
+ await self.save_chunk(message=message, chunk_number=1, chunk=body, background_tasks=background_tasks)
251
+
252
+ if message.total_chunks == 1 or message.message_type == MessageType.REPORT:
253
+ await self.accept_message(message=message, file_size=len(body), background_tasks=background_tasks)
254
+ else:
255
+ await self.save_message(message=message, background_tasks=background_tasks)
256
+
257
+ await self.store.add_to_outbox(message)
258
+
259
+ return message
260
+
261
+ @_TriggersEvent(event_name="accept_message")
262
+ @_IfNotReadonly()
263
+ async def accept_message(self, message: Message, file_size: int, background_tasks: BackgroundTasks):
264
+
265
+ if message.status != MessageStatus.ACCEPTED:
266
+ message.events.insert(0, MessageEvent(status=MessageStatus.ACCEPTED))
267
+
268
+ message.file_size = file_size
269
+
270
+ await self.save_message(message=message, background_tasks=background_tasks)
271
+ await self.store.add_to_inbox(message)
272
+
273
+ @_TriggersEvent(event_name="acknowledge_message")
274
+ @_IfNotReadonly()
275
+ async def acknowledge_message(self, message: Message, background_tasks: BackgroundTasks) -> Message:
276
+ if message.status != MessageStatus.ACCEPTED:
277
+ return message
278
+
279
+ message.events.insert(0, MessageEvent(status=MessageStatus.ACKNOWLEDGED))
280
+ await self.save_message(message=message, background_tasks=background_tasks)
281
+
282
+ return message
283
+
284
+ async def add_message_event(
285
+ self, message: Message, event: MessageEvent, background_tasks: BackgroundTasks
286
+ ) -> Message:
287
+
288
+ message.events.insert(0, event)
289
+ await self.save_message(message=message, background_tasks=background_tasks)
290
+
291
+ return message
292
+
293
+ @_TriggersEvent(event_name="save_chunk")
294
+ @_IfNotReadonly()
295
+ async def save_chunk(
296
+ self, message: Message, chunk_number: int, chunk: bytes, background_tasks: BackgroundTasks
297
+ ): # pylint: disable=unused-argument
298
+ return await self.store.save_chunk(message=message, chunk_number=chunk_number, chunk=chunk)
299
+
300
+ @_TriggersEvent(event_name="save_message")
301
+ @_IfNotReadonly()
302
+ async def save_message(
303
+ self, message: Message, background_tasks: Optional[BackgroundTasks] = None
304
+ ): # pylint: disable=unused-argument
305
+ return await self.store.save_message(message)
306
+
307
+ @_IfNotReadonly()
308
+ async def reset(self):
309
+ await self.store.reset()
310
+
311
+ @_IfNotReadonly()
312
+ async def reset_mailbox(self, mailbox_id: str):
313
+ await self.store.reset_mailbox(mailbox_id=mailbox_id)
314
+
315
+ async def get_chunk(self, message: Message, chunk_number: int) -> Optional[bytes]:
316
+ return await self.store.get_chunk(message=message, chunk_number=chunk_number)
317
+
318
+ async def get_mailbox(self, mailbox_id: str, accessed: bool = False) -> Optional[Mailbox]:
319
+ return await self.store.get_mailbox(mailbox_id=mailbox_id, accessed=accessed)
320
+
321
+ async def get_message(self, message_id: str) -> Optional[Message]:
322
+ return await self.store.get_message(message_id=message_id)
323
+
324
+ async def get_inbox_messages(
325
+ self, mailbox_id: str, predicate: Optional[Callable[[Message], bool]] = None
326
+ ) -> list[Message]:
327
+ return await self.store.get_inbox_messages(mailbox_id=mailbox_id, predicate=predicate)
328
+
329
+ async def get_outbox(self, mailbox_id: str) -> list[Message]:
330
+ return await self.store.get_outbox(mailbox_id=mailbox_id)
331
+
332
+ async def get_by_local_id(self, mailbox_id: str, local_id: str) -> list[Message]:
333
+ return await self.store.get_by_local_id(mailbox_id=mailbox_id, local_id=local_id)
334
+
335
+ async def lookup_by_ods_code_and_workflow_id(self, ods_code: str, workflow_id: str) -> list[Mailbox]:
336
+ return await self.store.lookup_by_ods_code_and_workflow_id(ods_code=ods_code, workflow_id=workflow_id)
337
+
338
+ async def lookup_by_workflow_id(self, workflow_id: str) -> list[Mailbox]:
339
+ return await self.store.lookup_by_workflow_id(workflow_id=workflow_id)
340
+
341
+ async def get_accepted_inbox_messages(self, mailbox_id: str) -> list[Message]:
342
+ return await self.get_inbox_messages(mailbox_id, _accepted_messages)
343
+
344
+ async def _validate_auth_token(self, mailbox_id: str, authorization: str) -> Optional[Mailbox]:
345
+
346
+ if self.config.auth_mode == "none":
347
+ return await self.get_mailbox(mailbox_id, accessed=True)
348
+
349
+ authorization = (authorization or "").strip()
350
+ if not authorization:
351
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=constants.ERROR_READING_AUTH_HEADER)
352
+
353
+ header_parts = try_parse_authorisation_token(authorization)
354
+ if not header_parts:
355
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=constants.ERROR_READING_AUTH_HEADER)
356
+
357
+ if header_parts.mailbox_id != mailbox_id:
358
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=constants.ERROR_MAILBOX_TOKEN_MISMATCH)
359
+
360
+ if self.config.auth_mode == "canned":
361
+
362
+ if header_parts.nonce.upper() != "VALID":
363
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=constants.ERROR_INVALID_AUTH_TOKEN)
364
+ return await self.get_mailbox(mailbox_id, accessed=True)
365
+
366
+ if header_parts.get_reasons_invalid():
367
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=constants.ERROR_READING_AUTH_HEADER)
368
+
369
+ mailbox = await self.get_mailbox(mailbox_id, accessed=True)
370
+
371
+ if not mailbox:
372
+ return None
373
+
374
+ cypher_text = generate_cipher_text(
375
+ self.config.shared_key,
376
+ header_parts.mailbox_id,
377
+ mailbox.password,
378
+ header_parts.timestamp,
379
+ header_parts.nonce,
380
+ header_parts.nonce_count,
381
+ )
382
+
383
+ if header_parts.cipher_text != cypher_text:
384
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=constants.ERROR_INVALID_AUTH_TOKEN)
385
+
386
+ return mailbox
387
+
388
+ async def authorise_mailbox(self, mailbox_id: str, authorization: str) -> Optional[Mailbox]:
389
+
390
+ mailbox = await self._validate_auth_token(mailbox_id, authorization)
391
+
392
+ if not mailbox:
393
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=constants.ERROR_NO_MAILBOX_MATCHES)
394
+
395
+ return mailbox
396
+
397
+ async def get_file_size(self, message: Message) -> int:
398
+ return await self.store.get_file_size(message)
mesh_sandbox/conftest.py CHANGED
@@ -11,7 +11,7 @@ from fastapi.testclient import TestClient
11
11
  from uvicorn import Config, Server # type: ignore[import]
12
12
 
13
13
  from .api import app
14
- from .dependencies import get_env_config, get_store
14
+ from .dependencies import get_env_config, get_messaging, get_store
15
15
  from .tests.helpers import temp_env_vars
16
16
 
17
17
 
@@ -20,6 +20,7 @@ def setup():
20
20
 
21
21
  get_store.cache_clear()
22
22
  get_env_config.cache_clear()
23
+ get_messaging.cache_clear()
23
24
 
24
25
  with temp_env_vars(
25
26
  ENV="local",
@@ -8,6 +8,7 @@ from fastapi import Depends, Header, HTTPException, Path, Query, Request
8
8
  from .common import EnvConfig
9
9
  from .common.constants import Headers
10
10
  from .common.fernet import FernetHelper
11
+ from .common.messaging import Messaging
11
12
  from .store.base import Store
12
13
  from .store.canned_store import CannedStore
13
14
  from .store.file_store import FileStore
@@ -132,6 +133,11 @@ def get_store() -> Store:
132
133
  raise ValueError(f"unrecognised store mode {config.store_mode}")
133
134
 
134
135
 
136
+ @lru_cache()
137
+ def get_messaging() -> Messaging:
138
+ return Messaging(store=get_store())
139
+
140
+
135
141
  @lru_cache()
136
142
  def get_fernet() -> FernetHelper:
137
143
  return FernetHelper()
@@ -149,7 +155,6 @@ async def authorised_mailbox(
149
155
  ),
150
156
  default="",
151
157
  ),
152
- store: Store = Depends(get_store),
158
+ messaging: Messaging = Depends(get_messaging),
153
159
  ):
154
-
155
- request.state.authorised_mailbox = await store.authorise_mailbox(mailbox_id, authorization)
160
+ request.state.authorised_mailbox = await messaging.authorise_mailbox(mailbox_id, authorization)
@@ -3,8 +3,8 @@ from uuid import uuid4
3
3
 
4
4
  from fastapi import BackgroundTasks, Depends, HTTPException, status
5
5
 
6
- from ..common import EnvConfig
7
- from ..dependencies import get_env_config, get_store
6
+ from ..common.messaging import Messaging
7
+ from ..dependencies import get_messaging
8
8
  from ..models.message import (
9
9
  Message,
10
10
  MessageEvent,
@@ -13,35 +13,33 @@ from ..models.message import (
13
13
  MessageStatus,
14
14
  MessageType,
15
15
  )
16
- from ..store.base import Store
17
- from ..views.admin import PutReportRequest
16
+ from ..views.admin import AddMessageEventRequest, CreateReportRequest
18
17
 
19
18
 
20
19
  class AdminHandler:
21
- def __init__(self, config: EnvConfig = Depends(get_env_config), store: Store = Depends(get_store)):
22
- self.config = config
23
- self.store = store
20
+ def __init__(self, messaging: Messaging = Depends(get_messaging)):
21
+ self.messaging = messaging
24
22
 
25
23
  async def reset(self, mailbox_id: Optional[str] = None):
26
24
 
27
- if not self.store.supports_reset:
25
+ if self.messaging.readonly:
28
26
  raise HTTPException(
29
27
  status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
30
- detail=f"reset not supported for {self.config.store_mode} store mode",
28
+ detail="reset not supported for current store mode",
31
29
  )
32
30
 
33
31
  if not mailbox_id:
34
- await self.store.reset()
32
+ await self.messaging.reset()
35
33
  return
36
34
 
37
- mailbox = await self.store.get_mailbox(mailbox_id, accessed=False)
35
+ mailbox = await self.messaging.get_mailbox(mailbox_id, accessed=False)
38
36
  if not mailbox:
39
37
  raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="mailbox does not exist")
40
38
 
41
- await self.store.reset_mailbox(mailbox.mailbox_id)
39
+ await self.messaging.reset_mailbox(mailbox.mailbox_id)
42
40
 
43
- async def put_report(self, request: PutReportRequest, background_tasks: BackgroundTasks) -> Message:
44
- recipient = await self.store.get_mailbox(request.mailbox_id, accessed=False)
41
+ async def create_report(self, request: CreateReportRequest, background_tasks: BackgroundTasks) -> Message:
42
+ recipient = await self.messaging.get_mailbox(request.mailbox_id, accessed=False)
45
43
  if not recipient:
46
44
  raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="mailbox does not exist")
47
45
 
@@ -85,6 +83,26 @@ class AdminHandler:
85
83
  ),
86
84
  )
87
85
 
88
- await self.store.send_message(message, b"", background_tasks)
86
+ await self.messaging.send_message(message=message, body=b"", background_tasks=background_tasks)
87
+
88
+ return message
89
+
90
+ async def add_message_event(
91
+ self, message_id: str, new_event: AddMessageEventRequest, background_tasks: BackgroundTasks
92
+ ):
93
+
94
+ message = await self.messaging.get_message(message_id)
95
+ if not message:
96
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
97
+
98
+ event = MessageEvent(
99
+ status=new_event.status,
100
+ code=new_event.code,
101
+ event=new_event.event,
102
+ description=new_event.description,
103
+ linked_message_id=new_event.linked_message_id,
104
+ )
105
+
106
+ message = await self.messaging.add_message_event(message, event, background_tasks)
89
107
 
90
108
  return message
@@ -8,20 +8,14 @@ from dateutil.tz import tzutc
8
8
  from fastapi import BackgroundTasks, Depends, HTTPException, Response, status
9
9
  from starlette.responses import JSONResponse
10
10
 
11
- from ..common import (
12
- MESH_MEDIA_TYPES,
13
- EnvConfig,
14
- constants,
15
- exclude_none_json_encoder,
16
- index_of,
17
- )
11
+ from ..common import MESH_MEDIA_TYPES, constants, exclude_none_json_encoder, index_of
18
12
  from ..common.constants import Headers
19
13
  from ..common.fernet import FernetHelper
20
14
  from ..common.handler_helpers import get_handler_uri
21
- from ..dependencies import get_env_config, get_fernet, get_store
15
+ from ..common.messaging import Messaging
16
+ from ..dependencies import get_fernet, get_messaging
22
17
  from ..models.mailbox import Mailbox
23
18
  from ..models.message import Message, MessageDeliveryStatus, MessageStatus, MessageType
24
- from ..store.base import Store
25
19
  from ..views.inbox import InboxV1, InboxV2, get_rich_inbox_view
26
20
 
27
21
  HTTP_DATETIME_FORMAT = "%a, %d %b %Y %H:%M:%S %Z"
@@ -39,12 +33,11 @@ def to_http_datetime(maybe_naive_dt: datetime, as_timezone: Optional[tzinfo] = N
39
33
  class InboxHandler:
40
34
  def __init__(
41
35
  self,
42
- config: EnvConfig = Depends(get_env_config),
43
- store: Store = Depends(get_store),
36
+ messaging: Messaging = Depends(get_messaging),
44
37
  fernet: FernetHelper = Depends(get_fernet),
45
38
  ):
46
- self.config = config
47
- self.store = store
39
+ self.messaging = messaging
40
+
48
41
  self.fernet = fernet
49
42
 
50
43
  @staticmethod
@@ -115,7 +108,7 @@ class InboxHandler:
115
108
 
116
109
  async def head_message(self, mailbox: Mailbox, message_id: str):
117
110
 
118
- message = await self.store.get_message(message_id)
111
+ message = await self.messaging.get_message(message_id)
119
112
 
120
113
  if not message:
121
114
  raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=constants.ERROR_MESSAGE_DOES_NOT_EXIST)
@@ -166,7 +159,7 @@ class InboxHandler:
166
159
  chunk_number: int = 1,
167
160
  accepts_api_version: int = 1,
168
161
  ):
169
- message = await self.store.get_message(message_id)
162
+ message = await self.messaging.get_message(message_id)
170
163
 
171
164
  if not message:
172
165
  raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=constants.ERROR_MESSAGE_DOES_NOT_EXIST)
@@ -187,7 +180,7 @@ class InboxHandler:
187
180
 
188
181
  status_code = status.HTTP_200_OK if chunk_number >= message.total_chunks else status.HTTP_206_PARTIAL_CONTENT
189
182
 
190
- chunk = await self.store.retrieve_chunk(message, chunk_number)
183
+ chunk = await self.messaging.get_chunk(message, chunk_number)
191
184
 
192
185
  if chunk is None:
193
186
  raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=constants.ERROR_MESSAGE_DOES_NOT_EXIST)
@@ -218,7 +211,7 @@ class InboxHandler:
218
211
  self, background_tasks: BackgroundTasks, mailbox: Mailbox, message_id: str, accepts_api_version: int = 1
219
212
  ):
220
213
 
221
- message = await self.store.get_message(message_id)
214
+ message = await self.messaging.get_message(message_id)
222
215
  if not message:
223
216
  raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=constants.ERROR_MESSAGE_DOES_NOT_EXIST)
224
217
 
@@ -234,7 +227,7 @@ class InboxHandler:
234
227
  if message.status != MessageStatus.ACCEPTED:
235
228
  return response()
236
229
 
237
- await self.store.acknowledge_message(message, background_tasks)
230
+ await self.messaging.acknowledge_message(message=message, background_tasks=background_tasks)
238
231
 
239
232
  return response()
240
233
 
@@ -248,9 +241,9 @@ class InboxHandler:
248
241
  ) -> tuple[list[Message], Optional[dict]]:
249
242
 
250
243
  messages = (
251
- await self.store.get_inbox_messages(mailbox.mailbox_id)
244
+ await self.messaging.get_inbox_messages(mailbox.mailbox_id)
252
245
  if rich
253
- else await self.store.get_accepted_inbox_messages(mailbox.mailbox_id)
246
+ else await self.messaging.get_accepted_inbox_messages(mailbox.mailbox_id)
254
247
  )
255
248
 
256
249
  if message_filter:
@@ -1,15 +1,13 @@
1
1
  from fastapi import Depends, HTTPException, status
2
2
 
3
- from ..common import EnvConfig
4
- from ..dependencies import get_env_config, get_store
5
- from ..store.base import Store
3
+ from ..common.messaging import Messaging
4
+ from ..dependencies import get_messaging
6
5
  from ..views.lookup import endpoint_lookup_response, workflow_search_response
7
6
 
8
7
 
9
8
  class LookupHandler:
10
- def __init__(self, config: EnvConfig = Depends(get_env_config), store: Store = Depends(get_store)):
11
- self.config = config
12
- self.store = store
9
+ def __init__(self, messaging: Messaging = Depends(get_messaging)):
10
+ self.messaging = messaging
13
11
 
14
12
  async def lookup_by_ods_code_and_workflow(self, ods_code: str, workflow_id: str, accepts_api_version: int = 1):
15
13
 
@@ -19,7 +17,7 @@ class LookupHandler:
19
17
  if not workflow_id or (workflow_id and not workflow_id.strip()):
20
18
  raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="workflow id missing")
21
19
 
22
- mailboxes = await self.store.lookup_by_ods_code_and_workflow_id(ods_code, workflow_id)
20
+ mailboxes = await self.messaging.lookup_by_ods_code_and_workflow_id(ods_code, workflow_id)
23
21
 
24
22
  return endpoint_lookup_response(mailboxes, accepts_api_version)
25
23
 
@@ -28,6 +26,6 @@ class LookupHandler:
28
26
  if not workflow_id or (workflow_id and not workflow_id.strip()):
29
27
  raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="workflow id missing")
30
28
 
31
- mailboxes = await self.store.lookup_by_workflow_id(workflow_id)
29
+ mailboxes = await self.messaging.lookup_by_workflow_id(workflow_id)
32
30
 
33
31
  return workflow_search_response(mailboxes, accepts_api_version)