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,635 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
from collections.abc import KeysView, ValuesView, ItemsView, MutableMapping
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from typing import Dict, Optional, Iterator, cast, List, Any, overload, OrderedDict
|
|
6
|
+
|
|
7
|
+
import msgpack
|
|
8
|
+
import zlib
|
|
9
|
+
|
|
10
|
+
from compressedfhir.utilities.compressed_dict.v1.compressed_dict_access_error import (
|
|
11
|
+
CompressedDictAccessError,
|
|
12
|
+
)
|
|
13
|
+
from compressedfhir.utilities.compressed_dict.v1.compressed_dict_storage_mode import (
|
|
14
|
+
CompressedDictStorageMode,
|
|
15
|
+
CompressedDictStorageType,
|
|
16
|
+
)
|
|
17
|
+
from compressedfhir.utilities.fhir_json_encoder import FhirJSONEncoder
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CompressedDict[K, V](MutableMapping[K, V]):
|
|
21
|
+
"""
|
|
22
|
+
A dictionary-like class that supports flexible storage options.
|
|
23
|
+
|
|
24
|
+
It can store data in raw format, MessagePack format, or compressed MessagePack format.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
# use slots to reduce memory usage
|
|
28
|
+
__slots__ = [
|
|
29
|
+
"_storage_mode",
|
|
30
|
+
"_working_dict",
|
|
31
|
+
"_raw_dict",
|
|
32
|
+
"_serialized_dict",
|
|
33
|
+
"_properties_to_cache",
|
|
34
|
+
"_cached_properties",
|
|
35
|
+
"_length",
|
|
36
|
+
"_transaction_depth",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
*,
|
|
42
|
+
initial_dict: Dict[K, V] | OrderedDict[K, V] | None = None,
|
|
43
|
+
storage_mode: CompressedDictStorageMode,
|
|
44
|
+
properties_to_cache: List[K] | None,
|
|
45
|
+
) -> None:
|
|
46
|
+
"""
|
|
47
|
+
Initialize a dictionary with flexible storage options
|
|
48
|
+
Args:
|
|
49
|
+
initial_dict: Initial dictionary to store
|
|
50
|
+
storage_mode: Storage method for dictionary contents
|
|
51
|
+
- 'raw': Store as original dictionary
|
|
52
|
+
- 'msgpack': Store as MessagePack serialized bytes
|
|
53
|
+
- 'compressed_msgpack': Store as compressed MessagePack bytes
|
|
54
|
+
"""
|
|
55
|
+
# Storage configuration
|
|
56
|
+
self._storage_mode: CompressedDictStorageMode = storage_mode
|
|
57
|
+
|
|
58
|
+
# Working copy of the dictionary during context
|
|
59
|
+
self._working_dict: Optional[OrderedDict[K, V]] = None
|
|
60
|
+
|
|
61
|
+
# Private storage options
|
|
62
|
+
self._raw_dict: OrderedDict[K, V] = OrderedDict[K, V]()
|
|
63
|
+
self._serialized_dict: Optional[bytes] = None
|
|
64
|
+
|
|
65
|
+
self._properties_to_cache: List[K] | None = properties_to_cache
|
|
66
|
+
|
|
67
|
+
self._cached_properties: Dict[K, V] = {}
|
|
68
|
+
|
|
69
|
+
self._length: int = 0
|
|
70
|
+
|
|
71
|
+
self._transaction_depth: int = 0
|
|
72
|
+
|
|
73
|
+
# Populate initial dictionary if provided
|
|
74
|
+
if initial_dict:
|
|
75
|
+
# Ensure we use an OrderedDict to maintain original order
|
|
76
|
+
initial_dict_ordered = (
|
|
77
|
+
initial_dict
|
|
78
|
+
if isinstance(initial_dict, OrderedDict)
|
|
79
|
+
else OrderedDict[K, V](initial_dict)
|
|
80
|
+
)
|
|
81
|
+
self.replace(value=initial_dict_ordered)
|
|
82
|
+
|
|
83
|
+
@contextmanager
|
|
84
|
+
def transaction(self) -> Iterator["CompressedDict[K, V]"]:
|
|
85
|
+
"""
|
|
86
|
+
Context manager to safely access and modify the dictionary.
|
|
87
|
+
|
|
88
|
+
Deserializes the dictionary before entering the context
|
|
89
|
+
Serializes the dictionary after exiting the context
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
CompressedDictAccessError: If methods are called outside the context
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
self.start_transaction()
|
|
96
|
+
|
|
97
|
+
# Yield the working dictionary
|
|
98
|
+
yield self
|
|
99
|
+
|
|
100
|
+
finally:
|
|
101
|
+
self.end_transaction()
|
|
102
|
+
|
|
103
|
+
def start_transaction(self) -> "CompressedDict[K, V]":
|
|
104
|
+
"""
|
|
105
|
+
Starts a transaction. Use transaction() for a contextmanager for simpler usage.
|
|
106
|
+
"""
|
|
107
|
+
# Increment transaction depth
|
|
108
|
+
self._transaction_depth += 1
|
|
109
|
+
# Ensure working dictionary is ready on first entry
|
|
110
|
+
if self._transaction_depth == 1:
|
|
111
|
+
self.ensure_working_dict()
|
|
112
|
+
|
|
113
|
+
return self
|
|
114
|
+
|
|
115
|
+
def end_transaction(self) -> "CompressedDict[K, V]":
|
|
116
|
+
"""
|
|
117
|
+
Ends a transaction. Use transaction() for a context_manager for simpler usage.
|
|
118
|
+
|
|
119
|
+
"""
|
|
120
|
+
# Decrement transaction depth
|
|
121
|
+
self._transaction_depth -= 1
|
|
122
|
+
# Only update serialized dict when outermost transaction completes
|
|
123
|
+
if self._transaction_depth == 0:
|
|
124
|
+
self._update_serialized_dict(current_dict=self._working_dict)
|
|
125
|
+
|
|
126
|
+
# Clear the working dictionary
|
|
127
|
+
self._working_dict = None
|
|
128
|
+
|
|
129
|
+
return self
|
|
130
|
+
|
|
131
|
+
def ensure_working_dict(self) -> None:
|
|
132
|
+
"""
|
|
133
|
+
Ensures that the working dictionary is initialized and deserialized.
|
|
134
|
+
|
|
135
|
+
"""
|
|
136
|
+
if not self._working_dict:
|
|
137
|
+
self._working_dict = self.create_working_dict()
|
|
138
|
+
|
|
139
|
+
def create_working_dict(self) -> OrderedDict[K, V]:
|
|
140
|
+
working_dict: OrderedDict[K, V]
|
|
141
|
+
# Deserialize the dictionary before entering the context
|
|
142
|
+
if self._storage_mode.storage_type == "raw":
|
|
143
|
+
# For raw mode, create a deep copy of the existing dictionary
|
|
144
|
+
working_dict = self._raw_dict
|
|
145
|
+
else:
|
|
146
|
+
# For serialized modes, deserialize
|
|
147
|
+
working_dict = (
|
|
148
|
+
self._deserialize_dict(
|
|
149
|
+
serialized_dict=self._serialized_dict,
|
|
150
|
+
storage_type=self._storage_mode.storage_type,
|
|
151
|
+
)
|
|
152
|
+
if self._serialized_dict
|
|
153
|
+
else OrderedDict[K, V]()
|
|
154
|
+
)
|
|
155
|
+
assert isinstance(working_dict, OrderedDict)
|
|
156
|
+
return working_dict
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def _serialize_dict(
|
|
160
|
+
*, dictionary: OrderedDict[K, V], storage_type: CompressedDictStorageType
|
|
161
|
+
) -> bytes:
|
|
162
|
+
"""
|
|
163
|
+
Serialize entire dictionary using MessagePack
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
dictionary: Dictionary to serialize
|
|
167
|
+
storage_type: Storage type to use for serialization
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Serialized bytes
|
|
171
|
+
"""
|
|
172
|
+
assert isinstance(dictionary, OrderedDict)
|
|
173
|
+
if storage_type == "compressed":
|
|
174
|
+
# Serialize to JSON and compress with zlib
|
|
175
|
+
json_str = json.dumps(
|
|
176
|
+
dictionary, separators=(",", ":"), cls=FhirJSONEncoder
|
|
177
|
+
) # Most compact JSON representation
|
|
178
|
+
return zlib.compress(
|
|
179
|
+
json_str.encode("utf-8"), level=zlib.Z_BEST_COMPRESSION
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Serialize using MessagePack
|
|
183
|
+
packed = msgpack.packb(
|
|
184
|
+
dictionary,
|
|
185
|
+
use_bin_type=True, # Preserve string/bytes distinction
|
|
186
|
+
use_single_float=True, # More compact float representation
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Optional compression
|
|
190
|
+
if storage_type == "compressed_msgpack":
|
|
191
|
+
packed = zlib.compress(packed, level=zlib.Z_BEST_COMPRESSION)
|
|
192
|
+
|
|
193
|
+
return packed
|
|
194
|
+
|
|
195
|
+
@staticmethod
|
|
196
|
+
def _deserialize_dict(
|
|
197
|
+
*,
|
|
198
|
+
serialized_dict: bytes,
|
|
199
|
+
storage_type: CompressedDictStorageType,
|
|
200
|
+
) -> OrderedDict[K, V]:
|
|
201
|
+
"""
|
|
202
|
+
Deserialize entire dictionary from MessagePack
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
serialized_dict: Serialized dictionary bytes
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Deserialized dictionary
|
|
209
|
+
"""
|
|
210
|
+
assert serialized_dict is not None, "Serialized dictionary cannot be None"
|
|
211
|
+
|
|
212
|
+
if storage_type == "compressed":
|
|
213
|
+
# Decompress and parse JSON
|
|
214
|
+
decompressed = zlib.decompress(serialized_dict)
|
|
215
|
+
decoded_text = decompressed.decode("utf-8")
|
|
216
|
+
# noinspection PyTypeChecker
|
|
217
|
+
decompressed_dict = json.loads(decoded_text, object_pairs_hook=OrderedDict)
|
|
218
|
+
assert isinstance(decompressed_dict, OrderedDict)
|
|
219
|
+
return cast(OrderedDict[K, V], decompressed_dict)
|
|
220
|
+
|
|
221
|
+
# Decompress if needed
|
|
222
|
+
to_unpack = (
|
|
223
|
+
zlib.decompress(serialized_dict)
|
|
224
|
+
if storage_type == "compressed_msgpack"
|
|
225
|
+
else serialized_dict
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Deserialize
|
|
229
|
+
unpacked_dict = msgpack.unpackb(
|
|
230
|
+
to_unpack,
|
|
231
|
+
raw=False, # Convert to strings
|
|
232
|
+
strict_map_key=False, # Handle potential key type variations
|
|
233
|
+
)
|
|
234
|
+
unpacked_dict = (
|
|
235
|
+
unpacked_dict
|
|
236
|
+
if isinstance(unpacked_dict, OrderedDict)
|
|
237
|
+
else OrderedDict[K, V](unpacked_dict)
|
|
238
|
+
)
|
|
239
|
+
assert isinstance(unpacked_dict, OrderedDict)
|
|
240
|
+
return cast(
|
|
241
|
+
OrderedDict[K, V],
|
|
242
|
+
unpacked_dict,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
def _get_dict(self) -> OrderedDict[K, V]:
|
|
246
|
+
"""
|
|
247
|
+
Get the dictionary, deserializing if necessary
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Current dictionary state
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
if self._working_dict is None:
|
|
254
|
+
raise CompressedDictAccessError(
|
|
255
|
+
"Dictionary access is only allowed within an transaction() block. "
|
|
256
|
+
"Use 'with compressed_dict.transaction() as d:' to access the dictionary."
|
|
257
|
+
f"You tried to access it with storage type {self._storage_mode.storage_type}."
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
if self._storage_mode.storage_type == "raw":
|
|
261
|
+
return self._raw_dict
|
|
262
|
+
|
|
263
|
+
# For non-raw modes, do not keep deserialized dict
|
|
264
|
+
return self._working_dict
|
|
265
|
+
|
|
266
|
+
def __getitem__(self, key: K) -> V:
|
|
267
|
+
"""
|
|
268
|
+
Retrieve a value
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
key: Dictionary key
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Value associated with the key
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
if self._properties_to_cache and key in self._properties_to_cache:
|
|
278
|
+
return self._cached_properties[key]
|
|
279
|
+
|
|
280
|
+
if self._working_dict is None:
|
|
281
|
+
raise CompressedDictAccessError(
|
|
282
|
+
"Dictionary access is only allowed within an transaction() block. "
|
|
283
|
+
"Use 'with compressed_dict.transaction() as d:' to access the dictionary."
|
|
284
|
+
)
|
|
285
|
+
return self._get_dict()[key]
|
|
286
|
+
|
|
287
|
+
def __setitem__(self, key: K, value: V) -> None:
|
|
288
|
+
"""
|
|
289
|
+
Set a value
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
key: Dictionary key
|
|
293
|
+
value: Value to store
|
|
294
|
+
"""
|
|
295
|
+
if self._working_dict is None:
|
|
296
|
+
raise CompressedDictAccessError(
|
|
297
|
+
"Dictionary modification is only allowed within an transaction() block. "
|
|
298
|
+
"Use 'with compressed_dict.transaction() as d:' to modify the dictionary."
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
# Update the working dictionary
|
|
302
|
+
self._working_dict[key] = value
|
|
303
|
+
|
|
304
|
+
if self._properties_to_cache and key in self._properties_to_cache:
|
|
305
|
+
# Update the cached properties if the key is in the list
|
|
306
|
+
self._cached_properties[key] = value
|
|
307
|
+
|
|
308
|
+
def _update_serialized_dict(self, current_dict: OrderedDict[K, V] | None) -> None:
|
|
309
|
+
if current_dict is None:
|
|
310
|
+
self._cached_properties.clear()
|
|
311
|
+
self._length = 0
|
|
312
|
+
self._serialized_dict = None
|
|
313
|
+
self._raw_dict = OrderedDict[K, V]()
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
if self._properties_to_cache:
|
|
317
|
+
for key in self._properties_to_cache:
|
|
318
|
+
if key in current_dict:
|
|
319
|
+
self._cached_properties[key] = current_dict[key]
|
|
320
|
+
|
|
321
|
+
self._length = len(current_dict)
|
|
322
|
+
|
|
323
|
+
if self._working_dict is not None:
|
|
324
|
+
# If the working dictionary is None, initialize it
|
|
325
|
+
self._working_dict = current_dict
|
|
326
|
+
|
|
327
|
+
if self._transaction_depth == 0:
|
|
328
|
+
# If we're in a transaction,
|
|
329
|
+
# The serialized dictionary will be updated after the transaction
|
|
330
|
+
if self._storage_mode.storage_type == "raw":
|
|
331
|
+
self._raw_dict = current_dict
|
|
332
|
+
else:
|
|
333
|
+
self._serialized_dict = (
|
|
334
|
+
self._serialize_dict(
|
|
335
|
+
dictionary=current_dict,
|
|
336
|
+
storage_type=self._storage_mode.storage_type,
|
|
337
|
+
)
|
|
338
|
+
if current_dict
|
|
339
|
+
else None
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
def __delitem__(self, key: K) -> None:
|
|
343
|
+
"""
|
|
344
|
+
Delete an item
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
key: Key to delete
|
|
348
|
+
"""
|
|
349
|
+
if self._working_dict is None:
|
|
350
|
+
raise CompressedDictAccessError(
|
|
351
|
+
"Dictionary modification is only allowed within an transaction() block. "
|
|
352
|
+
"Use 'with compressed_dict.transaction() as d:' to modify the dictionary."
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
del self._working_dict[key]
|
|
356
|
+
|
|
357
|
+
def __contains__(self, key: object) -> bool:
|
|
358
|
+
"""
|
|
359
|
+
Check if a key exists
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
key: Key to check
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
Whether the key exists
|
|
366
|
+
"""
|
|
367
|
+
# first check if the key is in the cached properties
|
|
368
|
+
if self._properties_to_cache and key in self._properties_to_cache:
|
|
369
|
+
return self._cached_properties.__contains__(key)
|
|
370
|
+
|
|
371
|
+
return self._get_dict().__contains__(key)
|
|
372
|
+
|
|
373
|
+
def __len__(self) -> int:
|
|
374
|
+
"""
|
|
375
|
+
Get the number of items
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
Number of items in the dictionary
|
|
379
|
+
"""
|
|
380
|
+
return self._length
|
|
381
|
+
|
|
382
|
+
def __iter__(self) -> Iterator[K]:
|
|
383
|
+
"""
|
|
384
|
+
Iterate over keys
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
Iterator of keys
|
|
388
|
+
"""
|
|
389
|
+
if self._working_dict is None:
|
|
390
|
+
raise CompressedDictAccessError(
|
|
391
|
+
"Dictionary modification is only allowed within an transaction() block. "
|
|
392
|
+
"Use 'with compressed_dict.transaction() as d:' to modify the dictionary."
|
|
393
|
+
)
|
|
394
|
+
return iter(self._get_dict())
|
|
395
|
+
|
|
396
|
+
def keys(self) -> KeysView[K]:
|
|
397
|
+
"""
|
|
398
|
+
Get an iterator of keys
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
Iterator of keys
|
|
402
|
+
"""
|
|
403
|
+
return self._get_dict().keys()
|
|
404
|
+
|
|
405
|
+
def values(self) -> ValuesView[V]:
|
|
406
|
+
"""
|
|
407
|
+
Get an iterator of values
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
Iterator of values
|
|
411
|
+
"""
|
|
412
|
+
return self._get_dict().values()
|
|
413
|
+
|
|
414
|
+
def items(self) -> ItemsView[K, V]:
|
|
415
|
+
"""
|
|
416
|
+
Get an iterator of key-value pairs
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
Iterator of key-value pairs
|
|
420
|
+
"""
|
|
421
|
+
return self._get_dict().items()
|
|
422
|
+
|
|
423
|
+
def dict(self, *, remove_nulls: bool = True) -> OrderedDict[K, V]:
|
|
424
|
+
"""
|
|
425
|
+
Convert to a standard dictionary
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
Standard dictionary with all values
|
|
429
|
+
"""
|
|
430
|
+
if self._working_dict:
|
|
431
|
+
return self._working_dict
|
|
432
|
+
else:
|
|
433
|
+
# if the working dict is None, return it but don't store it in the self._working_dict to keep memory low
|
|
434
|
+
return self.create_working_dict()
|
|
435
|
+
|
|
436
|
+
def __repr__(self) -> str:
|
|
437
|
+
"""
|
|
438
|
+
String representation of the dictionary
|
|
439
|
+
|
|
440
|
+
Returns:
|
|
441
|
+
String representation
|
|
442
|
+
"""
|
|
443
|
+
cached_property_list: List[str] = [
|
|
444
|
+
f"{k}={v}" for k, v in self._cached_properties.items()
|
|
445
|
+
]
|
|
446
|
+
return (
|
|
447
|
+
(
|
|
448
|
+
f"CompressedDict(storage_type='{self._storage_mode.storage_type}', keys={self._length}"
|
|
449
|
+
)
|
|
450
|
+
+ (", " if cached_property_list else "")
|
|
451
|
+
+ ", ".join(cached_property_list)
|
|
452
|
+
+ ")"
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
def replace(
|
|
456
|
+
self, *, value: Dict[K, V] | OrderedDict[K, V]
|
|
457
|
+
) -> "CompressedDict[K, V]":
|
|
458
|
+
"""
|
|
459
|
+
Replace the current dictionary with a new one
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
value: New dictionary to store
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
Self
|
|
466
|
+
"""
|
|
467
|
+
if not value:
|
|
468
|
+
self.clear()
|
|
469
|
+
return self
|
|
470
|
+
|
|
471
|
+
new_dict: OrderedDict[K, V] = (
|
|
472
|
+
value if isinstance(value, OrderedDict) else OrderedDict[K, V](value)
|
|
473
|
+
)
|
|
474
|
+
self._update_serialized_dict(current_dict=new_dict)
|
|
475
|
+
return self
|
|
476
|
+
|
|
477
|
+
def clear(self) -> None:
|
|
478
|
+
"""
|
|
479
|
+
Clear the dictionary
|
|
480
|
+
"""
|
|
481
|
+
self._update_serialized_dict(current_dict=None)
|
|
482
|
+
|
|
483
|
+
def __eq__(self, other: object) -> bool:
|
|
484
|
+
"""
|
|
485
|
+
Check equality with another dictionary
|
|
486
|
+
|
|
487
|
+
Args:
|
|
488
|
+
other: Dictionary to compare with (CompressedDict or plain dict)
|
|
489
|
+
|
|
490
|
+
Returns:
|
|
491
|
+
Whether the dictionaries are equal in keys and values
|
|
492
|
+
"""
|
|
493
|
+
# If other is not a dictionary-like object, return False
|
|
494
|
+
if not isinstance(other, (CompressedDict, dict, OrderedDict)):
|
|
495
|
+
return False
|
|
496
|
+
|
|
497
|
+
# Get the dictionary representation of self
|
|
498
|
+
self_dict = self.dict()
|
|
499
|
+
|
|
500
|
+
# If other is a CompressedDict, use its _get_dict() method
|
|
501
|
+
if isinstance(other, CompressedDict):
|
|
502
|
+
other_dict = other.dict()
|
|
503
|
+
else:
|
|
504
|
+
# If other is a plain dict, use it directly
|
|
505
|
+
if isinstance(other, OrderedDict):
|
|
506
|
+
# If other is an OrderedDict, use it directly
|
|
507
|
+
other_dict = other
|
|
508
|
+
else:
|
|
509
|
+
other_dict = OrderedDict[K, V](other)
|
|
510
|
+
|
|
511
|
+
# Compare keys and values
|
|
512
|
+
# Check that all keys in both dictionaries match exactly
|
|
513
|
+
return set(self_dict.keys()) == set(other_dict.keys()) and all(
|
|
514
|
+
self_dict[key] == other_dict[key] for key in self_dict
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
@overload
|
|
518
|
+
def get(self, key: K) -> Optional[V]:
|
|
519
|
+
"""
|
|
520
|
+
Get a value for an existing key
|
|
521
|
+
|
|
522
|
+
:param key: Key to retrieve
|
|
523
|
+
:return: Value or None if key is not found
|
|
524
|
+
"""
|
|
525
|
+
...
|
|
526
|
+
|
|
527
|
+
# noinspection PyMethodOverriding
|
|
528
|
+
@overload
|
|
529
|
+
def get[_T](self, key: K, /, default: V | _T) -> V | _T:
|
|
530
|
+
"""
|
|
531
|
+
Get a value with a default
|
|
532
|
+
|
|
533
|
+
Args:
|
|
534
|
+
key: Key to retrieve
|
|
535
|
+
default: Default value if key is not found
|
|
536
|
+
|
|
537
|
+
Returns:
|
|
538
|
+
Value associated with the key or default
|
|
539
|
+
"""
|
|
540
|
+
...
|
|
541
|
+
|
|
542
|
+
def get[_T](self, key: K, default: V | _T | None = None) -> V | _T | None:
|
|
543
|
+
if key in self:
|
|
544
|
+
return self[key]
|
|
545
|
+
return default
|
|
546
|
+
|
|
547
|
+
def __deepcopy__(self, memo: Dict[int, Any]) -> "CompressedDict[K, V]":
|
|
548
|
+
"""
|
|
549
|
+
Create a deep copy of the dictionary
|
|
550
|
+
|
|
551
|
+
Args:
|
|
552
|
+
memo: Memoization dictionary for deep copy
|
|
553
|
+
|
|
554
|
+
Returns:
|
|
555
|
+
Deep copy of the dictionary
|
|
556
|
+
"""
|
|
557
|
+
# Create a new instance with the same storage mode
|
|
558
|
+
new_instance = CompressedDict(
|
|
559
|
+
initial_dict=copy.deepcopy(self.dict()),
|
|
560
|
+
storage_mode=self._storage_mode,
|
|
561
|
+
properties_to_cache=self._properties_to_cache,
|
|
562
|
+
)
|
|
563
|
+
return new_instance
|
|
564
|
+
|
|
565
|
+
def copy(self) -> "CompressedDict[K,V]":
|
|
566
|
+
"""
|
|
567
|
+
Creates a copy of the BundleEntry object.
|
|
568
|
+
|
|
569
|
+
:return: A new BundleEntry object with the same attributes.
|
|
570
|
+
"""
|
|
571
|
+
return copy.deepcopy(self)
|
|
572
|
+
|
|
573
|
+
def get_storage_mode(self) -> CompressedDictStorageMode:
|
|
574
|
+
"""
|
|
575
|
+
Get the storage mode
|
|
576
|
+
|
|
577
|
+
Returns:
|
|
578
|
+
Storage mode
|
|
579
|
+
"""
|
|
580
|
+
return self._storage_mode
|
|
581
|
+
|
|
582
|
+
@overload
|
|
583
|
+
def pop(self, key: K, /) -> V:
|
|
584
|
+
"""
|
|
585
|
+
Remove and return a value for an existing key
|
|
586
|
+
|
|
587
|
+
:param key: Key to remove
|
|
588
|
+
:return: Removed value
|
|
589
|
+
:raises KeyError: If key is not found
|
|
590
|
+
"""
|
|
591
|
+
...
|
|
592
|
+
|
|
593
|
+
# noinspection PyMethodOverriding
|
|
594
|
+
@overload
|
|
595
|
+
def pop[_T](self, key: K, /, default: _T) -> V | _T:
|
|
596
|
+
"""
|
|
597
|
+
Remove and return a value, or return default if key is not found
|
|
598
|
+
|
|
599
|
+
:param key: Key to remove
|
|
600
|
+
:param default: Default value to return if key is not found
|
|
601
|
+
:return: Removed value or default
|
|
602
|
+
"""
|
|
603
|
+
...
|
|
604
|
+
|
|
605
|
+
def pop[_T](self, key: K, /, default: V | _T | None = None) -> V | _T | None:
|
|
606
|
+
"""
|
|
607
|
+
Remove and return a value
|
|
608
|
+
|
|
609
|
+
:param key: Key to remove
|
|
610
|
+
:param default: Optional default value if key is not found
|
|
611
|
+
:return: Removed value or default
|
|
612
|
+
:raises KeyError: If key is not found and no default is provided
|
|
613
|
+
"""
|
|
614
|
+
if self._working_dict is None:
|
|
615
|
+
raise CompressedDictAccessError(
|
|
616
|
+
"Dictionary modification is only allowed within a transaction() block. "
|
|
617
|
+
"Use 'with compressed_dict.transaction() as d:' to modify the dictionary."
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
# If no default is provided, use the standard dict.pop() behavior
|
|
621
|
+
if default is None:
|
|
622
|
+
return self._working_dict.pop(key)
|
|
623
|
+
|
|
624
|
+
return self._working_dict.pop(key, default)
|
|
625
|
+
|
|
626
|
+
def to_plain_dict(self) -> Dict[K, V]:
|
|
627
|
+
"""
|
|
628
|
+
Get the plain dictionary representation
|
|
629
|
+
|
|
630
|
+
Returns:
|
|
631
|
+
Plain dictionary
|
|
632
|
+
"""
|
|
633
|
+
return cast(
|
|
634
|
+
Dict[K, V], json.loads(json.dumps(self.dict(), cls=FhirJSONEncoder))
|
|
635
|
+
)
|
|
@@ -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")
|
|
File without changes
|