helix.fhir.client.sdk 4.2.3__py3-none-any.whl → 4.2.19__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.
- helix_fhir_client_sdk/fhir_auth_mixin.py +17 -10
- helix_fhir_client_sdk/fhir_client.py +152 -79
- helix_fhir_client_sdk/fhir_delete_mixin.py +62 -48
- helix_fhir_client_sdk/fhir_merge_mixin.py +188 -166
- helix_fhir_client_sdk/fhir_merge_resources_mixin.py +200 -15
- helix_fhir_client_sdk/fhir_patch_mixin.py +97 -84
- helix_fhir_client_sdk/fhir_update_mixin.py +71 -57
- helix_fhir_client_sdk/graph/simulated_graph_processor_mixin.py +147 -49
- helix_fhir_client_sdk/open_telemetry/__init__.py +0 -0
- helix_fhir_client_sdk/open_telemetry/attribute_names.py +7 -0
- helix_fhir_client_sdk/open_telemetry/span_names.py +12 -0
- helix_fhir_client_sdk/queue/request_queue_mixin.py +17 -12
- helix_fhir_client_sdk/responses/fhir_client_protocol.py +10 -6
- helix_fhir_client_sdk/responses/fhir_get_response.py +3 -4
- helix_fhir_client_sdk/responses/fhir_response_processor.py +73 -54
- helix_fhir_client_sdk/responses/get/fhir_get_bundle_response.py +49 -28
- helix_fhir_client_sdk/responses/get/fhir_get_error_response.py +0 -1
- helix_fhir_client_sdk/responses/get/fhir_get_list_by_resource_type_response.py +1 -1
- helix_fhir_client_sdk/responses/get/fhir_get_list_response.py +1 -1
- helix_fhir_client_sdk/responses/get/fhir_get_response_factory.py +0 -1
- helix_fhir_client_sdk/responses/get/fhir_get_single_response.py +1 -1
- helix_fhir_client_sdk/responses/merge/fhir_merge_resource_response_entry.py +30 -0
- helix_fhir_client_sdk/responses/resource_separator.py +35 -40
- helix_fhir_client_sdk/utilities/cache/request_cache.py +32 -43
- helix_fhir_client_sdk/utilities/retryable_aiohttp_client.py +185 -154
- helix_fhir_client_sdk/utilities/retryable_aiohttp_response.py +2 -1
- helix_fhir_client_sdk/validators/async_fhir_validator.py +3 -0
- helix_fhir_client_sdk-4.2.19.dist-info/METADATA +200 -0
- {helix_fhir_client_sdk-4.2.3.dist-info → helix_fhir_client_sdk-4.2.19.dist-info}/RECORD +36 -29
- tests/async/test_benchmark_compress.py +448 -0
- tests/async/test_benchmark_merge.py +506 -0
- tests/async/test_retryable_client_session_management.py +159 -0
- tests/test_fhir_client_clone.py +155 -0
- helix_fhir_client_sdk-4.2.3.dist-info/METADATA +0 -115
- {helix_fhir_client_sdk-4.2.3.dist-info → helix_fhir_client_sdk-4.2.19.dist-info}/WHEEL +0 -0
- {helix_fhir_client_sdk-4.2.3.dist-info → helix_fhir_client_sdk-4.2.19.dist-info}/licenses/LICENSE +0 -0
- {helix_fhir_client_sdk-4.2.3.dist-info → helix_fhir_client_sdk-4.2.19.dist-info}/top_level.txt +0 -0
|
@@ -11,10 +11,13 @@ from aiohttp.streams import AsyncStreamIterator
|
|
|
11
11
|
from compressedfhir.utilities.compressed_dict.v1.compressed_dict_storage_mode import (
|
|
12
12
|
CompressedDictStorageMode,
|
|
13
13
|
)
|
|
14
|
+
from opentelemetry import trace
|
|
15
|
+
from opentelemetry.trace import Status, StatusCode
|
|
14
16
|
|
|
15
17
|
from helix_fhir_client_sdk.function_types import (
|
|
16
18
|
HandleStreamingChunkFunction,
|
|
17
19
|
)
|
|
20
|
+
from helix_fhir_client_sdk.open_telemetry.span_names import FhirClientSdkOpenTelemetrySpanNames
|
|
18
21
|
from helix_fhir_client_sdk.responses.bundle_expander import (
|
|
19
22
|
BundleExpander,
|
|
20
23
|
BundleExpanderResult,
|
|
@@ -37,6 +40,8 @@ from helix_fhir_client_sdk.utilities.retryable_aiohttp_response import (
|
|
|
37
40
|
RetryableAioHttpResponse,
|
|
38
41
|
)
|
|
39
42
|
|
|
43
|
+
TRACER = trace.get_tracer(__name__)
|
|
44
|
+
|
|
40
45
|
|
|
41
46
|
class FhirResponseProcessor:
|
|
42
47
|
"""
|
|
@@ -93,60 +98,74 @@ class FhirResponseProcessor:
|
|
|
93
98
|
|
|
94
99
|
:return: An async generator of FhirGetResponse objects.
|
|
95
100
|
"""
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
101
|
+
span = TRACER.start_span(FhirClientSdkOpenTelemetrySpanNames.HANDLE_RESPONSE)
|
|
102
|
+
try:
|
|
103
|
+
# if request is ok (200) then return the data
|
|
104
|
+
if response.ok:
|
|
105
|
+
async for r in FhirResponseProcessor._handle_response_200(
|
|
106
|
+
full_url=full_url,
|
|
107
|
+
request_id=request_id,
|
|
108
|
+
response=response,
|
|
109
|
+
response_headers=response_headers,
|
|
110
|
+
fn_handle_streaming_chunk=fn_handle_streaming_chunk,
|
|
111
|
+
access_token=access_token,
|
|
112
|
+
resources_json=resources_json,
|
|
113
|
+
resource=resource,
|
|
114
|
+
id_=id_,
|
|
115
|
+
logger=logger,
|
|
116
|
+
use_data_streaming=use_data_streaming,
|
|
117
|
+
chunk_size=chunk_size,
|
|
118
|
+
extra_context_to_return=extra_context_to_return,
|
|
119
|
+
expand_fhir_bundle=expand_fhir_bundle,
|
|
120
|
+
url=url,
|
|
121
|
+
separate_bundle_resources=separate_bundle_resources,
|
|
122
|
+
storage_mode=storage_mode,
|
|
123
|
+
create_operation_outcome_for_error=create_operation_outcome_for_error,
|
|
124
|
+
):
|
|
125
|
+
yield r
|
|
126
|
+
elif response.status == 404: # not found
|
|
127
|
+
async for r in FhirResponseProcessor._handle_response_404(
|
|
128
|
+
full_url=full_url,
|
|
129
|
+
request_id=request_id,
|
|
130
|
+
response=response,
|
|
131
|
+
response_headers=response_headers,
|
|
132
|
+
extra_context_to_return=extra_context_to_return,
|
|
133
|
+
resource=resource,
|
|
134
|
+
logger=logger,
|
|
135
|
+
id_=id_,
|
|
136
|
+
access_token=access_token,
|
|
137
|
+
storage_mode=storage_mode,
|
|
138
|
+
create_operation_outcome_for_error=create_operation_outcome_for_error,
|
|
139
|
+
):
|
|
140
|
+
yield r
|
|
141
|
+
else: # unknown response
|
|
142
|
+
async for r in FhirResponseProcessor._handle_response_unknown(
|
|
143
|
+
full_url=full_url,
|
|
144
|
+
request_id=request_id,
|
|
145
|
+
response=response,
|
|
146
|
+
response_headers=response_headers,
|
|
147
|
+
resource=resource,
|
|
148
|
+
logger=logger,
|
|
149
|
+
access_token=access_token,
|
|
150
|
+
extra_context_to_return=extra_context_to_return,
|
|
151
|
+
id_=id_,
|
|
152
|
+
internal_logger=internal_logger,
|
|
153
|
+
storage_mode=storage_mode,
|
|
154
|
+
create_operation_outcome_for_error=create_operation_outcome_for_error,
|
|
155
|
+
):
|
|
156
|
+
yield r
|
|
157
|
+
# Mark span as successful
|
|
158
|
+
span.set_status(Status(StatusCode.OK))
|
|
159
|
+
|
|
160
|
+
except Exception as e:
|
|
161
|
+
# Record exception in span
|
|
162
|
+
span.record_exception(e)
|
|
163
|
+
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
164
|
+
raise
|
|
165
|
+
|
|
166
|
+
finally:
|
|
167
|
+
# Ensure span is ended after generator is exhausted or error occurs
|
|
168
|
+
span.end()
|
|
150
169
|
|
|
151
170
|
@staticmethod
|
|
152
171
|
async def _handle_response_unknown(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import logging
|
|
2
3
|
from collections.abc import AsyncGenerator, Generator
|
|
3
4
|
from datetime import datetime
|
|
4
5
|
from logging import Logger
|
|
@@ -59,7 +60,7 @@ class FhirGetBundleResponse(FhirGetResponse):
|
|
|
59
60
|
chunk_number: int | None = None,
|
|
60
61
|
cache_hits: int | None = None,
|
|
61
62
|
results_by_url: list[RetryableAioHttpUrlResult],
|
|
62
|
-
storage_mode: CompressedDictStorageMode
|
|
63
|
+
storage_mode: CompressedDictStorageMode,
|
|
63
64
|
) -> None:
|
|
64
65
|
super().__init__(
|
|
65
66
|
request_id=request_id,
|
|
@@ -311,36 +312,56 @@ class FhirGetBundleResponse(FhirGetResponse):
|
|
|
311
312
|
) -> "FhirGetBundleResponse":
|
|
312
313
|
"""
|
|
313
314
|
Removes the entries in the cache from the bundle
|
|
314
|
-
|
|
315
|
-
:param request_cache: RequestCache object to remove the entries from
|
|
316
|
-
:param compare_hash: if True, compare the hash of the resource with the hash in the cache
|
|
317
|
-
:return: self
|
|
318
315
|
"""
|
|
319
|
-
#
|
|
316
|
+
# Build a lookup of cache entries by (resource_type, id)
|
|
317
|
+
cache_map: dict[tuple[str, str], str | None] = {}
|
|
320
318
|
async for cached_entry in request_cache.get_entries_async():
|
|
321
|
-
if cached_entry.from_input_cache:
|
|
322
|
-
|
|
323
|
-
if
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
319
|
+
if cached_entry.from_input_cache and cached_entry.resource_type and cached_entry.id_:
|
|
320
|
+
cache_map[(cached_entry.resource_type, cached_entry.id_)] = (
|
|
321
|
+
cached_entry.raw_hash if compare_hash else None
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
if not cache_map:
|
|
325
|
+
return self
|
|
326
|
+
|
|
327
|
+
resource_hash = ResourceHash() if compare_hash else None
|
|
328
|
+
removed_entries: list[FhirBundleEntry] = []
|
|
329
|
+
|
|
330
|
+
def should_remove(entry: FhirBundleEntry) -> bool:
|
|
331
|
+
resource = entry.resource
|
|
332
|
+
if not resource or resource.id is None or resource.resource_type is None:
|
|
333
|
+
return False
|
|
334
|
+
key = (resource.resource_type, resource.id)
|
|
335
|
+
if key not in cache_map:
|
|
336
|
+
return False
|
|
337
|
+
if not compare_hash:
|
|
338
|
+
return True
|
|
339
|
+
# Compare normalized JSON hash
|
|
340
|
+
if resource_hash is None:
|
|
341
|
+
return False
|
|
342
|
+
try:
|
|
343
|
+
entry_hash = resource_hash.hash_value(json.dumps(json.loads(resource.json()), sort_keys=True))
|
|
344
|
+
return entry_hash == cache_map[key]
|
|
345
|
+
except Exception:
|
|
346
|
+
return False
|
|
347
|
+
|
|
348
|
+
# One pass filter; rebuild list to avoid many deque.remove calls
|
|
349
|
+
kept: list[FhirBundleEntry] = []
|
|
350
|
+
for entry in self._bundle_entries:
|
|
351
|
+
if should_remove(entry):
|
|
352
|
+
removed_entries.append(entry)
|
|
353
|
+
else:
|
|
354
|
+
kept.append(entry)
|
|
355
|
+
|
|
356
|
+
if logger and removed_entries and logger.isEnabledFor(logging.DEBUG):
|
|
357
|
+
for entry in removed_entries:
|
|
358
|
+
if entry.resource:
|
|
359
|
+
logger.debug(
|
|
360
|
+
f"Removing entry from bundle with id {entry.resource.id} and resource "
|
|
361
|
+
f"type {entry.resource.resource_type}"
|
|
362
|
+
)
|
|
343
363
|
|
|
364
|
+
self._bundle_entries = FhirBundleEntryList(kept)
|
|
344
365
|
return self
|
|
345
366
|
|
|
346
367
|
@classmethod
|
|
@@ -67,7 +67,6 @@ class FhirGetErrorResponse(FhirGetResponse):
|
|
|
67
67
|
storage_mode: CompressedDictStorageMode,
|
|
68
68
|
create_operation_outcome_for_error: bool | None,
|
|
69
69
|
) -> None:
|
|
70
|
-
storage_mode or CompressedDictStorageMode.raw()
|
|
71
70
|
super().__init__(
|
|
72
71
|
request_id=request_id,
|
|
73
72
|
url=url,
|
|
@@ -57,7 +57,7 @@ class FhirGetListByResourceTypeResponse(FhirGetResponse):
|
|
|
57
57
|
chunk_number: int | None = None,
|
|
58
58
|
cache_hits: int | None = None,
|
|
59
59
|
results_by_url: list[RetryableAioHttpUrlResult],
|
|
60
|
-
storage_mode: CompressedDictStorageMode
|
|
60
|
+
storage_mode: CompressedDictStorageMode,
|
|
61
61
|
) -> None:
|
|
62
62
|
super().__init__(
|
|
63
63
|
request_id=request_id,
|
|
@@ -59,7 +59,7 @@ class FhirGetListResponse(FhirGetResponse):
|
|
|
59
59
|
chunk_number: int | None = None,
|
|
60
60
|
cache_hits: int | None = None,
|
|
61
61
|
results_by_url: list[RetryableAioHttpUrlResult],
|
|
62
|
-
storage_mode: CompressedDictStorageMode
|
|
62
|
+
storage_mode: CompressedDictStorageMode,
|
|
63
63
|
) -> None:
|
|
64
64
|
super().__init__(
|
|
65
65
|
request_id=request_id,
|
|
@@ -50,7 +50,6 @@ class FhirGetResponseFactory:
|
|
|
50
50
|
storage_mode: CompressedDictStorageMode,
|
|
51
51
|
create_operation_outcome_for_error: bool | None,
|
|
52
52
|
) -> FhirGetResponse:
|
|
53
|
-
storage_mode = storage_mode or CompressedDictStorageMode.raw()
|
|
54
53
|
try:
|
|
55
54
|
if not error and response_text:
|
|
56
55
|
# test if responses is valid json
|
|
@@ -59,7 +59,7 @@ class FhirGetSingleResponse(FhirGetResponse):
|
|
|
59
59
|
chunk_number: int | None = None,
|
|
60
60
|
cache_hits: int | None = None,
|
|
61
61
|
results_by_url: list[RetryableAioHttpUrlResult],
|
|
62
|
-
storage_mode: CompressedDictStorageMode
|
|
62
|
+
storage_mode: CompressedDictStorageMode,
|
|
63
63
|
) -> None:
|
|
64
64
|
super().__init__(
|
|
65
65
|
request_id=request_id,
|
|
@@ -75,6 +75,36 @@ class FhirMergeResourceResponseEntry(BaseFhirMergeResourceResponseEntry):
|
|
|
75
75
|
status=data.get("status"),
|
|
76
76
|
)
|
|
77
77
|
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_dict_uncompressed(cls, data: dict[str, Any]) -> "FhirMergeResourceResponseEntry":
|
|
80
|
+
"""
|
|
81
|
+
Creates a FhirMergeResourceResponseEntry from a dictionary without storage_mode overhead.
|
|
82
|
+
Uses FhirResource.construct for faster object creation.
|
|
83
|
+
|
|
84
|
+
:param data: Dictionary containing the response entry data
|
|
85
|
+
:return: FhirMergeResourceResponseEntry instance
|
|
86
|
+
"""
|
|
87
|
+
resource_payload = data.get("resource")
|
|
88
|
+
resource_obj: FhirResource | None = (
|
|
89
|
+
FhirResource.construct(**resource_payload) if isinstance(resource_payload, dict) else None
|
|
90
|
+
)
|
|
91
|
+
return FhirMergeResourceResponseEntry(
|
|
92
|
+
created=data.get("created"),
|
|
93
|
+
updated=data.get("updated"),
|
|
94
|
+
deleted=data.get("deleted"),
|
|
95
|
+
id_=data.get("id"),
|
|
96
|
+
uuid=data.get("uuid"),
|
|
97
|
+
resource_type=data.get("resourceType"),
|
|
98
|
+
source_assigning_authority=data.get("source_assigning_authority"),
|
|
99
|
+
resource_version=data.get("resource_version"),
|
|
100
|
+
message=data.get("message"),
|
|
101
|
+
issue=data.get("issue"),
|
|
102
|
+
error=data.get("error"),
|
|
103
|
+
token=data.get("token"),
|
|
104
|
+
resource=resource_obj,
|
|
105
|
+
status=data.get("status"),
|
|
106
|
+
)
|
|
107
|
+
|
|
78
108
|
@classmethod
|
|
79
109
|
@override
|
|
80
110
|
def from_json(
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import dataclasses
|
|
2
|
-
from copy import deepcopy
|
|
3
2
|
from typing import Any, cast
|
|
4
3
|
|
|
5
4
|
|
|
@@ -27,46 +26,42 @@ class ResourceSeparator:
|
|
|
27
26
|
extra_context_to_return: dict[str, Any] | None,
|
|
28
27
|
) -> ResourceSeparatorResult:
|
|
29
28
|
"""
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
:param resources: The resources list.
|
|
33
|
-
:param access_token: The access token.
|
|
34
|
-
:param url: The URL.
|
|
35
|
-
:param extra_context_to_return: The extra context to return.
|
|
36
|
-
|
|
37
|
-
:return: None
|
|
29
|
+
Separate contained resources without copying or mutating input resources.
|
|
38
30
|
"""
|
|
39
31
|
resources_dicts: list[dict[str, str | None | list[dict[str, Any]]]] = []
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
for
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if
|
|
58
|
-
|
|
59
|
-
for contained_resource in
|
|
60
|
-
|
|
61
|
-
if
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
32
|
+
total_resource_count: int = 0
|
|
33
|
+
|
|
34
|
+
for parent_resource in resources:
|
|
35
|
+
resource_type_value = parent_resource.get("resourceType")
|
|
36
|
+
if not resource_type_value:
|
|
37
|
+
continue
|
|
38
|
+
|
|
39
|
+
resource_type_key = str(resource_type_value).lower()
|
|
40
|
+
resource_map: dict[str, str | None | list[dict[str, Any]]] = {}
|
|
41
|
+
|
|
42
|
+
# Add parent resource
|
|
43
|
+
parent_list = cast(list[dict[str, Any]], resource_map.setdefault(resource_type_key, []))
|
|
44
|
+
parent_list.append(parent_resource)
|
|
45
|
+
total_resource_count += 1
|
|
46
|
+
|
|
47
|
+
# Add contained resources (if present) without mutating parent
|
|
48
|
+
contained_list = parent_resource.get("contained")
|
|
49
|
+
if isinstance(contained_list, list) and contained_list:
|
|
50
|
+
total_resource_count += len(contained_list)
|
|
51
|
+
for contained_resource in contained_list:
|
|
52
|
+
contained_type_value = contained_resource.get("resourceType")
|
|
53
|
+
if not contained_type_value:
|
|
54
|
+
continue
|
|
55
|
+
contained_type_key = str(contained_type_value).lower()
|
|
56
|
+
contained_list_out = cast(list[dict[str, Any]], resource_map.setdefault(contained_type_key, []))
|
|
57
|
+
contained_list_out.append(contained_resource)
|
|
58
|
+
|
|
59
|
+
# Context
|
|
60
|
+
resource_map["token"] = access_token
|
|
61
|
+
resource_map["url"] = url
|
|
68
62
|
if extra_context_to_return:
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
resource_map.update(extra_context_to_return)
|
|
64
|
+
|
|
65
|
+
resources_dicts.append(resource_map)
|
|
71
66
|
|
|
72
|
-
return ResourceSeparatorResult(resources_dicts=resources_dicts, total_count=
|
|
67
|
+
return ResourceSeparatorResult(resources_dicts=resources_dicts, total_count=total_resource_count)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
from collections.abc import AsyncGenerator
|
|
3
2
|
from datetime import datetime
|
|
4
3
|
from types import TracebackType
|
|
@@ -11,7 +10,7 @@ from helix_fhir_client_sdk.utilities.cache.request_cache_entry import RequestCac
|
|
|
11
10
|
|
|
12
11
|
class RequestCache:
|
|
13
12
|
"""
|
|
14
|
-
This is a class that caches requests to the FHIR server
|
|
13
|
+
This is a class that caches requests to the FHIR server.
|
|
15
14
|
It is used to avoid multiple requests to the FHIR server when doing a large number
|
|
16
15
|
of requests for the same resource.
|
|
17
16
|
"""
|
|
@@ -20,7 +19,6 @@ class RequestCache:
|
|
|
20
19
|
"cache_hits",
|
|
21
20
|
"cache_misses",
|
|
22
21
|
"_cache",
|
|
23
|
-
"_lock",
|
|
24
22
|
"_clear_cache_at_the_end",
|
|
25
23
|
]
|
|
26
24
|
|
|
@@ -33,7 +31,6 @@ class RequestCache:
|
|
|
33
31
|
self.cache_hits: int = 0
|
|
34
32
|
self.cache_misses: int = 0
|
|
35
33
|
self._cache: dict[str, RequestCacheEntry] = initial_dict or {}
|
|
36
|
-
self._lock: asyncio.Lock = asyncio.Lock()
|
|
37
34
|
self._clear_cache_at_the_end: bool | None = clear_cache_at_the_end
|
|
38
35
|
|
|
39
36
|
async def __aenter__(self) -> "RequestCache":
|
|
@@ -42,8 +39,7 @@ class RequestCache:
|
|
|
42
39
|
It returns the RequestCache instance.
|
|
43
40
|
"""
|
|
44
41
|
if self._clear_cache_at_the_end:
|
|
45
|
-
|
|
46
|
-
self._cache.clear()
|
|
42
|
+
self._cache.clear()
|
|
47
43
|
return self
|
|
48
44
|
|
|
49
45
|
async def __aexit__(
|
|
@@ -57,8 +53,7 @@ class RequestCache:
|
|
|
57
53
|
It clears the cache.
|
|
58
54
|
"""
|
|
59
55
|
if self._clear_cache_at_the_end:
|
|
60
|
-
|
|
61
|
-
self._cache.clear()
|
|
56
|
+
self._cache.clear()
|
|
62
57
|
|
|
63
58
|
if exc_value is not None:
|
|
64
59
|
raise exc_value.with_traceback(traceback)
|
|
@@ -75,15 +70,14 @@ class RequestCache:
|
|
|
75
70
|
"""
|
|
76
71
|
key: str = f"{resource_type}/{resource_id}"
|
|
77
72
|
|
|
78
|
-
|
|
79
|
-
cached_entry = self._cache.get(key)
|
|
73
|
+
cached_entry = self._cache.get(key)
|
|
80
74
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
if cached_entry is not None:
|
|
76
|
+
self.cache_hits += 1
|
|
77
|
+
return cached_entry
|
|
84
78
|
|
|
85
|
-
|
|
86
|
-
|
|
79
|
+
self.cache_misses += 1
|
|
80
|
+
return None
|
|
87
81
|
|
|
88
82
|
async def add_async(
|
|
89
83
|
self,
|
|
@@ -111,43 +105,40 @@ class RequestCache:
|
|
|
111
105
|
"""
|
|
112
106
|
key: str = f"{resource_type}/{resource_id}"
|
|
113
107
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
self._cache[key] = cache_entry
|
|
108
|
+
# Create the cache entry
|
|
109
|
+
cache_entry = RequestCacheEntry(
|
|
110
|
+
id_=resource_id,
|
|
111
|
+
resource_type=resource_type,
|
|
112
|
+
status=status,
|
|
113
|
+
bundle_entry=bundle_entry,
|
|
114
|
+
last_modified=last_modified,
|
|
115
|
+
etag=etag,
|
|
116
|
+
from_input_cache=from_input_cache,
|
|
117
|
+
raw_hash=raw_hash,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Add to the dictionary
|
|
121
|
+
self._cache[key] = cache_entry
|
|
129
122
|
|
|
130
|
-
|
|
123
|
+
return True
|
|
131
124
|
|
|
132
125
|
async def remove_async(self, *, resource_key: str) -> bool:
|
|
133
126
|
"""
|
|
134
127
|
This method remove the given data from the cache.
|
|
135
128
|
:param resource_key: resource key contains both resourceType and resourceId. Eg: Patient/123
|
|
136
129
|
"""
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return False
|
|
130
|
+
if resource_key not in self._cache:
|
|
131
|
+
return False
|
|
140
132
|
|
|
141
|
-
|
|
133
|
+
del self._cache[resource_key]
|
|
142
134
|
|
|
143
|
-
|
|
135
|
+
return True
|
|
144
136
|
|
|
145
137
|
async def clear_async(self) -> None:
|
|
146
138
|
"""
|
|
147
139
|
This method clears the cache.
|
|
148
140
|
"""
|
|
149
|
-
|
|
150
|
-
self._cache.clear()
|
|
141
|
+
self._cache.clear()
|
|
151
142
|
|
|
152
143
|
async def get_entries_async(self) -> AsyncGenerator[RequestCacheEntry, None]:
|
|
153
144
|
"""
|
|
@@ -155,9 +146,8 @@ class RequestCache:
|
|
|
155
146
|
|
|
156
147
|
:return: The keys in the cache.
|
|
157
148
|
"""
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
yield entry
|
|
149
|
+
for entry in self._cache.values():
|
|
150
|
+
yield entry
|
|
161
151
|
|
|
162
152
|
async def get_keys_async(self) -> list[str]:
|
|
163
153
|
"""
|
|
@@ -165,8 +155,7 @@ class RequestCache:
|
|
|
165
155
|
|
|
166
156
|
:return: The entries in the cache.
|
|
167
157
|
"""
|
|
168
|
-
|
|
169
|
-
return list(self._cache.keys())
|
|
158
|
+
return list(self._cache.keys())
|
|
170
159
|
|
|
171
160
|
def __len__(self) -> int:
|
|
172
161
|
"""
|