compressedfhir 1.0.2__py3-none-any.whl → 1.0.4__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.

Potentially problematic release.


This version of compressedfhir might be problematic. Click here for more details.

Files changed (29) hide show
  1. compressedfhir/fhir/fhir_bundle.py +4 -3
  2. compressedfhir/fhir/fhir_bundle_entry.py +1 -1
  3. compressedfhir/fhir/fhir_bundle_entry_response.py +0 -1
  4. compressedfhir/fhir/fhir_resource.py +37 -27
  5. compressedfhir/fhir/fhir_resource_list.py +1 -1
  6. compressedfhir/utilities/compressed_dict/v1/compressed_dict.py +50 -16
  7. compressedfhir/utilities/compressed_dict/v1/compressed_dict_storage_mode.py +7 -7
  8. compressedfhir/utilities/compressed_dict/v1/test/test_compressed_dict.py +107 -0
  9. compressedfhir/utilities/fhir_json_encoder.py +1 -1
  10. compressedfhir/utilities/json_serializers/__init__.py +0 -0
  11. compressedfhir/utilities/json_serializers/test/__init__.py +0 -0
  12. compressedfhir/utilities/json_serializers/test/test_type_preservation_decoder.py +150 -0
  13. compressedfhir/utilities/json_serializers/test/test_type_preservation_encoder.py +59 -0
  14. compressedfhir/utilities/json_serializers/test/test_type_preservation_serializer.py +171 -0
  15. compressedfhir/utilities/json_serializers/type_preservation_decoder.py +110 -0
  16. compressedfhir/utilities/json_serializers/type_preservation_encoder.py +50 -0
  17. compressedfhir/utilities/json_serializers/type_preservation_serializer.py +57 -0
  18. compressedfhir/utilities/ordered_dict_to_dict_converter/__init__.py +0 -0
  19. compressedfhir/utilities/ordered_dict_to_dict_converter/ordered_dict_to_dict_converter.py +24 -0
  20. compressedfhir/utilities/string_compressor/__init__.py +0 -0
  21. compressedfhir/utilities/string_compressor/v1/__init__.py +0 -0
  22. compressedfhir/utilities/string_compressor/v1/string_compressor.py +99 -0
  23. compressedfhir/utilities/string_compressor/v1/test/__init__.py +0 -0
  24. compressedfhir/utilities/string_compressor/v1/test/test_string_compressor.py +189 -0
  25. {compressedfhir-1.0.2.dist-info → compressedfhir-1.0.4.dist-info}/METADATA +1 -1
  26. {compressedfhir-1.0.2.dist-info → compressedfhir-1.0.4.dist-info}/RECORD +29 -14
  27. {compressedfhir-1.0.2.dist-info → compressedfhir-1.0.4.dist-info}/WHEEL +0 -0
  28. {compressedfhir-1.0.2.dist-info → compressedfhir-1.0.4.dist-info}/licenses/LICENSE +0 -0
  29. {compressedfhir-1.0.2.dist-info → compressedfhir-1.0.4.dist-info}/top_level.txt +0 -0
@@ -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 cast(
290
- Dict[str, Any], json.loads(json.dumps(self.dict(), cls=FhirJSONEncoder))
291
- )
292
+ return OrderedDictToDictConverter.convert(self.dict())
@@ -217,7 +217,7 @@ class FhirBundleEntry:
217
217
  return (
218
218
  f"BundleEntry({self.resource.resource_type}/{self.resource.id})"
219
219
  if self.resource
220
- else f"BundleEntry(Empty)"
220
+ else "BundleEntry(Empty)"
221
221
  )
222
222
 
223
223
  def copy(self) -> "FhirBundleEntry":
@@ -41,7 +41,6 @@ class FhirBundleEntryResponse:
41
41
  def from_dict(
42
42
  cls, d: Dict[str, Any] | OrderedDict[str, Any]
43
43
  ) -> "FhirBundleEntryResponse":
44
-
45
44
  date_last_modified: Optional[datetime] = None
46
45
  if "lastModified" in d:
47
46
  if isinstance(d["lastModified"], datetime):
@@ -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
- entry: FhirResource
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.fhir_json_encoder import FhirJSONEncoder
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
- serialized_dict=self._serialized_dict,
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 = json.dumps(
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
- serialized_dict: bytes,
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
- serialized_dict: Serialized dictionary bytes
207
+ serialized_dict_bytes: Serialized dictionary bytes
206
208
 
207
209
  Returns:
208
210
  Deserialized dictionary
209
211
  """
210
- assert serialized_dict is not None, "Serialized dictionary cannot be None"
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
- decompressed = zlib.decompress(serialized_dict)
215
- decoded_text = decompressed.decode("utf-8")
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 = json.loads(decoded_text, object_pairs_hook=OrderedDict)
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(serialized_dict)
226
+ zlib.decompress(serialized_dict_bytes)
224
227
  if storage_type == "compressed_msgpack"
225
- else serialized_dict
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 cast(
634
- Dict[K, V], json.loads(json.dumps(self.dict(), cls=FhirJSONEncoder))
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
File without changes
@@ -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