compressedfhir 3.0.2__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.
Files changed (59) hide show
  1. compressedfhir/__init__.py +0 -0
  2. compressedfhir/fhir/__init__.py +0 -0
  3. compressedfhir/fhir/base_resource_list.py +165 -0
  4. compressedfhir/fhir/fhir_bundle.py +295 -0
  5. compressedfhir/fhir/fhir_bundle_entry.py +240 -0
  6. compressedfhir/fhir/fhir_bundle_entry_list.py +97 -0
  7. compressedfhir/fhir/fhir_bundle_entry_request.py +73 -0
  8. compressedfhir/fhir/fhir_bundle_entry_response.py +67 -0
  9. compressedfhir/fhir/fhir_bundle_entry_search.py +75 -0
  10. compressedfhir/fhir/fhir_identifier.py +84 -0
  11. compressedfhir/fhir/fhir_link.py +63 -0
  12. compressedfhir/fhir/fhir_meta.py +47 -0
  13. compressedfhir/fhir/fhir_resource.py +170 -0
  14. compressedfhir/fhir/fhir_resource_list.py +149 -0
  15. compressedfhir/fhir/fhir_resource_map.py +193 -0
  16. compressedfhir/fhir/test/__init__.py +0 -0
  17. compressedfhir/fhir/test/test_bundle_entry.py +129 -0
  18. compressedfhir/fhir/test/test_bundle_entry_list.py +187 -0
  19. compressedfhir/fhir/test/test_bundle_entry_request.py +74 -0
  20. compressedfhir/fhir/test/test_bundle_entry_response.py +65 -0
  21. compressedfhir/fhir/test/test_fhir_bundle.py +245 -0
  22. compressedfhir/fhir/test/test_fhir_resource.py +104 -0
  23. compressedfhir/fhir/test/test_fhir_resource_list.py +160 -0
  24. compressedfhir/fhir/test/test_fhir_resource_map.py +293 -0
  25. compressedfhir/py.typed +0 -0
  26. compressedfhir/utilities/__init__.py +0 -0
  27. compressedfhir/utilities/compressed_dict/__init__.py +0 -0
  28. compressedfhir/utilities/compressed_dict/v1/__init__.py +0 -0
  29. compressedfhir/utilities/compressed_dict/v1/compressed_dict.py +701 -0
  30. compressedfhir/utilities/compressed_dict/v1/compressed_dict_access_error.py +2 -0
  31. compressedfhir/utilities/compressed_dict/v1/compressed_dict_storage_mode.py +50 -0
  32. compressedfhir/utilities/compressed_dict/v1/test/__init__.py +0 -0
  33. compressedfhir/utilities/compressed_dict/v1/test/test_compressed_dict.py +467 -0
  34. compressedfhir/utilities/fhir_json_encoder.py +71 -0
  35. compressedfhir/utilities/json_helpers.py +181 -0
  36. compressedfhir/utilities/json_serializers/__init__.py +0 -0
  37. compressedfhir/utilities/json_serializers/test/__init__.py +0 -0
  38. compressedfhir/utilities/json_serializers/test/test_type_preservation_decoder.py +165 -0
  39. compressedfhir/utilities/json_serializers/test/test_type_preservation_encoder.py +71 -0
  40. compressedfhir/utilities/json_serializers/test/test_type_preservation_serializer.py +197 -0
  41. compressedfhir/utilities/json_serializers/type_preservation_decoder.py +135 -0
  42. compressedfhir/utilities/json_serializers/type_preservation_encoder.py +55 -0
  43. compressedfhir/utilities/json_serializers/type_preservation_serializer.py +57 -0
  44. compressedfhir/utilities/ordered_dict_to_dict_converter/__init__.py +0 -0
  45. compressedfhir/utilities/ordered_dict_to_dict_converter/ordered_dict_to_dict_converter.py +24 -0
  46. compressedfhir/utilities/string_compressor/__init__.py +0 -0
  47. compressedfhir/utilities/string_compressor/v1/__init__.py +0 -0
  48. compressedfhir/utilities/string_compressor/v1/string_compressor.py +99 -0
  49. compressedfhir/utilities/string_compressor/v1/test/__init__.py +0 -0
  50. compressedfhir/utilities/string_compressor/v1/test/test_string_compressor.py +189 -0
  51. compressedfhir/utilities/test/__init__.py +0 -0
  52. compressedfhir/utilities/test/test_fhir_json_encoder.py +177 -0
  53. compressedfhir/utilities/test/test_json_helpers.py +99 -0
  54. compressedfhir-3.0.2.dist-info/METADATA +139 -0
  55. compressedfhir-3.0.2.dist-info/RECORD +59 -0
  56. compressedfhir-3.0.2.dist-info/WHEEL +5 -0
  57. compressedfhir-3.0.2.dist-info/licenses/LICENSE +201 -0
  58. compressedfhir-3.0.2.dist-info/top_level.txt +2 -0
  59. tests/__init__.py +0 -0
@@ -0,0 +1,50 @@
1
+ import dataclasses
2
+ from typing import Literal, TypeAlias
3
+
4
+ CompressedDictStorageType: TypeAlias = Literal[
5
+ "raw", "compressed", "msgpack", "compressed_msgpack"
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
+ ###
14
+
15
+
16
+ @dataclasses.dataclass(slots=True)
17
+ class CompressedDictStorageMode:
18
+ """
19
+ CompressedDictStorageMode is a dataclass that defines the storage mode
20
+ """
21
+
22
+ storage_type: CompressedDictStorageType = "compressed"
23
+ """
24
+ CompressedDictStorageType is a type alias for the different storage types
25
+ raw: No compression
26
+ compressed: Compressed using zlib
27
+ msgpack: Compressed using msgpack
28
+ compressed_msgpack: Compressed using msgpack with zlib
29
+ """
30
+
31
+ @classmethod
32
+ def default(cls) -> "CompressedDictStorageMode":
33
+ """
34
+ Returns the default storage mode
35
+ """
36
+ return cls(storage_type="compressed")
37
+
38
+ @classmethod
39
+ def raw(cls) -> "CompressedDictStorageMode":
40
+ """
41
+ Returns the default storage mode
42
+ """
43
+ return cls(storage_type="raw")
44
+
45
+ @classmethod
46
+ def compressed(cls) -> "CompressedDictStorageMode":
47
+ """
48
+ Returns the default storage mode
49
+ """
50
+ return cls(storage_type="compressed")
@@ -0,0 +1,467 @@
1
+ from datetime import datetime
2
+
3
+ import pytest
4
+ from typing import Any, cast
5
+
6
+ from compressedfhir.utilities.compressed_dict.v1.compressed_dict import (
7
+ CompressedDict,
8
+ )
9
+ from compressedfhir.utilities.compressed_dict.v1.compressed_dict_access_error import (
10
+ CompressedDictAccessError,
11
+ )
12
+ from compressedfhir.utilities.compressed_dict.v1.compressed_dict_storage_mode import (
13
+ CompressedDictStorageMode,
14
+ CompressedDictStorageType,
15
+ )
16
+
17
+
18
+ class TestCompressedDict:
19
+ def test_init_empty(self) -> None:
20
+ """Test initialization with no initial data"""
21
+ cd: CompressedDict[str, Any] = CompressedDict(
22
+ storage_mode=CompressedDictStorageMode(storage_type="raw"),
23
+ properties_to_cache=[],
24
+ )
25
+ assert len(cd) == 0
26
+ assert cd._storage_mode.storage_type == "raw"
27
+
28
+ def test_init_with_dict(self) -> None:
29
+ """Test initialization with initial dictionary"""
30
+ initial_data = {"a": 1, "b": 2, "c": 3}
31
+ cd = CompressedDict(
32
+ initial_dict=initial_data,
33
+ storage_mode=CompressedDictStorageMode(storage_type="raw"),
34
+ properties_to_cache=[],
35
+ )
36
+
37
+ with cd.transaction():
38
+ assert len(cd) == 3
39
+ assert cd["a"] == 1
40
+ assert cd["b"] == 2
41
+ assert cd["c"] == 3
42
+
43
+ @pytest.mark.parametrize("storage_type", ["raw", "msgpack", "compressed_msgpack"])
44
+ def test_storage_modes(self, storage_type: CompressedDictStorageType) -> None:
45
+ """Test different storage modes"""
46
+ initial_data = {"key": "value"}
47
+ cd = CompressedDict(
48
+ initial_dict=initial_data,
49
+ storage_mode=CompressedDictStorageMode(storage_type=storage_type),
50
+ properties_to_cache=[],
51
+ )
52
+ assert cd._storage_mode.storage_type == storage_type
53
+ with cd.transaction():
54
+ assert cd["key"] == "value"
55
+
56
+ def test_setitem_and_getitem(self) -> None:
57
+ """Test setting and getting items"""
58
+ cd: CompressedDict[str, Any] = CompressedDict(
59
+ storage_mode=CompressedDictStorageMode(storage_type="compressed_msgpack"),
60
+ properties_to_cache=[],
61
+ )
62
+ with cd.transaction():
63
+ cd["key1"] = "value1"
64
+ cd["key2"] = 42
65
+ cd["key3"] = {"nested": "dict"}
66
+
67
+ with cd.transaction():
68
+ assert cd["key1"] == "value1"
69
+ assert cd["key2"] == 42
70
+ assert cd["key3"] == {"nested": "dict"}
71
+
72
+ def test_delitem(self) -> None:
73
+ """Test deleting items"""
74
+ cd = CompressedDict(
75
+ initial_dict={"a": 1, "b": 2},
76
+ storage_mode=CompressedDictStorageMode(storage_type="compressed_msgpack"),
77
+ properties_to_cache=[],
78
+ )
79
+ with cd.transaction():
80
+ del cd["a"]
81
+
82
+ with cd.transaction():
83
+ assert len(cd) == 1
84
+
85
+ with cd.transaction():
86
+ assert not cd.__contains__("a")
87
+ assert "a" not in cd
88
+ assert "b" in cd
89
+
90
+ def test_contains(self) -> None:
91
+ """Test key existence checks"""
92
+ cd = CompressedDict(
93
+ initial_dict={"a": 1, "b": 2},
94
+ storage_mode=CompressedDictStorageMode(),
95
+ properties_to_cache=[],
96
+ )
97
+
98
+ with cd.transaction():
99
+ assert "a" in cd
100
+ assert "b" in cd
101
+ assert "c" not in cd
102
+
103
+ def test_keys_and_values(self) -> None:
104
+ """Test keys and values methods"""
105
+ initial_data = {"a": 1, "b": 2, "c": 3}
106
+ cd: CompressedDict[str, int] = CompressedDict(
107
+ initial_dict=initial_data,
108
+ storage_mode=CompressedDictStorageMode(),
109
+ properties_to_cache=[],
110
+ )
111
+
112
+ with cd.transaction():
113
+ assert set(cd.keys()) == {"a", "b", "c"}
114
+ assert set(cd.values()) == {1, 2, 3}
115
+
116
+ def test_items(self) -> None:
117
+ """Test items method"""
118
+ initial_data = {"a": 1, "b": 2, "c": 3}
119
+ cd = CompressedDict(
120
+ initial_dict=initial_data,
121
+ storage_mode=CompressedDictStorageMode(),
122
+ properties_to_cache=[],
123
+ )
124
+
125
+ with cd.transaction():
126
+ assert set(cd.items()) == {("a", 1), ("b", 2), ("c", 3)}
127
+
128
+ def test_get_method(self) -> None:
129
+ """Test get method with default"""
130
+ cd: CompressedDict[str, Any] = CompressedDict(
131
+ initial_dict={"a": 1},
132
+ storage_mode=CompressedDictStorageMode(),
133
+ properties_to_cache=[],
134
+ )
135
+ with cd.transaction():
136
+ assert cd.get("a") == 1
137
+ assert cd.get("b") is None
138
+ assert cd.get("b", "default") == "default"
139
+
140
+ def test_to_dict(self) -> None:
141
+ """Test conversion to standard dictionary"""
142
+ initial_data = {"a": 1, "b": 2}
143
+ cd = CompressedDict(
144
+ initial_dict=initial_data,
145
+ storage_mode=CompressedDictStorageMode(),
146
+ properties_to_cache=[],
147
+ )
148
+
149
+ with cd.transaction():
150
+ assert cd.dict() == initial_data
151
+
152
+ def test_complex_nested_structures(self) -> None:
153
+ """Test storage of complex nested structures"""
154
+ complex_data = {
155
+ "nested_dict": {"inner_key": "inner_value"},
156
+ "list": [1, 2, 3],
157
+ "mixed": [{"a": 1}, 2, "three"],
158
+ }
159
+
160
+ # Test each storage storage_type
161
+ for storage_type in ["raw", "msgpack", "compressed_msgpack"]:
162
+ cd = CompressedDict(
163
+ initial_dict=complex_data,
164
+ storage_mode=CompressedDictStorageMode(
165
+ storage_type=cast(CompressedDictStorageType, storage_type)
166
+ ),
167
+ properties_to_cache=[],
168
+ )
169
+ with cd.transaction():
170
+ assert cd["nested_dict"] == {"inner_key": "inner_value"}
171
+ assert cd["list"] == [1, 2, 3]
172
+ assert cd["mixed"] == [{"a": 1}, 2, "three"]
173
+
174
+ def test_repr(self) -> None:
175
+ """Test string representation"""
176
+ cd = CompressedDict(
177
+ initial_dict={"a": 1, "b": 2},
178
+ storage_mode=CompressedDictStorageMode(),
179
+ properties_to_cache=[],
180
+ )
181
+ repr_str = repr(cd)
182
+
183
+ assert repr_str == "CompressedDict(storage_type='compressed', keys=2)"
184
+
185
+ def test_error_handling(self) -> None:
186
+ """Test error scenarios"""
187
+ cd: CompressedDict[str, Any] = CompressedDict(
188
+ storage_mode=CompressedDictStorageMode(), properties_to_cache=[]
189
+ )
190
+
191
+ # Test KeyError
192
+ with pytest.raises(KeyError):
193
+ with cd.transaction():
194
+ _ = cd["non_existent_key"]
195
+
196
+ @pytest.mark.parametrize("storage_type", ["raw", "msgpack", "compressed_msgpack"])
197
+ def test_large_data_handling(self, storage_type: CompressedDictStorageType) -> None:
198
+ """Test handling of large datasets"""
199
+ large_data = {f"key_{i}": f"value_{i}" for i in range(1000)}
200
+
201
+ cd = CompressedDict(
202
+ initial_dict=large_data,
203
+ storage_mode=CompressedDictStorageMode(storage_type=storage_type),
204
+ properties_to_cache=[],
205
+ )
206
+
207
+ assert len(cd) == 1000
208
+ with cd.transaction():
209
+ assert cd["key_500"] == "value_500"
210
+
211
+
212
+ def test_transaction_basic_raw_storage() -> None:
213
+ """
214
+ Test basic transaction functionality with raw storage mode
215
+ """
216
+ storage_mode = CompressedDictStorageMode(storage_type="raw")
217
+ initial_dict = {"key1": "value1", "key2": "value2"}
218
+
219
+ compressed_dict = CompressedDict(
220
+ initial_dict=initial_dict, storage_mode=storage_mode, properties_to_cache=None
221
+ )
222
+
223
+ # Verify initial state
224
+ assert compressed_dict._transaction_depth == 0
225
+
226
+ # Use transaction context
227
+ with compressed_dict.transaction() as d:
228
+ assert compressed_dict._transaction_depth == 1
229
+ assert d._working_dict is not None
230
+ assert d._working_dict == initial_dict
231
+
232
+ # Modify the dictionary
233
+ d["key3"] = "value3"
234
+
235
+ # After transaction
236
+ assert compressed_dict._transaction_depth == 0
237
+ assert compressed_dict.raw_dict() == {
238
+ "key1": "value1",
239
+ "key2": "value2",
240
+ "key3": "value3",
241
+ }
242
+
243
+
244
+ def test_transaction_nested_context() -> None:
245
+ """
246
+ Test nested transaction contexts
247
+ """
248
+ storage_mode = CompressedDictStorageMode(storage_type="msgpack")
249
+ initial_dict = {"key1": "value1"}
250
+
251
+ compressed_dict = CompressedDict(
252
+ initial_dict=initial_dict, storage_mode=storage_mode, properties_to_cache=None
253
+ )
254
+
255
+ with compressed_dict.transaction():
256
+ assert compressed_dict._transaction_depth == 1
257
+
258
+ with compressed_dict.transaction():
259
+ assert compressed_dict._transaction_depth == 2
260
+ compressed_dict["key2"] = "value2"
261
+
262
+ assert compressed_dict._transaction_depth == 1
263
+
264
+ assert compressed_dict._transaction_depth == 0
265
+ assert compressed_dict.raw_dict() == {"key1": "value1", "key2": "value2"}
266
+
267
+
268
+ def test_transaction_access_error() -> None:
269
+ """
270
+ Test that accessing or modifying dictionary outside transaction raises an error
271
+ """
272
+ storage_mode = CompressedDictStorageMode(storage_type="compressed_msgpack")
273
+ compressed_dict = CompressedDict(
274
+ initial_dict={"key1": "value1"},
275
+ storage_mode=storage_mode,
276
+ properties_to_cache=None,
277
+ )
278
+
279
+ # Test getdict outside transaction
280
+ with pytest.raises(CompressedDictAccessError):
281
+ compressed_dict._get_dict()
282
+
283
+ # Test __getitem__ outside transaction
284
+ with pytest.raises(CompressedDictAccessError):
285
+ _ = compressed_dict["key1"]
286
+
287
+ # Test __setitem__ outside transaction
288
+ with pytest.raises(CompressedDictAccessError):
289
+ compressed_dict["key2"] = "value2"
290
+
291
+
292
+ def test_transaction_different_storage_modes() -> None:
293
+ """
294
+ Test transaction with different storage modes
295
+ """
296
+ storage_modes = [
297
+ CompressedDictStorageMode(storage_type="raw"),
298
+ CompressedDictStorageMode(storage_type="msgpack"),
299
+ CompressedDictStorageMode(storage_type="compressed_msgpack"),
300
+ ]
301
+
302
+ for storage_mode in storage_modes:
303
+ initial_dict = {"key1": "value1"}
304
+
305
+ compressed_dict = CompressedDict(
306
+ initial_dict=initial_dict,
307
+ storage_mode=storage_mode,
308
+ properties_to_cache=None,
309
+ )
310
+
311
+ with compressed_dict.transaction() as d:
312
+ d["key2"] = "value2"
313
+
314
+ assert compressed_dict.raw_dict() == {"key1": "value1", "key2": "value2"}
315
+
316
+
317
+ def test_transaction_with_properties_to_cache() -> None:
318
+ """
319
+ Test transaction with properties to cache
320
+ """
321
+ storage_mode = CompressedDictStorageMode(storage_type="raw")
322
+ initial_dict = {"key1": "value1", "important_prop": "cached_value"}
323
+
324
+ compressed_dict = CompressedDict(
325
+ initial_dict=initial_dict,
326
+ storage_mode=storage_mode,
327
+ properties_to_cache=["important_prop"],
328
+ )
329
+
330
+ with compressed_dict.transaction() as d:
331
+ d["key2"] = "value2"
332
+
333
+ assert compressed_dict.raw_dict() == {
334
+ "key1": "value1",
335
+ "important_prop": "cached_value",
336
+ "key2": "value2",
337
+ }
338
+ assert compressed_dict._cached_properties == {"important_prop": "cached_value"}
339
+
340
+
341
+ def test_transaction_error_handling() -> None:
342
+ """
343
+ Test error handling during transaction
344
+ """
345
+ storage_mode = CompressedDictStorageMode(storage_type="compressed")
346
+ compressed_dict = CompressedDict(
347
+ initial_dict={"key1": "value1"},
348
+ storage_mode=storage_mode,
349
+ properties_to_cache=None,
350
+ )
351
+
352
+ try:
353
+ with compressed_dict.transaction():
354
+ compressed_dict["key2"] = "value2"
355
+ raise ValueError("Simulated error")
356
+ except ValueError:
357
+ # Ensure transaction depth is reset even after an error
358
+ assert compressed_dict._transaction_depth == 0
359
+
360
+ # Verify the dictionary state remains unchanged
361
+ with compressed_dict.transaction() as d:
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,71 @@
1
+ import dataclasses
2
+ import json
3
+ import uuid
4
+ from datetime import datetime, date, time
5
+ from decimal import Decimal
6
+ from enum import Enum
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ # Optional: Import for additional type support
11
+ try:
12
+ import ipaddress
13
+ except ImportError:
14
+ ipaddress = None # type:ignore[assignment]
15
+
16
+
17
+ class FhirJSONEncoder(json.JSONEncoder):
18
+ def default(self, o: Any) -> Any:
19
+ # Existing type handlers
20
+ if dataclasses.is_dataclass(o):
21
+ return dataclasses.asdict(o) # type:ignore[arg-type]
22
+
23
+ if isinstance(o, Enum):
24
+ return o.value
25
+
26
+ if isinstance(o, Decimal):
27
+ # Custom Decimal conversion
28
+ if o == o.to_integral_value():
29
+ return int(o)
30
+ else:
31
+ return float(o)
32
+
33
+ if isinstance(o, bytes):
34
+ return o.decode("utf-8")
35
+
36
+ if isinstance(o, (datetime, date)):
37
+ return o.isoformat()
38
+
39
+ if isinstance(o, time):
40
+ return o.isoformat()
41
+
42
+ if hasattr(o, "to_dict"):
43
+ return o.to_dict()
44
+
45
+ # New type handlers
46
+
47
+ # UUID handling
48
+ if isinstance(o, uuid.UUID):
49
+ return str(o)
50
+
51
+ # Set and frozenset handling
52
+ if isinstance(o, (set, frozenset)):
53
+ return list(o)
54
+
55
+ # Complex number handling
56
+ if isinstance(o, complex):
57
+ return {"real": o.real, "imag": o.imag}
58
+
59
+ # Path-like objects
60
+ if isinstance(o, (Path, Path)):
61
+ return str(o)
62
+
63
+ # IP Address handling (if ipaddress module is available)
64
+ if ipaddress and isinstance(o, (ipaddress.IPv4Address, ipaddress.IPv6Address)):
65
+ return str(o)
66
+
67
+ # Custom object serialization fallback
68
+ if hasattr(o, "__dict__"):
69
+ return o.__dict__
70
+
71
+ return super().default(o)