helix.fhir.client.sdk 4.2.3__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 (36) hide show
  1. helix_fhir_client_sdk/fhir_auth_mixin.py +17 -10
  2. helix_fhir_client_sdk/fhir_client.py +152 -79
  3. helix_fhir_client_sdk/fhir_delete_mixin.py +62 -48
  4. helix_fhir_client_sdk/fhir_merge_mixin.py +188 -166
  5. helix_fhir_client_sdk/fhir_merge_resources_mixin.py +200 -15
  6. helix_fhir_client_sdk/fhir_patch_mixin.py +97 -84
  7. helix_fhir_client_sdk/fhir_update_mixin.py +71 -57
  8. helix_fhir_client_sdk/graph/simulated_graph_processor_mixin.py +0 -24
  9. helix_fhir_client_sdk/open_telemetry/__init__.py +0 -0
  10. helix_fhir_client_sdk/open_telemetry/attribute_names.py +7 -0
  11. helix_fhir_client_sdk/open_telemetry/span_names.py +12 -0
  12. helix_fhir_client_sdk/queue/request_queue_mixin.py +17 -12
  13. helix_fhir_client_sdk/responses/fhir_client_protocol.py +10 -6
  14. helix_fhir_client_sdk/responses/fhir_get_response.py +1 -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 +1 -1
  17. helix_fhir_client_sdk/responses/get/fhir_get_error_response.py +0 -1
  18. helix_fhir_client_sdk/responses/get/fhir_get_list_by_resource_type_response.py +1 -1
  19. helix_fhir_client_sdk/responses/get/fhir_get_list_response.py +1 -1
  20. helix_fhir_client_sdk/responses/get/fhir_get_response_factory.py +0 -1
  21. helix_fhir_client_sdk/responses/get/fhir_get_single_response.py +1 -1
  22. helix_fhir_client_sdk/responses/merge/fhir_merge_resource_response_entry.py +30 -0
  23. helix_fhir_client_sdk/utilities/cache/request_cache.py +32 -43
  24. helix_fhir_client_sdk/utilities/retryable_aiohttp_client.py +185 -154
  25. helix_fhir_client_sdk/utilities/retryable_aiohttp_response.py +2 -1
  26. helix_fhir_client_sdk/validators/async_fhir_validator.py +3 -0
  27. helix_fhir_client_sdk-4.2.18.dist-info/METADATA +200 -0
  28. {helix_fhir_client_sdk-4.2.3.dist-info → helix_fhir_client_sdk-4.2.18.dist-info}/RECORD +35 -28
  29. tests/async/test_benchmark_compress.py +448 -0
  30. tests/async/test_benchmark_merge.py +506 -0
  31. tests/async/test_retryable_client_session_management.py +159 -0
  32. tests/test_fhir_client_clone.py +155 -0
  33. helix_fhir_client_sdk-4.2.3.dist-info/METADATA +0 -115
  34. {helix_fhir_client_sdk-4.2.3.dist-info → helix_fhir_client_sdk-4.2.18.dist-info}/WHEEL +0 -0
  35. {helix_fhir_client_sdk-4.2.3.dist-info → helix_fhir_client_sdk-4.2.18.dist-info}/licenses/LICENSE +0 -0
  36. {helix_fhir_client_sdk-4.2.3.dist-info → helix_fhir_client_sdk-4.2.18.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
- 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,31 +480,48 @@ class FhirClient(
466
480
  self._throw_exception_on_error = throw_exception_on_error
467
481
  return self
468
482
 
469
- def set_persistent_session(self, session: ClientSession | None) -> FhirClient:
483
+ def use_http_session(self, fn_create_http_session: Callable[[], ClientSession] | None) -> FhirClient:
470
484
  """
471
- Sets the persistent session to use for requests
485
+ Sets a custom callable to create HTTP sessions.
472
486
 
473
- :param session: persistent session
474
- """
475
- self._persistent_session = session
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
- def use_persistent_session(self, value: bool) -> FhirClient:
479
- """
480
- Sets the use_persistent_session flag
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
- :param value: whether to use the persistent session
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
- def close_session_after_request(self, value: bool) -> FhirClient:
488
- """
489
- Sets the _close_session flag (default is True)
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
- :param value: whether to close the session after each request
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._close_session = value
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
- instance_variables_text = convert_dict_to_str(FhirClientLogger.get_variables_to_log(vars(self)))
610
- if self._logger:
611
- # self._logger.info(f"LOGLEVEL: {self._log_level}")
612
- self._logger.debug(f"parameters: {instance_variables_text}")
613
- else:
614
- self._internal_logger.debug(f"LOGLEVEL (InternalLogger): {self._log_level}")
615
- self._internal_logger.debug(f"parameters: {instance_variables_text}")
616
- ids: list[str] | None = None
617
- if self._id:
618
- ids = self._id if isinstance(self._id, list) else [self._id]
619
- # actually make the request
620
- full_response: FhirGetResponse | None = None
621
- async for response in self._get_with_session_async(
622
- ids=ids,
623
- fn_handle_streaming_chunk=data_chunk_handler,
624
- page_number=None,
625
- id_above=None,
626
- additional_parameters=None,
627
- resource_type=None,
628
- ):
629
- if response:
630
- if full_response:
631
- full_response = full_response.append(response)
632
- else:
633
- full_response = response
634
- assert full_response
635
- 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
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
- instance_variables_text = convert_dict_to_str(FhirClientLogger.get_variables_to_log(vars(self)))
677
- if self._logger:
678
- # self._logger.info(f"LOGLEVEL: {self._log_level}")
679
- self._logger.info(f"parameters: {instance_variables_text}")
680
- else:
681
- self._internal_logger.info(f"LOGLEVEL (InternalLogger): {self._log_level}")
682
- self._internal_logger.info(f"parameters: {instance_variables_text}")
683
- ids: list[str] | None = None
684
- if self._id:
685
- ids = self._id if isinstance(self._id, list) else [self._id]
686
- # actually make the request
687
- response: FhirGetResponse | None
688
- async for response in self._get_with_session_async(
689
- ids=ids,
690
- fn_handle_streaming_chunk=data_chunk_handler,
691
- page_number=None,
692
- id_above=None,
693
- additional_parameters=None,
694
- resource_type=None,
695
- ):
696
- 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()
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 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.
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 a custom filters that derives from BaseFilter
884
+ Allows adding in custom filters that derive from BaseFilter
839
885
 
840
886
 
841
- :param filter_: list of custom filter instances that derives from BaseFilter.
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
- return FhirDeleteResponse(
71
- request_id=request_id,
72
- url=full_uri.tostr(),
73
- responses=await response.get_text_async(),
74
- error=f"{response.status}" if not response.status == 200 else None,
75
- access_token=access_token,
76
- status=response.status,
77
- resource_type=self._resource,
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=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,
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.info(f"X-Request-ID={request_id}")
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}")