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