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,143 @@
|
|
|
1
|
+
from typing import (
|
|
2
|
+
List,
|
|
3
|
+
Set,
|
|
4
|
+
Optional,
|
|
5
|
+
AsyncGenerator,
|
|
6
|
+
cast,
|
|
7
|
+
Generator,
|
|
8
|
+
Dict,
|
|
9
|
+
override,
|
|
10
|
+
Iterable,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from compressedfhir.fhir.base_resource_list import BaseResourceList
|
|
14
|
+
from compressedfhir.fhir.fhir_resource import FhirResource
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FhirResourceList(BaseResourceList[FhirResource]):
|
|
18
|
+
"""
|
|
19
|
+
Represents a list of FHIR resources.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
__slots__: List[str] = BaseResourceList.__slots__
|
|
23
|
+
|
|
24
|
+
def get_resource_type_and_ids(self) -> List[str]:
|
|
25
|
+
"""
|
|
26
|
+
Get the resource type and IDs of the resources in the list.
|
|
27
|
+
|
|
28
|
+
:return: A list of strings representing the resource type and IDs.
|
|
29
|
+
"""
|
|
30
|
+
resource_type_and_ids: List[str] = []
|
|
31
|
+
for resource in self:
|
|
32
|
+
resource_type_and_ids.append(f"{resource.resource_type}/{resource.id}")
|
|
33
|
+
return resource_type_and_ids
|
|
34
|
+
|
|
35
|
+
def get_operation_outcomes(self) -> "FhirResourceList":
|
|
36
|
+
"""
|
|
37
|
+
Gets the operation outcomes from the response
|
|
38
|
+
|
|
39
|
+
:return: list of operation outcomes
|
|
40
|
+
"""
|
|
41
|
+
return FhirResourceList(
|
|
42
|
+
[r for r in self if r.resource_type == "OperationOutcome"]
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def get_resources_except_operation_outcomes(self) -> "FhirResourceList":
|
|
46
|
+
"""
|
|
47
|
+
Gets the normal FHIR resources by skipping any OperationOutcome resources
|
|
48
|
+
|
|
49
|
+
:return: list of valid resources
|
|
50
|
+
"""
|
|
51
|
+
return FhirResourceList(
|
|
52
|
+
[r for r in self if r.resource_type != "OperationOutcome"]
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def remove_duplicates(self) -> None:
|
|
56
|
+
"""
|
|
57
|
+
removes duplicate resources from the response i.e., resources with same resourceType and id
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
# remove duplicates from the list if they have the same resourceType and id
|
|
62
|
+
seen: Set[str] = set()
|
|
63
|
+
i = 0
|
|
64
|
+
while i < len(self):
|
|
65
|
+
# Create a tuple of values for specified keys
|
|
66
|
+
comparison_key = self[i].resource_type_and_id
|
|
67
|
+
|
|
68
|
+
if not comparison_key:
|
|
69
|
+
# Skip if resourceType or id is None
|
|
70
|
+
i += 1
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
if comparison_key in seen or self[i].id is None:
|
|
74
|
+
# Remove duplicate entry
|
|
75
|
+
self.remove(self[i])
|
|
76
|
+
else:
|
|
77
|
+
seen.add(comparison_key)
|
|
78
|
+
i += 1
|
|
79
|
+
|
|
80
|
+
async def consume_resource_batch_async(
|
|
81
|
+
self,
|
|
82
|
+
*,
|
|
83
|
+
batch_size: Optional[int],
|
|
84
|
+
) -> AsyncGenerator["FhirResourceList", None]:
|
|
85
|
+
async for r in super().consume_resource_batch_async(batch_size=batch_size):
|
|
86
|
+
yield cast(FhirResourceList, r)
|
|
87
|
+
|
|
88
|
+
def consume_resource_batch(
|
|
89
|
+
self,
|
|
90
|
+
*,
|
|
91
|
+
batch_size: Optional[int],
|
|
92
|
+
) -> Generator["FhirResourceList", None, None]:
|
|
93
|
+
for r in super().consume_resource_batch(batch_size=batch_size):
|
|
94
|
+
yield cast(FhirResourceList, r)
|
|
95
|
+
|
|
96
|
+
def get_count_by_resource_type(self) -> Dict[str, int]:
|
|
97
|
+
"""
|
|
98
|
+
Gets the count of resources by resource type.
|
|
99
|
+
|
|
100
|
+
:return: The count of resources by resource type
|
|
101
|
+
"""
|
|
102
|
+
resources_by_type: Dict[str, int] = dict()
|
|
103
|
+
if len(self) == 0:
|
|
104
|
+
return resources_by_type
|
|
105
|
+
|
|
106
|
+
entry: FhirResource
|
|
107
|
+
for resource in [r for r in self if r is not None]:
|
|
108
|
+
resource_type: str = resource.resource_type or "unknown"
|
|
109
|
+
if resource_type not in resources_by_type:
|
|
110
|
+
resources_by_type[resource_type] = 0
|
|
111
|
+
resources_by_type[resource_type] += 1
|
|
112
|
+
return resources_by_type
|
|
113
|
+
|
|
114
|
+
@override
|
|
115
|
+
def append(self, x: FhirResource, /) -> None:
|
|
116
|
+
"""
|
|
117
|
+
Append an entry to the FhirBundleEntryList.
|
|
118
|
+
|
|
119
|
+
:param x: The entry to append.
|
|
120
|
+
"""
|
|
121
|
+
if not isinstance(x, FhirResource):
|
|
122
|
+
raise TypeError("Only FhirBundleEntry instances can be appended.")
|
|
123
|
+
|
|
124
|
+
# check that we don't have a duplicate entry
|
|
125
|
+
key: Optional[str] = x.resource_type_and_id
|
|
126
|
+
if key is None:
|
|
127
|
+
super().append(x)
|
|
128
|
+
else:
|
|
129
|
+
for entry in self:
|
|
130
|
+
if entry.resource_type_and_id == key:
|
|
131
|
+
# we have a duplicate entry
|
|
132
|
+
return
|
|
133
|
+
super().append(x)
|
|
134
|
+
|
|
135
|
+
@override
|
|
136
|
+
def extend(self, iterable: Iterable[FhirResource], /) -> None:
|
|
137
|
+
"""
|
|
138
|
+
Extend the FhirBundleEntryList with entries from an iterable.
|
|
139
|
+
|
|
140
|
+
:param iterable: The iterable containing entries to extend.
|
|
141
|
+
"""
|
|
142
|
+
for entry in iterable:
|
|
143
|
+
self.append(entry)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import (
|
|
3
|
+
Any,
|
|
4
|
+
Optional,
|
|
5
|
+
Dict,
|
|
6
|
+
Deque,
|
|
7
|
+
AsyncGenerator,
|
|
8
|
+
Generator,
|
|
9
|
+
List,
|
|
10
|
+
Tuple,
|
|
11
|
+
OrderedDict,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from compressedfhir.fhir.fhir_resource_list import FhirResourceList
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FhirResourceMap:
|
|
18
|
+
"""
|
|
19
|
+
FhirResourceMap is a class that represents a map of FHIR resources.
|
|
20
|
+
Each key is a string representing the resource type, and the value is a deque of FhirResource objects.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
__slots__ = [
|
|
24
|
+
"_resource_map",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
initial_dict: Dict[str, FhirResourceList] | None = None,
|
|
30
|
+
) -> None:
|
|
31
|
+
"""
|
|
32
|
+
This class represents a map of FHIR resources, where each key is a string
|
|
33
|
+
and the value is a deque of FhirResource objects.
|
|
34
|
+
|
|
35
|
+
:param initial_dict: A dictionary where the keys are strings and the values
|
|
36
|
+
"""
|
|
37
|
+
self._resource_map: Dict[str, FhirResourceList] = initial_dict or {}
|
|
38
|
+
|
|
39
|
+
def dict(self) -> OrderedDict[str, Any]:
|
|
40
|
+
"""
|
|
41
|
+
Convert the FhirResourceMap to a dictionary representation.
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
result: OrderedDict[str, Any] = OrderedDict[str, Any]()
|
|
45
|
+
for key, value in self._resource_map.items():
|
|
46
|
+
result[key] = [resource.dict(remove_nulls=True) for resource in value]
|
|
47
|
+
return result
|
|
48
|
+
|
|
49
|
+
def get(self, *, resource_type: str) -> Optional[FhirResourceList]:
|
|
50
|
+
"""
|
|
51
|
+
Get the FHIR resources for a specific resource type.
|
|
52
|
+
|
|
53
|
+
:param resource_type: The resource type to retrieve.
|
|
54
|
+
:return: A deque of FhirResource objects or None if not found.
|
|
55
|
+
"""
|
|
56
|
+
return self._resource_map.get(resource_type, None)
|
|
57
|
+
|
|
58
|
+
async def consume_resource_async(
|
|
59
|
+
self,
|
|
60
|
+
) -> AsyncGenerator[Dict[str, FhirResourceList], None]:
|
|
61
|
+
while self._resource_map:
|
|
62
|
+
# Get the first key
|
|
63
|
+
resource_type: str = next(iter(self._resource_map))
|
|
64
|
+
# Pop and process the item
|
|
65
|
+
resources_for_resource_type: FhirResourceList = self._resource_map.pop(
|
|
66
|
+
resource_type
|
|
67
|
+
)
|
|
68
|
+
yield {
|
|
69
|
+
resource_type: resources_for_resource_type,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
def consume_resource(self) -> Generator[Dict[str, FhirResourceList], None, None]:
|
|
73
|
+
while self._resource_map:
|
|
74
|
+
# Get the first key
|
|
75
|
+
resource_type: str = next(iter(self._resource_map))
|
|
76
|
+
# Pop and process the item
|
|
77
|
+
resources_for_resource_type: FhirResourceList = self._resource_map.pop(
|
|
78
|
+
resource_type
|
|
79
|
+
)
|
|
80
|
+
yield {
|
|
81
|
+
resource_type: resources_for_resource_type,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
def get_resources(self) -> FhirResourceList:
|
|
85
|
+
"""
|
|
86
|
+
Get all resources in the map.
|
|
87
|
+
|
|
88
|
+
:return: A deque of FhirResource objects.
|
|
89
|
+
"""
|
|
90
|
+
resources: FhirResourceList = FhirResourceList()
|
|
91
|
+
for resource_type, resource_list in self._resource_map.items():
|
|
92
|
+
resources.extend(resource_list)
|
|
93
|
+
return resources
|
|
94
|
+
|
|
95
|
+
def __setitem__(self, key: str, value: FhirResourceList) -> None:
|
|
96
|
+
"""Set the value for a specific key in the resource map."""
|
|
97
|
+
if not isinstance(value, Deque):
|
|
98
|
+
raise TypeError("Value must be a deque of FhirResource objects.")
|
|
99
|
+
self._resource_map[key] = value
|
|
100
|
+
|
|
101
|
+
def __getitem__(self, key: str) -> FhirResourceList:
|
|
102
|
+
"""Get the value for a specific key in the resource map."""
|
|
103
|
+
return self._resource_map[key]
|
|
104
|
+
|
|
105
|
+
def __delitem__(self, key: str) -> None:
|
|
106
|
+
"""Delete a key-value pair from the resource map."""
|
|
107
|
+
if key in self._resource_map:
|
|
108
|
+
del self._resource_map[key]
|
|
109
|
+
else:
|
|
110
|
+
raise KeyError(f"Key '{key}' not found in resource map.")
|
|
111
|
+
|
|
112
|
+
def __contains__(self, key: str) -> bool:
|
|
113
|
+
"""Check if a key exists in the resource map."""
|
|
114
|
+
return key in self._resource_map
|
|
115
|
+
|
|
116
|
+
def items(self) -> List[Tuple[str, FhirResourceList]]:
|
|
117
|
+
"""Get all items in the resource map."""
|
|
118
|
+
return [(k, v) for k, v in self._resource_map.items()]
|
|
119
|
+
|
|
120
|
+
def get_resource_count(self) -> int:
|
|
121
|
+
return sum(len(resources) for resources in self._resource_map.values())
|
|
122
|
+
|
|
123
|
+
def clear(self) -> None:
|
|
124
|
+
"""Clear the resource map."""
|
|
125
|
+
self._resource_map.clear()
|
|
126
|
+
|
|
127
|
+
def get_resource_type_and_ids(self) -> List[str]:
|
|
128
|
+
"""
|
|
129
|
+
Get the resource type and IDs of the resources in the map.
|
|
130
|
+
|
|
131
|
+
:return: A list of strings representing the resource type and IDs.
|
|
132
|
+
"""
|
|
133
|
+
resource_type_and_ids: List[str] = []
|
|
134
|
+
for resource_type, resources in self._resource_map.items():
|
|
135
|
+
for resource in resources:
|
|
136
|
+
resource_type_and_ids.append(f"{resource_type}/{resource['id']}")
|
|
137
|
+
return resource_type_and_ids
|
|
138
|
+
|
|
139
|
+
def get_operation_outcomes(self) -> FhirResourceList:
|
|
140
|
+
"""
|
|
141
|
+
Gets the operation outcomes from the response
|
|
142
|
+
|
|
143
|
+
:return: list of operation outcomes
|
|
144
|
+
"""
|
|
145
|
+
return (
|
|
146
|
+
self._resource_map["OperationOutcome"]
|
|
147
|
+
if "OperationOutcome" in self._resource_map
|
|
148
|
+
else FhirResourceList()
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def get_resources_except_operation_outcomes(self) -> FhirResourceList:
|
|
152
|
+
"""
|
|
153
|
+
Gets the normal FHIR resources by skipping any OperationOutcome resources
|
|
154
|
+
|
|
155
|
+
:return: list of valid resources
|
|
156
|
+
"""
|
|
157
|
+
combined_resources: FhirResourceList = FhirResourceList()
|
|
158
|
+
for resource_type, resources in self._resource_map.items():
|
|
159
|
+
if resource_type != "OperationOutcome":
|
|
160
|
+
combined_resources.extend(resources)
|
|
161
|
+
return combined_resources
|
|
162
|
+
|
|
163
|
+
def json(self) -> str:
|
|
164
|
+
"""
|
|
165
|
+
Convert the list of FhirResource objects to a JSON string.
|
|
166
|
+
|
|
167
|
+
:return: JSON string representation of the list.
|
|
168
|
+
"""
|
|
169
|
+
return json.dumps(self.dict())
|
|
170
|
+
|
|
171
|
+
def get_count_of_resource_types(self) -> int:
|
|
172
|
+
"""
|
|
173
|
+
Get the count of unique resource types in the map.
|
|
174
|
+
|
|
175
|
+
:return: The count of unique resource types.
|
|
176
|
+
"""
|
|
177
|
+
return len(self._resource_map)
|
|
178
|
+
|
|
179
|
+
def __deepcopy__(self, memo: Dict[int, Any]) -> "FhirResourceMap":
|
|
180
|
+
"""
|
|
181
|
+
Create a copy of the FhirResourceMap.
|
|
182
|
+
|
|
183
|
+
:return: A new FhirResourceMap object with the same attributes.
|
|
184
|
+
"""
|
|
185
|
+
return FhirResourceMap(initial_dict=self._resource_map.copy())
|
|
186
|
+
|
|
187
|
+
def __repr__(self) -> str:
|
|
188
|
+
"""
|
|
189
|
+
Return a string representation of the FhirResourceMap.
|
|
190
|
+
|
|
191
|
+
:return: String representation of the FhirResourceMap.
|
|
192
|
+
"""
|
|
193
|
+
return f"FhirResourceMap(keys: {len(self._resource_map.keys())})"
|
|
File without changes
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
|
|
3
|
+
from compressedfhir.fhir.fhir_bundle_entry import FhirBundleEntry
|
|
4
|
+
from compressedfhir.fhir.fhir_bundle_entry_request import FhirBundleEntryRequest
|
|
5
|
+
from compressedfhir.fhir.fhir_bundle_entry_response import (
|
|
6
|
+
FhirBundleEntryResponse,
|
|
7
|
+
)
|
|
8
|
+
from compressedfhir.utilities.compressed_dict.v1.compressed_dict_storage_mode import (
|
|
9
|
+
CompressedDictStorageMode,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestBundleEntry:
|
|
14
|
+
def test_init_minimal(self) -> None:
|
|
15
|
+
"""Test initialization with minimal parameters."""
|
|
16
|
+
entry = FhirBundleEntry(
|
|
17
|
+
resource={"resourceType": "Patient"},
|
|
18
|
+
request=None,
|
|
19
|
+
response=None,
|
|
20
|
+
storage_mode=CompressedDictStorageMode(),
|
|
21
|
+
)
|
|
22
|
+
assert entry.resource is not None
|
|
23
|
+
with entry.resource.transaction():
|
|
24
|
+
assert entry.resource.dict() == {"resourceType": "Patient"}
|
|
25
|
+
assert entry.request is None
|
|
26
|
+
assert entry.response is None
|
|
27
|
+
assert entry.fullUrl is None
|
|
28
|
+
|
|
29
|
+
def test_init_full(self) -> None:
|
|
30
|
+
"""Test initialization with all parameters."""
|
|
31
|
+
resource = {"resourceType": "Patient", "id": "123"}
|
|
32
|
+
request = FhirBundleEntryRequest(url="https://example.com")
|
|
33
|
+
response = FhirBundleEntryResponse(status="200", etag=None, lastModified=None)
|
|
34
|
+
|
|
35
|
+
entry = FhirBundleEntry(
|
|
36
|
+
resource=resource,
|
|
37
|
+
request=request,
|
|
38
|
+
response=response,
|
|
39
|
+
fullUrl="https://example.com/Patient/123",
|
|
40
|
+
storage_mode=CompressedDictStorageMode(),
|
|
41
|
+
)
|
|
42
|
+
assert entry.resource is not None
|
|
43
|
+
with entry.resource.transaction():
|
|
44
|
+
assert entry.resource.dict() == resource
|
|
45
|
+
assert entry.request == request
|
|
46
|
+
assert entry.response == response
|
|
47
|
+
assert entry.fullUrl == "https://example.com/Patient/123"
|
|
48
|
+
|
|
49
|
+
def test_to_dict_minimal(self) -> None:
|
|
50
|
+
"""Test converting to dictionary with minimal parameters."""
|
|
51
|
+
entry = FhirBundleEntry(
|
|
52
|
+
resource={"resourceType": "Patient"},
|
|
53
|
+
request=None,
|
|
54
|
+
response=None,
|
|
55
|
+
storage_mode=CompressedDictStorageMode(),
|
|
56
|
+
)
|
|
57
|
+
result = entry.dict()
|
|
58
|
+
assert result == {"resource": {"resourceType": "Patient"}}
|
|
59
|
+
|
|
60
|
+
def test_to_dict_full(self) -> None:
|
|
61
|
+
"""Test converting to dictionary with all parameters."""
|
|
62
|
+
resource = {"resourceType": "Patient", "id": "123"}
|
|
63
|
+
request = FhirBundleEntryRequest(url="https://example.com")
|
|
64
|
+
response = FhirBundleEntryResponse(status="200", etag=None, lastModified=None)
|
|
65
|
+
|
|
66
|
+
entry = FhirBundleEntry(
|
|
67
|
+
resource=resource,
|
|
68
|
+
request=request,
|
|
69
|
+
response=response,
|
|
70
|
+
fullUrl="https://example.com/Patient/123",
|
|
71
|
+
storage_mode=CompressedDictStorageMode(),
|
|
72
|
+
)
|
|
73
|
+
result = entry.dict()
|
|
74
|
+
assert result == {
|
|
75
|
+
"fullUrl": "https://example.com/Patient/123",
|
|
76
|
+
"resource": resource,
|
|
77
|
+
"request": request.dict(),
|
|
78
|
+
"response": response.dict(),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
def test_from_dict_minimal(self) -> None:
|
|
82
|
+
"""Test creating from dictionary with minimal parameters."""
|
|
83
|
+
data = {"resource": {"resourceType": "Patient"}}
|
|
84
|
+
entry = FhirBundleEntry.from_dict(
|
|
85
|
+
data, storage_mode=CompressedDictStorageMode()
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
assert entry.resource is not None
|
|
89
|
+
with entry.resource.transaction():
|
|
90
|
+
assert entry.resource.dict() == {"resourceType": "Patient"}
|
|
91
|
+
assert entry.request is None
|
|
92
|
+
assert entry.response is None
|
|
93
|
+
assert entry.fullUrl is None
|
|
94
|
+
|
|
95
|
+
def test_from_dict_full(self) -> None:
|
|
96
|
+
"""Test creating from dictionary with all parameters."""
|
|
97
|
+
now = datetime.now(timezone.utc)
|
|
98
|
+
data = {
|
|
99
|
+
"fullUrl": "https://example.com/Patient/123",
|
|
100
|
+
"resource": {"resourceType": "Patient", "id": "123"},
|
|
101
|
+
"request": {"url": "https://example.com", "method": "GET"},
|
|
102
|
+
"response": {
|
|
103
|
+
"status": "200",
|
|
104
|
+
"lastModified": now.isoformat(),
|
|
105
|
+
"etag": 'W/"abc"',
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
entry = FhirBundleEntry.from_dict(
|
|
109
|
+
data, storage_mode=CompressedDictStorageMode()
|
|
110
|
+
)
|
|
111
|
+
assert entry.resource is not None
|
|
112
|
+
with entry.resource.transaction():
|
|
113
|
+
assert entry.fullUrl == "https://example.com/Patient/123"
|
|
114
|
+
assert entry.resource.dict() == {"resourceType": "Patient", "id": "123"}
|
|
115
|
+
assert entry.request is not None
|
|
116
|
+
assert entry.request.url == "https://example.com"
|
|
117
|
+
assert entry.response is not None
|
|
118
|
+
assert entry.response.status == "200"
|
|
119
|
+
|
|
120
|
+
def test_repr(self) -> None:
|
|
121
|
+
"""Test string representation of BundleEntry."""
|
|
122
|
+
resource = {"resourceType": "Patient", "id": "123"}
|
|
123
|
+
entry = FhirBundleEntry(
|
|
124
|
+
resource=resource,
|
|
125
|
+
request=None,
|
|
126
|
+
response=None,
|
|
127
|
+
storage_mode=CompressedDictStorageMode(),
|
|
128
|
+
)
|
|
129
|
+
assert repr(entry) == "BundleEntry(Patient/123)"
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from compressedfhir.fhir.fhir_bundle_entry import FhirBundleEntry
|
|
3
|
+
from compressedfhir.fhir.fhir_bundle_entry_list import FhirBundleEntryList
|
|
4
|
+
from compressedfhir.utilities.compressed_dict.v1.compressed_dict_storage_mode import (
|
|
5
|
+
CompressedDictStorageMode,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.asyncio
|
|
10
|
+
class TestFhirBundleEntryList:
|
|
11
|
+
async def test_consume_resource_async_with_none_batch_size(self) -> None:
|
|
12
|
+
"""
|
|
13
|
+
Test consuming bundle entries when batch_size is None.
|
|
14
|
+
Each call should yield a single entry.
|
|
15
|
+
"""
|
|
16
|
+
# Arrange
|
|
17
|
+
entries = [
|
|
18
|
+
FhirBundleEntry(
|
|
19
|
+
resource={"id": "1"},
|
|
20
|
+
request=None,
|
|
21
|
+
response=None,
|
|
22
|
+
fullUrl=None,
|
|
23
|
+
storage_mode=CompressedDictStorageMode(),
|
|
24
|
+
),
|
|
25
|
+
FhirBundleEntry(
|
|
26
|
+
resource={"id": "2"},
|
|
27
|
+
request=None,
|
|
28
|
+
response=None,
|
|
29
|
+
fullUrl=None,
|
|
30
|
+
storage_mode=CompressedDictStorageMode(),
|
|
31
|
+
),
|
|
32
|
+
FhirBundleEntry(
|
|
33
|
+
resource={"id": "3"},
|
|
34
|
+
request=None,
|
|
35
|
+
response=None,
|
|
36
|
+
fullUrl=None,
|
|
37
|
+
storage_mode=CompressedDictStorageMode(),
|
|
38
|
+
),
|
|
39
|
+
]
|
|
40
|
+
bundle_list = FhirBundleEntryList(entries)
|
|
41
|
+
|
|
42
|
+
# Act
|
|
43
|
+
batches = []
|
|
44
|
+
async for batch in bundle_list.consume_resource_async(batch_size=None):
|
|
45
|
+
batches.append(batch)
|
|
46
|
+
|
|
47
|
+
# Assert
|
|
48
|
+
assert len(batches) == 3
|
|
49
|
+
assert all(len(batch) == 1 for batch in batches)
|
|
50
|
+
assert len(bundle_list) == 0 # All entries should be consumed
|
|
51
|
+
|
|
52
|
+
@pytest.mark.parametrize("batch_size", [1, 2, 3, 5])
|
|
53
|
+
async def test_consume_resource_async_with_valid_batch_sizes(
|
|
54
|
+
self, batch_size: int
|
|
55
|
+
) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Test consuming bundle entries with various valid batch sizes.
|
|
58
|
+
"""
|
|
59
|
+
# Arrange
|
|
60
|
+
entries = [
|
|
61
|
+
FhirBundleEntry(
|
|
62
|
+
resource={"id": "1"},
|
|
63
|
+
request=None,
|
|
64
|
+
response=None,
|
|
65
|
+
fullUrl=None,
|
|
66
|
+
storage_mode=CompressedDictStorageMode(),
|
|
67
|
+
),
|
|
68
|
+
FhirBundleEntry(
|
|
69
|
+
resource={"id": "2"},
|
|
70
|
+
request=None,
|
|
71
|
+
response=None,
|
|
72
|
+
fullUrl=None,
|
|
73
|
+
storage_mode=CompressedDictStorageMode(),
|
|
74
|
+
),
|
|
75
|
+
FhirBundleEntry(
|
|
76
|
+
resource={"id": "3"},
|
|
77
|
+
request=None,
|
|
78
|
+
response=None,
|
|
79
|
+
fullUrl=None,
|
|
80
|
+
storage_mode=CompressedDictStorageMode(),
|
|
81
|
+
),
|
|
82
|
+
FhirBundleEntry(
|
|
83
|
+
resource={"id": "4"},
|
|
84
|
+
request=None,
|
|
85
|
+
response=None,
|
|
86
|
+
fullUrl=None,
|
|
87
|
+
storage_mode=CompressedDictStorageMode(),
|
|
88
|
+
),
|
|
89
|
+
FhirBundleEntry(
|
|
90
|
+
resource={"id": "5"},
|
|
91
|
+
request=None,
|
|
92
|
+
response=None,
|
|
93
|
+
fullUrl=None,
|
|
94
|
+
storage_mode=CompressedDictStorageMode(),
|
|
95
|
+
),
|
|
96
|
+
]
|
|
97
|
+
bundle_list = FhirBundleEntryList(entries)
|
|
98
|
+
|
|
99
|
+
# Act
|
|
100
|
+
batches = []
|
|
101
|
+
async for batch in bundle_list.consume_resource_async(batch_size=batch_size):
|
|
102
|
+
batches.append(batch)
|
|
103
|
+
|
|
104
|
+
# Assert
|
|
105
|
+
expected_batch_count = (len(entries) + batch_size - 1) // batch_size
|
|
106
|
+
assert len(batches) == expected_batch_count
|
|
107
|
+
assert all(len(batch) <= batch_size for batch in batches)
|
|
108
|
+
assert sum(len(batch) for batch in batches) == len(entries)
|
|
109
|
+
assert len(bundle_list) == 0 # All entries should be consumed
|
|
110
|
+
|
|
111
|
+
async def test_consume_resource_async_with_zero_batch_size(self) -> None:
|
|
112
|
+
"""
|
|
113
|
+
Test that consuming with batch_size <= 0 raises a ValueError.
|
|
114
|
+
"""
|
|
115
|
+
# Arrange
|
|
116
|
+
entries = [
|
|
117
|
+
FhirBundleEntry(
|
|
118
|
+
resource={"id": "1"},
|
|
119
|
+
request=None,
|
|
120
|
+
response=None,
|
|
121
|
+
fullUrl=None,
|
|
122
|
+
storage_mode=CompressedDictStorageMode(),
|
|
123
|
+
)
|
|
124
|
+
]
|
|
125
|
+
bundle_list = FhirBundleEntryList(entries)
|
|
126
|
+
|
|
127
|
+
# Act & Assert
|
|
128
|
+
with pytest.raises(ValueError, match="Batch size must be greater than 0."):
|
|
129
|
+
async for _ in bundle_list.consume_resource_async(batch_size=0):
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
async def test_consume_resource_async_with_empty_list(self) -> None:
|
|
133
|
+
"""
|
|
134
|
+
Test consuming an empty list.
|
|
135
|
+
"""
|
|
136
|
+
# Arrange
|
|
137
|
+
bundle_list = FhirBundleEntryList()
|
|
138
|
+
|
|
139
|
+
# Act
|
|
140
|
+
batches = []
|
|
141
|
+
async for batch in bundle_list.consume_resource_async(batch_size=2):
|
|
142
|
+
batches.append(batch)
|
|
143
|
+
|
|
144
|
+
# Assert
|
|
145
|
+
assert len(batches) == 0
|
|
146
|
+
|
|
147
|
+
def test_class_inheritance(self) -> None:
|
|
148
|
+
"""
|
|
149
|
+
Verify that FhirBundleEntryList inherits from collections.deque
|
|
150
|
+
and supports deque operations.
|
|
151
|
+
"""
|
|
152
|
+
# Arrange
|
|
153
|
+
entries = [
|
|
154
|
+
FhirBundleEntry(
|
|
155
|
+
resource={"id": "1"},
|
|
156
|
+
request=None,
|
|
157
|
+
response=None,
|
|
158
|
+
fullUrl=None,
|
|
159
|
+
storage_mode=CompressedDictStorageMode(),
|
|
160
|
+
),
|
|
161
|
+
FhirBundleEntry(
|
|
162
|
+
resource={"id": "2"},
|
|
163
|
+
request=None,
|
|
164
|
+
response=None,
|
|
165
|
+
fullUrl=None,
|
|
166
|
+
storage_mode=CompressedDictStorageMode(),
|
|
167
|
+
),
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
# Act
|
|
171
|
+
bundle_list = FhirBundleEntryList(entries)
|
|
172
|
+
|
|
173
|
+
# Assert
|
|
174
|
+
assert len(bundle_list) == 2
|
|
175
|
+
bundle_list.append(
|
|
176
|
+
FhirBundleEntry(
|
|
177
|
+
resource={"id": "3"},
|
|
178
|
+
request=None,
|
|
179
|
+
response=None,
|
|
180
|
+
fullUrl=None,
|
|
181
|
+
storage_mode=CompressedDictStorageMode(),
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
assert len(bundle_list) == 3
|
|
185
|
+
resource = bundle_list.popleft().resource
|
|
186
|
+
assert resource is not None
|
|
187
|
+
assert resource.dict() == {"id": "1"}
|