Visitar URL original
Merge pull request #49 from NHSDigital/mm-mesh-1786-generic-retrieve-… · NHSDigital/mesh-client@8fb4ac9 · GitHub
Skip to content

Commit 8fb4ac9

Browse files
authored
Merge pull request #49 from NHSDigital/mm-mesh-1786-generic-retrieve-chunk
mesh-1786: generic retrieve chunk
2 parents 4bad0ea + de7a80b commit 8fb4ac9

5 files changed

Lines changed: 108 additions & 64 deletions

File tree

‎mesh_client/__init__.py‎

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def _get_version(*names: str) -> str:
7373
LIVE_CA_CERT = os.path.join(_PACKAGE_DIR, "nhs-live-ca-bundle.pem")
7474

7575

76-
_OPTIONAL_HEADERS = {
76+
_OPTIONAL_HEADERS: Dict[str, str] = {
7777
"workflow_id": "Mex-WorkflowID",
7878
"filename": "Mex-FileName",
7979
"local_id": "Mex-LocalID",
@@ -85,6 +85,11 @@ def _get_version(*names: str) -> str:
8585
"content_type": "Content-Type",
8686
}
8787

88+
89+
def optional_header_map() -> Dict[str, str]:
90+
return _OPTIONAL_HEADERS.copy()
91+
92+
8893
_SEND_HEADERS = {**_OPTIONAL_HEADERS}
8994
_SEND_HEADERS.update(
9095
{
@@ -345,7 +350,8 @@ def __init__( # noqa: C901
345350
and whether messages should be compressed, transparently, before
346351
sending.
347352
"""
348-
353+
if isinstance(shared_key, str):
354+
shared_key = shared_key.encode(encoding="utf-8")
349355
shared_key = shared_key or get_shared_key_from_environ()
350356

351357
self._mailbox = mailbox
@@ -534,44 +540,52 @@ def retrieve_message(self, message_id: str) -> "Message":
534540
https://digital.nhs.uk/developer/api-catalogue/message-exchange-for-social-care-and-health-api#get-/messageexchange/-mailbox_id-/inbox/-message_id-
535541
"""
536542
message_id = getattr(message_id, "_msg_id", message_id)
537-
response = self._session.get(f"{self.mailbox_url}/inbox/{q(message_id)}", stream=True, timeout=self._timeout)
538-
response.raise_for_status()
543+
response = self.retrieve_message_chunk(message_id, chunk_num=1)
539544
return Message(message_id, response, self)
540545

541-
def retrieve_message_chunk(self, message_id: str, chunk_num: Union[int, str]) -> Response:
546+
def retrieve_message_chunk(self, message_id: str, chunk_num: int) -> Response:
542547
"""
543548
get a chunk
544549
https://digital.nhs.uk/developer/api-catalogue/message-exchange-for-social-care-and-health-api#get-/messageexchange/-mailbox_id-/inbox/-message_id-/-chunk_number-
545550
Args:
546-
message_id (str): message id to receive
547-
chunk_num (int): chunk number
551+
message_id: message id to receive
552+
chunk_num: chunk number
548553
549554
Returns:
550555
Response: http response
551556
"""
557+
chunk_num = int(chunk_num)
558+
uri = (
559+
f"{self.mailbox_url}/inbox/{q(message_id)}/{chunk_num}"
560+
if chunk_num > 1
561+
else f"{self.mailbox_url}/inbox/{q(message_id)}"
562+
)
563+
552564
response = self._session.get(
553-
f"{self.mailbox_url}/inbox/{q(message_id)}/{chunk_num}",
565+
uri,
554566
stream=True,
555567
timeout=self._timeout,
556568
)
557569
response.raise_for_status()
558570
return response
559571

560572
@staticmethod
561-
def _headers(recipient: str, chunk_no: int, total_chunks: int, compress: bool, **kwargs) -> Dict[str, str]:
573+
def _headers_for_chunk(
574+
recipient: str, chunk_num: int, total_chunks: int, compress: bool, **kwargs
575+
) -> Dict[str, str]:
562576
content_type = kwargs.get("content_type", "application/octet-stream")
563-
if chunk_no > 1:
577+
if chunk_num > 1:
564578
headers = {
565579
"Content-Type": content_type,
566-
"Mex-Chunk-Range": f"{chunk_no}:{total_chunks}",
580+
"Mex-Chunk-Range": f"{chunk_num}:{total_chunks}",
567581
}
568582
if compress:
569583
headers["Content-Encoding"] = "gzip"
570584
return headers
571585

572586
headers = {
573587
"Content-Type": content_type,
574-
"Mex-Chunk-Range": f"{chunk_no}:{total_chunks}",
588+
"Mex-Chunk-Range": f"{chunk_num}:{total_chunks}",
575589
"Mex-To": recipient,
576590
}
577591

@@ -593,7 +607,7 @@ def send_chunk(
593607
self,
594608
recipient: str,
595609
chunk,
596-
chunk_no: int,
610+
chunk_num: int,
597611
total_chunks: int,
598612
compress: Optional[bool] = None,
599613
message_id: Optional[str] = None,
@@ -604,36 +618,36 @@ def send_chunk(
604618
Args:
605619
recipient (str): mailbox id to send to
606620
chunk: data to send
607-
chunk_no: chunk number >= 1
621+
chunk_num: chunk number >= 1
608622
total_chunks: total number of chunks >= 1
609623
compress: instruction to compress this (overrides the 'transparent_compress' at the mailbox level)
610-
message_id: message id, required if chunk_no > 1
624+
message_id: message id, required if chunk_num > 1
611625
**kwargs: optional mesh header args workflow_id, filename, local_id, subject, encrypted,
612626
compressed, checksum, partner_id, content_type
613627
614628
Returns:
615629
Response: raw http response
616630
"""
617-
631+
chunk_num = int(chunk_num)
618632
compress = self._transparent_compress if compress is None else compress
619633

620634
def maybe_compressed(maybe_compress: bytes):
621635
if not compress:
622636
return maybe_compress
623637
return GzipCompressStream(maybe_compress)
624638

625-
headers = self._headers(
626-
recipient=recipient, chunk_no=chunk_no, total_chunks=total_chunks, compress=compress, **kwargs
639+
headers = self._headers_for_chunk(
640+
recipient=recipient, chunk_num=chunk_num, total_chunks=total_chunks, compress=compress, **kwargs
627641
)
628642

629643
data = maybe_compressed(chunk)
630644

631645
buffer = data
632-
if chunk_no > 1 and self._retries:
646+
if chunk_num > 1 and self._retries:
633647
# urllib3 body_pos requires a seekable stream to allow rewinding on retry ( we never retry the first chunk )
634648
buffer = BytesIO(data.read() if hasattr(data, "read") else data)
635649

636-
if chunk_no == 1:
650+
if chunk_num == 1:
637651
assert not message_id, "message_id should not be sent with the first chunk"
638652

639653
response = self._session.post(
@@ -651,7 +665,7 @@ def maybe_compressed(maybe_compress: bytes):
651665
assert message_id, "message_id is required for chunks number >= 2"
652666

653667
response = self._session.post(
654-
f"{self.mailbox_url}/outbox/{q(message_id)}/{chunk_no}",
668+
f"{self.mailbox_url}/outbox/{q(message_id)}/{chunk_num}",
655669
data=buffer,
656670
headers=headers,
657671
timeout=self._timeout,
@@ -715,7 +729,7 @@ def send_message(
715729
total_chunks = len(chunks)
716730

717731
response1 = self.send_chunk(
718-
recipient=recipient, chunk=first_chunk, chunk_no=1, total_chunks=total_chunks, compress=compress, **kwargs
732+
recipient=recipient, chunk=first_chunk, chunk_num=1, total_chunks=total_chunks, compress=compress, **kwargs
719733
)
720734

721735
response_dict = response1.json()
@@ -734,7 +748,7 @@ def send_message(
734748
_response = self.send_chunk(
735749
recipient=recipient,
736750
chunk=chunk,
737-
chunk_no=chunk_num,
751+
chunk_num=chunk_num,
738752
message_id=message_id,
739753
total_chunks=total_chunks,
740754
compress=compress,
@@ -937,10 +951,10 @@ def __init__(self, msg_id: str, response, client):
937951
def maybe_decompress(resp):
938952
return GzipDecompressStream(resp.raw) if resp.headers.get("Content-Encoding") == "gzip" else resp.raw
939953

940-
self._response = CombineStreams(
954+
self._stream = CombineStreams(
941955
chain(
942956
[maybe_decompress(response)],
943-
(maybe_decompress(client.retrieve_message_chunk(msg_id, str(i + 2))) for i in range(chunk_count - 1)),
957+
(maybe_decompress(client.retrieve_message_chunk(msg_id, i + 2)) for i in range(chunk_count - 1)),
944958
)
945959
)
946960

@@ -957,27 +971,27 @@ def read(self, n=None) -> bytes:
957971
Read up to n bytes from the message, or read the remainder of the
958972
message, if n is not provided.
959973
"""
960-
return self._response.read(n)
974+
return self._stream.read(n)
961975

962976
def readline(self) -> bytes:
963977
"""
964978
Read a single line from the message
965979
"""
966-
return self._response.readline()
980+
return self._stream.readline()
967981

968982
def readlines(self) -> List[bytes]:
969983
"""
970984
Read all lines from the message
971985
"""
972-
return self._response.readlines()
986+
return self._stream.readlines()
973987

974988
def close(self):
975989
"""Close the stream underlying this message"""
976-
if hasattr(self._response, "close"):
990+
if hasattr(self._stream, "close"):
977991
try:
978-
self._response.close()
992+
self._stream.close()
979993
finally:
980-
self._response = None # type: ignore[assignment]
994+
self._stream = None # type: ignore[assignment]
981995

982996
def acknowledge(self):
983997
"""
@@ -1014,7 +1028,7 @@ def __iter__(self):
10141028
"""
10151029
Iterate through lines of the message
10161030
"""
1017-
return iter(self._response)
1031+
return iter(self._stream)
10181032

10191033

10201034
class Message(_BaseMessage, _MessageAttrs):

‎poetry.lock‎

Lines changed: 39 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎pyproject.toml‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ types-pkg-resources = "^0.1.3"
4545
coverage = "^7.2.7"
4646
pytest = "^7.4.0"
4747
importlib-metadata = {version = ">=4.11.4", python = "<3.12"}
48-
#importlib-resources = {version = "*", python = "<3.9"}
4948
pytest-httpserver = {version = "^1.0.8", python = ">=3.8,<4.0"}
5049
ruff = "^0.1.5"
5150
boto3 = "^1.33.7"

‎tests/mesh_noretry_send_chunk_tests.py‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,14 @@ def test_chunk_retries(httpserver: HTTPServer, alice: MeshClient, bob: MeshClien
6161
def send_chunk_handler(request: Request):
6262
last_path = request.path.split("/")[-1]
6363

64-
chunk_no = int(last_path) if last_path.isdigit() else 1
65-
chunk_call_counts[chunk_no] += 1
64+
chunk_num = int(last_path) if last_path.isdigit() else 1
65+
chunk_call_counts[chunk_num] += 1
6666

67-
if chunk_no == 1:
67+
if chunk_num == 1:
6868
received_chunks.append(request.data)
6969
return json_response(cast(SendMessageResponse_v2, {"message_id": message_id}), status=202)
7070

71-
if chunk_no == 2 and chunk_call_counts[chunk_no] < 4:
71+
if chunk_num == 2 and chunk_call_counts[chunk_num] < 4:
7272
return plain_response("", status=502)
7373

7474
received_chunks.append(request.data)
@@ -101,10 +101,10 @@ def test_chunk_all_retries_fail(httpserver: HTTPServer, alice: MeshClient, bob:
101101
def send_chunk_handler(request: Request):
102102
last_path = request.path.split("/")[-1]
103103

104-
chunk_no = int(last_path) if last_path.isdigit() else 1
105-
chunk_call_counts[chunk_no] += 1
104+
chunk_num = int(last_path) if last_path.isdigit() else 1
105+
chunk_call_counts[chunk_num] += 1
106106

107-
if chunk_no == 1:
107+
if chunk_num == 1:
108108
received_chunks.append(request.data)
109109
return json_response(cast(SendMessageResponse_v2, {"message_id": message_id}), status=202)
110110

@@ -139,14 +139,14 @@ def test_chunk_retries_with_file(httpserver: HTTPServer, alice: MeshClient, bob:
139139
def send_chunk_handler(request: Request):
140140
last_path = request.path.split("/")[-1]
141141

142-
chunk_no = int(last_path) if last_path.isdigit() else 1
143-
chunk_call_counts[chunk_no] += 1
142+
chunk_num = int(last_path) if last_path.isdigit() else 1
143+
chunk_call_counts[chunk_num] += 1
144144

145-
if chunk_no == 1:
145+
if chunk_num == 1:
146146
received_chunks.append(request.data)
147147
return json_response(cast(SendMessageResponse_v2, {"message_id": message_id}), status=202)
148148

149-
if chunk_no == 2 and chunk_call_counts[chunk_no] < 4:
149+
if chunk_num == 2 and chunk_call_counts[chunk_num] < 4:
150150
return plain_response("", status=502)
151151

152152
received_chunks.append(request.data)

0 commit comments

Comments
 (0)