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
|
@@ -8,11 +8,13 @@ from threading import Lock
|
|
|
8
8
|
from typing import TYPE_CHECKING, Any, cast
|
|
9
9
|
|
|
10
10
|
from furl import furl
|
|
11
|
+
from opentelemetry import trace
|
|
11
12
|
|
|
12
13
|
from helix_fhir_client_sdk.function_types import (
|
|
13
14
|
RefreshTokenFunction,
|
|
14
15
|
RefreshTokenResult,
|
|
15
16
|
)
|
|
17
|
+
from helix_fhir_client_sdk.open_telemetry.span_names import FhirClientSdkOpenTelemetrySpanNames
|
|
16
18
|
from helix_fhir_client_sdk.responses.fhir_client_protocol import FhirClientProtocol
|
|
17
19
|
from helix_fhir_client_sdk.structures.get_access_token_result import (
|
|
18
20
|
GetAccessTokenResult,
|
|
@@ -31,6 +33,9 @@ if TYPE_CHECKING:
|
|
|
31
33
|
from helix_fhir_client_sdk.fhir_client import FhirClient
|
|
32
34
|
|
|
33
35
|
|
|
36
|
+
TRACER = trace.get_tracer(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
34
39
|
class FhirAuthMixin(FhirClientProtocol):
|
|
35
40
|
_time_to_live_in_secs_for_cache: int = 10 * 60
|
|
36
41
|
|
|
@@ -119,15 +124,15 @@ class FhirAuthMixin(FhirClientProtocol):
|
|
|
119
124
|
access_token=self._access_token,
|
|
120
125
|
expiry_date=self._access_token_expiry_date,
|
|
121
126
|
)
|
|
127
|
+
with TRACER.start_as_current_span(FhirClientSdkOpenTelemetrySpanNames.GET_ACCESS_TOKEN):
|
|
128
|
+
refresh_token_result: RefreshTokenResult = await self._refresh_token_function(
|
|
129
|
+
url=None, status_code=0, current_token=None, expiry_date=None, retry_count=0
|
|
130
|
+
)
|
|
131
|
+
assert isinstance(refresh_token_result, RefreshTokenResult)
|
|
122
132
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
assert isinstance(refresh_token_result, RefreshTokenResult)
|
|
127
|
-
|
|
128
|
-
self.set_access_token(refresh_token_result.access_token)
|
|
129
|
-
self.set_access_token_expiry_date(refresh_token_result.expiry_date)
|
|
130
|
-
return GetAccessTokenResult(access_token=self._access_token, expiry_date=self._access_token_expiry_date)
|
|
133
|
+
self.set_access_token(refresh_token_result.access_token)
|
|
134
|
+
self.set_access_token_expiry_date(refresh_token_result.expiry_date)
|
|
135
|
+
return GetAccessTokenResult(access_token=self._access_token, expiry_date=self._access_token_expiry_date)
|
|
131
136
|
|
|
132
137
|
def authenticate_async_wrapper(self) -> RefreshTokenFunction:
|
|
133
138
|
"""
|
|
@@ -165,7 +170,8 @@ class FhirAuthMixin(FhirClientProtocol):
|
|
|
165
170
|
:return: auth server url or None
|
|
166
171
|
"""
|
|
167
172
|
async with RetryableAioHttpClient(
|
|
168
|
-
fn_get_session=
|
|
173
|
+
fn_get_session=self._fn_create_http_session or self.create_http_session,
|
|
174
|
+
caller_managed_session=self._fn_create_http_session is not None,
|
|
169
175
|
exclude_status_codes_from_retry=[404],
|
|
170
176
|
throw_exception_on_error=False,
|
|
171
177
|
use_data_streaming=False,
|
|
@@ -267,7 +273,8 @@ class FhirAuthMixin(FhirClientProtocol):
|
|
|
267
273
|
}
|
|
268
274
|
|
|
269
275
|
async with RetryableAioHttpClient(
|
|
270
|
-
fn_get_session=
|
|
276
|
+
fn_get_session=self._fn_create_http_session or self.create_http_session,
|
|
277
|
+
caller_managed_session=self._fn_create_http_session is not None,
|
|
271
278
|
use_data_streaming=False,
|
|
272
279
|
compress=False,
|
|
273
280
|
exclude_status_codes_from_retry=None,
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import logging
|
|
4
4
|
import ssl
|
|
5
5
|
import uuid
|
|
6
|
-
from collections.abc import AsyncGenerator
|
|
6
|
+
from collections.abc import AsyncGenerator, Callable
|
|
7
7
|
from datetime import datetime
|
|
8
8
|
from logging import Logger
|
|
9
9
|
from os import environ
|
|
@@ -26,6 +26,8 @@ from compressedfhir.utilities.compressed_dict.v1.compressed_dict_storage_mode im
|
|
|
26
26
|
CompressedDictStorageMode,
|
|
27
27
|
)
|
|
28
28
|
from furl import furl
|
|
29
|
+
from opentelemetry import trace
|
|
30
|
+
from opentelemetry.trace import Status, StatusCode
|
|
29
31
|
from requests.adapters import BaseAdapter
|
|
30
32
|
|
|
31
33
|
from helix_fhir_client_sdk.dictionary_writer import convert_dict_to_str
|
|
@@ -46,6 +48,7 @@ from helix_fhir_client_sdk.graph.fhir_graph_mixin import FhirGraphMixin
|
|
|
46
48
|
from helix_fhir_client_sdk.graph.simulated_graph_processor_mixin import (
|
|
47
49
|
SimulatedGraphProcessorMixin,
|
|
48
50
|
)
|
|
51
|
+
from helix_fhir_client_sdk.open_telemetry.span_names import FhirClientSdkOpenTelemetrySpanNames
|
|
49
52
|
from helix_fhir_client_sdk.queue.request_queue_mixin import RequestQueueMixin
|
|
50
53
|
from helix_fhir_client_sdk.responses.fhir_client_protocol import FhirClientProtocol
|
|
51
54
|
from helix_fhir_client_sdk.responses.fhir_get_response import FhirGetResponse
|
|
@@ -55,6 +58,8 @@ from helix_fhir_client_sdk.structures.get_access_token_result import (
|
|
|
55
58
|
from helix_fhir_client_sdk.utilities.async_runner import AsyncRunner
|
|
56
59
|
from helix_fhir_client_sdk.utilities.fhir_client_logger import FhirClientLogger
|
|
57
60
|
|
|
61
|
+
TRACER = trace.get_tracer(__name__)
|
|
62
|
+
|
|
58
63
|
|
|
59
64
|
class FhirClient(
|
|
60
65
|
SimulatedGraphProcessorMixin,
|
|
@@ -145,9 +150,13 @@ class FhirClient(
|
|
|
145
150
|
self._log_all_response_urls: bool = False
|
|
146
151
|
""" If True, logs all response URLs and status codes. Can take a lot of memory for when there are many responses. """
|
|
147
152
|
|
|
148
|
-
|
|
153
|
+
# Default to "raw" storage mode - no in-memory compression, resources stored as plain Python dicts
|
|
154
|
+
self._storage_mode: CompressedDictStorageMode = CompressedDictStorageMode(storage_type="raw")
|
|
155
|
+
|
|
156
|
+
self._create_operation_outcome_for_error: bool | None = False
|
|
149
157
|
|
|
150
|
-
|
|
158
|
+
# Optional callable to create HTTP sessions. When set, create_http_session() will use this.
|
|
159
|
+
self._fn_create_http_session: Callable[[], ClientSession] | None = None
|
|
151
160
|
|
|
152
161
|
def action(self, action: str) -> FhirClient:
|
|
153
162
|
"""
|
|
@@ -450,9 +459,14 @@ class FhirClient(
|
|
|
450
459
|
|
|
451
460
|
def compress(self, compress: bool) -> FhirClient:
|
|
452
461
|
"""
|
|
453
|
-
Sets the
|
|
462
|
+
Sets whether to use HTTP compression (gzip) when sending request data to the server.
|
|
463
|
+
|
|
464
|
+
This controls compression of the HTTP request body only, not in-memory storage.
|
|
465
|
+
Default is True (compression enabled).
|
|
466
|
+
|
|
467
|
+
To disable all compression, call: .compress(False)
|
|
454
468
|
|
|
455
|
-
:param compress: whether to compress
|
|
469
|
+
:param compress: whether to compress HTTP request body (default: True)
|
|
456
470
|
"""
|
|
457
471
|
self._compress = compress
|
|
458
472
|
return self
|
|
@@ -466,31 +480,48 @@ class FhirClient(
|
|
|
466
480
|
self._throw_exception_on_error = throw_exception_on_error
|
|
467
481
|
return self
|
|
468
482
|
|
|
469
|
-
def
|
|
483
|
+
def use_http_session(self, fn_create_http_session: Callable[[], ClientSession] | None) -> FhirClient:
|
|
470
484
|
"""
|
|
471
|
-
Sets
|
|
485
|
+
Sets a custom callable to create HTTP sessions.
|
|
472
486
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
return self
|
|
487
|
+
When set, create_http_session() will use this callable instead of the default
|
|
488
|
+
implementation. This allows for custom session management, connection pooling,
|
|
489
|
+
or persistent session support.
|
|
477
490
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
491
|
+
**Important**: When you provide a custom session factory, YOU are responsible
|
|
492
|
+
for managing the session lifecycle, including closing it when done. The SDK
|
|
493
|
+
will NOT automatically close user-provided sessions.
|
|
481
494
|
|
|
482
|
-
|
|
483
|
-
"""
|
|
484
|
-
self._use_persistent_session = value
|
|
485
|
-
return self
|
|
495
|
+
Example with a persistent session for connection reuse (~4× performance boost):
|
|
486
496
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
497
|
+
.. code-block:: python
|
|
498
|
+
|
|
499
|
+
import aiohttp
|
|
500
|
+
from helix_fhir_client_sdk.fhir_client import FhirClient
|
|
501
|
+
|
|
502
|
+
# Create persistent session
|
|
503
|
+
session = aiohttp.ClientSession()
|
|
504
|
+
|
|
505
|
+
try:
|
|
506
|
+
# Configure FhirClient to use persistent session
|
|
507
|
+
fhir_client = (
|
|
508
|
+
FhirClient()
|
|
509
|
+
.url("http://fhir.example.com")
|
|
510
|
+
.resource("Patient")
|
|
511
|
+
.use_http_session(lambda: session) # User provides session
|
|
512
|
+
)
|
|
490
513
|
|
|
491
|
-
|
|
514
|
+
# Multiple requests reuse the same connection
|
|
515
|
+
response1 = await fhir_client.get_async()
|
|
516
|
+
response2 = await fhir_client.clone().resource("Observation").get_async()
|
|
517
|
+
|
|
518
|
+
finally:
|
|
519
|
+
# User must close the session when done
|
|
520
|
+
await session.close()
|
|
521
|
+
|
|
522
|
+
:param fn_create_http_session: callable that returns a ClientSession, or None to use default
|
|
492
523
|
"""
|
|
493
|
-
self.
|
|
524
|
+
self._fn_create_http_session = fn_create_http_session
|
|
494
525
|
return self
|
|
495
526
|
|
|
496
527
|
# noinspection PyUnusedLocal
|
|
@@ -606,33 +637,34 @@ class FhirClient(
|
|
|
606
637
|
|
|
607
638
|
:return: response
|
|
608
639
|
"""
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
if
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
640
|
+
with TRACER.start_as_current_span(FhirClientSdkOpenTelemetrySpanNames.GET):
|
|
641
|
+
instance_variables_text = convert_dict_to_str(FhirClientLogger.get_variables_to_log(vars(self)))
|
|
642
|
+
if self._logger:
|
|
643
|
+
# self._logger.info(f"LOGLEVEL: {self._log_level}")
|
|
644
|
+
self._logger.debug(f"parameters: {instance_variables_text}")
|
|
645
|
+
else:
|
|
646
|
+
self._internal_logger.debug(f"LOGLEVEL (InternalLogger): {self._log_level}")
|
|
647
|
+
self._internal_logger.debug(f"parameters: {instance_variables_text}")
|
|
648
|
+
ids: list[str] | None = None
|
|
649
|
+
if self._id:
|
|
650
|
+
ids = self._id if isinstance(self._id, list) else [self._id]
|
|
651
|
+
# actually make the request
|
|
652
|
+
full_response: FhirGetResponse | None = None
|
|
653
|
+
async for response in self._get_with_session_async(
|
|
654
|
+
ids=ids,
|
|
655
|
+
fn_handle_streaming_chunk=data_chunk_handler,
|
|
656
|
+
page_number=None,
|
|
657
|
+
id_above=None,
|
|
658
|
+
additional_parameters=None,
|
|
659
|
+
resource_type=None,
|
|
660
|
+
):
|
|
661
|
+
if response:
|
|
662
|
+
if full_response:
|
|
663
|
+
full_response = full_response.append(response)
|
|
664
|
+
else:
|
|
665
|
+
full_response = response
|
|
666
|
+
assert full_response
|
|
667
|
+
return full_response
|
|
636
668
|
|
|
637
669
|
async def get_raw_resources_async(
|
|
638
670
|
self,
|
|
@@ -673,27 +705,37 @@ class FhirClient(
|
|
|
673
705
|
|
|
674
706
|
:return: async generator of responses
|
|
675
707
|
"""
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
self._logger
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
ids
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
708
|
+
span = TRACER.start_span(FhirClientSdkOpenTelemetrySpanNames.GET_STREAMING)
|
|
709
|
+
try:
|
|
710
|
+
instance_variables_text = convert_dict_to_str(FhirClientLogger.get_variables_to_log(vars(self)))
|
|
711
|
+
if self._logger:
|
|
712
|
+
# self._logger.info(f"LOGLEVEL: {self._log_level}")
|
|
713
|
+
self._logger.info(f"parameters: {instance_variables_text}")
|
|
714
|
+
else:
|
|
715
|
+
self._internal_logger.info(f"LOGLEVEL (InternalLogger): {self._log_level}")
|
|
716
|
+
self._internal_logger.info(f"parameters: {instance_variables_text}")
|
|
717
|
+
ids: list[str] | None = None
|
|
718
|
+
if self._id:
|
|
719
|
+
ids = self._id if isinstance(self._id, list) else [self._id]
|
|
720
|
+
# actually make the request
|
|
721
|
+
response: FhirGetResponse | None
|
|
722
|
+
async for response in self._get_with_session_async(
|
|
723
|
+
ids=ids,
|
|
724
|
+
fn_handle_streaming_chunk=data_chunk_handler,
|
|
725
|
+
page_number=None,
|
|
726
|
+
id_above=None,
|
|
727
|
+
additional_parameters=None,
|
|
728
|
+
resource_type=None,
|
|
729
|
+
):
|
|
730
|
+
yield response
|
|
731
|
+
except BaseException as exc: # propagate cancellation/errors but keep span informative
|
|
732
|
+
# Record exception in span
|
|
733
|
+
span.record_exception(exc)
|
|
734
|
+
span.set_status(Status(StatusCode.ERROR, str(exc)))
|
|
735
|
+
raise
|
|
736
|
+
finally:
|
|
737
|
+
# Ensure span is ended after generator is exhausted or error occurs
|
|
738
|
+
span.end()
|
|
697
739
|
|
|
698
740
|
def get(self) -> FhirGetResponse:
|
|
699
741
|
"""
|
|
@@ -804,8 +846,12 @@ class FhirClient(
|
|
|
804
846
|
|
|
805
847
|
def create_http_session(self) -> ClientSession:
|
|
806
848
|
"""
|
|
807
|
-
Creates
|
|
849
|
+
Creates the SDK's default HTTP Session.
|
|
850
|
+
|
|
851
|
+
This method always creates a new aiohttp ClientSession with standard configuration.
|
|
852
|
+
The SDK manages the lifecycle of sessions created by this method.
|
|
808
853
|
|
|
854
|
+
Note: If you want to provide your own session factory, use use_http_session() instead.
|
|
809
855
|
"""
|
|
810
856
|
trace_config = aiohttp.TraceConfig()
|
|
811
857
|
# trace_config.on_request_start.append(on_request_start)
|
|
@@ -828,17 +874,17 @@ class FhirClient(
|
|
|
828
874
|
Whether to ask the server to include the total count in the result
|
|
829
875
|
|
|
830
876
|
|
|
831
|
-
:param include_total: whether to include total count
|
|
877
|
+
:param include_total: whether to include the total count
|
|
832
878
|
"""
|
|
833
879
|
self._include_total = include_total
|
|
834
880
|
return self
|
|
835
881
|
|
|
836
882
|
def filter(self, filter_: list[BaseFilter]) -> FhirClient:
|
|
837
883
|
"""
|
|
838
|
-
Allows adding in
|
|
884
|
+
Allows adding in custom filters that derive from BaseFilter
|
|
839
885
|
|
|
840
886
|
|
|
841
|
-
:param filter_: list of custom filter instances that
|
|
887
|
+
:param filter_: list of custom filter instances that derive from BaseFilter.
|
|
842
888
|
"""
|
|
843
889
|
assert isinstance(filter_, list), "This function requires a list"
|
|
844
890
|
self._filters.extend(filter_)
|
|
@@ -879,6 +925,8 @@ class FhirClient(
|
|
|
879
925
|
fhir_client._client_id = self._client_id
|
|
880
926
|
fhir_client._auth_server_url = self._auth_server_url
|
|
881
927
|
fhir_client._login_token = self._login_token
|
|
928
|
+
fhir_client._access_token = self._access_token
|
|
929
|
+
fhir_client._access_token_expiry_date = self._access_token_expiry_date
|
|
882
930
|
fhir_client._refresh_token_function = self._refresh_token_function
|
|
883
931
|
fhir_client._exclude_status_codes_from_retry = self._exclude_status_codes_from_retry
|
|
884
932
|
fhir_client._chunk_size = self._chunk_size
|
|
@@ -891,6 +939,19 @@ class FhirClient(
|
|
|
891
939
|
fhir_client._time_to_live_in_secs_for_cache = self._time_to_live_in_secs_for_cache
|
|
892
940
|
fhir_client._validation_server_url = self._validation_server_url
|
|
893
941
|
fhir_client._smart_merge = self._smart_merge
|
|
942
|
+
fhir_client._compress = self._compress
|
|
943
|
+
fhir_client._storage_mode = self._storage_mode
|
|
944
|
+
fhir_client._send_data_as_chunked = self._send_data_as_chunked
|
|
945
|
+
fhir_client._use_post_for_search = self._use_post_for_search
|
|
946
|
+
fhir_client._maximum_time_to_retry_on_429 = self._maximum_time_to_retry_on_429
|
|
947
|
+
fhir_client._retry_count = self._retry_count
|
|
948
|
+
fhir_client._throw_exception_on_error = self._throw_exception_on_error
|
|
949
|
+
fhir_client._trace_request_function = self._trace_request_function
|
|
950
|
+
fhir_client._log_all_response_urls = self._log_all_response_urls
|
|
951
|
+
fhir_client._create_operation_outcome_for_error = self._create_operation_outcome_for_error
|
|
952
|
+
fhir_client._fn_create_http_session = self._fn_create_http_session
|
|
953
|
+
if self._max_concurrent_requests is not None:
|
|
954
|
+
fhir_client.set_max_concurrent_requests(self._max_concurrent_requests)
|
|
894
955
|
return fhir_client
|
|
895
956
|
|
|
896
957
|
def set_log_all_response_urls(self, value: bool) -> FhirClient:
|
|
@@ -913,18 +974,30 @@ class FhirClient(
|
|
|
913
974
|
|
|
914
975
|
def set_storage_mode(self, value: CompressedDictStorageMode) -> FhirClient:
|
|
915
976
|
"""
|
|
916
|
-
Sets the storage mode
|
|
977
|
+
Sets the in-memory storage mode for FHIR resources.
|
|
978
|
+
|
|
979
|
+
This controls how FHIR resources are stored in memory after being received.
|
|
980
|
+
The default is "raw" (no compression - resources stored as plain Python dicts).
|
|
981
|
+
|
|
982
|
+
Available storage types:
|
|
983
|
+
- "raw": No compression, standard Python dictionaries (default)
|
|
984
|
+
- "compressed": Zlib/gzip compression in memory
|
|
985
|
+
- "msgpack": MessagePack binary serialization
|
|
986
|
+
- "compressed_msgpack": MessagePack + compression
|
|
987
|
+
|
|
988
|
+
Note: This is separate from HTTP compression (controlled by .compress()).
|
|
989
|
+
With default settings, no in-memory compression is applied.
|
|
917
990
|
|
|
918
|
-
:param value: storage mode
|
|
991
|
+
:param value: storage mode (default: raw)
|
|
919
992
|
"""
|
|
920
993
|
self._storage_mode = value
|
|
921
994
|
return self
|
|
922
995
|
|
|
923
|
-
def set_create_operation_outcome_for_error(self, value: bool) -> FhirClient:
|
|
996
|
+
def set_create_operation_outcome_for_error(self, value: bool | None) -> FhirClient:
|
|
924
997
|
"""
|
|
925
998
|
Sets the create_operation_outcome_for_error flag
|
|
926
999
|
|
|
927
|
-
:param value: whether to create operation outcome for error
|
|
1000
|
+
:param value: whether to create an operation outcome for error (True, False, or None)
|
|
928
1001
|
"""
|
|
929
1002
|
self._create_operation_outcome_for_error = value
|
|
930
1003
|
return self
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import json
|
|
2
2
|
|
|
3
3
|
from furl import furl
|
|
4
|
+
from opentelemetry import trace
|
|
5
|
+
from opentelemetry.trace import Status, StatusCode
|
|
4
6
|
|
|
7
|
+
from helix_fhir_client_sdk.open_telemetry.attribute_names import FhirClientSdkOpenTelemetryAttributeNames
|
|
8
|
+
from helix_fhir_client_sdk.open_telemetry.span_names import FhirClientSdkOpenTelemetrySpanNames
|
|
5
9
|
from helix_fhir_client_sdk.responses.fhir_client_protocol import FhirClientProtocol
|
|
6
10
|
from helix_fhir_client_sdk.responses.fhir_delete_response import FhirDeleteResponse
|
|
7
11
|
from helix_fhir_client_sdk.structures.get_access_token_result import (
|
|
@@ -15,6 +19,8 @@ from helix_fhir_client_sdk.utilities.retryable_aiohttp_response import (
|
|
|
15
19
|
RetryableAioHttpResponse,
|
|
16
20
|
)
|
|
17
21
|
|
|
22
|
+
TRACER = trace.get_tracer(__name__)
|
|
23
|
+
|
|
18
24
|
|
|
19
25
|
class FhirDeleteMixin(FhirClientProtocol):
|
|
20
26
|
async def delete_async(self) -> FhirDeleteResponse:
|
|
@@ -29,53 +35,60 @@ class FhirDeleteMixin(FhirClientProtocol):
|
|
|
29
35
|
raise ValueError("delete requires the ID of FHIR object to delete")
|
|
30
36
|
if not self._resource:
|
|
31
37
|
raise ValueError("delete requires a FHIR resource type")
|
|
32
|
-
full_uri: furl = furl(self._url)
|
|
33
|
-
full_uri /= self._resource
|
|
34
|
-
full_uri /= id_list
|
|
35
|
-
# setup retry
|
|
36
|
-
# set up headers
|
|
37
|
-
headers: dict[str, str] = {}
|
|
38
|
-
headers.update(self._additional_request_headers)
|
|
39
|
-
self._internal_logger.debug(f"Request headers: {headers}")
|
|
40
|
-
|
|
41
|
-
access_token_result: GetAccessTokenResult = await self.get_access_token_async()
|
|
42
|
-
access_token: str | None = access_token_result.access_token
|
|
43
|
-
# set access token in request if present
|
|
44
|
-
if access_token:
|
|
45
|
-
headers["Authorization"] = f"Bearer {access_token}"
|
|
46
|
-
|
|
47
|
-
async with RetryableAioHttpClient(
|
|
48
|
-
fn_get_session=lambda: self.create_http_session(),
|
|
49
|
-
refresh_token_func=self._refresh_token_function,
|
|
50
|
-
retries=self._retry_count,
|
|
51
|
-
exclude_status_codes_from_retry=self._exclude_status_codes_from_retry,
|
|
52
|
-
use_data_streaming=self._use_data_streaming,
|
|
53
|
-
compress=False,
|
|
54
|
-
throw_exception_on_error=self._throw_exception_on_error,
|
|
55
|
-
log_all_url_results=self._log_all_response_urls,
|
|
56
|
-
access_token=self._access_token,
|
|
57
|
-
access_token_expiry_date=self._access_token_expiry_date,
|
|
58
|
-
tracer_request_func=self._trace_request_function,
|
|
59
|
-
persistent_session=self._persistent_session,
|
|
60
|
-
use_persistent_session=self._use_persistent_session,
|
|
61
|
-
close_session_on_exit=self._close_session,
|
|
62
|
-
) as client:
|
|
63
|
-
response: RetryableAioHttpResponse = await client.delete(url=full_uri.tostr(), headers=headers)
|
|
64
|
-
request_id = response.response_headers.get("X-Request-ID", None)
|
|
65
|
-
self._internal_logger.info(f"X-Request-ID={request_id}")
|
|
66
|
-
if response.status == 200:
|
|
67
|
-
if self._logger:
|
|
68
|
-
self._logger.info(f"Successfully deleted: {full_uri}")
|
|
69
38
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
39
|
+
with TRACER.start_as_current_span(FhirClientSdkOpenTelemetrySpanNames.DELETE) as span:
|
|
40
|
+
span.set_attribute(FhirClientSdkOpenTelemetryAttributeNames.URL, self._url or "")
|
|
41
|
+
span.set_attribute(FhirClientSdkOpenTelemetryAttributeNames.RESOURCE, self._resource or "")
|
|
42
|
+
try:
|
|
43
|
+
full_uri: furl = furl(self._url)
|
|
44
|
+
full_uri /= self._resource
|
|
45
|
+
full_uri /= id_list
|
|
46
|
+
# setup retry
|
|
47
|
+
# set up headers
|
|
48
|
+
headers: dict[str, str] = {}
|
|
49
|
+
headers.update(self._additional_request_headers)
|
|
50
|
+
self._internal_logger.debug(f"Request headers: {headers}")
|
|
51
|
+
|
|
52
|
+
access_token_result: GetAccessTokenResult = await self.get_access_token_async()
|
|
53
|
+
access_token: str | None = access_token_result.access_token
|
|
54
|
+
# set access token in request if present
|
|
55
|
+
if access_token:
|
|
56
|
+
headers["Authorization"] = f"Bearer {access_token}"
|
|
57
|
+
|
|
58
|
+
async with RetryableAioHttpClient(
|
|
59
|
+
fn_get_session=self._fn_create_http_session or self.create_http_session,
|
|
60
|
+
caller_managed_session=self._fn_create_http_session is not None,
|
|
61
|
+
refresh_token_func=self._refresh_token_function,
|
|
62
|
+
retries=self._retry_count,
|
|
63
|
+
exclude_status_codes_from_retry=self._exclude_status_codes_from_retry,
|
|
64
|
+
use_data_streaming=self._use_data_streaming,
|
|
65
|
+
compress=False,
|
|
66
|
+
throw_exception_on_error=self._throw_exception_on_error,
|
|
67
|
+
log_all_url_results=self._log_all_response_urls,
|
|
68
|
+
access_token=self._access_token,
|
|
69
|
+
access_token_expiry_date=self._access_token_expiry_date,
|
|
70
|
+
tracer_request_func=self._trace_request_function,
|
|
71
|
+
) as client:
|
|
72
|
+
response: RetryableAioHttpResponse = await client.delete(url=full_uri.tostr(), headers=headers)
|
|
73
|
+
request_id = response.response_headers.get("X-Request-ID", None)
|
|
74
|
+
self._internal_logger.debug(f"X-Request-ID={request_id}")
|
|
75
|
+
if response.status == 200:
|
|
76
|
+
if self._logger:
|
|
77
|
+
self._logger.info(f"Successfully deleted: {full_uri}")
|
|
78
|
+
|
|
79
|
+
return FhirDeleteResponse(
|
|
80
|
+
request_id=request_id,
|
|
81
|
+
url=full_uri.tostr(),
|
|
82
|
+
responses=await response.get_text_async(),
|
|
83
|
+
error=f"{response.status}" if not response.status == 200 else None,
|
|
84
|
+
access_token=access_token,
|
|
85
|
+
status=response.status,
|
|
86
|
+
resource_type=self._resource,
|
|
87
|
+
)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
span.record_exception(e)
|
|
90
|
+
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
91
|
+
raise
|
|
79
92
|
|
|
80
93
|
def delete(self) -> FhirDeleteResponse:
|
|
81
94
|
"""
|
|
@@ -117,7 +130,8 @@ class FhirDeleteMixin(FhirClientProtocol):
|
|
|
117
130
|
headers["Authorization"] = f"Bearer {access_token}"
|
|
118
131
|
|
|
119
132
|
async with RetryableAioHttpClient(
|
|
120
|
-
fn_get_session=
|
|
133
|
+
fn_get_session=self._fn_create_http_session or self.create_http_session,
|
|
134
|
+
caller_managed_session=self._fn_create_http_session is not None,
|
|
121
135
|
refresh_token_func=self._refresh_token_function,
|
|
122
136
|
retries=self._retry_count,
|
|
123
137
|
exclude_status_codes_from_retry=self._exclude_status_codes_from_retry,
|
|
@@ -131,7 +145,7 @@ class FhirDeleteMixin(FhirClientProtocol):
|
|
|
131
145
|
) as client:
|
|
132
146
|
response: RetryableAioHttpResponse = await client.delete(url=full_url, headers=headers)
|
|
133
147
|
request_id = response.response_headers.get("X-Request-ID", None)
|
|
134
|
-
self._internal_logger.
|
|
148
|
+
self._internal_logger.debug(f"X-Request-ID={request_id}")
|
|
135
149
|
if response.status == 200:
|
|
136
150
|
if self._logger:
|
|
137
151
|
self._logger.info(f"Successfully deleted: {full_uri}")
|