compressedfhir 1.0.2__tar.gz → 1.0.4__tar.gz
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.
Potentially problematic release.
This version of compressedfhir might be problematic. Click here for more details.
- {compressedfhir-1.0.2/compressedfhir.egg-info → compressedfhir-1.0.4}/PKG-INFO +1 -1
- compressedfhir-1.0.4/VERSION +1 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_bundle.py +4 -3
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_bundle_entry.py +1 -1
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_bundle_entry_response.py +0 -1
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_resource.py +37 -27
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_resource_list.py +1 -1
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/utilities/compressed_dict/v1/compressed_dict.py +50 -16
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/utilities/compressed_dict/v1/compressed_dict_storage_mode.py +7 -7
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/utilities/compressed_dict/v1/test/test_compressed_dict.py +107 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/utilities/fhir_json_encoder.py +1 -1
- compressedfhir-1.0.4/compressedfhir/utilities/json_serializers/test/test_type_preservation_decoder.py +150 -0
- compressedfhir-1.0.4/compressedfhir/utilities/json_serializers/test/test_type_preservation_encoder.py +59 -0
- compressedfhir-1.0.4/compressedfhir/utilities/json_serializers/test/test_type_preservation_serializer.py +171 -0
- compressedfhir-1.0.4/compressedfhir/utilities/json_serializers/type_preservation_decoder.py +110 -0
- compressedfhir-1.0.4/compressedfhir/utilities/json_serializers/type_preservation_encoder.py +50 -0
- compressedfhir-1.0.4/compressedfhir/utilities/json_serializers/type_preservation_serializer.py +57 -0
- compressedfhir-1.0.4/compressedfhir/utilities/ordered_dict_to_dict_converter/__init__.py +0 -0
- compressedfhir-1.0.4/compressedfhir/utilities/ordered_dict_to_dict_converter/ordered_dict_to_dict_converter.py +24 -0
- compressedfhir-1.0.4/compressedfhir/utilities/string_compressor/__init__.py +0 -0
- compressedfhir-1.0.4/compressedfhir/utilities/string_compressor/v1/__init__.py +0 -0
- compressedfhir-1.0.4/compressedfhir/utilities/string_compressor/v1/string_compressor.py +99 -0
- compressedfhir-1.0.4/compressedfhir/utilities/string_compressor/v1/test/__init__.py +0 -0
- compressedfhir-1.0.4/compressedfhir/utilities/string_compressor/v1/test/test_string_compressor.py +189 -0
- compressedfhir-1.0.4/compressedfhir/utilities/test/__init__.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4/compressedfhir.egg-info}/PKG-INFO +1 -1
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir.egg-info/SOURCES.txt +15 -0
- compressedfhir-1.0.4/tests/__init__.py +0 -0
- compressedfhir-1.0.2/VERSION +0 -1
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/LICENSE +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/MANIFEST.in +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/Makefile +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/README.md +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/__init__.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/__init__.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/base_resource_list.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_bundle_entry_list.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_bundle_entry_request.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_bundle_entry_search.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_identifier.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_link.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_meta.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_resource_map.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/test/__init__.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/test/test_bundle_entry.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/test/test_bundle_entry_list.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/test/test_bundle_entry_request.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/test/test_bundle_entry_response.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/test/test_fhir_bundle.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/test/test_fhir_resource.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/test/test_fhir_resource_list.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/fhir/test/test_fhir_resource_map.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/py.typed +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/utilities/__init__.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/utilities/compressed_dict/__init__.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/utilities/compressed_dict/v1/__init__.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/utilities/compressed_dict/v1/compressed_dict_access_error.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/utilities/compressed_dict/v1/test/__init__.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/utilities/json_helpers.py +0 -0
- {compressedfhir-1.0.2/compressedfhir/utilities/test → compressedfhir-1.0.4/compressedfhir/utilities/json_serializers}/__init__.py +0 -0
- {compressedfhir-1.0.2/tests → compressedfhir-1.0.4/compressedfhir/utilities/json_serializers/test}/__init__.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/utilities/test/test_fhir_json_encoder.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir/utilities/test/test_json_helpers.py +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir.egg-info/dependency_links.txt +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir.egg-info/not-zip-safe +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir.egg-info/requires.txt +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/compressedfhir.egg-info/top_level.txt +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/setup.cfg +0 -0
- {compressedfhir-1.0.2 → compressedfhir-1.0.4}/setup.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.0.4
|
|
@@ -13,6 +13,9 @@ from compressedfhir.utilities.compressed_dict.v1.compressed_dict_storage_mode im
|
|
|
13
13
|
)
|
|
14
14
|
from compressedfhir.utilities.fhir_json_encoder import FhirJSONEncoder
|
|
15
15
|
from compressedfhir.utilities.json_helpers import FhirClientJsonHelpers
|
|
16
|
+
from compressedfhir.utilities.ordered_dict_to_dict_converter.ordered_dict_to_dict_converter import (
|
|
17
|
+
OrderedDictToDictConverter,
|
|
18
|
+
)
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
class FhirBundle:
|
|
@@ -286,6 +289,4 @@ class FhirBundle:
|
|
|
286
289
|
|
|
287
290
|
:return: Plain dictionary representation of the Bundle
|
|
288
291
|
"""
|
|
289
|
-
return
|
|
290
|
-
Dict[str, Any], json.loads(json.dumps(self.dict(), cls=FhirJSONEncoder))
|
|
291
|
-
)
|
|
292
|
+
return OrderedDictToDictConverter.convert(self.dict())
|
|
@@ -65,17 +65,6 @@ class FhirResource(CompressedDict[str, Any]):
|
|
|
65
65
|
"""Convert the resource to a JSON string."""
|
|
66
66
|
return json.dumps(obj=self.dict(), cls=FhirJSONEncoder)
|
|
67
67
|
|
|
68
|
-
@classmethod
|
|
69
|
-
def from_json(cls, json_str: str) -> "FhirResource":
|
|
70
|
-
"""
|
|
71
|
-
Create a FhirResource object from a JSON string.
|
|
72
|
-
|
|
73
|
-
:param json_str: The JSON string to convert.
|
|
74
|
-
:return: A FhirResource object.
|
|
75
|
-
"""
|
|
76
|
-
data = json.loads(json_str)
|
|
77
|
-
return cls.from_dict(data)
|
|
78
|
-
|
|
79
68
|
def __deepcopy__(self, memo: Dict[int, Any]) -> "FhirResource":
|
|
80
69
|
"""Create a copy of the resource."""
|
|
81
70
|
return FhirResource(
|
|
@@ -112,22 +101,6 @@ class FhirResource(CompressedDict[str, Any]):
|
|
|
112
101
|
|
|
113
102
|
return result
|
|
114
103
|
|
|
115
|
-
@classmethod
|
|
116
|
-
def from_dict(
|
|
117
|
-
cls,
|
|
118
|
-
d: Dict[str, Any],
|
|
119
|
-
*,
|
|
120
|
-
storage_mode: CompressedDictStorageMode = CompressedDictStorageMode.default(),
|
|
121
|
-
) -> "FhirResource":
|
|
122
|
-
"""
|
|
123
|
-
Creates a FhirResource object from a dictionary.
|
|
124
|
-
|
|
125
|
-
:param d: The dictionary to convert.
|
|
126
|
-
:param storage_mode: The storage mode for the CompressedDict.
|
|
127
|
-
:return: A FhirResource object.
|
|
128
|
-
"""
|
|
129
|
-
return cls(initial_dict=d, storage_mode=storage_mode)
|
|
130
|
-
|
|
131
104
|
def remove_nulls(self) -> None:
|
|
132
105
|
"""
|
|
133
106
|
Removes None values from the resource dictionary.
|
|
@@ -161,3 +134,40 @@ class FhirResource(CompressedDict[str, Any]):
|
|
|
161
134
|
else:
|
|
162
135
|
assert isinstance(value, FhirMeta)
|
|
163
136
|
self["meta"] = value.dict()
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
@override
|
|
140
|
+
def from_json(cls, json_str: str) -> "FhirResource":
|
|
141
|
+
"""
|
|
142
|
+
Creates a FhirResource object from a JSON string.
|
|
143
|
+
|
|
144
|
+
:param json_str: JSON string representing the resource.
|
|
145
|
+
:return: A FhirResource object.
|
|
146
|
+
"""
|
|
147
|
+
return cast(FhirResource, super().from_json(json_str=json_str))
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
@override
|
|
151
|
+
def from_dict(
|
|
152
|
+
cls,
|
|
153
|
+
d: Dict[str, Any],
|
|
154
|
+
*,
|
|
155
|
+
storage_mode: CompressedDictStorageMode = CompressedDictStorageMode.default(),
|
|
156
|
+
properties_to_cache: List[str] | None = None,
|
|
157
|
+
) -> "FhirResource":
|
|
158
|
+
"""
|
|
159
|
+
Creates a FhirResource object from a dictionary.
|
|
160
|
+
|
|
161
|
+
:param d: Dictionary representing the resource.
|
|
162
|
+
:param storage_mode: Storage mode for the CompressedDict.
|
|
163
|
+
:param properties_to_cache: List of properties to cache.
|
|
164
|
+
:return: A FhirResource object.
|
|
165
|
+
"""
|
|
166
|
+
return cast(
|
|
167
|
+
FhirResource,
|
|
168
|
+
super().from_dict(
|
|
169
|
+
d=d,
|
|
170
|
+
storage_mode=storage_mode,
|
|
171
|
+
properties_to_cache=properties_to_cache,
|
|
172
|
+
),
|
|
173
|
+
)
|
|
@@ -103,7 +103,7 @@ class FhirResourceList(BaseResourceList[FhirResource]):
|
|
|
103
103
|
if len(self) == 0:
|
|
104
104
|
return resources_by_type
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
resource: FhirResource
|
|
107
107
|
for resource in [r for r in self if r is not None]:
|
|
108
108
|
resource_type: str = resource.resource_type or "unknown"
|
|
109
109
|
if resource_type not in resources_by_type:
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import copy
|
|
2
|
-
import json
|
|
3
2
|
from collections.abc import KeysView, ValuesView, ItemsView, MutableMapping
|
|
4
3
|
from contextlib import contextmanager
|
|
5
4
|
from typing import Dict, Optional, Iterator, cast, List, Any, overload, OrderedDict
|
|
@@ -14,7 +13,12 @@ from compressedfhir.utilities.compressed_dict.v1.compressed_dict_storage_mode im
|
|
|
14
13
|
CompressedDictStorageMode,
|
|
15
14
|
CompressedDictStorageType,
|
|
16
15
|
)
|
|
17
|
-
from compressedfhir.utilities.
|
|
16
|
+
from compressedfhir.utilities.json_serializers.type_preservation_serializer import (
|
|
17
|
+
TypePreservationSerializer,
|
|
18
|
+
)
|
|
19
|
+
from compressedfhir.utilities.ordered_dict_to_dict_converter.ordered_dict_to_dict_converter import (
|
|
20
|
+
OrderedDictToDictConverter,
|
|
21
|
+
)
|
|
18
22
|
|
|
19
23
|
|
|
20
24
|
class CompressedDict[K, V](MutableMapping[K, V]):
|
|
@@ -146,7 +150,7 @@ class CompressedDict[K, V](MutableMapping[K, V]):
|
|
|
146
150
|
# For serialized modes, deserialize
|
|
147
151
|
working_dict = (
|
|
148
152
|
self._deserialize_dict(
|
|
149
|
-
|
|
153
|
+
serialized_dict_bytes=self._serialized_dict,
|
|
150
154
|
storage_type=self._storage_mode.storage_type,
|
|
151
155
|
)
|
|
152
156
|
if self._serialized_dict
|
|
@@ -172,9 +176,7 @@ class CompressedDict[K, V](MutableMapping[K, V]):
|
|
|
172
176
|
assert isinstance(dictionary, OrderedDict)
|
|
173
177
|
if storage_type == "compressed":
|
|
174
178
|
# Serialize to JSON and compress with zlib
|
|
175
|
-
json_str =
|
|
176
|
-
dictionary, separators=(",", ":"), cls=FhirJSONEncoder
|
|
177
|
-
) # Most compact JSON representation
|
|
179
|
+
json_str = TypePreservationSerializer.serialize(dictionary)
|
|
178
180
|
return zlib.compress(
|
|
179
181
|
json_str.encode("utf-8"), level=zlib.Z_BEST_COMPRESSION
|
|
180
182
|
)
|
|
@@ -195,34 +197,35 @@ class CompressedDict[K, V](MutableMapping[K, V]):
|
|
|
195
197
|
@staticmethod
|
|
196
198
|
def _deserialize_dict(
|
|
197
199
|
*,
|
|
198
|
-
|
|
200
|
+
serialized_dict_bytes: bytes,
|
|
199
201
|
storage_type: CompressedDictStorageType,
|
|
200
202
|
) -> OrderedDict[K, V]:
|
|
201
203
|
"""
|
|
202
204
|
Deserialize entire dictionary from MessagePack
|
|
203
205
|
|
|
204
206
|
Args:
|
|
205
|
-
|
|
207
|
+
serialized_dict_bytes: Serialized dictionary bytes
|
|
206
208
|
|
|
207
209
|
Returns:
|
|
208
210
|
Deserialized dictionary
|
|
209
211
|
"""
|
|
210
|
-
assert
|
|
212
|
+
assert serialized_dict_bytes is not None, "Serialized dictionary cannot be None"
|
|
213
|
+
assert isinstance(serialized_dict_bytes, bytes)
|
|
211
214
|
|
|
212
215
|
if storage_type == "compressed":
|
|
213
216
|
# Decompress and parse JSON
|
|
214
|
-
|
|
215
|
-
decoded_text =
|
|
217
|
+
decompressed_bytes: bytes = zlib.decompress(serialized_dict_bytes)
|
|
218
|
+
decoded_text: str = decompressed_bytes.decode("utf-8")
|
|
216
219
|
# noinspection PyTypeChecker
|
|
217
|
-
decompressed_dict =
|
|
220
|
+
decompressed_dict = TypePreservationSerializer.deserialize(decoded_text)
|
|
218
221
|
assert isinstance(decompressed_dict, OrderedDict)
|
|
219
222
|
return cast(OrderedDict[K, V], decompressed_dict)
|
|
220
223
|
|
|
221
224
|
# Decompress if needed
|
|
222
225
|
to_unpack = (
|
|
223
|
-
zlib.decompress(
|
|
226
|
+
zlib.decompress(serialized_dict_bytes)
|
|
224
227
|
if storage_type == "compressed_msgpack"
|
|
225
|
-
else
|
|
228
|
+
else serialized_dict_bytes
|
|
226
229
|
)
|
|
227
230
|
|
|
228
231
|
# Deserialize
|
|
@@ -630,6 +633,37 @@ class CompressedDict[K, V](MutableMapping[K, V]):
|
|
|
630
633
|
Returns:
|
|
631
634
|
Plain dictionary
|
|
632
635
|
"""
|
|
633
|
-
return
|
|
634
|
-
|
|
636
|
+
return OrderedDictToDictConverter.convert(self.dict())
|
|
637
|
+
|
|
638
|
+
@classmethod
|
|
639
|
+
def from_json(cls, json_str: str) -> "CompressedDict[K, V]":
|
|
640
|
+
"""
|
|
641
|
+
Create a FhirResource object from a JSON string.
|
|
642
|
+
|
|
643
|
+
:param json_str: The JSON string to convert.
|
|
644
|
+
:return: A FhirResource object.
|
|
645
|
+
"""
|
|
646
|
+
data = TypePreservationSerializer.deserialize(json_str)
|
|
647
|
+
return cls.from_dict(data)
|
|
648
|
+
|
|
649
|
+
@classmethod
|
|
650
|
+
def from_dict(
|
|
651
|
+
cls,
|
|
652
|
+
d: Dict[K, V],
|
|
653
|
+
*,
|
|
654
|
+
storage_mode: CompressedDictStorageMode = CompressedDictStorageMode.default(),
|
|
655
|
+
properties_to_cache: List[K] | None = None,
|
|
656
|
+
) -> "CompressedDict[K, V]":
|
|
657
|
+
"""
|
|
658
|
+
Creates a FhirResource object from a dictionary.
|
|
659
|
+
|
|
660
|
+
:param d: The dictionary to convert.
|
|
661
|
+
:param storage_mode: The storage mode for the CompressedDict.
|
|
662
|
+
:param properties_to_cache: Optional list of properties to cache
|
|
663
|
+
:return: A FhirResource object.
|
|
664
|
+
"""
|
|
665
|
+
return cls(
|
|
666
|
+
initial_dict=d,
|
|
667
|
+
storage_mode=storage_mode,
|
|
668
|
+
properties_to_cache=properties_to_cache,
|
|
635
669
|
)
|
|
@@ -4,13 +4,13 @@ from typing import Literal, TypeAlias
|
|
|
4
4
|
CompressedDictStorageType: TypeAlias = Literal[
|
|
5
5
|
"raw", "compressed", "msgpack", "compressed_msgpack"
|
|
6
6
|
]
|
|
7
|
-
|
|
8
|
-
CompressedDictStorageType is a type alias for the different storage types
|
|
9
|
-
raw: No compression
|
|
10
|
-
compressed: Compressed using zlib
|
|
11
|
-
msgpack: Compressed using msgpack
|
|
12
|
-
compressed_msgpack: Compressed using msgpack with zlib
|
|
13
|
-
|
|
7
|
+
###
|
|
8
|
+
# CompressedDictStorageType is a type alias for the different storage types
|
|
9
|
+
# raw: No compression
|
|
10
|
+
# compressed: Compressed using zlib
|
|
11
|
+
# msgpack: Compressed using msgpack
|
|
12
|
+
# compressed_msgpack: Compressed using msgpack with zlib
|
|
13
|
+
###
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@dataclasses.dataclass(slots=True)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
1
3
|
import pytest
|
|
2
4
|
from typing import Any, cast
|
|
3
5
|
|
|
@@ -358,3 +360,108 @@ def test_transaction_error_handling() -> None:
|
|
|
358
360
|
# Verify the dictionary state remains unchanged
|
|
359
361
|
with compressed_dict.transaction() as d:
|
|
360
362
|
assert d.dict() == {"key1": "value1", "key2": "value2"}
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def test_nested_dict_with_datetime() -> None:
|
|
366
|
+
nested_dict = {
|
|
367
|
+
"beneficiary": {"reference": "Patient/1234567890123456703", "type": "Patient"},
|
|
368
|
+
"class": [
|
|
369
|
+
{
|
|
370
|
+
"name": "Aetna Plan",
|
|
371
|
+
"type": {
|
|
372
|
+
"coding": [
|
|
373
|
+
{
|
|
374
|
+
"code": "plan",
|
|
375
|
+
"display": "Plan",
|
|
376
|
+
"system": "http://terminology.hl7.org/CodeSystem/coverage-class",
|
|
377
|
+
}
|
|
378
|
+
]
|
|
379
|
+
},
|
|
380
|
+
"value": "AE303",
|
|
381
|
+
}
|
|
382
|
+
],
|
|
383
|
+
"costToBeneficiary": [
|
|
384
|
+
{
|
|
385
|
+
"type": {"text": "Annual Physical Exams NMC - In Network"},
|
|
386
|
+
"valueQuantity": {
|
|
387
|
+
"system": "http://aetna.com/Medicare/CostToBeneficiary/ValueQuantity/code",
|
|
388
|
+
"unit": "$",
|
|
389
|
+
"value": 50.0,
|
|
390
|
+
},
|
|
391
|
+
}
|
|
392
|
+
],
|
|
393
|
+
"id": "3456789012345670304",
|
|
394
|
+
"identifier": [
|
|
395
|
+
{
|
|
396
|
+
"system": "https://sources.aetna.com/coverage/identifier/membershipid/59",
|
|
397
|
+
"type": {
|
|
398
|
+
"coding": [
|
|
399
|
+
{
|
|
400
|
+
"code": "SN",
|
|
401
|
+
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
|
402
|
+
}
|
|
403
|
+
]
|
|
404
|
+
},
|
|
405
|
+
"value": "435679010300+AE303+2021-01-01",
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
"id": "uuid",
|
|
409
|
+
"system": "https://www.icanbwell.com/uuid",
|
|
410
|
+
"value": "92266603-aa8b-58c6-99bd-326fd1da1896",
|
|
411
|
+
},
|
|
412
|
+
],
|
|
413
|
+
"meta": {
|
|
414
|
+
"security": [
|
|
415
|
+
{"code": "aetna", "system": "https://www.icanbwell.com/owner"},
|
|
416
|
+
{"code": "aetna", "system": "https://www.icanbwell.com/access"},
|
|
417
|
+
{"code": "aetna", "system": "https://www.icanbwell.com/vendor"},
|
|
418
|
+
{"code": "proa", "system": "https://www.icanbwell.com/connectionType"},
|
|
419
|
+
],
|
|
420
|
+
"source": "http://mock-server:1080/test_patient_access_transformer/source/4_0_0/Coverage/3456789012345670304",
|
|
421
|
+
},
|
|
422
|
+
"network": "Medicare - MA/NY/NJ - Full Reciprocity",
|
|
423
|
+
"payor": [
|
|
424
|
+
{
|
|
425
|
+
"display": "Aetna",
|
|
426
|
+
"reference": "Organization/6667778889990000015",
|
|
427
|
+
"type": "Organization",
|
|
428
|
+
}
|
|
429
|
+
],
|
|
430
|
+
"period": {
|
|
431
|
+
"end": datetime.fromisoformat("2021-12-31").date(),
|
|
432
|
+
"start": datetime.fromisoformat("2021-01-01").date(),
|
|
433
|
+
},
|
|
434
|
+
"policyHolder": {"reference": "Patient/1234567890123456703", "type": "Patient"},
|
|
435
|
+
"relationship": {
|
|
436
|
+
"coding": [
|
|
437
|
+
{
|
|
438
|
+
"code": "self",
|
|
439
|
+
"system": "http://terminology.hl7.org/CodeSystem/subscriber-relationship",
|
|
440
|
+
}
|
|
441
|
+
]
|
|
442
|
+
},
|
|
443
|
+
"resourceType": "Coverage",
|
|
444
|
+
"status": "active",
|
|
445
|
+
"subscriber": {"reference": "Patient/1234567890123456703", "type": "Patient"},
|
|
446
|
+
"subscriberId": "435679010300",
|
|
447
|
+
"type": {
|
|
448
|
+
"coding": [
|
|
449
|
+
{
|
|
450
|
+
"code": "PPO",
|
|
451
|
+
"display": "preferred provider organization policy",
|
|
452
|
+
"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
|
|
453
|
+
}
|
|
454
|
+
]
|
|
455
|
+
},
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
compressed_dict = CompressedDict(
|
|
459
|
+
initial_dict=nested_dict,
|
|
460
|
+
storage_mode=CompressedDictStorageMode.compressed(),
|
|
461
|
+
properties_to_cache=[],
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
plain_dict = compressed_dict.to_plain_dict()
|
|
465
|
+
|
|
466
|
+
assert plain_dict["period"]["start"] == nested_dict["period"]["start"] # type: ignore[index]
|
|
467
|
+
assert plain_dict == nested_dict
|
|
@@ -18,7 +18,7 @@ class FhirJSONEncoder(json.JSONEncoder):
|
|
|
18
18
|
def default(self, o: Any) -> Any:
|
|
19
19
|
# Existing type handlers
|
|
20
20
|
if dataclasses.is_dataclass(o):
|
|
21
|
-
return dataclasses.asdict(o) # type:ignore
|
|
21
|
+
return dataclasses.asdict(o) # type:ignore[arg-type]
|
|
22
22
|
|
|
23
23
|
if isinstance(o, Enum):
|
|
24
24
|
return o.value
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
from datetime import datetime, date
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
from typing import Type, Any, Dict, Optional
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from compressedfhir.utilities.json_serializers.type_preservation_decoder import (
|
|
7
|
+
TypePreservationDecoder,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestCustomObject:
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
name: str,
|
|
15
|
+
value: int,
|
|
16
|
+
created_at: Optional[datetime] = None,
|
|
17
|
+
nested_data: Optional[Dict[str, Any]] = None,
|
|
18
|
+
):
|
|
19
|
+
self.name: str = name
|
|
20
|
+
self.value: int = value
|
|
21
|
+
self.created_at: Optional[datetime] = created_at
|
|
22
|
+
self.nested_data: Optional[Dict[str, Any]] = nested_data
|
|
23
|
+
|
|
24
|
+
def __eq__(self, other: Any) -> bool:
|
|
25
|
+
if not isinstance(other, TestCustomObject):
|
|
26
|
+
return False
|
|
27
|
+
return (
|
|
28
|
+
self.name == other.name
|
|
29
|
+
and self.value == other.value
|
|
30
|
+
and self.created_at == other.created_at
|
|
31
|
+
and self.nested_data == other.nested_data
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.mark.parametrize(
|
|
36
|
+
"input_type, input_dict, expected_type",
|
|
37
|
+
[
|
|
38
|
+
(
|
|
39
|
+
"datetime",
|
|
40
|
+
{"__type__": "datetime", "iso": "2023-01-01T00:00:00+00:00"},
|
|
41
|
+
datetime,
|
|
42
|
+
),
|
|
43
|
+
("date", {"__type__": "date", "iso": "2023-01-01"}, date),
|
|
44
|
+
("decimal", {"__type__": "decimal", "value": "3.14"}, Decimal),
|
|
45
|
+
("complex", {"__type__": "complex", "real": 3, "imag": 4}, complex),
|
|
46
|
+
("bytes", {"__type__": "bytes", "value": "test"}, bytes),
|
|
47
|
+
("set", {"__type__": "set", "values": [1, 2, 3]}, set),
|
|
48
|
+
],
|
|
49
|
+
)
|
|
50
|
+
def test_complex_type_decoding(
|
|
51
|
+
input_type: str, input_dict: Dict[str, Any], expected_type: Type[Any]
|
|
52
|
+
) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Test decoding of various complex types
|
|
55
|
+
"""
|
|
56
|
+
decoded = TypePreservationDecoder.decode(input_dict)
|
|
57
|
+
assert isinstance(decoded, expected_type)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_custom_object_decoding() -> None:
|
|
61
|
+
"""
|
|
62
|
+
Test decoding of custom objects
|
|
63
|
+
"""
|
|
64
|
+
custom_obj_dict = {
|
|
65
|
+
"__type__": "TestCustomObject",
|
|
66
|
+
"__module__": __name__,
|
|
67
|
+
"attributes": {"name": "test", "value": 42},
|
|
68
|
+
}
|
|
69
|
+
decoded = TypePreservationDecoder.decode(custom_obj_dict)
|
|
70
|
+
assert isinstance(decoded, TestCustomObject)
|
|
71
|
+
assert decoded.name == "test"
|
|
72
|
+
assert decoded.value == 42
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_custom_decoder() -> None:
|
|
76
|
+
"""
|
|
77
|
+
Test custom decoder functionality
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def custom_decoder(data: Dict[str, Any]) -> Any:
|
|
81
|
+
if data.get("__type__") == "special_type":
|
|
82
|
+
return f"Decoded: {data['value']}"
|
|
83
|
+
return data
|
|
84
|
+
|
|
85
|
+
special_dict = {"__type__": "special_type", "value": "test"}
|
|
86
|
+
decoded = TypePreservationDecoder.decode(
|
|
87
|
+
special_dict, custom_decoders={"special_type": custom_decoder}
|
|
88
|
+
)
|
|
89
|
+
assert decoded == "Decoded: test"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_nested_datetime_decoding() -> None:
|
|
93
|
+
"""
|
|
94
|
+
Test decoding of nested datetime fields
|
|
95
|
+
"""
|
|
96
|
+
nested_datetime_dict = {
|
|
97
|
+
"__type__": "TestCustomObject",
|
|
98
|
+
"__module__": __name__,
|
|
99
|
+
"attributes": {
|
|
100
|
+
"name": "test",
|
|
101
|
+
"value": 42,
|
|
102
|
+
"created_at": {"__type__": "datetime", "iso": "2023-06-15T10:30:00"},
|
|
103
|
+
"nested_data": {
|
|
104
|
+
"timestamp": {"__type__": "datetime", "iso": "2023-06-16T15:45:00"}
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
decoded: TestCustomObject = TypePreservationDecoder.decode(nested_datetime_dict)
|
|
110
|
+
|
|
111
|
+
assert isinstance(decoded, TestCustomObject)
|
|
112
|
+
assert decoded.name == "test"
|
|
113
|
+
assert decoded.value == 42
|
|
114
|
+
|
|
115
|
+
# Check nested datetime fields
|
|
116
|
+
assert hasattr(decoded, "created_at")
|
|
117
|
+
assert isinstance(decoded.created_at, datetime)
|
|
118
|
+
assert decoded.created_at.year == 2023
|
|
119
|
+
assert decoded.created_at.month == 6
|
|
120
|
+
assert decoded.created_at.day == 15
|
|
121
|
+
|
|
122
|
+
assert hasattr(decoded, "nested_data")
|
|
123
|
+
assert isinstance(decoded.nested_data, dict)
|
|
124
|
+
assert "timestamp" in decoded.nested_data
|
|
125
|
+
assert isinstance(decoded.nested_data["timestamp"], datetime)
|
|
126
|
+
assert decoded.nested_data["timestamp"].year == 2023
|
|
127
|
+
assert decoded.nested_data["timestamp"].month == 6
|
|
128
|
+
assert decoded.nested_data["timestamp"].day == 16
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_direct_value_decoding() -> None:
|
|
132
|
+
"""
|
|
133
|
+
Test decoding of direct values without type markers
|
|
134
|
+
"""
|
|
135
|
+
# Test datetime direct string
|
|
136
|
+
datetime_str = "2023-01-01T00:00:00"
|
|
137
|
+
decoded_datetime = TypePreservationDecoder.decode(datetime_str)
|
|
138
|
+
assert decoded_datetime == datetime_str
|
|
139
|
+
|
|
140
|
+
# Test list with mixed types
|
|
141
|
+
mixed_list = [
|
|
142
|
+
{"__type__": "datetime", "iso": "2023-06-15T10:30:00"},
|
|
143
|
+
42,
|
|
144
|
+
"plain string",
|
|
145
|
+
]
|
|
146
|
+
decoded_list = TypePreservationDecoder.decode(mixed_list)
|
|
147
|
+
assert len(decoded_list) == 3
|
|
148
|
+
assert isinstance(decoded_list[0], datetime)
|
|
149
|
+
assert decoded_list[1] == 42
|
|
150
|
+
assert decoded_list[2] == "plain string"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from datetime import datetime, timezone, date
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
from typing import Type, Any
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from compressedfhir.utilities.json_serializers.type_preservation_encoder import (
|
|
8
|
+
TypePreservationEncoder,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestCustomObject:
|
|
13
|
+
def __init__(self, name: str, value: int):
|
|
14
|
+
self.name: str = name
|
|
15
|
+
self.value: int = value
|
|
16
|
+
|
|
17
|
+
def __eq__(self, other: Any) -> bool:
|
|
18
|
+
if not isinstance(other, TestCustomObject):
|
|
19
|
+
return False
|
|
20
|
+
return self.name == other.name and self.value == other.value
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.mark.parametrize(
|
|
24
|
+
"input_type, input_value, expected_type",
|
|
25
|
+
[
|
|
26
|
+
(datetime, datetime(2023, 1, 1, tzinfo=timezone.utc), "datetime"),
|
|
27
|
+
(date, date(2023, 1, 1), "date"),
|
|
28
|
+
(Decimal, Decimal("3.14"), "decimal"),
|
|
29
|
+
(complex, 3 + 4j, "complex"),
|
|
30
|
+
(bytes, b"test", "bytes"),
|
|
31
|
+
(set, {1, 2, 3}, "set"),
|
|
32
|
+
],
|
|
33
|
+
)
|
|
34
|
+
def test_complex_type_serialization(
|
|
35
|
+
input_type: Type[Any], input_value: Any, expected_type: str
|
|
36
|
+
) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Test serialization of various complex types
|
|
39
|
+
"""
|
|
40
|
+
encoder = TypePreservationEncoder()
|
|
41
|
+
serialized = encoder.default(input_value)
|
|
42
|
+
|
|
43
|
+
assert isinstance(serialized, dict)
|
|
44
|
+
assert serialized.get("__type__") == expected_type
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# noinspection PyMethodMayBeStatic
|
|
48
|
+
def test_custom_object_serialization() -> None:
|
|
49
|
+
"""
|
|
50
|
+
Test serialization of custom objects
|
|
51
|
+
"""
|
|
52
|
+
custom_obj = TestCustomObject("test", 42)
|
|
53
|
+
encoder = TypePreservationEncoder()
|
|
54
|
+
serialized = encoder.default(custom_obj)
|
|
55
|
+
|
|
56
|
+
assert isinstance(serialized, dict)
|
|
57
|
+
assert serialized.get("__type__") == "TestCustomObject"
|
|
58
|
+
assert serialized.get("__module__") == __name__
|
|
59
|
+
assert "attributes" in serialized
|