-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor message serialisation and deserialisation (#197)
* Refactor message serialization and deserialization Addiing `Message` and `SerialisedMessage` classes in attempt to improve information hiding and decoupling. * Rename `utils.py` -> `message.py` * Add `decode()` method for `SerialisedMessage` * Update docstring * Use new classes in message testing * Refactor message processing in the CLI * Refactor `process_message` to use `SerialisedMessage` class in EHR API * Refactor `process_message` to use `SerialisedMessage` class in imaging API * Fix `ImagingStudy` initalisation in `ImagingStudy.from_message()` * Fix imports * Fix test: access serialised message bodies * Turn `Message` into a `dataclass` * Fix failing tests * Use `jsonpickle` for (de)serializing messages This also removes the need for the `SerialisedMessage` class * Fix `test_deserialise_datetime()` so it uses the `Message` class to assert the `study_datetime` * Add `study_datetime` property for `Message` * No need to test deserialising individual fields, already covered by `test_deserialise()` which deserialises the entire object * Remove `study_date_from_serialised()`, use the class attribute `study_datetime` instead * Revert "Add `study_datetime` property for `Message`" This reverts commit 8719153. * Remove `Messages` class, use `list[Message]` instead * Add type checking for messages parsed from parquet input * Update `test_messages_from_parquet()` to use JSON strings instead of bytes * Update `PixlProducer.publish()` to use a list of Message objects and handle serialisation * Convert JSON string to bytes when serialising * Revert "Update `test_messages_from_parquet()` to use JSON strings instead of bytes" This reverts commit 0e4fce4. * `PixlProducer.publish()` should take a `list[Message]` as input in tests * Update EHR API to use new `Message` design * Update imaging API to use new `Message` design * Update deserialise function to accept bytes-encoded JSON string * Assert messages against list of `Message`s * Print dataclass in logs * `jsonpickle.decode()` can handle bytes so no need to decode first Also add a note about why we ignore ruff rule S301 * Make `deserialisable` a keyword only argument * Copilot forgot to convert dates to datetimes 🥲 * Refactor PixlConsumer run method to accept Message object as callback parameter and deserialise * Update consumer in `test_subscriber` to accept Message object instead of bytes
- Loading branch information
Showing
15 changed files
with
253 additions
and
241 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
# Copyright (c) 2022 University College London Hospitals NHS Foundation Trust | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
"""Classes to represent messages in the patient queue.""" | ||
|
||
import logging | ||
from dataclasses import dataclass | ||
from datetime import datetime | ||
from typing import Any | ||
|
||
from jsonpickle import decode, encode | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
@dataclass | ||
class Message: | ||
"""Class to represent a message containing the relevant information for a study.""" | ||
|
||
mrn: str | ||
accession_number: str | ||
study_datetime: datetime | ||
procedure_occurrence_id: str | ||
project_name: str | ||
omop_es_timestamp: datetime | ||
|
||
def serialise(self, *, deserialisable: bool = True) -> bytes: | ||
""" | ||
Serialise the message into a JSON string and convert to bytes. | ||
:param deserialisable: If True, the serialised message will be deserialisable, by setting | ||
the unpicklable flag to False in jsonpickle.encode(), meaning that the original Message | ||
object can be recovered by `deserialise()`. If False, calling `deserialise()` on the | ||
serialised message will return a dictionary. | ||
""" | ||
msg = ( | ||
"Serialising message with\n" | ||
" * patient id: %s\n" | ||
" * accession number: %s\n" | ||
" * timestamp: %s\n" | ||
" * procedure_occurrence_id: %s\n", | ||
" * project_name: %s\n * omop_es_timestamp: %s", | ||
self.mrn, | ||
self.accession_number, | ||
self.study_datetime, | ||
self.procedure_occurrence_id, | ||
self.project_name, | ||
self.omop_es_timestamp, | ||
) | ||
logger.debug(msg) | ||
|
||
return str.encode(encode(self, unpicklable=deserialisable)) | ||
|
||
|
||
def deserialise(serialised_msg: bytes) -> Any: | ||
""" | ||
Deserialise a message from a bytes-encoded JSON string. | ||
If the message was serialised with `deserialisable=True`, the original Message object will be | ||
returned. Otherwise, a dictionary will be returned. | ||
:param serialised_msg: The serialised message. | ||
""" | ||
return decode(serialised_msg) # noqa: S301, since we control the input, so no security risks |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.