compressedfhir 0.0.1__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.
- compressedfhir/__init__.py +0 -0
- compressedfhir/fhir/__init__.py +0 -0
- compressedfhir/fhir/base_resource_list.py +165 -0
- compressedfhir/fhir/fhir_bundle.py +291 -0
- compressedfhir/fhir/fhir_bundle_entry.py +234 -0
- compressedfhir/fhir/fhir_bundle_entry_list.py +82 -0
- compressedfhir/fhir/fhir_bundle_entry_request.py +71 -0
- compressedfhir/fhir/fhir_bundle_entry_response.py +64 -0
- compressedfhir/fhir/fhir_bundle_entry_search.py +75 -0
- compressedfhir/fhir/fhir_identifier.py +84 -0
- compressedfhir/fhir/fhir_link.py +63 -0
- compressedfhir/fhir/fhir_meta.py +47 -0
- compressedfhir/fhir/fhir_resource.py +163 -0
- compressedfhir/fhir/fhir_resource_list.py +143 -0
- compressedfhir/fhir/fhir_resource_map.py +193 -0
- compressedfhir/fhir/test/__init__.py +0 -0
- compressedfhir/fhir/test/test_bundle_entry.py +129 -0
- compressedfhir/fhir/test/test_bundle_entry_list.py +187 -0
- compressedfhir/fhir/test/test_bundle_entry_request.py +74 -0
- compressedfhir/fhir/test/test_bundle_entry_response.py +65 -0
- compressedfhir/fhir/test/test_fhir_bundle.py +245 -0
- compressedfhir/fhir/test/test_fhir_resource.py +225 -0
- compressedfhir/fhir/test/test_fhir_resource_list.py +160 -0
- compressedfhir/fhir/test/test_fhir_resource_map.py +293 -0
- compressedfhir/py.typed +0 -0
- compressedfhir/utilities/__init__.py +0 -0
- compressedfhir/utilities/compressed_dict/__init__.py +0 -0
- compressedfhir/utilities/compressed_dict/v1/__init__.py +0 -0
- compressedfhir/utilities/compressed_dict/v1/compressed_dict.py +635 -0
- compressedfhir/utilities/compressed_dict/v1/compressed_dict_access_error.py +2 -0
- compressedfhir/utilities/compressed_dict/v1/compressed_dict_storage_mode.py +50 -0
- compressedfhir/utilities/compressed_dict/v1/test/__init__.py +0 -0
- compressedfhir/utilities/compressed_dict/v1/test/test_compressed_dict.py +360 -0
- compressedfhir/utilities/fhir_json_encoder.py +30 -0
- compressedfhir/utilities/json_helpers.py +181 -0
- compressedfhir-0.0.1.dist-info/METADATA +28 -0
- compressedfhir-0.0.1.dist-info/RECORD +41 -0
- compressedfhir-0.0.1.dist-info/WHEEL +5 -0
- compressedfhir-0.0.1.dist-info/licenses/LICENSE +201 -0
- compressedfhir-0.0.1.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from typing import Any, cast
|
|
3
|
+
|
|
4
|
+
from compressedfhir.utilities.compressed_dict.v1.compressed_dict import (
|
|
5
|
+
CompressedDict,
|
|
6
|
+
)
|
|
7
|
+
from compressedfhir.utilities.compressed_dict.v1.compressed_dict_access_error import (
|
|
8
|
+
CompressedDictAccessError,
|
|
9
|
+
)
|
|
10
|
+
from compressedfhir.utilities.compressed_dict.v1.compressed_dict_storage_mode import (
|
|
11
|
+
CompressedDictStorageMode,
|
|
12
|
+
CompressedDictStorageType,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestCompressedDict:
|
|
17
|
+
def test_init_empty(self) -> None:
|
|
18
|
+
"""Test initialization with no initial data"""
|
|
19
|
+
cd: CompressedDict[str, Any] = CompressedDict(
|
|
20
|
+
storage_mode=CompressedDictStorageMode(storage_type="raw"),
|
|
21
|
+
properties_to_cache=[],
|
|
22
|
+
)
|
|
23
|
+
assert len(cd) == 0
|
|
24
|
+
assert cd._storage_mode.storage_type == "raw"
|
|
25
|
+
|
|
26
|
+
def test_init_with_dict(self) -> None:
|
|
27
|
+
"""Test initialization with initial dictionary"""
|
|
28
|
+
initial_data = {"a": 1, "b": 2, "c": 3}
|
|
29
|
+
cd = CompressedDict(
|
|
30
|
+
initial_dict=initial_data,
|
|
31
|
+
storage_mode=CompressedDictStorageMode(storage_type="raw"),
|
|
32
|
+
properties_to_cache=[],
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
with cd.transaction():
|
|
36
|
+
assert len(cd) == 3
|
|
37
|
+
assert cd["a"] == 1
|
|
38
|
+
assert cd["b"] == 2
|
|
39
|
+
assert cd["c"] == 3
|
|
40
|
+
|
|
41
|
+
@pytest.mark.parametrize("storage_type", ["raw", "msgpack", "compressed_msgpack"])
|
|
42
|
+
def test_storage_modes(self, storage_type: CompressedDictStorageType) -> None:
|
|
43
|
+
"""Test different storage modes"""
|
|
44
|
+
initial_data = {"key": "value"}
|
|
45
|
+
cd = CompressedDict(
|
|
46
|
+
initial_dict=initial_data,
|
|
47
|
+
storage_mode=CompressedDictStorageMode(storage_type=storage_type),
|
|
48
|
+
properties_to_cache=[],
|
|
49
|
+
)
|
|
50
|
+
assert cd._storage_mode.storage_type == storage_type
|
|
51
|
+
with cd.transaction():
|
|
52
|
+
assert cd["key"] == "value"
|
|
53
|
+
|
|
54
|
+
def test_setitem_and_getitem(self) -> None:
|
|
55
|
+
"""Test setting and getting items"""
|
|
56
|
+
cd: CompressedDict[str, Any] = CompressedDict(
|
|
57
|
+
storage_mode=CompressedDictStorageMode(storage_type="compressed_msgpack"),
|
|
58
|
+
properties_to_cache=[],
|
|
59
|
+
)
|
|
60
|
+
with cd.transaction():
|
|
61
|
+
cd["key1"] = "value1"
|
|
62
|
+
cd["key2"] = 42
|
|
63
|
+
cd["key3"] = {"nested": "dict"}
|
|
64
|
+
|
|
65
|
+
with cd.transaction():
|
|
66
|
+
assert cd["key1"] == "value1"
|
|
67
|
+
assert cd["key2"] == 42
|
|
68
|
+
assert cd["key3"] == {"nested": "dict"}
|
|
69
|
+
|
|
70
|
+
def test_delitem(self) -> None:
|
|
71
|
+
"""Test deleting items"""
|
|
72
|
+
cd = CompressedDict(
|
|
73
|
+
initial_dict={"a": 1, "b": 2},
|
|
74
|
+
storage_mode=CompressedDictStorageMode(storage_type="compressed_msgpack"),
|
|
75
|
+
properties_to_cache=[],
|
|
76
|
+
)
|
|
77
|
+
with cd.transaction():
|
|
78
|
+
del cd["a"]
|
|
79
|
+
|
|
80
|
+
with cd.transaction():
|
|
81
|
+
assert len(cd) == 1
|
|
82
|
+
|
|
83
|
+
with cd.transaction():
|
|
84
|
+
assert not cd.__contains__("a")
|
|
85
|
+
assert "a" not in cd
|
|
86
|
+
assert "b" in cd
|
|
87
|
+
|
|
88
|
+
def test_contains(self) -> None:
|
|
89
|
+
"""Test key existence checks"""
|
|
90
|
+
cd = CompressedDict(
|
|
91
|
+
initial_dict={"a": 1, "b": 2},
|
|
92
|
+
storage_mode=CompressedDictStorageMode(),
|
|
93
|
+
properties_to_cache=[],
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
with cd.transaction():
|
|
97
|
+
assert "a" in cd
|
|
98
|
+
assert "b" in cd
|
|
99
|
+
assert "c" not in cd
|
|
100
|
+
|
|
101
|
+
def test_keys_and_values(self) -> None:
|
|
102
|
+
"""Test keys and values methods"""
|
|
103
|
+
initial_data = {"a": 1, "b": 2, "c": 3}
|
|
104
|
+
cd: CompressedDict[str, int] = CompressedDict(
|
|
105
|
+
initial_dict=initial_data,
|
|
106
|
+
storage_mode=CompressedDictStorageMode(),
|
|
107
|
+
properties_to_cache=[],
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
with cd.transaction():
|
|
111
|
+
assert set(cd.keys()) == {"a", "b", "c"}
|
|
112
|
+
assert set(cd.values()) == {1, 2, 3}
|
|
113
|
+
|
|
114
|
+
def test_items(self) -> None:
|
|
115
|
+
"""Test items method"""
|
|
116
|
+
initial_data = {"a": 1, "b": 2, "c": 3}
|
|
117
|
+
cd = CompressedDict(
|
|
118
|
+
initial_dict=initial_data,
|
|
119
|
+
storage_mode=CompressedDictStorageMode(),
|
|
120
|
+
properties_to_cache=[],
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
with cd.transaction():
|
|
124
|
+
assert set(cd.items()) == {("a", 1), ("b", 2), ("c", 3)}
|
|
125
|
+
|
|
126
|
+
def test_get_method(self) -> None:
|
|
127
|
+
"""Test get method with default"""
|
|
128
|
+
cd: CompressedDict[str, Any] = CompressedDict(
|
|
129
|
+
initial_dict={"a": 1},
|
|
130
|
+
storage_mode=CompressedDictStorageMode(),
|
|
131
|
+
properties_to_cache=[],
|
|
132
|
+
)
|
|
133
|
+
with cd.transaction():
|
|
134
|
+
assert cd.get("a") == 1
|
|
135
|
+
assert cd.get("b") is None
|
|
136
|
+
assert cd.get("b", "default") == "default"
|
|
137
|
+
|
|
138
|
+
def test_to_dict(self) -> None:
|
|
139
|
+
"""Test conversion to standard dictionary"""
|
|
140
|
+
initial_data = {"a": 1, "b": 2}
|
|
141
|
+
cd = CompressedDict(
|
|
142
|
+
initial_dict=initial_data,
|
|
143
|
+
storage_mode=CompressedDictStorageMode(),
|
|
144
|
+
properties_to_cache=[],
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
with cd.transaction():
|
|
148
|
+
assert cd.dict() == initial_data
|
|
149
|
+
|
|
150
|
+
def test_complex_nested_structures(self) -> None:
|
|
151
|
+
"""Test storage of complex nested structures"""
|
|
152
|
+
complex_data = {
|
|
153
|
+
"nested_dict": {"inner_key": "inner_value"},
|
|
154
|
+
"list": [1, 2, 3],
|
|
155
|
+
"mixed": [{"a": 1}, 2, "three"],
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# Test each storage storage_type
|
|
159
|
+
for storage_type in ["raw", "msgpack", "compressed_msgpack"]:
|
|
160
|
+
cd = CompressedDict(
|
|
161
|
+
initial_dict=complex_data,
|
|
162
|
+
storage_mode=CompressedDictStorageMode(
|
|
163
|
+
storage_type=cast(CompressedDictStorageType, storage_type)
|
|
164
|
+
),
|
|
165
|
+
properties_to_cache=[],
|
|
166
|
+
)
|
|
167
|
+
with cd.transaction():
|
|
168
|
+
assert cd["nested_dict"] == {"inner_key": "inner_value"}
|
|
169
|
+
assert cd["list"] == [1, 2, 3]
|
|
170
|
+
assert cd["mixed"] == [{"a": 1}, 2, "three"]
|
|
171
|
+
|
|
172
|
+
def test_repr(self) -> None:
|
|
173
|
+
"""Test string representation"""
|
|
174
|
+
cd = CompressedDict(
|
|
175
|
+
initial_dict={"a": 1, "b": 2},
|
|
176
|
+
storage_mode=CompressedDictStorageMode(),
|
|
177
|
+
properties_to_cache=[],
|
|
178
|
+
)
|
|
179
|
+
repr_str = repr(cd)
|
|
180
|
+
|
|
181
|
+
assert repr_str == "CompressedDict(storage_type='compressed', keys=2)"
|
|
182
|
+
|
|
183
|
+
def test_error_handling(self) -> None:
|
|
184
|
+
"""Test error scenarios"""
|
|
185
|
+
cd: CompressedDict[str, Any] = CompressedDict(
|
|
186
|
+
storage_mode=CompressedDictStorageMode(), properties_to_cache=[]
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Test KeyError
|
|
190
|
+
with pytest.raises(KeyError):
|
|
191
|
+
with cd.transaction():
|
|
192
|
+
_ = cd["non_existent_key"]
|
|
193
|
+
|
|
194
|
+
@pytest.mark.parametrize("storage_type", ["raw", "msgpack", "compressed_msgpack"])
|
|
195
|
+
def test_large_data_handling(self, storage_type: CompressedDictStorageType) -> None:
|
|
196
|
+
"""Test handling of large datasets"""
|
|
197
|
+
large_data = {f"key_{i}": f"value_{i}" for i in range(1000)}
|
|
198
|
+
|
|
199
|
+
cd = CompressedDict(
|
|
200
|
+
initial_dict=large_data,
|
|
201
|
+
storage_mode=CompressedDictStorageMode(storage_type=storage_type),
|
|
202
|
+
properties_to_cache=[],
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
assert len(cd) == 1000
|
|
206
|
+
with cd.transaction():
|
|
207
|
+
assert cd["key_500"] == "value_500"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def test_transaction_basic_raw_storage() -> None:
|
|
211
|
+
"""
|
|
212
|
+
Test basic transaction functionality with raw storage mode
|
|
213
|
+
"""
|
|
214
|
+
storage_mode = CompressedDictStorageMode(storage_type="raw")
|
|
215
|
+
initial_dict = {"key1": "value1", "key2": "value2"}
|
|
216
|
+
|
|
217
|
+
compressed_dict = CompressedDict(
|
|
218
|
+
initial_dict=initial_dict, storage_mode=storage_mode, properties_to_cache=None
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Verify initial state
|
|
222
|
+
assert compressed_dict._transaction_depth == 0
|
|
223
|
+
|
|
224
|
+
# Use transaction context
|
|
225
|
+
with compressed_dict.transaction() as d:
|
|
226
|
+
assert compressed_dict._transaction_depth == 1
|
|
227
|
+
assert d._working_dict is not None
|
|
228
|
+
assert d._working_dict == initial_dict
|
|
229
|
+
|
|
230
|
+
# Modify the dictionary
|
|
231
|
+
d["key3"] = "value3"
|
|
232
|
+
|
|
233
|
+
# After transaction
|
|
234
|
+
assert compressed_dict._transaction_depth == 0
|
|
235
|
+
assert compressed_dict.dict() == {
|
|
236
|
+
"key1": "value1",
|
|
237
|
+
"key2": "value2",
|
|
238
|
+
"key3": "value3",
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def test_transaction_nested_context() -> None:
|
|
243
|
+
"""
|
|
244
|
+
Test nested transaction contexts
|
|
245
|
+
"""
|
|
246
|
+
storage_mode = CompressedDictStorageMode(storage_type="msgpack")
|
|
247
|
+
initial_dict = {"key1": "value1"}
|
|
248
|
+
|
|
249
|
+
compressed_dict = CompressedDict(
|
|
250
|
+
initial_dict=initial_dict, storage_mode=storage_mode, properties_to_cache=None
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
with compressed_dict.transaction():
|
|
254
|
+
assert compressed_dict._transaction_depth == 1
|
|
255
|
+
|
|
256
|
+
with compressed_dict.transaction():
|
|
257
|
+
assert compressed_dict._transaction_depth == 2
|
|
258
|
+
compressed_dict["key2"] = "value2"
|
|
259
|
+
|
|
260
|
+
assert compressed_dict._transaction_depth == 1
|
|
261
|
+
|
|
262
|
+
assert compressed_dict._transaction_depth == 0
|
|
263
|
+
assert compressed_dict.dict() == {"key1": "value1", "key2": "value2"}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def test_transaction_access_error() -> None:
|
|
267
|
+
"""
|
|
268
|
+
Test that accessing or modifying dictionary outside transaction raises an error
|
|
269
|
+
"""
|
|
270
|
+
storage_mode = CompressedDictStorageMode(storage_type="compressed_msgpack")
|
|
271
|
+
compressed_dict = CompressedDict(
|
|
272
|
+
initial_dict={"key1": "value1"},
|
|
273
|
+
storage_mode=storage_mode,
|
|
274
|
+
properties_to_cache=None,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Test getdict outside transaction
|
|
278
|
+
with pytest.raises(CompressedDictAccessError):
|
|
279
|
+
compressed_dict._get_dict()
|
|
280
|
+
|
|
281
|
+
# Test __getitem__ outside transaction
|
|
282
|
+
with pytest.raises(CompressedDictAccessError):
|
|
283
|
+
_ = compressed_dict["key1"]
|
|
284
|
+
|
|
285
|
+
# Test __setitem__ outside transaction
|
|
286
|
+
with pytest.raises(CompressedDictAccessError):
|
|
287
|
+
compressed_dict["key2"] = "value2"
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def test_transaction_different_storage_modes() -> None:
|
|
291
|
+
"""
|
|
292
|
+
Test transaction with different storage modes
|
|
293
|
+
"""
|
|
294
|
+
storage_modes = [
|
|
295
|
+
CompressedDictStorageMode(storage_type="raw"),
|
|
296
|
+
CompressedDictStorageMode(storage_type="msgpack"),
|
|
297
|
+
CompressedDictStorageMode(storage_type="compressed_msgpack"),
|
|
298
|
+
]
|
|
299
|
+
|
|
300
|
+
for storage_mode in storage_modes:
|
|
301
|
+
initial_dict = {"key1": "value1"}
|
|
302
|
+
|
|
303
|
+
compressed_dict = CompressedDict(
|
|
304
|
+
initial_dict=initial_dict,
|
|
305
|
+
storage_mode=storage_mode,
|
|
306
|
+
properties_to_cache=None,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
with compressed_dict.transaction() as d:
|
|
310
|
+
d["key2"] = "value2"
|
|
311
|
+
|
|
312
|
+
assert compressed_dict.dict() == {"key1": "value1", "key2": "value2"}
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def test_transaction_with_properties_to_cache() -> None:
|
|
316
|
+
"""
|
|
317
|
+
Test transaction with properties to cache
|
|
318
|
+
"""
|
|
319
|
+
storage_mode = CompressedDictStorageMode(storage_type="raw")
|
|
320
|
+
initial_dict = {"key1": "value1", "important_prop": "cached_value"}
|
|
321
|
+
|
|
322
|
+
compressed_dict = CompressedDict(
|
|
323
|
+
initial_dict=initial_dict,
|
|
324
|
+
storage_mode=storage_mode,
|
|
325
|
+
properties_to_cache=["important_prop"],
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
with compressed_dict.transaction() as d:
|
|
329
|
+
d["key2"] = "value2"
|
|
330
|
+
|
|
331
|
+
assert compressed_dict.dict() == {
|
|
332
|
+
"key1": "value1",
|
|
333
|
+
"important_prop": "cached_value",
|
|
334
|
+
"key2": "value2",
|
|
335
|
+
}
|
|
336
|
+
assert compressed_dict._cached_properties == {"important_prop": "cached_value"}
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def test_transaction_error_handling() -> None:
|
|
340
|
+
"""
|
|
341
|
+
Test error handling during transaction
|
|
342
|
+
"""
|
|
343
|
+
storage_mode = CompressedDictStorageMode(storage_type="compressed")
|
|
344
|
+
compressed_dict = CompressedDict(
|
|
345
|
+
initial_dict={"key1": "value1"},
|
|
346
|
+
storage_mode=storage_mode,
|
|
347
|
+
properties_to_cache=None,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
try:
|
|
351
|
+
with compressed_dict.transaction():
|
|
352
|
+
compressed_dict["key2"] = "value2"
|
|
353
|
+
raise ValueError("Simulated error")
|
|
354
|
+
except ValueError:
|
|
355
|
+
# Ensure transaction depth is reset even after an error
|
|
356
|
+
assert compressed_dict._transaction_depth == 0
|
|
357
|
+
|
|
358
|
+
# Verify the dictionary state remains unchanged
|
|
359
|
+
with compressed_dict.transaction() as d:
|
|
360
|
+
assert d.dict() == {"key1": "value1", "key2": "value2"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import json
|
|
3
|
+
from datetime import datetime, date
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FhirJSONEncoder(json.JSONEncoder):
|
|
10
|
+
def default(self, o: Any) -> Any:
|
|
11
|
+
if dataclasses.is_dataclass(o):
|
|
12
|
+
return dataclasses.asdict(o) # type:ignore
|
|
13
|
+
if isinstance(o, Enum):
|
|
14
|
+
return o.value
|
|
15
|
+
if isinstance(o, Decimal):
|
|
16
|
+
# Custom Decimal conversion
|
|
17
|
+
if o == o.to_integral_value():
|
|
18
|
+
# If Decimal is a whole number, return as int
|
|
19
|
+
return int(o)
|
|
20
|
+
else:
|
|
21
|
+
# If Decimal has a non-zero decimal part, return as float
|
|
22
|
+
return float(o)
|
|
23
|
+
if isinstance(o, bytes):
|
|
24
|
+
return o.decode("utf-8")
|
|
25
|
+
if isinstance(o, (datetime, date)):
|
|
26
|
+
return o.isoformat()
|
|
27
|
+
if hasattr(o, "to_dict"):
|
|
28
|
+
return o.dict()
|
|
29
|
+
|
|
30
|
+
return super().default(o)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any, Dict, List, cast, Union, Optional, OrderedDict, overload
|
|
3
|
+
from datetime import datetime, date
|
|
4
|
+
|
|
5
|
+
import orjson
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FhirClientJsonHelpers:
|
|
9
|
+
def __init__(self) -> None:
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def json_serial(obj: Any) -> str:
|
|
14
|
+
"""JSON serializer for objects not serializable by default json code"""
|
|
15
|
+
|
|
16
|
+
# https://stackoverflow.com/questions/11875770/how-to-overcome-datetime-datetime-not-json-serializable
|
|
17
|
+
if isinstance(obj, (datetime, date)):
|
|
18
|
+
return obj.isoformat()
|
|
19
|
+
return str(obj)
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def remove_empty_elements(
|
|
23
|
+
d: List[Dict[str, Any]] | Dict[str, Any],
|
|
24
|
+
) -> List[Dict[str, Any]] | Dict[str, Any]:
|
|
25
|
+
"""
|
|
26
|
+
Recursively remove empty lists, empty dicts, or None elements from a dictionary
|
|
27
|
+
or a list of dictionaries
|
|
28
|
+
:param d: dictionary or list of dictionaries
|
|
29
|
+
:return: dictionary or list of dictionaries
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def empty(x: Any) -> bool:
|
|
33
|
+
# Check if the input is None, an empty list, an empty dict, or an empty string
|
|
34
|
+
return (
|
|
35
|
+
x is None
|
|
36
|
+
or x == []
|
|
37
|
+
or x == {}
|
|
38
|
+
or (isinstance(x, str) and x.strip() == "")
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if not isinstance(d, (dict, list)):
|
|
42
|
+
return d
|
|
43
|
+
elif isinstance(d, list):
|
|
44
|
+
return [
|
|
45
|
+
cast(Dict[str, Any], v)
|
|
46
|
+
for v in (FhirClientJsonHelpers.remove_empty_elements(v) for v in d)
|
|
47
|
+
if not empty(v)
|
|
48
|
+
]
|
|
49
|
+
else:
|
|
50
|
+
return {
|
|
51
|
+
k: v
|
|
52
|
+
for k, v in (
|
|
53
|
+
(k, FhirClientJsonHelpers.remove_empty_elements(v))
|
|
54
|
+
for k, v in d.items()
|
|
55
|
+
)
|
|
56
|
+
if not empty(v)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
@overload
|
|
61
|
+
def remove_empty_elements_from_ordered_dict(
|
|
62
|
+
d: List[OrderedDict[str, Any]],
|
|
63
|
+
) -> List[OrderedDict[str, Any]]: ...
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
@overload
|
|
67
|
+
def remove_empty_elements_from_ordered_dict(
|
|
68
|
+
d: OrderedDict[str, Any],
|
|
69
|
+
) -> OrderedDict[str, Any]: ...
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def remove_empty_elements_from_ordered_dict(
|
|
73
|
+
d: List[OrderedDict[str, Any]] | OrderedDict[str, Any],
|
|
74
|
+
) -> List[OrderedDict[str, Any]] | OrderedDict[str, Any]:
|
|
75
|
+
"""
|
|
76
|
+
Recursively remove empty lists, empty dicts, or None elements from a dictionary
|
|
77
|
+
or a list of dictionaries
|
|
78
|
+
:param d: dictionary or list of dictionaries
|
|
79
|
+
:return: dictionary or list of dictionaries
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def empty(x: Any) -> bool:
|
|
83
|
+
# Check if the input is None, an empty list, an empty dict, or an empty string
|
|
84
|
+
return (
|
|
85
|
+
x is None
|
|
86
|
+
or x == []
|
|
87
|
+
or x == {}
|
|
88
|
+
or (isinstance(x, str) and x.strip() == "")
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if not isinstance(d, (OrderedDict, list, Dict)):
|
|
92
|
+
return d
|
|
93
|
+
elif isinstance(d, list):
|
|
94
|
+
return [
|
|
95
|
+
v
|
|
96
|
+
for v in (
|
|
97
|
+
FhirClientJsonHelpers.remove_empty_elements_from_ordered_dict(v)
|
|
98
|
+
for v in d
|
|
99
|
+
)
|
|
100
|
+
if not empty(v)
|
|
101
|
+
]
|
|
102
|
+
else:
|
|
103
|
+
return OrderedDict[str, Any](
|
|
104
|
+
{
|
|
105
|
+
k: v
|
|
106
|
+
for k, v in (
|
|
107
|
+
(
|
|
108
|
+
k,
|
|
109
|
+
FhirClientJsonHelpers.remove_empty_elements_from_ordered_dict(
|
|
110
|
+
v
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
for k, v in d.items()
|
|
114
|
+
)
|
|
115
|
+
if not empty(v)
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
def convert_dict_to_fhir_json(dict_: Dict[str, Any]) -> str:
|
|
121
|
+
"""
|
|
122
|
+
Returns dictionary as json string
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
:return:
|
|
126
|
+
"""
|
|
127
|
+
instance_variables: Dict[str, Any] = cast(
|
|
128
|
+
Dict[str, Any], FhirClientJsonHelpers.remove_empty_elements(dict_)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
instance_variables_text: str = json.dumps(
|
|
132
|
+
instance_variables, default=FhirClientJsonHelpers.json_serial
|
|
133
|
+
)
|
|
134
|
+
return instance_variables_text
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
def orjson_dumps(
|
|
138
|
+
obj: Any, indent: Optional[int] = None, sort_keys: bool = False
|
|
139
|
+
) -> str:
|
|
140
|
+
"""
|
|
141
|
+
Wrapper for orjson.dumps() to mimic json.dumps() behavior
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
obj: Object to serialize
|
|
145
|
+
indent: Optional indentation (note: orjson has limited indent support)
|
|
146
|
+
sort_keys: Whether to sort dictionary keys
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
JSON string
|
|
150
|
+
"""
|
|
151
|
+
# Serialization options
|
|
152
|
+
options = 0
|
|
153
|
+
|
|
154
|
+
# Handle sorting keys
|
|
155
|
+
if sort_keys:
|
|
156
|
+
options |= orjson.OPT_SORT_KEYS
|
|
157
|
+
|
|
158
|
+
# Handle indentation (limited support)
|
|
159
|
+
if indent is not None:
|
|
160
|
+
options |= orjson.OPT_INDENT_2 # Fixed indentation
|
|
161
|
+
|
|
162
|
+
# Serialize to bytes
|
|
163
|
+
json_bytes = orjson.dumps(obj, option=options)
|
|
164
|
+
|
|
165
|
+
# Convert bytes to string
|
|
166
|
+
return json_bytes.decode("utf-8")
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def orjson_loads(json_input: Union[str, bytes]) -> Any:
|
|
170
|
+
"""
|
|
171
|
+
Safely load JSON with type flexibility
|
|
172
|
+
"""
|
|
173
|
+
try:
|
|
174
|
+
# Converts input to appropriate type if needed
|
|
175
|
+
if isinstance(json_input, str):
|
|
176
|
+
json_input = json_input.encode("utf-8")
|
|
177
|
+
|
|
178
|
+
return orjson.loads(json_input)
|
|
179
|
+
except (orjson.JSONDecodeError, TypeError) as e:
|
|
180
|
+
print(f"JSON Parsing Error: {e}")
|
|
181
|
+
return None
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: compressedfhir
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Stores FHIR JSON resources in compressed form in memory
|
|
5
|
+
Home-page: https://github.com/icanbwell/compressed-fhir
|
|
6
|
+
Author: Imran Qureshi
|
|
7
|
+
Author-email: imran.qureshi@icanbwell.com
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: msgpack>=1.0.0
|
|
16
|
+
Requires-Dist: orjson>=3.10.16
|
|
17
|
+
Dynamic: author
|
|
18
|
+
Dynamic: author-email
|
|
19
|
+
Dynamic: classifier
|
|
20
|
+
Dynamic: description
|
|
21
|
+
Dynamic: description-content-type
|
|
22
|
+
Dynamic: home-page
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
Dynamic: requires-dist
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
Dynamic: summary
|
|
27
|
+
|
|
28
|
+
# compressedfhir
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
compressedfhir/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
compressedfhir/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
compressedfhir/fhir/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
compressedfhir/fhir/base_resource_list.py,sha256=hhlQLT_HFrLmVPuirTsiXQsiUadxpfSQTPS4CofthTM,4924
|
|
5
|
+
compressedfhir/fhir/fhir_bundle.py,sha256=MG98a7TG0K71PSQz5CvMzHFXZBv81JZ7_m1bzOsZQsE,10103
|
|
6
|
+
compressedfhir/fhir/fhir_bundle_entry.py,sha256=eQT-NSZvMbPbKubIKOFPo_70f4CVAfSwvnQVbC5Y3LI,8459
|
|
7
|
+
compressedfhir/fhir/fhir_bundle_entry_list.py,sha256=tjZueiviQ4ucSDNGSR9CpN-Kwv3BIBcmal3_0J1HE_E,2655
|
|
8
|
+
compressedfhir/fhir/fhir_bundle_entry_request.py,sha256=djoK7Izq8PxLVjUvG8Gy5o1bNXuNL7s6iaCRVx1CrVs,2607
|
|
9
|
+
compressedfhir/fhir/fhir_bundle_entry_response.py,sha256=_Q5r3AlLcT4J_TSVzoSZDO1U_wtBBZM23_PPG7djbTc,2302
|
|
10
|
+
compressedfhir/fhir/fhir_bundle_entry_search.py,sha256=uYVJxuNN3gt3Q6BZ5FhRs47x7l54Lo_H-7JdoOvkx94,2554
|
|
11
|
+
compressedfhir/fhir/fhir_identifier.py,sha256=tA_nmhBaYHu5zjJdE0IWMFEF8lrIPV3_nu-yairiIKw,2711
|
|
12
|
+
compressedfhir/fhir/fhir_link.py,sha256=jf2RrwmsPrKW3saP77y42xVqI0xwHFYXxm6YHQJk7gU,1922
|
|
13
|
+
compressedfhir/fhir/fhir_meta.py,sha256=vNI4O6SoG4hJRHyd-bJ_QnYFTfBHyR3UA6h21ByQmWo,1669
|
|
14
|
+
compressedfhir/fhir/fhir_resource.py,sha256=iHc7HGxiGTf8E2tSf4rXXAztc5NkY61rLTdzXq9iZIQ,5259
|
|
15
|
+
compressedfhir/fhir/fhir_resource_list.py,sha256=YsGqLN7vE-qfrN0Yg_UpPHjBjfB5JSsliTbNllM69-w,4477
|
|
16
|
+
compressedfhir/fhir/fhir_resource_map.py,sha256=6Zt_K8KVolS-lgT_Ztu_6YxNo8BXhweQfWO-QFriInA,6588
|
|
17
|
+
compressedfhir/fhir/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
compressedfhir/fhir/test/test_bundle_entry.py,sha256=Ki2sSu1V1WZkAM6UTCghtzjvjYYI8UcF6AXnx8FWlMI,5115
|
|
19
|
+
compressedfhir/fhir/test/test_bundle_entry_list.py,sha256=KtMrbQYezdEw9FJbBzwSePdJK2R9P03mSRfo59T-6iM,6041
|
|
20
|
+
compressedfhir/fhir/test/test_bundle_entry_request.py,sha256=9bN3Vt9BAXPLjH7FFt_MYSdanFJzWk9HbA0C9kZxPXY,2853
|
|
21
|
+
compressedfhir/fhir/test/test_bundle_entry_response.py,sha256=jk5nUi07_q-yz-qz2YR86vU91e3DVxc2cptrS6tsCco,2539
|
|
22
|
+
compressedfhir/fhir/test/test_fhir_bundle.py,sha256=Kt1IpxEnUuPOJBDWsdy4cC7kR3FR-uPOf7PB9ejJ7ZM,8700
|
|
23
|
+
compressedfhir/fhir/test/test_fhir_resource.py,sha256=4Fl6QaqjW4CsYqkxVj2WRXITv_MeozUIrZgN4bMBGIw,8002
|
|
24
|
+
compressedfhir/fhir/test/test_fhir_resource_list.py,sha256=SrSPJ1yWU4UgMUCht6JwgKh2Y5JeTS4-Wky0kWZOXH8,5664
|
|
25
|
+
compressedfhir/fhir/test/test_fhir_resource_map.py,sha256=jtQ5fq_jhmFfhHGyK5mdiwIQiO-Sfp2eG9mco_Tr9Qk,10995
|
|
26
|
+
compressedfhir/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
compressedfhir/utilities/fhir_json_encoder.py,sha256=vUx0hdrdbo9mVgH7mpwKG1Th96QiDIoPkvL_0ba5SBM,954
|
|
28
|
+
compressedfhir/utilities/json_helpers.py,sha256=lEiPapLN0p-kLu6PFm-h971ieXRxwPB2M-8FCZ2Buo8,5642
|
|
29
|
+
compressedfhir/utilities/compressed_dict/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
+
compressedfhir/utilities/compressed_dict/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
+
compressedfhir/utilities/compressed_dict/v1/compressed_dict.py,sha256=tt1wCI1o4nLJwi_E-UXP5kZUT1-jo7rdOZzsZTKwiok,19970
|
|
32
|
+
compressedfhir/utilities/compressed_dict/v1/compressed_dict_access_error.py,sha256=xuwED0KGZcQORIcZRfi--5CdXplHJ5vYLBUqpbDi344,132
|
|
33
|
+
compressedfhir/utilities/compressed_dict/v1/compressed_dict_storage_mode.py,sha256=GmwvQX2e9DbdKq2oKnW-VaqbigeJfVqDEhnOBfcbpdA,1402
|
|
34
|
+
compressedfhir/utilities/compressed_dict/v1/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
|
+
compressedfhir/utilities/compressed_dict/v1/test/test_compressed_dict.py,sha256=7AsOX1Nw7Woo9C7OzdBXMXFQhgEBAZZ8py1aHfFh-4k,11970
|
|
36
|
+
compressedfhir-0.0.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
37
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
+
compressedfhir-0.0.1.dist-info/METADATA,sha256=MkBZCtq_UoUJXwSQR7fv40dd70OSjGFnB_AZ___2p-E,828
|
|
39
|
+
compressedfhir-0.0.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
40
|
+
compressedfhir-0.0.1.dist-info/top_level.txt,sha256=YMKdvBBdiCzFbpI9fG8BUDjaRd-f4R0qAvUoVETpoWw,21
|
|
41
|
+
compressedfhir-0.0.1.dist-info/RECORD,,
|