helix.fhir.client.sdk 4.1.67__py3-none-any.whl → 4.2.18__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.
Files changed (33) hide show
  1. helix_fhir_client_sdk/dictionary_parser.py +4 -0
  2. helix_fhir_client_sdk/fhir_auth_mixin.py +17 -10
  3. helix_fhir_client_sdk/fhir_client.py +161 -61
  4. helix_fhir_client_sdk/fhir_delete_mixin.py +62 -45
  5. helix_fhir_client_sdk/fhir_merge_mixin.py +188 -163
  6. helix_fhir_client_sdk/fhir_merge_resources_mixin.py +200 -9
  7. helix_fhir_client_sdk/fhir_patch_mixin.py +97 -81
  8. helix_fhir_client_sdk/fhir_update_mixin.py +71 -54
  9. helix_fhir_client_sdk/graph/simulated_graph_processor_mixin.py +5 -174
  10. helix_fhir_client_sdk/open_telemetry/__init__.py +0 -0
  11. helix_fhir_client_sdk/open_telemetry/attribute_names.py +7 -0
  12. helix_fhir_client_sdk/open_telemetry/span_names.py +12 -0
  13. helix_fhir_client_sdk/queue/request_queue_mixin.py +46 -119
  14. helix_fhir_client_sdk/responses/fhir_client_protocol.py +9 -1
  15. helix_fhir_client_sdk/responses/fhir_response_processor.py +73 -54
  16. helix_fhir_client_sdk/responses/get/fhir_get_bundle_response.py +0 -2
  17. helix_fhir_client_sdk/responses/merge/fhir_merge_resource_response_entry.py +30 -0
  18. helix_fhir_client_sdk/utilities/async_parallel_processor/v1/async_parallel_processor.py +1 -24
  19. helix_fhir_client_sdk/utilities/cache/request_cache.py +32 -43
  20. helix_fhir_client_sdk/utilities/retryable_aiohttp_client.py +184 -144
  21. helix_fhir_client_sdk/utilities/retryable_aiohttp_response.py +2 -1
  22. helix_fhir_client_sdk/utilities/url_checker.py +46 -12
  23. helix_fhir_client_sdk/validators/async_fhir_validator.py +3 -0
  24. helix_fhir_client_sdk-4.2.18.dist-info/METADATA +200 -0
  25. {helix_fhir_client_sdk-4.1.67.dist-info → helix_fhir_client_sdk-4.2.18.dist-info}/RECORD +32 -25
  26. tests/async/test_benchmark_compress.py +448 -0
  27. tests/async/test_benchmark_merge.py +506 -0
  28. tests/async/test_retryable_client_session_management.py +159 -0
  29. tests/test_fhir_client_clone.py +155 -0
  30. helix_fhir_client_sdk-4.1.67.dist-info/METADATA +0 -115
  31. {helix_fhir_client_sdk-4.1.67.dist-info → helix_fhir_client_sdk-4.2.18.dist-info}/WHEEL +0 -0
  32. {helix_fhir_client_sdk-4.1.67.dist-info → helix_fhir_client_sdk-4.2.18.dist-info}/licenses/LICENSE +0 -0
  33. {helix_fhir_client_sdk-4.1.67.dist-info → helix_fhir_client_sdk-4.2.18.dist-info}/top_level.txt +0 -0
@@ -52,6 +52,10 @@ class DictionaryParser:
52
52
  # Iterate through the list, try a .get(part) on each iteration for part (remove the [x] first),
53
53
  # and create a new list of the values in result
54
54
  elif part.endswith("[x]") and isinstance(result, list):
55
+ if result and isinstance(result[0], list):
56
+ # result is a list of lists - flatten it first
57
+ result = DictionaryParser.flatten(result)
58
+
55
59
  result = [value for result_entry in result if (value := result_entry.get(part[:-3]))]
56
60
 
57
61
  # part is looking for a repeating field, but result is currently a dict.
@@ -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
- refresh_token_result: RefreshTokenResult = await self._refresh_token_function(
124
- url=None, status_code=0, current_token=None, expiry_date=None, retry_count=0
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=lambda: self.create_http_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=lambda: self.create_http_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
- self._storage_mode = CompressedDictStorageMode(storage_type="raw")
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
- self._create_operation_outcome_for_error = False
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 compress flag
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 the response
469
+ :param compress: whether to compress HTTP request body (default: True)
456
470
  """
457
471
  self._compress = compress
458
472
  return self
@@ -466,6 +480,50 @@ class FhirClient(
466
480
  self._throw_exception_on_error = throw_exception_on_error
467
481
  return self
468
482
 
483
+ def use_http_session(self, fn_create_http_session: Callable[[], ClientSession] | None) -> FhirClient:
484
+ """
485
+ Sets a custom callable to create HTTP sessions.
486
+
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.
490
+
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.
494
+
495
+ Example with a persistent session for connection reuse (~4× performance boost):
496
+
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
+ )
513
+
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
523
+ """
524
+ self._fn_create_http_session = fn_create_http_session
525
+ return self
526
+
469
527
  # noinspection PyUnusedLocal
470
528
  @staticmethod
471
529
  async def on_request_end(
@@ -579,33 +637,34 @@ class FhirClient(
579
637
 
580
638
  :return: response
581
639
  """
582
- instance_variables_text = convert_dict_to_str(FhirClientLogger.get_variables_to_log(vars(self)))
583
- if self._logger:
584
- # self._logger.info(f"LOGLEVEL: {self._log_level}")
585
- self._logger.debug(f"parameters: {instance_variables_text}")
586
- else:
587
- self._internal_logger.debug(f"LOGLEVEL (InternalLogger): {self._log_level}")
588
- self._internal_logger.debug(f"parameters: {instance_variables_text}")
589
- ids: list[str] | None = None
590
- if self._id:
591
- ids = self._id if isinstance(self._id, list) else [self._id]
592
- # actually make the request
593
- full_response: FhirGetResponse | None = None
594
- async for response in self._get_with_session_async(
595
- ids=ids,
596
- fn_handle_streaming_chunk=data_chunk_handler,
597
- page_number=None,
598
- id_above=None,
599
- additional_parameters=None,
600
- resource_type=None,
601
- ):
602
- if response:
603
- if full_response:
604
- full_response = full_response.append(response)
605
- else:
606
- full_response = response
607
- assert full_response
608
- return full_response
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
609
668
 
610
669
  async def get_raw_resources_async(
611
670
  self,
@@ -646,27 +705,37 @@ class FhirClient(
646
705
 
647
706
  :return: async generator of responses
648
707
  """
649
- instance_variables_text = convert_dict_to_str(FhirClientLogger.get_variables_to_log(vars(self)))
650
- if self._logger:
651
- # self._logger.info(f"LOGLEVEL: {self._log_level}")
652
- self._logger.info(f"parameters: {instance_variables_text}")
653
- else:
654
- self._internal_logger.info(f"LOGLEVEL (InternalLogger): {self._log_level}")
655
- self._internal_logger.info(f"parameters: {instance_variables_text}")
656
- ids: list[str] | None = None
657
- if self._id:
658
- ids = self._id if isinstance(self._id, list) else [self._id]
659
- # actually make the request
660
- response: FhirGetResponse | None
661
- async for response in self._get_with_session_async(
662
- ids=ids,
663
- fn_handle_streaming_chunk=data_chunk_handler,
664
- page_number=None,
665
- id_above=None,
666
- additional_parameters=None,
667
- resource_type=None,
668
- ):
669
- yield response
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()
670
739
 
671
740
  def get(self) -> FhirGetResponse:
672
741
  """
@@ -777,8 +846,12 @@ class FhirClient(
777
846
 
778
847
  def create_http_session(self) -> ClientSession:
779
848
  """
780
- Creates an HTTP Session
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.
781
853
 
854
+ Note: If you want to provide your own session factory, use use_http_session() instead.
782
855
  """
783
856
  trace_config = aiohttp.TraceConfig()
784
857
  # trace_config.on_request_start.append(on_request_start)
@@ -801,17 +874,17 @@ class FhirClient(
801
874
  Whether to ask the server to include the total count in the result
802
875
 
803
876
 
804
- :param include_total: whether to include total count
877
+ :param include_total: whether to include the total count
805
878
  """
806
879
  self._include_total = include_total
807
880
  return self
808
881
 
809
882
  def filter(self, filter_: list[BaseFilter]) -> FhirClient:
810
883
  """
811
- Allows adding in a custom filters that derives from BaseFilter
884
+ Allows adding in custom filters that derive from BaseFilter
812
885
 
813
886
 
814
- :param filter_: list of custom filter instances that derives from BaseFilter.
887
+ :param filter_: list of custom filter instances that derive from BaseFilter.
815
888
  """
816
889
  assert isinstance(filter_, list), "This function requires a list"
817
890
  self._filters.extend(filter_)
@@ -852,6 +925,8 @@ class FhirClient(
852
925
  fhir_client._client_id = self._client_id
853
926
  fhir_client._auth_server_url = self._auth_server_url
854
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
855
930
  fhir_client._refresh_token_function = self._refresh_token_function
856
931
  fhir_client._exclude_status_codes_from_retry = self._exclude_status_codes_from_retry
857
932
  fhir_client._chunk_size = self._chunk_size
@@ -864,6 +939,19 @@ class FhirClient(
864
939
  fhir_client._time_to_live_in_secs_for_cache = self._time_to_live_in_secs_for_cache
865
940
  fhir_client._validation_server_url = self._validation_server_url
866
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)
867
955
  return fhir_client
868
956
 
869
957
  def set_log_all_response_urls(self, value: bool) -> FhirClient:
@@ -886,18 +974,30 @@ class FhirClient(
886
974
 
887
975
  def set_storage_mode(self, value: CompressedDictStorageMode) -> FhirClient:
888
976
  """
889
- 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.
890
990
 
891
- :param value: storage mode
991
+ :param value: storage mode (default: raw)
892
992
  """
893
993
  self._storage_mode = value
894
994
  return self
895
995
 
896
- def set_create_operation_outcome_for_error(self, value: bool) -> FhirClient:
996
+ def set_create_operation_outcome_for_error(self, value: bool | None) -> FhirClient:
897
997
  """
898
998
  Sets the create_operation_outcome_for_error flag
899
999
 
900
- :param value: whether to create operation outcome for error
1000
+ :param value: whether to create an operation outcome for error (True, False, or None)
901
1001
  """
902
1002
  self._create_operation_outcome_for_error = value
903
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,50 +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
- ) as client:
60
- response: RetryableAioHttpResponse = await client.delete(url=full_uri.tostr(), headers=headers)
61
- request_id = response.response_headers.get("X-Request-ID", None)
62
- self._internal_logger.info(f"X-Request-ID={request_id}")
63
- if response.status == 200:
64
- if self._logger:
65
- self._logger.info(f"Successfully deleted: {full_uri}")
66
38
 
67
- return FhirDeleteResponse(
68
- request_id=request_id,
69
- url=full_uri.tostr(),
70
- responses=await response.get_text_async(),
71
- error=f"{response.status}" if not response.status == 200 else None,
72
- access_token=access_token,
73
- status=response.status,
74
- resource_type=self._resource,
75
- )
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
76
92
 
77
93
  def delete(self) -> FhirDeleteResponse:
78
94
  """
@@ -114,7 +130,8 @@ class FhirDeleteMixin(FhirClientProtocol):
114
130
  headers["Authorization"] = f"Bearer {access_token}"
115
131
 
116
132
  async with RetryableAioHttpClient(
117
- fn_get_session=lambda: self.create_http_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,
118
135
  refresh_token_func=self._refresh_token_function,
119
136
  retries=self._retry_count,
120
137
  exclude_status_codes_from_retry=self._exclude_status_codes_from_retry,
@@ -128,7 +145,7 @@ class FhirDeleteMixin(FhirClientProtocol):
128
145
  ) as client:
129
146
  response: RetryableAioHttpResponse = await client.delete(url=full_url, headers=headers)
130
147
  request_id = response.response_headers.get("X-Request-ID", None)
131
- self._internal_logger.info(f"X-Request-ID={request_id}")
148
+ self._internal_logger.debug(f"X-Request-ID={request_id}")
132
149
  if response.status == 200:
133
150
  if self._logger:
134
151
  self._logger.info(f"Successfully deleted: {full_uri}")