compressedfhir 1.0.3__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.

Files changed (72) hide show
  1. {compressedfhir-1.0.3/compressedfhir.egg-info → compressedfhir-1.0.4}/PKG-INFO +1 -1
  2. compressedfhir-1.0.4/VERSION +1 -0
  3. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/compressed_dict/v1/compressed_dict.py +2 -6
  4. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/compressed_dict/v1/test/test_compressed_dict.py +107 -0
  5. compressedfhir-1.0.4/compressedfhir/utilities/json_serializers/test/test_type_preservation_decoder.py +150 -0
  6. compressedfhir-1.0.4/compressedfhir/utilities/json_serializers/test/test_type_preservation_serializer.py +171 -0
  7. compressedfhir-1.0.4/compressedfhir/utilities/json_serializers/type_preservation_decoder.py +110 -0
  8. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/json_serializers/type_preservation_serializer.py +3 -1
  9. {compressedfhir-1.0.3 → compressedfhir-1.0.4/compressedfhir.egg-info}/PKG-INFO +1 -1
  10. compressedfhir-1.0.3/VERSION +0 -1
  11. compressedfhir-1.0.3/compressedfhir/utilities/json_serializers/test/test_type_preservation_decoder.py +0 -82
  12. compressedfhir-1.0.3/compressedfhir/utilities/json_serializers/test/test_type_preservation_serializer.py +0 -60
  13. compressedfhir-1.0.3/compressedfhir/utilities/json_serializers/type_preservation_decoder.py +0 -63
  14. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/LICENSE +0 -0
  15. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/MANIFEST.in +0 -0
  16. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/Makefile +0 -0
  17. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/README.md +0 -0
  18. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/__init__.py +0 -0
  19. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/__init__.py +0 -0
  20. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/base_resource_list.py +0 -0
  21. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_bundle.py +0 -0
  22. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_bundle_entry.py +0 -0
  23. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_bundle_entry_list.py +0 -0
  24. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_bundle_entry_request.py +0 -0
  25. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_bundle_entry_response.py +0 -0
  26. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_bundle_entry_search.py +0 -0
  27. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_identifier.py +0 -0
  28. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_link.py +0 -0
  29. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_meta.py +0 -0
  30. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_resource.py +0 -0
  31. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_resource_list.py +0 -0
  32. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/fhir_resource_map.py +0 -0
  33. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/test/__init__.py +0 -0
  34. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/test/test_bundle_entry.py +0 -0
  35. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/test/test_bundle_entry_list.py +0 -0
  36. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/test/test_bundle_entry_request.py +0 -0
  37. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/test/test_bundle_entry_response.py +0 -0
  38. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/test/test_fhir_bundle.py +0 -0
  39. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/test/test_fhir_resource.py +0 -0
  40. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/test/test_fhir_resource_list.py +0 -0
  41. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/fhir/test/test_fhir_resource_map.py +0 -0
  42. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/py.typed +0 -0
  43. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/__init__.py +0 -0
  44. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/compressed_dict/__init__.py +0 -0
  45. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/compressed_dict/v1/__init__.py +0 -0
  46. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/compressed_dict/v1/compressed_dict_access_error.py +0 -0
  47. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/compressed_dict/v1/compressed_dict_storage_mode.py +0 -0
  48. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/compressed_dict/v1/test/__init__.py +0 -0
  49. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/fhir_json_encoder.py +0 -0
  50. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/json_helpers.py +0 -0
  51. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/json_serializers/__init__.py +0 -0
  52. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/json_serializers/test/__init__.py +0 -0
  53. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/json_serializers/test/test_type_preservation_encoder.py +0 -0
  54. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/json_serializers/type_preservation_encoder.py +0 -0
  55. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/ordered_dict_to_dict_converter/__init__.py +0 -0
  56. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/ordered_dict_to_dict_converter/ordered_dict_to_dict_converter.py +0 -0
  57. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/string_compressor/__init__.py +0 -0
  58. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/string_compressor/v1/__init__.py +0 -0
  59. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/string_compressor/v1/string_compressor.py +0 -0
  60. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/string_compressor/v1/test/__init__.py +0 -0
  61. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/string_compressor/v1/test/test_string_compressor.py +0 -0
  62. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/test/__init__.py +0 -0
  63. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/test/test_fhir_json_encoder.py +0 -0
  64. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir/utilities/test/test_json_helpers.py +0 -0
  65. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir.egg-info/SOURCES.txt +0 -0
  66. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir.egg-info/dependency_links.txt +0 -0
  67. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir.egg-info/not-zip-safe +0 -0
  68. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir.egg-info/requires.txt +0 -0
  69. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/compressedfhir.egg-info/top_level.txt +0 -0
  70. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/setup.cfg +0 -0
  71. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/setup.py +0 -0
  72. {compressedfhir-1.0.3 → compressedfhir-1.0.4}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: compressedfhir
3
- Version: 1.0.3
3
+ Version: 1.0.4
4
4
  Summary: Stores FHIR JSON resources in compressed form in memory
5
5
  Home-page: https://github.com/icanbwell/compressed-fhir
6
6
  Author: Imran Qureshi
@@ -0,0 +1 @@
1
+ 1.0.4
@@ -176,9 +176,7 @@ class CompressedDict[K, V](MutableMapping[K, V]):
176
176
  assert isinstance(dictionary, OrderedDict)
177
177
  if storage_type == "compressed":
178
178
  # Serialize to JSON and compress with zlib
179
- json_str = TypePreservationSerializer.serialize(
180
- dictionary, separators=(",", ":")
181
- )
179
+ json_str = TypePreservationSerializer.serialize(dictionary)
182
180
  return zlib.compress(
183
181
  json_str.encode("utf-8"), level=zlib.Z_BEST_COMPRESSION
184
182
  )
@@ -219,9 +217,7 @@ class CompressedDict[K, V](MutableMapping[K, V]):
219
217
  decompressed_bytes: bytes = zlib.decompress(serialized_dict_bytes)
220
218
  decoded_text: str = decompressed_bytes.decode("utf-8")
221
219
  # noinspection PyTypeChecker
222
- decompressed_dict = TypePreservationSerializer.deserialize(
223
- decoded_text, object_pairs_hook=OrderedDict
224
- )
220
+ decompressed_dict = TypePreservationSerializer.deserialize(decoded_text)
225
221
  assert isinstance(decompressed_dict, OrderedDict)
226
222
  return cast(OrderedDict[K, V], decompressed_dict)
227
223
 
@@ -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
@@ -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,171 @@
1
+ import logging
2
+ from datetime import datetime, timezone, date
3
+ from decimal import Decimal
4
+ from logging import Logger
5
+ from typing import Any
6
+
7
+ from compressedfhir.utilities.json_serializers.type_preservation_serializer import (
8
+ TypePreservationSerializer,
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
+ def test_complex_data_serialization() -> None:
24
+ """
25
+ Test serialization and deserialization of complex data
26
+ """
27
+ complex_data = {
28
+ "timestamp": datetime.now(timezone.utc),
29
+ "today": date.today(),
30
+ "precise_value": Decimal("3.14159"),
31
+ "complex_number": 3 + 4j,
32
+ "byte_data": b"Hello",
33
+ "unique_items": {1, 2, 3},
34
+ "custom_obj": TestCustomObject("test", 42),
35
+ }
36
+
37
+ # Serialize
38
+ serialized = TypePreservationSerializer.serialize(complex_data)
39
+
40
+ # Deserialize
41
+ deserialized = TypePreservationSerializer.deserialize(serialized)
42
+
43
+ # Verify types
44
+ assert isinstance(deserialized["timestamp"], datetime)
45
+ assert isinstance(deserialized["today"], date)
46
+ assert isinstance(deserialized["precise_value"], Decimal)
47
+ assert isinstance(deserialized["complex_number"], complex)
48
+ assert isinstance(deserialized["byte_data"], bytes)
49
+ assert isinstance(deserialized["unique_items"], set)
50
+ assert isinstance(deserialized["custom_obj"], TestCustomObject)
51
+
52
+
53
+ def test_nested_complex_data() -> None:
54
+ """
55
+ Test serialization of nested complex data
56
+ """
57
+ nested_data = {"level1": {"level2": {"timestamp": datetime.now(timezone.utc)}}}
58
+
59
+ serialized = TypePreservationSerializer.serialize(nested_data)
60
+ deserialized = TypePreservationSerializer.deserialize(serialized)
61
+
62
+ assert isinstance(deserialized["level1"]["level2"]["timestamp"], datetime)
63
+
64
+
65
+ def test_nested_dict() -> None:
66
+ """
67
+ Test serialization of nested dictionaries
68
+ """
69
+ logger: Logger = logging.getLogger(__name__)
70
+ nested_dict = {
71
+ "beneficiary": {"reference": "Patient/1234567890123456703", "type": "Patient"},
72
+ "class": [
73
+ {
74
+ "name": "Aetna Plan",
75
+ "type": {
76
+ "coding": [
77
+ {
78
+ "code": "plan",
79
+ "display": "Plan",
80
+ "system": "http://terminology.hl7.org/CodeSystem/coverage-class",
81
+ }
82
+ ]
83
+ },
84
+ "value": "AE303",
85
+ }
86
+ ],
87
+ "costToBeneficiary": [
88
+ {
89
+ "type": {"text": "Annual Physical Exams NMC - In Network"},
90
+ "valueQuantity": {
91
+ "system": "http://aetna.com/Medicare/CostToBeneficiary/ValueQuantity/code",
92
+ "unit": "$",
93
+ "value": 50.0,
94
+ },
95
+ }
96
+ ],
97
+ "id": "3456789012345670304",
98
+ "identifier": [
99
+ {
100
+ "system": "https://sources.aetna.com/coverage/identifier/membershipid/59",
101
+ "type": {
102
+ "coding": [
103
+ {
104
+ "code": "SN",
105
+ "system": "http://terminology.hl7.org/CodeSystem/v2-0203",
106
+ }
107
+ ]
108
+ },
109
+ "value": "435679010300+AE303+2021-01-01",
110
+ },
111
+ {
112
+ "id": "uuid",
113
+ "system": "https://www.icanbwell.com/uuid",
114
+ "value": "92266603-aa8b-58c6-99bd-326fd1da1896",
115
+ },
116
+ ],
117
+ "meta": {
118
+ "security": [
119
+ {"code": "aetna", "system": "https://www.icanbwell.com/owner"},
120
+ {"code": "aetna", "system": "https://www.icanbwell.com/access"},
121
+ {"code": "aetna", "system": "https://www.icanbwell.com/vendor"},
122
+ {"code": "proa", "system": "https://www.icanbwell.com/connectionType"},
123
+ ],
124
+ "source": "http://mock-server:1080/test_patient_access_transformer/source/4_0_0/Coverage/3456789012345670304",
125
+ },
126
+ "network": "Medicare - MA/NY/NJ - Full Reciprocity",
127
+ "payor": [
128
+ {
129
+ "display": "Aetna",
130
+ "reference": "Organization/6667778889990000015",
131
+ "type": "Organization",
132
+ }
133
+ ],
134
+ "period": {
135
+ "end": datetime.fromisoformat("2021-12-31").date(),
136
+ "start": datetime.fromisoformat("2021-01-01").date(),
137
+ },
138
+ "policyHolder": {"reference": "Patient/1234567890123456703", "type": "Patient"},
139
+ "relationship": {
140
+ "coding": [
141
+ {
142
+ "code": "self",
143
+ "system": "http://terminology.hl7.org/CodeSystem/subscriber-relationship",
144
+ }
145
+ ]
146
+ },
147
+ "resourceType": "Coverage",
148
+ "status": "active",
149
+ "subscriber": {"reference": "Patient/1234567890123456703", "type": "Patient"},
150
+ "subscriberId": "435679010300",
151
+ "type": {
152
+ "coding": [
153
+ {
154
+ "code": "PPO",
155
+ "display": "preferred provider organization policy",
156
+ "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
157
+ }
158
+ ]
159
+ },
160
+ }
161
+
162
+ logger.info("-------- Serialized --------")
163
+ serialized = TypePreservationSerializer.serialize(nested_dict)
164
+ logger.info(serialized)
165
+ logger.info("-------- Deserialized --------")
166
+ deserialized = TypePreservationSerializer.deserialize(serialized)
167
+ logger.info(deserialized)
168
+
169
+ assert isinstance(deserialized["period"]["start"], date)
170
+ assert isinstance(deserialized["period"]["end"], date)
171
+ assert nested_dict == deserialized
@@ -0,0 +1,110 @@
1
+ import logging
2
+ from collections import OrderedDict
3
+ from datetime import datetime, date
4
+ from decimal import Decimal
5
+ from logging import Logger
6
+ from typing import Any, Dict, Callable, Optional, Union, cast, List
7
+
8
+
9
+ class TypePreservationDecoder:
10
+ """
11
+ Advanced JSON decoder for complex type reconstruction with nested type support
12
+ """
13
+
14
+ @classmethod
15
+ def decode(
16
+ cls,
17
+ dct: Union[str, Dict[str, Any], List[Any]],
18
+ custom_decoders: Optional[Dict[str, Callable[[Any], Any]]] = None,
19
+ use_ordered_dict: bool = True,
20
+ ) -> Any:
21
+ """
22
+ Decode complex types, including nested datetime fields
23
+
24
+ Args:
25
+ dct: Dictionary to decode
26
+ custom_decoders: Optional additional custom decoders
27
+ use_ordered_dict: Flag to control whether to use OrderedDict or not
28
+
29
+ Returns:
30
+ Reconstructed object or original dictionary
31
+ """
32
+ logger: Logger = logging.getLogger(__name__)
33
+
34
+ # Default decoders for built-in types with nested support
35
+ def datetime_decoder(d: Union[str, Dict[str, Any]]) -> datetime:
36
+ if isinstance(d, str):
37
+ return datetime.fromisoformat(d)
38
+ elif isinstance(d, dict) and "iso" in d:
39
+ return datetime.fromisoformat(d["iso"])
40
+ return cast(datetime, d)
41
+
42
+ def date_decoder(d: Union[str, Dict[str, Any]]) -> date:
43
+ if isinstance(d, str):
44
+ return date.fromisoformat(d)
45
+ elif isinstance(d, dict) and "iso" in d:
46
+ return date.fromisoformat(d["iso"])
47
+ return cast(date, d)
48
+
49
+ default_decoders: Dict[str, Callable[[Any], Any]] = {
50
+ "datetime": datetime_decoder,
51
+ "date": date_decoder,
52
+ "decimal": lambda d: Decimal(d["value"] if isinstance(d, dict) else d),
53
+ "complex": lambda d: complex(d["real"], d["imag"])
54
+ if isinstance(d, dict)
55
+ else d,
56
+ "bytes": lambda d: d["value"].encode("latin-1")
57
+ if isinstance(d, dict)
58
+ else d,
59
+ "set": lambda d: set(d["values"]) if isinstance(d, dict) else d,
60
+ }
61
+
62
+ # Merge custom decoders with default decoders
63
+ decoders = {**default_decoders, **(custom_decoders or {})}
64
+
65
+ # Recursively decode nested structures
66
+ def recursive_decode(value: Any) -> Any:
67
+ if isinstance(value, dict):
68
+ # Check for type marker in the dictionary
69
+ if "__type__" in value:
70
+ type_name = value["__type__"]
71
+
72
+ # Handle built-in type decoders
73
+ if type_name in decoders:
74
+ return decoders[type_name](value)
75
+
76
+ # Handle custom object reconstruction
77
+ if "__module__" in value and "attributes" in value:
78
+ try:
79
+ # Dynamically import the class
80
+ module = __import__(
81
+ value["__module__"], fromlist=[type_name]
82
+ )
83
+ cls_ = getattr(module, type_name)
84
+
85
+ # Create instance and set attributes with recursive decoding
86
+ obj = cls_.__new__(cls_)
87
+ obj.__dict__.update(
88
+ {
89
+ k: recursive_decode(v)
90
+ for k, v in value["attributes"].items()
91
+ }
92
+ )
93
+ return obj
94
+ except (ImportError, AttributeError) as e:
95
+ logger.error(f"Could not reconstruct {type_name}: {e}")
96
+ return value
97
+
98
+ # Recursively decode dictionary values
99
+ # Conditionally use OrderedDict or regular dict
100
+ dict_type = OrderedDict if use_ordered_dict else dict
101
+ return dict_type((k, recursive_decode(v)) for k, v in value.items())
102
+
103
+ # Recursively decode list or tuple
104
+ elif isinstance(value, (list, tuple)):
105
+ return type(value)(recursive_decode(item) for item in value)
106
+
107
+ return value
108
+
109
+ # Start recursive decoding
110
+ return recursive_decode(dct)
@@ -26,7 +26,9 @@ class TypePreservationSerializer:
26
26
  Returns:
27
27
  JSON string representation
28
28
  """
29
- return json.dumps(data, cls=TypePreservationEncoder, indent=2, **kwargs)
29
+ return json.dumps(
30
+ data, cls=TypePreservationEncoder, separators=(",", ":"), **kwargs
31
+ )
30
32
 
31
33
  @classmethod
32
34
  def deserialize(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: compressedfhir
3
- Version: 1.0.3
3
+ Version: 1.0.4
4
4
  Summary: Stores FHIR JSON resources in compressed form in memory
5
5
  Home-page: https://github.com/icanbwell/compressed-fhir
6
6
  Author: Imran Qureshi
@@ -1 +0,0 @@
1
- 1.0.3
@@ -1,82 +0,0 @@
1
- from datetime import datetime, date
2
- from decimal import Decimal
3
- from typing import Type, Any, Dict
4
-
5
- import pytest
6
-
7
- from compressedfhir.utilities.json_serializers.type_preservation_decoder import (
8
- TypePreservationDecoder,
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_dict, expected_type",
25
- [
26
- (
27
- "datetime",
28
- {"__type__": "datetime", "iso": "2023-01-01T00:00:00+00:00"},
29
- datetime,
30
- ),
31
- ("date", {"__type__": "date", "iso": "2023-01-01"}, date),
32
- ("decimal", {"__type__": "decimal", "value": "3.14"}, Decimal),
33
- ("complex", {"__type__": "complex", "real": 3, "imag": 4}, complex),
34
- ("bytes", {"__type__": "bytes", "value": "test"}, bytes),
35
- ("set", {"__type__": "set", "values": [1, 2, 3]}, set),
36
- ],
37
- )
38
- def test_complex_type_decoding(
39
- input_type: str, input_dict: Dict[str, Any], expected_type: Type[Any]
40
- ) -> None:
41
- """
42
- Test decoding of various complex types
43
- """
44
- decoded = TypePreservationDecoder.decode(input_dict)
45
-
46
- assert isinstance(decoded, expected_type)
47
-
48
-
49
- def test_custom_object_decoding() -> None:
50
- """
51
- Test decoding of custom objects
52
- """
53
- custom_obj_dict = {
54
- "__type__": "TestCustomObject",
55
- "__module__": __name__,
56
- "attributes": {"name": "test", "value": 42},
57
- }
58
-
59
- decoded = TypePreservationDecoder.decode(custom_obj_dict)
60
-
61
- assert isinstance(decoded, TestCustomObject)
62
- assert decoded.name == "test"
63
- assert decoded.value == 42
64
-
65
-
66
- def test_custom_decoder() -> None:
67
- """
68
- Test custom decoder functionality
69
- """
70
-
71
- def custom_decoder(data: Dict[str, Any]) -> Any:
72
- if data.get("__type__") == "special_type":
73
- return f"Decoded: {data['value']}"
74
- return data
75
-
76
- special_dict = {"__type__": "special_type", "value": "test"}
77
-
78
- decoded = TypePreservationDecoder.decode(
79
- special_dict, custom_decoders={"special_type": custom_decoder}
80
- )
81
-
82
- assert decoded == "Decoded: test"
@@ -1,60 +0,0 @@
1
- from datetime import datetime, timezone, date
2
- from decimal import Decimal
3
- from typing import Any
4
-
5
- from compressedfhir.utilities.json_serializers.type_preservation_serializer import (
6
- TypePreservationSerializer,
7
- )
8
-
9
-
10
- class TestCustomObject:
11
- def __init__(self, name: str, value: int):
12
- self.name: str = name
13
- self.value: int = value
14
-
15
- def __eq__(self, other: Any) -> bool:
16
- if not isinstance(other, TestCustomObject):
17
- return False
18
- return self.name == other.name and self.value == other.value
19
-
20
-
21
- def test_complex_data_serialization() -> None:
22
- """
23
- Test serialization and deserialization of complex data
24
- """
25
- complex_data = {
26
- "timestamp": datetime.now(timezone.utc),
27
- "today": date.today(),
28
- "precise_value": Decimal("3.14159"),
29
- "complex_number": 3 + 4j,
30
- "byte_data": b"Hello",
31
- "unique_items": {1, 2, 3},
32
- "custom_obj": TestCustomObject("test", 42),
33
- }
34
-
35
- # Serialize
36
- serialized = TypePreservationSerializer.serialize(complex_data)
37
-
38
- # Deserialize
39
- deserialized = TypePreservationSerializer.deserialize(serialized)
40
-
41
- # Verify types
42
- assert isinstance(deserialized["timestamp"], datetime)
43
- assert isinstance(deserialized["today"], date)
44
- assert isinstance(deserialized["precise_value"], Decimal)
45
- assert isinstance(deserialized["complex_number"], complex)
46
- assert isinstance(deserialized["byte_data"], bytes)
47
- assert isinstance(deserialized["unique_items"], set)
48
- assert isinstance(deserialized["custom_obj"], TestCustomObject)
49
-
50
-
51
- def test_nested_complex_data() -> None:
52
- """
53
- Test serialization of nested complex data
54
- """
55
- nested_data = {"level1": {"level2": {"timestamp": datetime.now(timezone.utc)}}}
56
-
57
- serialized = TypePreservationSerializer.serialize(nested_data)
58
- deserialized = TypePreservationSerializer.deserialize(serialized)
59
-
60
- assert isinstance(deserialized["level1"]["level2"]["timestamp"], datetime)
@@ -1,63 +0,0 @@
1
- from datetime import datetime, date
2
- from decimal import Decimal
3
- from typing import Any, Dict, Callable
4
-
5
-
6
- class TypePreservationDecoder:
7
- """
8
- Advanced JSON decoder for complex type reconstruction
9
- """
10
-
11
- @classmethod
12
- def decode(
13
- cls,
14
- dct: Dict[str, Any],
15
- custom_decoders: Dict[str, Callable[[Any], Any]] | None = None,
16
- ) -> Any:
17
- """
18
- Decode complex types
19
-
20
- Args:
21
- dct: Dictionary to decode
22
- custom_decoders: Optional additional custom decoders
23
-
24
- Returns:
25
- Reconstructed object or original dictionary
26
- """
27
- # Default decoders for built-in types
28
- default_decoders: Dict[str, Callable[[Any], Any]] = {
29
- "datetime": lambda d: datetime.fromisoformat(d["iso"]),
30
- "date": lambda d: date.fromisoformat(d["iso"]),
31
- "decimal": lambda d: Decimal(d["value"]),
32
- "complex": lambda d: complex(d["real"], d["imag"]),
33
- "bytes": lambda d: d["value"].encode("latin-1"),
34
- "set": lambda d: set(d["values"]),
35
- }
36
-
37
- # Merge custom decoders with default decoders
38
- decoders = {**default_decoders, **(custom_decoders or {})}
39
-
40
- # Check for type marker
41
- if "__type__" in dct:
42
- type_name = dct["__type__"]
43
-
44
- # Handle built-in type decoders
45
- if type_name in decoders:
46
- return decoders[type_name](dct)
47
-
48
- # Handle custom object reconstruction
49
- if "__module__" in dct and "attributes" in dct:
50
- try:
51
- # Dynamically import the class
52
- module = __import__(dct["__module__"], fromlist=[type_name])
53
- cls_ = getattr(module, type_name)
54
-
55
- # Create instance and set attributes
56
- obj = cls_.__new__(cls_)
57
- obj.__dict__.update(dct["attributes"])
58
- return obj
59
- except (ImportError, AttributeError) as e:
60
- print(f"Could not reconstruct {type_name}: {e}")
61
- return dct
62
-
63
- return dct
File without changes
File without changes
File without changes
File without changes
File without changes