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.

Files changed (41) 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 +291 -0
  5. compressedfhir/fhir/fhir_bundle_entry.py +234 -0
  6. compressedfhir/fhir/fhir_bundle_entry_list.py +82 -0
  7. compressedfhir/fhir/fhir_bundle_entry_request.py +71 -0
  8. compressedfhir/fhir/fhir_bundle_entry_response.py +64 -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 +163 -0
  14. compressedfhir/fhir/fhir_resource_list.py +143 -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 +225 -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 +635 -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 +360 -0
  34. compressedfhir/utilities/fhir_json_encoder.py +30 -0
  35. compressedfhir/utilities/json_helpers.py +181 -0
  36. compressedfhir-0.0.1.dist-info/METADATA +28 -0
  37. compressedfhir-0.0.1.dist-info/RECORD +41 -0
  38. compressedfhir-0.0.1.dist-info/WHEEL +5 -0
  39. compressedfhir-0.0.1.dist-info/licenses/LICENSE +201 -0
  40. compressedfhir-0.0.1.dist-info/top_level.txt +2 -0
  41. 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"}