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.
- compressedfhir/__init__.py +0 -0
- compressedfhir/fhir/__init__.py +0 -0
- compressedfhir/fhir/base_resource_list.py +165 -0
- compressedfhir/fhir/fhir_bundle.py +295 -0
- compressedfhir/fhir/fhir_bundle_entry.py +240 -0
- compressedfhir/fhir/fhir_bundle_entry_list.py +97 -0
- compressedfhir/fhir/fhir_bundle_entry_request.py +73 -0
- compressedfhir/fhir/fhir_bundle_entry_response.py +67 -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 +170 -0
- compressedfhir/fhir/fhir_resource_list.py +149 -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 +104 -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 +701 -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 +467 -0
- compressedfhir/utilities/fhir_json_encoder.py +71 -0
- compressedfhir/utilities/json_helpers.py +181 -0
- compressedfhir/utilities/json_serializers/__init__.py +0 -0
- compressedfhir/utilities/json_serializers/test/__init__.py +0 -0
- compressedfhir/utilities/json_serializers/test/test_type_preservation_decoder.py +165 -0
- compressedfhir/utilities/json_serializers/test/test_type_preservation_encoder.py +71 -0
- compressedfhir/utilities/json_serializers/test/test_type_preservation_serializer.py +197 -0
- compressedfhir/utilities/json_serializers/type_preservation_decoder.py +135 -0
- compressedfhir/utilities/json_serializers/type_preservation_encoder.py +55 -0
- compressedfhir/utilities/json_serializers/type_preservation_serializer.py +57 -0
- compressedfhir/utilities/ordered_dict_to_dict_converter/__init__.py +0 -0
- compressedfhir/utilities/ordered_dict_to_dict_converter/ordered_dict_to_dict_converter.py +24 -0
- compressedfhir/utilities/string_compressor/__init__.py +0 -0
- compressedfhir/utilities/string_compressor/v1/__init__.py +0 -0
- compressedfhir/utilities/string_compressor/v1/string_compressor.py +99 -0
- compressedfhir/utilities/string_compressor/v1/test/__init__.py +0 -0
- compressedfhir/utilities/string_compressor/v1/test/test_string_compressor.py +189 -0
- compressedfhir/utilities/test/__init__.py +0 -0
- compressedfhir/utilities/test/test_fhir_json_encoder.py +177 -0
- compressedfhir/utilities/test/test_json_helpers.py +99 -0
- compressedfhir-3.0.2.dist-info/METADATA +139 -0
- compressedfhir-3.0.2.dist-info/RECORD +59 -0
- compressedfhir-3.0.2.dist-info/WHEEL +5 -0
- compressedfhir-3.0.2.dist-info/licenses/LICENSE +201 -0
- compressedfhir-3.0.2.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
from typing import (
|
|
5
|
+
Deque,
|
|
6
|
+
List,
|
|
7
|
+
AsyncGenerator,
|
|
8
|
+
Optional,
|
|
9
|
+
Any,
|
|
10
|
+
Dict,
|
|
11
|
+
Iterator,
|
|
12
|
+
override,
|
|
13
|
+
Iterable,
|
|
14
|
+
Generator,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from compressedfhir.utilities.compressed_dict.v1.compressed_dict import (
|
|
18
|
+
CompressedDict,
|
|
19
|
+
)
|
|
20
|
+
from compressedfhir.utilities.fhir_json_encoder import FhirJSONEncoder
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BaseResourceList[T: CompressedDict[str, Any]](Deque[T]):
|
|
24
|
+
"""
|
|
25
|
+
Represents a list of FHIR resources.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
__slots__: List[str] = []
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
iterable: Iterable[T] | None = None,
|
|
33
|
+
maxlen: Optional[int] = None,
|
|
34
|
+
) -> None:
|
|
35
|
+
# if iterable is not None:
|
|
36
|
+
# assert all([isinstance(r, CompressedDict) for r in iterable]), f"type {type(next(iter(iterable)))}"
|
|
37
|
+
super().__init__(iterable, maxlen=maxlen) if iterable else super().__init__()
|
|
38
|
+
|
|
39
|
+
@override
|
|
40
|
+
def append(self, x: T, /) -> None:
|
|
41
|
+
super().append(x)
|
|
42
|
+
# assert isinstance(x, CompressedDict), f"{type(x)} is not a CompressedDict"
|
|
43
|
+
|
|
44
|
+
def json(self) -> str:
|
|
45
|
+
"""
|
|
46
|
+
Convert the list of FhirResource objects to a JSON string.
|
|
47
|
+
|
|
48
|
+
:return: JSON string representation of the list.
|
|
49
|
+
"""
|
|
50
|
+
return json.dumps([r.dict() for r in self], cls=FhirJSONEncoder)
|
|
51
|
+
|
|
52
|
+
def get_resources(self) -> "BaseResourceList[T]":
|
|
53
|
+
"""
|
|
54
|
+
Get all resources in the list.
|
|
55
|
+
|
|
56
|
+
:return: A list of FhirResource objects.
|
|
57
|
+
"""
|
|
58
|
+
# this is here to keep compatibility with FhirResourceMap
|
|
59
|
+
return self
|
|
60
|
+
|
|
61
|
+
async def consume_resource_batch_async(
|
|
62
|
+
self,
|
|
63
|
+
*,
|
|
64
|
+
batch_size: Optional[int],
|
|
65
|
+
) -> AsyncGenerator["BaseResourceList[T]", None]:
|
|
66
|
+
"""
|
|
67
|
+
Consume resources in batches asynchronously.
|
|
68
|
+
|
|
69
|
+
:param batch_size: The size of each batch.
|
|
70
|
+
:return: An async generator yielding batches of FhirResourceList.
|
|
71
|
+
"""
|
|
72
|
+
batch: BaseResourceList[T]
|
|
73
|
+
if batch_size is None:
|
|
74
|
+
batch = BaseResourceList[T]()
|
|
75
|
+
while self:
|
|
76
|
+
batch.append(self.popleft())
|
|
77
|
+
yield batch
|
|
78
|
+
elif batch_size <= 0:
|
|
79
|
+
raise ValueError("Batch size must be greater than 0.")
|
|
80
|
+
else:
|
|
81
|
+
while self:
|
|
82
|
+
batch = BaseResourceList[T]()
|
|
83
|
+
for _ in range(min(batch_size, len(self))):
|
|
84
|
+
batch.append(self.popleft())
|
|
85
|
+
yield batch
|
|
86
|
+
|
|
87
|
+
def consume_resource_batch(
|
|
88
|
+
self,
|
|
89
|
+
*,
|
|
90
|
+
batch_size: Optional[int],
|
|
91
|
+
) -> Generator["BaseResourceList[T]", None, None]:
|
|
92
|
+
"""
|
|
93
|
+
Consume resources in batches asynchronously.
|
|
94
|
+
|
|
95
|
+
:param batch_size: The size of each batch.
|
|
96
|
+
:return: An async generator yielding batches of FhirResourceList.
|
|
97
|
+
"""
|
|
98
|
+
batch: BaseResourceList[T]
|
|
99
|
+
if batch_size is None:
|
|
100
|
+
batch = BaseResourceList[T]()
|
|
101
|
+
while self:
|
|
102
|
+
batch.append(self.popleft())
|
|
103
|
+
yield batch
|
|
104
|
+
elif batch_size <= 0:
|
|
105
|
+
raise ValueError("Batch size must be greater than 0.")
|
|
106
|
+
else:
|
|
107
|
+
while self:
|
|
108
|
+
batch = BaseResourceList[T]()
|
|
109
|
+
for _ in range(min(batch_size, len(self))):
|
|
110
|
+
batch.append(self.popleft())
|
|
111
|
+
yield batch
|
|
112
|
+
|
|
113
|
+
def __deepcopy__(self, memo: Dict[int, Any]) -> "BaseResourceList[T]":
|
|
114
|
+
"""
|
|
115
|
+
Create a copy of the FhirResourceList.
|
|
116
|
+
|
|
117
|
+
:return: A new FhirResourceList object with the same resources.
|
|
118
|
+
"""
|
|
119
|
+
return BaseResourceList[T]([copy.deepcopy(r) for r in self])
|
|
120
|
+
|
|
121
|
+
def __repr__(self) -> str:
|
|
122
|
+
"""
|
|
123
|
+
Return a string representation of the FhirResourceList.
|
|
124
|
+
|
|
125
|
+
:return: A string representation of the FhirResourceList.
|
|
126
|
+
"""
|
|
127
|
+
return f"FhirResourceList(resources: {len(self)})"
|
|
128
|
+
|
|
129
|
+
@contextmanager
|
|
130
|
+
def transaction(self) -> Iterator["BaseResourceList[T]"]:
|
|
131
|
+
"""
|
|
132
|
+
Opens a transaction for all resources in the list.
|
|
133
|
+
|
|
134
|
+
Deserializes the dictionary before entering the context
|
|
135
|
+
Serializes the dictionary after exiting the context
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
CompressedDictAccessError: If methods are called outside the context
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
try:
|
|
142
|
+
self.start_transaction()
|
|
143
|
+
|
|
144
|
+
yield self
|
|
145
|
+
|
|
146
|
+
finally:
|
|
147
|
+
self.end_transaction()
|
|
148
|
+
|
|
149
|
+
def start_transaction(self) -> "BaseResourceList[T]":
|
|
150
|
+
"""
|
|
151
|
+
Starts a transaction for each resource in the list. Use transaction() for a contextmanager for simpler usage.
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
for resource in self:
|
|
155
|
+
resource.start_transaction()
|
|
156
|
+
return self
|
|
157
|
+
|
|
158
|
+
def end_transaction(self) -> "BaseResourceList[T]":
|
|
159
|
+
"""
|
|
160
|
+
Ends a transaction. Use transaction() for a context_manager for simpler usage.
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
for resource in self:
|
|
164
|
+
resource.end_transaction()
|
|
165
|
+
return self
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
from typing import Any, Dict, List, Optional, cast, OrderedDict
|
|
4
|
+
|
|
5
|
+
from compressedfhir.fhir.fhir_bundle_entry import FhirBundleEntry
|
|
6
|
+
from compressedfhir.fhir.fhir_bundle_entry_list import FhirBundleEntryList
|
|
7
|
+
from compressedfhir.fhir.fhir_identifier import FhirIdentifier
|
|
8
|
+
from compressedfhir.fhir.fhir_link import FhirLink
|
|
9
|
+
from compressedfhir.fhir.fhir_meta import FhirMeta
|
|
10
|
+
from compressedfhir.fhir.fhir_resource import FhirResource
|
|
11
|
+
from compressedfhir.utilities.compressed_dict.v1.compressed_dict_storage_mode import (
|
|
12
|
+
CompressedDictStorageMode,
|
|
13
|
+
)
|
|
14
|
+
from compressedfhir.utilities.fhir_json_encoder import FhirJSONEncoder
|
|
15
|
+
from compressedfhir.utilities.json_helpers import FhirClientJsonHelpers
|
|
16
|
+
from compressedfhir.utilities.ordered_dict_to_dict_converter.ordered_dict_to_dict_converter import (
|
|
17
|
+
OrderedDictToDictConverter,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FhirBundle:
|
|
22
|
+
"""
|
|
23
|
+
FhirBundle represents a FHIR Bundle resource.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
__slots__ = [
|
|
27
|
+
"entry",
|
|
28
|
+
"total",
|
|
29
|
+
"id_",
|
|
30
|
+
"identifier",
|
|
31
|
+
"timestamp",
|
|
32
|
+
"type_",
|
|
33
|
+
"link",
|
|
34
|
+
"meta",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
*,
|
|
40
|
+
id_: Optional[str] = None,
|
|
41
|
+
identifier: Optional[FhirIdentifier] = None,
|
|
42
|
+
timestamp: Optional[str] = None,
|
|
43
|
+
type_: str,
|
|
44
|
+
entry: Optional[FhirBundleEntryList] = None,
|
|
45
|
+
total: Optional[int] = None,
|
|
46
|
+
link: Optional[List[FhirLink]] = None,
|
|
47
|
+
meta: Optional[FhirMeta] = None,
|
|
48
|
+
) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Initializes a FhirBundle object.
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
:param id_: The ID of the Bundle.
|
|
54
|
+
:param identifier: The identifier of the Bundle.
|
|
55
|
+
:param timestamp: The timestamp of the Bundle.
|
|
56
|
+
:param type_: The type of the Bundle (e.g., "searchset").
|
|
57
|
+
:param entry: The entries in the Bundle.
|
|
58
|
+
:param total: The total number of entries in the Bundle.
|
|
59
|
+
:param link: The links associated with the Bundle.
|
|
60
|
+
:param meta: The metadata associated with the Bundle.
|
|
61
|
+
"""
|
|
62
|
+
self.entry: FhirBundleEntryList = entry or FhirBundleEntryList()
|
|
63
|
+
self.total: Optional[int] = total
|
|
64
|
+
self.id_: Optional[str] = id_
|
|
65
|
+
self.identifier: Optional[FhirIdentifier] = identifier
|
|
66
|
+
self.timestamp: Optional[str] = timestamp
|
|
67
|
+
self.type_: str = type_
|
|
68
|
+
self.link: Optional[List[FhirLink]] = link
|
|
69
|
+
self.meta: Optional[FhirMeta] = meta
|
|
70
|
+
|
|
71
|
+
def dict(self) -> OrderedDict[str, Any]:
|
|
72
|
+
entries: List[Dict[str, Any]] | None = (
|
|
73
|
+
[entry.dict() for entry in self.entry] if self.entry else None
|
|
74
|
+
)
|
|
75
|
+
result: OrderedDict[str, Any] = OrderedDict[str, Any](
|
|
76
|
+
{"type": self.type_, "resourceType": "Bundle"}
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if self.id_ is not None:
|
|
80
|
+
result["id"] = self.id_
|
|
81
|
+
if self.identifier is not None:
|
|
82
|
+
result["identifier"] = self.identifier.dict()
|
|
83
|
+
if self.timestamp is not None:
|
|
84
|
+
result["timestamp"] = self.timestamp
|
|
85
|
+
if self.total is not None:
|
|
86
|
+
result["total"] = self.total
|
|
87
|
+
if entries and len(entries) > 0:
|
|
88
|
+
result["entry"] = entries
|
|
89
|
+
if self.link:
|
|
90
|
+
result["link"] = [link.dict() for link in self.link]
|
|
91
|
+
if self.meta:
|
|
92
|
+
result["meta"] = self.meta.dict()
|
|
93
|
+
return FhirClientJsonHelpers.remove_empty_elements_from_ordered_dict(result)
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def construct(
|
|
97
|
+
cls,
|
|
98
|
+
*,
|
|
99
|
+
id_: Optional[str] = None,
|
|
100
|
+
identifier: Optional[FhirIdentifier] = None,
|
|
101
|
+
timestamp: Optional[str] = None,
|
|
102
|
+
type_: str,
|
|
103
|
+
entry: Optional[FhirBundleEntryList] = None,
|
|
104
|
+
total: Optional[int] = None,
|
|
105
|
+
link: Optional[List[FhirLink]] = None,
|
|
106
|
+
meta: Optional[FhirMeta] = None,
|
|
107
|
+
) -> "FhirBundle":
|
|
108
|
+
"""
|
|
109
|
+
Constructs a FhirBundle object from keyword arguments.
|
|
110
|
+
|
|
111
|
+
:param id_: The ID of the Bundle.
|
|
112
|
+
:param identifier: The identifier of the Bundle.
|
|
113
|
+
:param timestamp: The timestamp of the Bundle.
|
|
114
|
+
:param type_: The type of the Bundle (e.g., "searchset").
|
|
115
|
+
:param entry: The entries in the Bundle.
|
|
116
|
+
:param total: The total number of entries in the Bundle.
|
|
117
|
+
:param link: The links associated with the Bundle.
|
|
118
|
+
:param meta: The metadata associated with the Bundle.
|
|
119
|
+
"""
|
|
120
|
+
return cls(
|
|
121
|
+
id_=id_,
|
|
122
|
+
identifier=identifier,
|
|
123
|
+
timestamp=timestamp,
|
|
124
|
+
type_=type_,
|
|
125
|
+
entry=entry,
|
|
126
|
+
total=total,
|
|
127
|
+
link=link,
|
|
128
|
+
meta=meta,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def from_dict(
|
|
133
|
+
cls,
|
|
134
|
+
data: OrderedDict[str, Any] | Dict[str, Any],
|
|
135
|
+
storage_mode: CompressedDictStorageMode | None = None,
|
|
136
|
+
) -> "FhirBundle":
|
|
137
|
+
"""
|
|
138
|
+
Creates a FhirBundle object from a dictionary.
|
|
139
|
+
|
|
140
|
+
:param data: A dictionary containing the Bundle data.
|
|
141
|
+
:param storage_mode: The storage mode for the Bundle.
|
|
142
|
+
:return: A FhirBundle object.
|
|
143
|
+
"""
|
|
144
|
+
if storage_mode is None:
|
|
145
|
+
storage_mode = CompressedDictStorageMode.default()
|
|
146
|
+
|
|
147
|
+
bundle = cls(
|
|
148
|
+
id_=data.get("id") if isinstance(data.get("id"), str) else None,
|
|
149
|
+
identifier=(
|
|
150
|
+
FhirIdentifier.from_dict(data["identifier"])
|
|
151
|
+
if "identifier" in data
|
|
152
|
+
else None
|
|
153
|
+
),
|
|
154
|
+
timestamp=(
|
|
155
|
+
data.get("timestamp")
|
|
156
|
+
if isinstance(data.get("timestamp"), str)
|
|
157
|
+
else None
|
|
158
|
+
),
|
|
159
|
+
type_=(
|
|
160
|
+
cast(str, data.get("type"))
|
|
161
|
+
if isinstance(data.get("type"), str)
|
|
162
|
+
else "collection"
|
|
163
|
+
),
|
|
164
|
+
total=data.get("total") if isinstance(data.get("total"), int) else None,
|
|
165
|
+
entry=(
|
|
166
|
+
FhirBundleEntryList(
|
|
167
|
+
[
|
|
168
|
+
FhirBundleEntry.from_dict(entry, storage_mode=storage_mode)
|
|
169
|
+
for entry in (data.get("entry", []) or [])
|
|
170
|
+
]
|
|
171
|
+
)
|
|
172
|
+
if "entry" in data
|
|
173
|
+
else FhirBundleEntryList()
|
|
174
|
+
),
|
|
175
|
+
link=[FhirLink.from_dict(link) for link in (data.get("link", []) or [])],
|
|
176
|
+
meta=data.get("meta"),
|
|
177
|
+
)
|
|
178
|
+
return bundle
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def add_diagnostics_to_operation_outcomes(
|
|
182
|
+
*, resource: FhirResource, diagnostics_coding: List[Dict[str, Any]]
|
|
183
|
+
) -> FhirResource:
|
|
184
|
+
"""
|
|
185
|
+
Adds diagnostic coding to OperationOutcome resources to identify which call resulted in that OperationOutcome
|
|
186
|
+
being returned by the server
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
:param resource: The resource to add the diagnostics to
|
|
190
|
+
:param diagnostics_coding: The diagnostics coding to add
|
|
191
|
+
:return: The resource with the diagnostics added
|
|
192
|
+
"""
|
|
193
|
+
with resource.transaction():
|
|
194
|
+
if resource.resource_type == "OperationOutcome":
|
|
195
|
+
issues = resource.get("issue")
|
|
196
|
+
if issues:
|
|
197
|
+
for issue in issues:
|
|
198
|
+
coding: List[Dict[str, Any]] = issue.setdefault(
|
|
199
|
+
"details", {}
|
|
200
|
+
).setdefault("coding", [])
|
|
201
|
+
coding.extend(diagnostics_coding)
|
|
202
|
+
return resource
|
|
203
|
+
|
|
204
|
+
def json(self) -> str:
|
|
205
|
+
"""
|
|
206
|
+
Converts the Bundle to a JSON string.
|
|
207
|
+
|
|
208
|
+
:return: JSON string representation of the Bundle
|
|
209
|
+
"""
|
|
210
|
+
bundle_dict = self.dict()
|
|
211
|
+
return json.dumps(bundle_dict, cls=FhirJSONEncoder)
|
|
212
|
+
|
|
213
|
+
def copy(self) -> "FhirBundle":
|
|
214
|
+
return copy.deepcopy(self)
|
|
215
|
+
|
|
216
|
+
def __deepcopy__(self, memo: Dict[int, Any]) -> "FhirBundle":
|
|
217
|
+
"""
|
|
218
|
+
Creates a copy of the Bundle.
|
|
219
|
+
|
|
220
|
+
:return: A new FhirBundle object with the same attributes
|
|
221
|
+
"""
|
|
222
|
+
return FhirBundle(
|
|
223
|
+
entry=copy.deepcopy(self.entry) if self.entry else None,
|
|
224
|
+
total=self.total,
|
|
225
|
+
id_=self.id_,
|
|
226
|
+
identifier=copy.deepcopy(self.identifier) if self.identifier else None,
|
|
227
|
+
timestamp=self.timestamp,
|
|
228
|
+
type_=self.type_,
|
|
229
|
+
link=[copy.deepcopy(link) for link in self.link] if self.link else None,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
def __repr__(self) -> str:
|
|
233
|
+
"""
|
|
234
|
+
Returns a string representation of the Bundle.
|
|
235
|
+
|
|
236
|
+
:return: String representation of the Bundle
|
|
237
|
+
"""
|
|
238
|
+
properties: List[str] = []
|
|
239
|
+
if self.id_:
|
|
240
|
+
properties.append(f"id_={self.id_}")
|
|
241
|
+
if self.type_:
|
|
242
|
+
properties.append(f"type_={self.type_}")
|
|
243
|
+
if self.timestamp:
|
|
244
|
+
properties.append(f"timestamp={self.timestamp}")
|
|
245
|
+
if self.total:
|
|
246
|
+
properties.append(f"total={self.total}")
|
|
247
|
+
if self.entry:
|
|
248
|
+
properties.append(f"entry={len(self.entry)}")
|
|
249
|
+
if self.link:
|
|
250
|
+
properties.append(f"link={len(self.link)}")
|
|
251
|
+
|
|
252
|
+
return f"FhirBundle({', '.join(properties)})"
|
|
253
|
+
|
|
254
|
+
def get_count_by_resource_type(self) -> Dict[str, int]:
|
|
255
|
+
"""
|
|
256
|
+
Gets the count of resources by resource type.
|
|
257
|
+
|
|
258
|
+
:return: The count of resources by resource type
|
|
259
|
+
"""
|
|
260
|
+
resources_by_type: Dict[str, int] = dict()
|
|
261
|
+
if self.entry is None:
|
|
262
|
+
return resources_by_type
|
|
263
|
+
|
|
264
|
+
entry: FhirBundleEntry
|
|
265
|
+
for entry in [e for e in self.entry if e is not None]:
|
|
266
|
+
if entry.resource is not None:
|
|
267
|
+
resource = entry.resource
|
|
268
|
+
resource_type: str = resource.resource_type or "unknown"
|
|
269
|
+
if resource_type not in resources_by_type:
|
|
270
|
+
resources_by_type[resource_type] = 0
|
|
271
|
+
resources_by_type[resource_type] += 1
|
|
272
|
+
return resources_by_type
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def id(self) -> Optional[str]:
|
|
276
|
+
"""Get the ID of the Bundle."""
|
|
277
|
+
return self.id_
|
|
278
|
+
|
|
279
|
+
@id.setter
|
|
280
|
+
def id(self, value: Optional[str]) -> None:
|
|
281
|
+
"""Set the ID of the Bundle."""
|
|
282
|
+
self.id_ = value
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def resource_type(self) -> str:
|
|
286
|
+
"""Get the resource type of the Bundle."""
|
|
287
|
+
return "Bundle"
|
|
288
|
+
|
|
289
|
+
def to_plain_dict(self) -> Dict[str, Any]:
|
|
290
|
+
"""
|
|
291
|
+
Converts the Bundle to a plain dictionary.
|
|
292
|
+
|
|
293
|
+
:return: Plain dictionary representation of the Bundle
|
|
294
|
+
"""
|
|
295
|
+
return OrderedDictToDictConverter.convert(self.dict())
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
from typing import Any, Dict, Optional, List, OrderedDict
|
|
4
|
+
|
|
5
|
+
from compressedfhir.fhir.fhir_bundle_entry_search import FhirBundleEntrySearch
|
|
6
|
+
from compressedfhir.fhir.fhir_link import FhirLink
|
|
7
|
+
from compressedfhir.fhir.fhir_bundle_entry_request import FhirBundleEntryRequest
|
|
8
|
+
from compressedfhir.fhir.fhir_bundle_entry_response import (
|
|
9
|
+
FhirBundleEntryResponse,
|
|
10
|
+
)
|
|
11
|
+
from compressedfhir.fhir.fhir_resource import FhirResource
|
|
12
|
+
from compressedfhir.utilities.compressed_dict.v1.compressed_dict import (
|
|
13
|
+
CompressedDict,
|
|
14
|
+
)
|
|
15
|
+
from compressedfhir.utilities.compressed_dict.v1.compressed_dict_storage_mode import (
|
|
16
|
+
CompressedDictStorageMode,
|
|
17
|
+
)
|
|
18
|
+
from compressedfhir.utilities.fhir_json_encoder import FhirJSONEncoder
|
|
19
|
+
from compressedfhir.utilities.json_helpers import FhirClientJsonHelpers
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FhirBundleEntry:
|
|
23
|
+
"""
|
|
24
|
+
Represents a single entry in a FHIR Bundle.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
__slots__ = [
|
|
28
|
+
"_resource",
|
|
29
|
+
"request",
|
|
30
|
+
"response",
|
|
31
|
+
"fullUrl",
|
|
32
|
+
"link",
|
|
33
|
+
"search",
|
|
34
|
+
"storage_mode",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
# noinspection PyPep8Naming
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
*,
|
|
41
|
+
fullUrl: Optional[str] = None,
|
|
42
|
+
resource: Dict[str, Any] | FhirResource | OrderedDict[str, Any] | None,
|
|
43
|
+
request: Optional[FhirBundleEntryRequest] = None,
|
|
44
|
+
response: Optional[FhirBundleEntryResponse] = None,
|
|
45
|
+
link: Optional[List[FhirLink]] = None,
|
|
46
|
+
search: Optional[FhirBundleEntrySearch] = None,
|
|
47
|
+
storage_mode: CompressedDictStorageMode | None = None,
|
|
48
|
+
) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Initializes a BundleEntry object.
|
|
51
|
+
|
|
52
|
+
:param fullUrl: The full URL of the entry.
|
|
53
|
+
:param resource: The FHIR resource associated with the entry.
|
|
54
|
+
:param request: The request information associated with the entry.
|
|
55
|
+
:param response: The response information associated with the entry.
|
|
56
|
+
:param storage_mode: The storage mode for the resource.
|
|
57
|
+
"""
|
|
58
|
+
if storage_mode is None:
|
|
59
|
+
storage_mode = CompressedDictStorageMode.default()
|
|
60
|
+
self._resource: Optional[FhirResource] = (
|
|
61
|
+
resource
|
|
62
|
+
if isinstance(resource, CompressedDict)
|
|
63
|
+
else (
|
|
64
|
+
FhirResource(initial_dict=resource, storage_mode=storage_mode)
|
|
65
|
+
if resource is not None
|
|
66
|
+
else None
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
self.request: Optional[FhirBundleEntryRequest] = request
|
|
70
|
+
self.response: Optional[FhirBundleEntryResponse] = response
|
|
71
|
+
self.fullUrl: Optional[str] = fullUrl
|
|
72
|
+
self.link: Optional[List[FhirLink]] = link
|
|
73
|
+
self.search: Optional[FhirBundleEntrySearch] = search
|
|
74
|
+
self.storage_mode: CompressedDictStorageMode = storage_mode
|
|
75
|
+
|
|
76
|
+
# noinspection PyPep8Naming
|
|
77
|
+
@classmethod
|
|
78
|
+
def construct(
|
|
79
|
+
cls,
|
|
80
|
+
*,
|
|
81
|
+
fullUrl: Optional[str] = None,
|
|
82
|
+
resource: Dict[str, Any] | FhirResource | OrderedDict[str, Any] | None,
|
|
83
|
+
request: Optional[FhirBundleEntryRequest] = None,
|
|
84
|
+
response: Optional[FhirBundleEntryResponse] = None,
|
|
85
|
+
link: Optional[List[FhirLink]] = None,
|
|
86
|
+
search: Optional[FhirBundleEntrySearch] = None,
|
|
87
|
+
storage_mode: CompressedDictStorageMode | None = None,
|
|
88
|
+
) -> "FhirBundleEntry":
|
|
89
|
+
"""
|
|
90
|
+
Constructs a BundleEntry object with the given parameters.
|
|
91
|
+
|
|
92
|
+
:param fullUrl: The full URL of the entry.
|
|
93
|
+
:param resource: The FHIR resource associated with the entry.
|
|
94
|
+
:param request: The request information associated with the entry.
|
|
95
|
+
:param response: The response information associated with the entry.
|
|
96
|
+
:param link: The links associated with the entry.
|
|
97
|
+
:param search: The search information associated with the entry.
|
|
98
|
+
:param storage_mode: The storage mode for the resource.
|
|
99
|
+
"""
|
|
100
|
+
if storage_mode is None:
|
|
101
|
+
storage_mode = CompressedDictStorageMode.default()
|
|
102
|
+
return cls(
|
|
103
|
+
fullUrl=fullUrl,
|
|
104
|
+
resource=resource,
|
|
105
|
+
request=request,
|
|
106
|
+
response=response,
|
|
107
|
+
link=link,
|
|
108
|
+
search=search,
|
|
109
|
+
storage_mode=storage_mode,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def resource(self) -> Optional[FhirResource]:
|
|
114
|
+
"""
|
|
115
|
+
Returns the FHIR resource associated with the entry.
|
|
116
|
+
|
|
117
|
+
:return: The FHIR resource.
|
|
118
|
+
"""
|
|
119
|
+
return self._resource
|
|
120
|
+
|
|
121
|
+
@resource.setter
|
|
122
|
+
def resource(self, value: Dict[str, Any] | FhirResource | None) -> None:
|
|
123
|
+
"""
|
|
124
|
+
Sets the FHIR resource associated with the entry.
|
|
125
|
+
|
|
126
|
+
:param value: The FHIR resource to set.
|
|
127
|
+
"""
|
|
128
|
+
if value is not None:
|
|
129
|
+
if isinstance(value, CompressedDict):
|
|
130
|
+
self._resource = value
|
|
131
|
+
else:
|
|
132
|
+
if self._resource is None:
|
|
133
|
+
self._resource = FhirResource(
|
|
134
|
+
initial_dict=value, storage_mode=self.storage_mode
|
|
135
|
+
)
|
|
136
|
+
else:
|
|
137
|
+
self._resource.replace(value=value)
|
|
138
|
+
else:
|
|
139
|
+
self._resource = None
|
|
140
|
+
|
|
141
|
+
def dict(self) -> OrderedDict[str, Any]:
|
|
142
|
+
result: OrderedDict[str, Any] = OrderedDict[str, Any]()
|
|
143
|
+
if self.fullUrl is not None:
|
|
144
|
+
result["fullUrl"] = self.fullUrl
|
|
145
|
+
if self.resource is not None:
|
|
146
|
+
result["resource"] = self.resource.dict()
|
|
147
|
+
if self.request is not None:
|
|
148
|
+
result["request"] = self.request.dict()
|
|
149
|
+
if self.response is not None:
|
|
150
|
+
result["response"] = self.response.dict()
|
|
151
|
+
if self.link is not None:
|
|
152
|
+
result["link"] = [link.dict() for link in self.link]
|
|
153
|
+
if self.search is not None:
|
|
154
|
+
result["search"] = self.search.dict()
|
|
155
|
+
return FhirClientJsonHelpers.remove_empty_elements_from_ordered_dict(result)
|
|
156
|
+
|
|
157
|
+
@classmethod
|
|
158
|
+
def from_dict(
|
|
159
|
+
cls,
|
|
160
|
+
d: OrderedDict[str, Any] | Dict[str, Any],
|
|
161
|
+
storage_mode: CompressedDictStorageMode | None = None,
|
|
162
|
+
) -> "FhirBundleEntry":
|
|
163
|
+
if storage_mode is None:
|
|
164
|
+
storage_mode = CompressedDictStorageMode.default()
|
|
165
|
+
return cls(
|
|
166
|
+
fullUrl=d["fullUrl"] if "fullUrl" in d else None,
|
|
167
|
+
resource=(
|
|
168
|
+
FhirResource(initial_dict=d["resource"], storage_mode=storage_mode)
|
|
169
|
+
if "resource" in d
|
|
170
|
+
else None
|
|
171
|
+
),
|
|
172
|
+
request=(
|
|
173
|
+
FhirBundleEntryRequest.from_dict(d["request"])
|
|
174
|
+
if "request" in d
|
|
175
|
+
else None
|
|
176
|
+
),
|
|
177
|
+
response=(
|
|
178
|
+
FhirBundleEntryResponse.from_dict(d["response"])
|
|
179
|
+
if "response" in d
|
|
180
|
+
else None
|
|
181
|
+
),
|
|
182
|
+
link=(
|
|
183
|
+
[FhirLink.from_dict(link) for link in d["link"]]
|
|
184
|
+
if "link" in d
|
|
185
|
+
else None
|
|
186
|
+
),
|
|
187
|
+
search=(
|
|
188
|
+
FhirBundleEntrySearch.from_dict(d["search"]) if "search" in d else None
|
|
189
|
+
),
|
|
190
|
+
storage_mode=storage_mode,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def json(self) -> str:
|
|
194
|
+
"""
|
|
195
|
+
Converts the BundleEntry object to a JSON string.
|
|
196
|
+
|
|
197
|
+
:return: A JSON string representation of the BundleEntry.
|
|
198
|
+
"""
|
|
199
|
+
return json.dumps(obj=self.dict(), cls=FhirJSONEncoder)
|
|
200
|
+
|
|
201
|
+
def __deepcopy__(self, memo: Dict[int, Any]) -> "FhirBundleEntry":
|
|
202
|
+
"""
|
|
203
|
+
Creates a copy of the BundleEntry object.
|
|
204
|
+
|
|
205
|
+
:return: A new BundleEntry object with the same attributes.
|
|
206
|
+
"""
|
|
207
|
+
return FhirBundleEntry(
|
|
208
|
+
fullUrl=self.fullUrl,
|
|
209
|
+
resource=copy.deepcopy(self.resource) if self.resource else None,
|
|
210
|
+
request=copy.deepcopy(self.request) if self.request else None,
|
|
211
|
+
response=copy.deepcopy(self.response) if self.response else None,
|
|
212
|
+
link=[copy.deepcopy(link) for link in self.link] if self.link else None,
|
|
213
|
+
search=copy.deepcopy(self.search) if self.search else None,
|
|
214
|
+
storage_mode=self.storage_mode,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
def __repr__(self) -> str:
|
|
218
|
+
"""
|
|
219
|
+
Returns a string representation of the BundleEntry object.
|
|
220
|
+
|
|
221
|
+
:return: A string representation of the BundleEntry.
|
|
222
|
+
"""
|
|
223
|
+
return (
|
|
224
|
+
f"BundleEntry({self.resource.resource_type}/{self.resource.id})"
|
|
225
|
+
if self.resource
|
|
226
|
+
else "BundleEntry(Empty)"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
def copy(self) -> "FhirBundleEntry":
|
|
230
|
+
"""
|
|
231
|
+
Creates a copy of the BundleEntry object.
|
|
232
|
+
|
|
233
|
+
:return: A new BundleEntry object with the same attributes.
|
|
234
|
+
"""
|
|
235
|
+
return copy.deepcopy(self)
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def resource_type_and_id(self) -> Optional[str]:
|
|
239
|
+
"""Get the key from the resource."""
|
|
240
|
+
return self.resource.resource_type_and_id if self.resource else None
|