airbyte-cdk 6.5.3rc2__py3-none-any.whl → 6.6.0__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 (200) hide show
  1. airbyte_cdk/__init__.py +17 -2
  2. airbyte_cdk/config_observation.py +10 -3
  3. airbyte_cdk/connector.py +19 -9
  4. airbyte_cdk/connector_builder/connector_builder_handler.py +28 -8
  5. airbyte_cdk/connector_builder/main.py +26 -6
  6. airbyte_cdk/connector_builder/message_grouper.py +95 -25
  7. airbyte_cdk/destinations/destination.py +47 -14
  8. airbyte_cdk/destinations/vector_db_based/config.py +36 -14
  9. airbyte_cdk/destinations/vector_db_based/document_processor.py +49 -11
  10. airbyte_cdk/destinations/vector_db_based/embedder.py +52 -11
  11. airbyte_cdk/destinations/vector_db_based/test_utils.py +14 -4
  12. airbyte_cdk/destinations/vector_db_based/utils.py +8 -2
  13. airbyte_cdk/destinations/vector_db_based/writer.py +15 -4
  14. airbyte_cdk/entrypoint.py +82 -26
  15. airbyte_cdk/exception_handler.py +13 -3
  16. airbyte_cdk/logger.py +10 -2
  17. airbyte_cdk/models/airbyte_protocol.py +11 -5
  18. airbyte_cdk/models/airbyte_protocol_serializers.py +9 -3
  19. airbyte_cdk/models/well_known_types.py +1 -1
  20. airbyte_cdk/sources/abstract_source.py +63 -17
  21. airbyte_cdk/sources/concurrent_source/concurrent_read_processor.py +47 -14
  22. airbyte_cdk/sources/concurrent_source/concurrent_source.py +25 -7
  23. airbyte_cdk/sources/concurrent_source/concurrent_source_adapter.py +27 -6
  24. airbyte_cdk/sources/concurrent_source/thread_pool_manager.py +9 -3
  25. airbyte_cdk/sources/connector_state_manager.py +32 -10
  26. airbyte_cdk/sources/declarative/async_job/job.py +3 -1
  27. airbyte_cdk/sources/declarative/async_job/job_orchestrator.py +68 -14
  28. airbyte_cdk/sources/declarative/async_job/job_tracker.py +24 -6
  29. airbyte_cdk/sources/declarative/async_job/repository.py +3 -1
  30. airbyte_cdk/sources/declarative/auth/declarative_authenticator.py +3 -1
  31. airbyte_cdk/sources/declarative/auth/jwt.py +27 -7
  32. airbyte_cdk/sources/declarative/auth/oauth.py +35 -11
  33. airbyte_cdk/sources/declarative/auth/selective_authenticator.py +3 -1
  34. airbyte_cdk/sources/declarative/auth/token.py +25 -8
  35. airbyte_cdk/sources/declarative/checks/check_stream.py +12 -4
  36. airbyte_cdk/sources/declarative/checks/connection_checker.py +3 -1
  37. airbyte_cdk/sources/declarative/concurrency_level/concurrency_level.py +11 -3
  38. airbyte_cdk/sources/declarative/concurrent_declarative_source.py +106 -50
  39. airbyte_cdk/sources/declarative/datetime/min_max_datetime.py +20 -6
  40. airbyte_cdk/sources/declarative/declarative_component_schema.yaml +43 -0
  41. airbyte_cdk/sources/declarative/declarative_source.py +3 -1
  42. airbyte_cdk/sources/declarative/declarative_stream.py +27 -6
  43. airbyte_cdk/sources/declarative/decoders/__init__.py +2 -2
  44. airbyte_cdk/sources/declarative/decoders/decoder.py +3 -1
  45. airbyte_cdk/sources/declarative/decoders/json_decoder.py +48 -13
  46. airbyte_cdk/sources/declarative/decoders/pagination_decoder_decorator.py +3 -1
  47. airbyte_cdk/sources/declarative/decoders/xml_decoder.py +6 -2
  48. airbyte_cdk/sources/declarative/extractors/dpath_extractor.py +6 -2
  49. airbyte_cdk/sources/declarative/extractors/record_filter.py +24 -7
  50. airbyte_cdk/sources/declarative/extractors/record_selector.py +10 -3
  51. airbyte_cdk/sources/declarative/extractors/response_to_file_extractor.py +15 -5
  52. airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py +96 -31
  53. airbyte_cdk/sources/declarative/incremental/global_substream_cursor.py +22 -8
  54. airbyte_cdk/sources/declarative/incremental/per_partition_cursor.py +46 -15
  55. airbyte_cdk/sources/declarative/incremental/per_partition_with_global.py +19 -5
  56. airbyte_cdk/sources/declarative/incremental/resumable_full_refresh_cursor.py +3 -1
  57. airbyte_cdk/sources/declarative/interpolation/interpolated_boolean.py +20 -2
  58. airbyte_cdk/sources/declarative/interpolation/interpolated_mapping.py +5 -1
  59. airbyte_cdk/sources/declarative/interpolation/interpolated_nested_mapping.py +10 -3
  60. airbyte_cdk/sources/declarative/interpolation/interpolated_string.py +6 -2
  61. airbyte_cdk/sources/declarative/interpolation/interpolation.py +7 -1
  62. airbyte_cdk/sources/declarative/interpolation/jinja.py +6 -2
  63. airbyte_cdk/sources/declarative/interpolation/macros.py +19 -4
  64. airbyte_cdk/sources/declarative/manifest_declarative_source.py +106 -24
  65. airbyte_cdk/sources/declarative/migrations/legacy_to_per_partition_state_migration.py +14 -5
  66. airbyte_cdk/sources/declarative/models/declarative_component_schema.py +697 -678
  67. airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py +13 -4
  68. airbyte_cdk/sources/declarative/parsers/manifest_reference_resolver.py +9 -2
  69. airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +802 -232
  70. airbyte_cdk/sources/declarative/partition_routers/cartesian_product_stream_slicer.py +29 -7
  71. airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py +25 -7
  72. airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py +54 -15
  73. airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/constant_backoff_strategy.py +6 -2
  74. airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/header_helper.py +3 -1
  75. airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_time_from_header_backoff_strategy.py +17 -5
  76. airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_until_time_from_header_backoff_strategy.py +15 -5
  77. airbyte_cdk/sources/declarative/requesters/error_handlers/composite_error_handler.py +3 -1
  78. airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py +18 -8
  79. airbyte_cdk/sources/declarative/requesters/error_handlers/default_http_response_filter.py +16 -7
  80. airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py +51 -14
  81. airbyte_cdk/sources/declarative/requesters/http_job_repository.py +29 -8
  82. airbyte_cdk/sources/declarative/requesters/http_requester.py +58 -16
  83. airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +49 -14
  84. airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py +3 -1
  85. airbyte_cdk/sources/declarative/requesters/paginators/paginator.py +3 -1
  86. airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py +17 -5
  87. airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py +24 -7
  88. airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py +9 -3
  89. airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py +3 -1
  90. airbyte_cdk/sources/declarative/requesters/paginators/strategies/stop_condition.py +6 -2
  91. airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py +19 -6
  92. airbyte_cdk/sources/declarative/requesters/request_options/default_request_options_provider.py +3 -1
  93. airbyte_cdk/sources/declarative/requesters/request_options/interpolated_nested_request_input_provider.py +21 -7
  94. airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_input_provider.py +18 -6
  95. airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py +27 -8
  96. airbyte_cdk/sources/declarative/requesters/requester.py +3 -1
  97. airbyte_cdk/sources/declarative/retrievers/async_retriever.py +12 -5
  98. airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +105 -24
  99. airbyte_cdk/sources/declarative/schema/default_schema_loader.py +3 -1
  100. airbyte_cdk/sources/declarative/spec/spec.py +8 -2
  101. airbyte_cdk/sources/declarative/stream_slicers/stream_slicer.py +3 -1
  102. airbyte_cdk/sources/declarative/transformations/add_fields.py +12 -3
  103. airbyte_cdk/sources/declarative/transformations/remove_fields.py +6 -2
  104. airbyte_cdk/sources/declarative/types.py +8 -1
  105. airbyte_cdk/sources/declarative/yaml_declarative_source.py +3 -1
  106. airbyte_cdk/sources/embedded/base_integration.py +14 -4
  107. airbyte_cdk/sources/embedded/catalog.py +16 -4
  108. airbyte_cdk/sources/embedded/runner.py +19 -3
  109. airbyte_cdk/sources/embedded/tools.py +3 -1
  110. airbyte_cdk/sources/file_based/availability_strategy/abstract_file_based_availability_strategy.py +12 -4
  111. airbyte_cdk/sources/file_based/availability_strategy/default_file_based_availability_strategy.py +27 -7
  112. airbyte_cdk/sources/file_based/config/abstract_file_based_spec.py +12 -6
  113. airbyte_cdk/sources/file_based/config/csv_format.py +21 -9
  114. airbyte_cdk/sources/file_based/config/file_based_stream_config.py +6 -2
  115. airbyte_cdk/sources/file_based/config/unstructured_format.py +10 -3
  116. airbyte_cdk/sources/file_based/discovery_policy/abstract_discovery_policy.py +2 -4
  117. airbyte_cdk/sources/file_based/discovery_policy/default_discovery_policy.py +7 -2
  118. airbyte_cdk/sources/file_based/exceptions.py +13 -15
  119. airbyte_cdk/sources/file_based/file_based_source.py +82 -24
  120. airbyte_cdk/sources/file_based/file_based_stream_reader.py +16 -5
  121. airbyte_cdk/sources/file_based/file_types/avro_parser.py +58 -17
  122. airbyte_cdk/sources/file_based/file_types/csv_parser.py +89 -26
  123. airbyte_cdk/sources/file_based/file_types/excel_parser.py +25 -7
  124. airbyte_cdk/sources/file_based/file_types/file_transfer.py +8 -2
  125. airbyte_cdk/sources/file_based/file_types/file_type_parser.py +4 -1
  126. airbyte_cdk/sources/file_based/file_types/jsonl_parser.py +20 -6
  127. airbyte_cdk/sources/file_based/file_types/parquet_parser.py +57 -16
  128. airbyte_cdk/sources/file_based/file_types/unstructured_parser.py +64 -15
  129. airbyte_cdk/sources/file_based/schema_helpers.py +33 -10
  130. airbyte_cdk/sources/file_based/schema_validation_policies/abstract_schema_validation_policy.py +3 -1
  131. airbyte_cdk/sources/file_based/schema_validation_policies/default_schema_validation_policies.py +16 -5
  132. airbyte_cdk/sources/file_based/stream/abstract_file_based_stream.py +33 -10
  133. airbyte_cdk/sources/file_based/stream/concurrent/adapters.py +47 -11
  134. airbyte_cdk/sources/file_based/stream/concurrent/cursor/abstract_concurrent_file_based_cursor.py +13 -22
  135. airbyte_cdk/sources/file_based/stream/concurrent/cursor/file_based_concurrent_cursor.py +53 -17
  136. airbyte_cdk/sources/file_based/stream/concurrent/cursor/file_based_final_state_cursor.py +17 -5
  137. airbyte_cdk/sources/file_based/stream/cursor/abstract_file_based_cursor.py +3 -1
  138. airbyte_cdk/sources/file_based/stream/cursor/default_file_based_cursor.py +26 -9
  139. airbyte_cdk/sources/file_based/stream/default_file_based_stream.py +67 -21
  140. airbyte_cdk/sources/http_logger.py +5 -1
  141. airbyte_cdk/sources/message/repository.py +18 -4
  142. airbyte_cdk/sources/source.py +17 -7
  143. airbyte_cdk/sources/streams/availability_strategy.py +9 -3
  144. airbyte_cdk/sources/streams/call_rate.py +63 -19
  145. airbyte_cdk/sources/streams/checkpoint/checkpoint_reader.py +31 -7
  146. airbyte_cdk/sources/streams/checkpoint/substream_resumable_full_refresh_cursor.py +6 -2
  147. airbyte_cdk/sources/streams/concurrent/adapters.py +77 -22
  148. airbyte_cdk/sources/streams/concurrent/cursor.py +56 -20
  149. airbyte_cdk/sources/streams/concurrent/default_stream.py +9 -2
  150. airbyte_cdk/sources/streams/concurrent/helpers.py +6 -2
  151. airbyte_cdk/sources/streams/concurrent/partition_enqueuer.py +9 -2
  152. airbyte_cdk/sources/streams/concurrent/partition_reader.py +4 -1
  153. airbyte_cdk/sources/streams/concurrent/partitions/record.py +10 -2
  154. airbyte_cdk/sources/streams/concurrent/partitions/types.py +6 -2
  155. airbyte_cdk/sources/streams/concurrent/state_converters/abstract_stream_state_converter.py +25 -10
  156. airbyte_cdk/sources/streams/concurrent/state_converters/datetime_stream_state_converter.py +32 -16
  157. airbyte_cdk/sources/streams/core.py +77 -22
  158. airbyte_cdk/sources/streams/http/availability_strategy.py +3 -1
  159. airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py +4 -1
  160. airbyte_cdk/sources/streams/http/error_handlers/error_handler.py +3 -1
  161. airbyte_cdk/sources/streams/http/error_handlers/http_status_error_handler.py +16 -5
  162. airbyte_cdk/sources/streams/http/error_handlers/response_models.py +9 -3
  163. airbyte_cdk/sources/streams/http/exceptions.py +2 -2
  164. airbyte_cdk/sources/streams/http/http.py +133 -33
  165. airbyte_cdk/sources/streams/http/http_client.py +91 -29
  166. airbyte_cdk/sources/streams/http/rate_limiting.py +23 -7
  167. airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +19 -6
  168. airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py +38 -11
  169. airbyte_cdk/sources/streams/http/requests_native_auth/token.py +13 -3
  170. airbyte_cdk/sources/types.py +5 -1
  171. airbyte_cdk/sources/utils/record_helper.py +12 -3
  172. airbyte_cdk/sources/utils/schema_helpers.py +9 -3
  173. airbyte_cdk/sources/utils/slice_logger.py +4 -1
  174. airbyte_cdk/sources/utils/transform.py +24 -9
  175. airbyte_cdk/sql/exceptions.py +19 -6
  176. airbyte_cdk/sql/secrets.py +3 -1
  177. airbyte_cdk/sql/shared/catalog_providers.py +13 -4
  178. airbyte_cdk/sql/shared/sql_processor.py +44 -14
  179. airbyte_cdk/test/catalog_builder.py +19 -8
  180. airbyte_cdk/test/entrypoint_wrapper.py +27 -8
  181. airbyte_cdk/test/mock_http/mocker.py +41 -11
  182. airbyte_cdk/test/mock_http/request.py +9 -3
  183. airbyte_cdk/test/mock_http/response.py +3 -1
  184. airbyte_cdk/test/mock_http/response_builder.py +29 -7
  185. airbyte_cdk/test/state_builder.py +10 -2
  186. airbyte_cdk/test/utils/data.py +6 -2
  187. airbyte_cdk/test/utils/http_mocking.py +3 -1
  188. airbyte_cdk/utils/airbyte_secrets_utils.py +3 -1
  189. airbyte_cdk/utils/analytics_message.py +10 -2
  190. airbyte_cdk/utils/datetime_format_inferrer.py +4 -1
  191. airbyte_cdk/utils/mapping_helpers.py +3 -1
  192. airbyte_cdk/utils/message_utils.py +11 -4
  193. airbyte_cdk/utils/print_buffer.py +6 -1
  194. airbyte_cdk/utils/schema_inferrer.py +30 -9
  195. airbyte_cdk/utils/spec_schema_transformations.py +3 -1
  196. airbyte_cdk/utils/traced_exception.py +35 -9
  197. {airbyte_cdk-6.5.3rc2.dist-info → airbyte_cdk-6.6.0.dist-info}/METADATA +8 -7
  198. {airbyte_cdk-6.5.3rc2.dist-info → airbyte_cdk-6.6.0.dist-info}/RECORD +200 -200
  199. {airbyte_cdk-6.5.3rc2.dist-info → airbyte_cdk-6.6.0.dist-info}/LICENSE.txt +0 -0
  200. {airbyte_cdk-6.5.3rc2.dist-info → airbyte_cdk-6.6.0.dist-info}/WHEEL +0 -0
@@ -10,11 +10,15 @@ from dataclasses import InitVar, dataclass
10
10
  from typing import Any, Iterable, List, Mapping, Optional
11
11
 
12
12
  from airbyte_cdk.sources.declarative.partition_routers.partition_router import PartitionRouter
13
- from airbyte_cdk.sources.declarative.partition_routers.substream_partition_router import SubstreamPartitionRouter
13
+ from airbyte_cdk.sources.declarative.partition_routers.substream_partition_router import (
14
+ SubstreamPartitionRouter,
15
+ )
14
16
  from airbyte_cdk.sources.types import StreamSlice, StreamState
15
17
 
16
18
 
17
- def check_for_substream_in_slicers(slicers: Iterable[PartitionRouter], log_warning: Callable[[str], None]) -> None:
19
+ def check_for_substream_in_slicers(
20
+ slicers: Iterable[PartitionRouter], log_warning: Callable[[str], None]
21
+ ) -> None:
18
22
  """
19
23
  Recursively checks for the presence of SubstreamPartitionRouter within slicers.
20
24
  Logs a warning if a SubstreamPartitionRouter is found within a CartesianProductStreamSlicer.
@@ -69,7 +73,11 @@ class CartesianProductStreamSlicer(PartitionRouter):
69
73
  return dict(
70
74
  ChainMap(
71
75
  *[ # type: ignore # ChainMap expects a MutableMapping[Never, Never] for reasons
72
- s.get_request_params(stream_state=stream_state, stream_slice=stream_slice, next_page_token=next_page_token)
76
+ s.get_request_params(
77
+ stream_state=stream_state,
78
+ stream_slice=stream_slice,
79
+ next_page_token=next_page_token,
80
+ )
73
81
  for s in self.stream_slicers
74
82
  ]
75
83
  )
@@ -85,7 +93,11 @@ class CartesianProductStreamSlicer(PartitionRouter):
85
93
  return dict(
86
94
  ChainMap(
87
95
  *[ # type: ignore # ChainMap expects a MutableMapping[Never, Never] for reasons
88
- s.get_request_headers(stream_state=stream_state, stream_slice=stream_slice, next_page_token=next_page_token)
96
+ s.get_request_headers(
97
+ stream_state=stream_state,
98
+ stream_slice=stream_slice,
99
+ next_page_token=next_page_token,
100
+ )
89
101
  for s in self.stream_slicers
90
102
  ]
91
103
  )
@@ -101,7 +113,11 @@ class CartesianProductStreamSlicer(PartitionRouter):
101
113
  return dict(
102
114
  ChainMap(
103
115
  *[ # type: ignore # ChainMap expects a MutableMapping[Never, Never] for reasons
104
- s.get_request_body_data(stream_state=stream_state, stream_slice=stream_slice, next_page_token=next_page_token)
116
+ s.get_request_body_data(
117
+ stream_state=stream_state,
118
+ stream_slice=stream_slice,
119
+ next_page_token=next_page_token,
120
+ )
105
121
  for s in self.stream_slicers
106
122
  ]
107
123
  )
@@ -117,7 +133,11 @@ class CartesianProductStreamSlicer(PartitionRouter):
117
133
  return dict(
118
134
  ChainMap(
119
135
  *[ # type: ignore # ChainMap expects a MutableMapping[Never, Never] for reasons
120
- s.get_request_body_json(stream_state=stream_state, stream_slice=stream_slice, next_page_token=next_page_token)
136
+ s.get_request_body_json(
137
+ stream_state=stream_state,
138
+ stream_slice=stream_slice,
139
+ next_page_token=next_page_token,
140
+ )
121
141
  for s in self.stream_slicers
122
142
  ]
123
143
  )
@@ -130,7 +150,9 @@ class CartesianProductStreamSlicer(PartitionRouter):
130
150
  partition = dict(ChainMap(*[s.partition for s in stream_slice_tuple])) # type: ignore # ChainMap expects a MutableMapping[Never, Never] for reasons
131
151
  cursor_slices = [s.cursor_slice for s in stream_slice_tuple if s.cursor_slice]
132
152
  if len(cursor_slices) > 1:
133
- raise ValueError(f"There should only be a single cursor slice. Found {cursor_slices}")
153
+ raise ValueError(
154
+ f"There should only be a single cursor slice. Found {cursor_slices}"
155
+ )
134
156
  if cursor_slices:
135
157
  cursor_slice = cursor_slices[0]
136
158
  else:
@@ -7,7 +7,10 @@ from typing import Any, Iterable, List, Mapping, Optional, Union
7
7
 
8
8
  from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
9
9
  from airbyte_cdk.sources.declarative.partition_routers.partition_router import PartitionRouter
10
- from airbyte_cdk.sources.declarative.requesters.request_option import RequestOption, RequestOptionType
10
+ from airbyte_cdk.sources.declarative.requesters.request_option import (
11
+ RequestOption,
12
+ RequestOptionType,
13
+ )
11
14
  from airbyte_cdk.sources.types import Config, StreamSlice, StreamState
12
15
 
13
16
 
@@ -32,9 +35,13 @@ class ListPartitionRouter(PartitionRouter):
32
35
 
33
36
  def __post_init__(self, parameters: Mapping[str, Any]) -> None:
34
37
  if isinstance(self.values, str):
35
- self.values = InterpolatedString.create(self.values, parameters=parameters).eval(self.config)
38
+ self.values = InterpolatedString.create(self.values, parameters=parameters).eval(
39
+ self.config
40
+ )
36
41
  self._cursor_field = (
37
- InterpolatedString(string=self.cursor_field, parameters=parameters) if isinstance(self.cursor_field, str) else self.cursor_field
42
+ InterpolatedString(string=self.cursor_field, parameters=parameters)
43
+ if isinstance(self.cursor_field, str)
44
+ else self.cursor_field
38
45
  )
39
46
 
40
47
  self._cursor = None
@@ -76,10 +83,21 @@ class ListPartitionRouter(PartitionRouter):
76
83
  return self._get_request_option(RequestOptionType.body_json, stream_slice)
77
84
 
78
85
  def stream_slices(self) -> Iterable[StreamSlice]:
79
- return [StreamSlice(partition={self._cursor_field.eval(self.config): slice_value}, cursor_slice={}) for slice_value in self.values]
80
-
81
- def _get_request_option(self, request_option_type: RequestOptionType, stream_slice: Optional[StreamSlice]) -> Mapping[str, Any]:
82
- if self.request_option and self.request_option.inject_into == request_option_type and stream_slice:
86
+ return [
87
+ StreamSlice(
88
+ partition={self._cursor_field.eval(self.config): slice_value}, cursor_slice={}
89
+ )
90
+ for slice_value in self.values
91
+ ]
92
+
93
+ def _get_request_option(
94
+ self, request_option_type: RequestOptionType, stream_slice: Optional[StreamSlice]
95
+ ) -> Mapping[str, Any]:
96
+ if (
97
+ self.request_option
98
+ and self.request_option.inject_into == request_option_type
99
+ and stream_slice
100
+ ):
83
101
  slice_value = stream_slice.get(self._cursor_field.eval(self.config))
84
102
  if slice_value:
85
103
  return {self.request_option.field_name.eval(self.config): slice_value} # type: ignore # field_name is always casted to InterpolatedString
@@ -11,7 +11,10 @@ from airbyte_cdk.models import AirbyteMessage
11
11
  from airbyte_cdk.models import Type as MessageType
12
12
  from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
13
13
  from airbyte_cdk.sources.declarative.partition_routers.partition_router import PartitionRouter
14
- from airbyte_cdk.sources.declarative.requesters.request_option import RequestOption, RequestOptionType
14
+ from airbyte_cdk.sources.declarative.requesters.request_option import (
15
+ RequestOption,
16
+ RequestOptionType,
17
+ )
15
18
  from airbyte_cdk.sources.types import Config, Record, StreamSlice, StreamState
16
19
  from airbyte_cdk.utils import AirbyteTracedException
17
20
 
@@ -37,17 +40,22 @@ class ParentStreamConfig:
37
40
  partition_field: Union[InterpolatedString, str]
38
41
  config: Config
39
42
  parameters: InitVar[Mapping[str, Any]]
40
- extra_fields: Optional[Union[List[List[str]], List[List[InterpolatedString]]]] = None # List of field paths (arrays of strings)
43
+ extra_fields: Optional[Union[List[List[str]], List[List[InterpolatedString]]]] = (
44
+ None # List of field paths (arrays of strings)
45
+ )
41
46
  request_option: Optional[RequestOption] = None
42
47
  incremental_dependency: bool = False
43
48
 
44
49
  def __post_init__(self, parameters: Mapping[str, Any]) -> None:
45
50
  self.parent_key = InterpolatedString.create(self.parent_key, parameters=parameters)
46
- self.partition_field = InterpolatedString.create(self.partition_field, parameters=parameters)
51
+ self.partition_field = InterpolatedString.create(
52
+ self.partition_field, parameters=parameters
53
+ )
47
54
  if self.extra_fields:
48
55
  # Create InterpolatedString for each field path in extra_keys
49
56
  self.extra_fields = [
50
- [InterpolatedString.create(path, parameters=parameters) for path in key_path] for key_path in self.extra_fields
57
+ [InterpolatedString.create(path, parameters=parameters) for path in key_path]
58
+ for key_path in self.extra_fields
51
59
  ]
52
60
 
53
61
 
@@ -106,15 +114,26 @@ class SubstreamPartitionRouter(PartitionRouter):
106
114
  # Pass the stream_slice from the argument, not the cursor because the cursor is updated after processing the response
107
115
  return self._get_request_option(RequestOptionType.body_json, stream_slice)
108
116
 
109
- def _get_request_option(self, option_type: RequestOptionType, stream_slice: Optional[StreamSlice]) -> Mapping[str, Any]:
117
+ def _get_request_option(
118
+ self, option_type: RequestOptionType, stream_slice: Optional[StreamSlice]
119
+ ) -> Mapping[str, Any]:
110
120
  params = {}
111
121
  if stream_slice:
112
122
  for parent_config in self.parent_stream_configs:
113
- if parent_config.request_option and parent_config.request_option.inject_into == option_type:
123
+ if (
124
+ parent_config.request_option
125
+ and parent_config.request_option.inject_into == option_type
126
+ ):
114
127
  key = parent_config.partition_field.eval(self.config) # type: ignore # partition_field is always casted to an interpolated string
115
128
  value = stream_slice.get(key)
116
129
  if value:
117
- params.update({parent_config.request_option.field_name.eval(config=self.config): value}) # type: ignore # field_name is always casted to an interpolated string
130
+ params.update(
131
+ {
132
+ parent_config.request_option.field_name.eval(
133
+ config=self.config
134
+ ): value
135
+ }
136
+ ) # type: ignore # field_name is always casted to an interpolated string
118
137
  return params
119
138
 
120
139
  def stream_slices(self) -> Iterable[StreamSlice]:
@@ -141,7 +160,10 @@ class SubstreamPartitionRouter(PartitionRouter):
141
160
  partition_field = parent_stream_config.partition_field.eval(self.config) # type: ignore # partition_field is always casted to an interpolated string
142
161
  extra_fields = None
143
162
  if parent_stream_config.extra_fields:
144
- extra_fields = [[field_path_part.eval(self.config) for field_path_part in field_path] for field_path in parent_stream_config.extra_fields] # type: ignore # extra_fields is always casted to an interpolated string
163
+ extra_fields = [
164
+ [field_path_part.eval(self.config) for field_path_part in field_path]
165
+ for field_path in parent_stream_config.extra_fields
166
+ ] # type: ignore # extra_fields is always casted to an interpolated string
145
167
 
146
168
  # read_stateless() assumes the parent is not concurrent. This is currently okay since the concurrent CDK does
147
169
  # not support either substreams or RFR, but something that needs to be considered once we do
@@ -157,11 +179,17 @@ class SubstreamPartitionRouter(PartitionRouter):
157
179
  else:
158
180
  continue
159
181
  elif isinstance(parent_record, Record):
160
- parent_partition = parent_record.associated_slice.partition if parent_record.associated_slice else {}
182
+ parent_partition = (
183
+ parent_record.associated_slice.partition
184
+ if parent_record.associated_slice
185
+ else {}
186
+ )
161
187
  parent_record = parent_record.data
162
188
  elif not isinstance(parent_record, Mapping):
163
189
  # The parent_record should only take the form of a Record, AirbyteMessage, or Mapping. Anything else is invalid
164
- raise AirbyteTracedException(message=f"Parent stream returned records as invalid type {type(parent_record)}")
190
+ raise AirbyteTracedException(
191
+ message=f"Parent stream returned records as invalid type {type(parent_record)}"
192
+ )
165
193
  try:
166
194
  partition_value = dpath.get(parent_record, parent_field)
167
195
  except KeyError:
@@ -171,13 +199,18 @@ class SubstreamPartitionRouter(PartitionRouter):
171
199
  extracted_extra_fields = self._extract_extra_fields(parent_record, extra_fields)
172
200
 
173
201
  yield StreamSlice(
174
- partition={partition_field: partition_value, "parent_slice": parent_partition or {}},
202
+ partition={
203
+ partition_field: partition_value,
204
+ "parent_slice": parent_partition or {},
205
+ },
175
206
  cursor_slice={},
176
207
  extra_fields=extracted_extra_fields,
177
208
  )
178
209
 
179
210
  def _extract_extra_fields(
180
- self, parent_record: Mapping[str, Any] | AirbyteMessage, extra_fields: Optional[List[List[str]]] = None
211
+ self,
212
+ parent_record: Mapping[str, Any] | AirbyteMessage,
213
+ extra_fields: Optional[List[List[str]]] = None,
181
214
  ) -> Mapping[str, Any]:
182
215
  """
183
216
  Extracts additional fields specified by their paths from the parent record.
@@ -195,7 +228,9 @@ class SubstreamPartitionRouter(PartitionRouter):
195
228
  for extra_field_path in extra_fields:
196
229
  try:
197
230
  extra_field_value = dpath.get(parent_record, extra_field_path)
198
- self.logger.debug(f"Extracted extra_field_path: {extra_field_path} with value: {extra_field_value}")
231
+ self.logger.debug(
232
+ f"Extracted extra_field_path: {extra_field_path} with value: {extra_field_value}"
233
+ )
199
234
  except KeyError:
200
235
  self.logger.debug(f"Failed to extract extra_field_path: {extra_field_path}")
201
236
  extra_field_value = None
@@ -246,7 +281,9 @@ class SubstreamPartitionRouter(PartitionRouter):
246
281
 
247
282
  # If `parent_state` doesn't exist and at least one parent stream has an incremental dependency,
248
283
  # copy the child state to parent streams with incremental dependencies.
249
- incremental_dependency = any([parent_config.incremental_dependency for parent_config in self.parent_stream_configs])
284
+ incremental_dependency = any(
285
+ [parent_config.incremental_dependency for parent_config in self.parent_stream_configs]
286
+ )
250
287
  if not parent_state and not incremental_dependency:
251
288
  return
252
289
 
@@ -260,7 +297,9 @@ class SubstreamPartitionRouter(PartitionRouter):
260
297
  if substream_state:
261
298
  for parent_config in self.parent_stream_configs:
262
299
  if parent_config.incremental_dependency:
263
- parent_state[parent_config.stream.name] = {parent_config.stream.cursor_field: substream_state}
300
+ parent_state[parent_config.stream.name] = {
301
+ parent_config.stream.cursor_field: substream_state
302
+ }
264
303
 
265
304
  # Set state for each parent stream with an incremental dependency
266
305
  for parent_config in self.parent_stream_configs:
@@ -28,9 +28,13 @@ class ConstantBackoffStrategy(BackoffStrategy):
28
28
  if not isinstance(self.backoff_time_in_seconds, InterpolatedString):
29
29
  self.backoff_time_in_seconds = str(self.backoff_time_in_seconds)
30
30
  if isinstance(self.backoff_time_in_seconds, float):
31
- self.backoff_time_in_seconds = InterpolatedString.create(str(self.backoff_time_in_seconds), parameters=parameters)
31
+ self.backoff_time_in_seconds = InterpolatedString.create(
32
+ str(self.backoff_time_in_seconds), parameters=parameters
33
+ )
32
34
  else:
33
- self.backoff_time_in_seconds = InterpolatedString.create(self.backoff_time_in_seconds, parameters=parameters)
35
+ self.backoff_time_in_seconds = InterpolatedString.create(
36
+ self.backoff_time_in_seconds, parameters=parameters
37
+ )
34
38
 
35
39
  def backoff_time(
36
40
  self,
@@ -9,7 +9,9 @@ from typing import Optional
9
9
  import requests
10
10
 
11
11
 
12
- def get_numeric_value_from_header(response: requests.Response, header: str, regex: Optional[Pattern[str]]) -> Optional[float]:
12
+ def get_numeric_value_from_header(
13
+ response: requests.Response, header: str, regex: Optional[Pattern[str]]
14
+ ) -> Optional[float]:
13
15
  """
14
16
  Extract a header value from the response as a float
15
17
  :param response: response the extract header value from
@@ -9,8 +9,12 @@ from typing import Any, Mapping, Optional, Union
9
9
  import requests
10
10
  from airbyte_cdk.models import FailureType
11
11
  from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
12
- from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies.header_helper import get_numeric_value_from_header
13
- from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategy import BackoffStrategy
12
+ from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies.header_helper import (
13
+ get_numeric_value_from_header,
14
+ )
15
+ from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategy import (
16
+ BackoffStrategy,
17
+ )
14
18
  from airbyte_cdk.sources.types import Config
15
19
  from airbyte_cdk.utils import AirbyteTracedException
16
20
 
@@ -33,11 +37,15 @@ class WaitTimeFromHeaderBackoffStrategy(BackoffStrategy):
33
37
  max_waiting_time_in_seconds: Optional[float] = None
34
38
 
35
39
  def __post_init__(self, parameters: Mapping[str, Any]) -> None:
36
- self.regex = InterpolatedString.create(self.regex, parameters=parameters) if self.regex else None
40
+ self.regex = (
41
+ InterpolatedString.create(self.regex, parameters=parameters) if self.regex else None
42
+ )
37
43
  self.header = InterpolatedString.create(self.header, parameters=parameters)
38
44
 
39
45
  def backoff_time(
40
- self, response_or_exception: Optional[Union[requests.Response, requests.RequestException]], attempt_count: int
46
+ self,
47
+ response_or_exception: Optional[Union[requests.Response, requests.RequestException]],
48
+ attempt_count: int,
41
49
  ) -> Optional[float]:
42
50
  header = self.header.eval(config=self.config) # type: ignore # header is always cast to an interpolated stream
43
51
  if self.regex:
@@ -48,7 +56,11 @@ class WaitTimeFromHeaderBackoffStrategy(BackoffStrategy):
48
56
  header_value = None
49
57
  if isinstance(response_or_exception, requests.Response):
50
58
  header_value = get_numeric_value_from_header(response_or_exception, header, regex)
51
- if self.max_waiting_time_in_seconds and header_value and header_value >= self.max_waiting_time_in_seconds:
59
+ if (
60
+ self.max_waiting_time_in_seconds
61
+ and header_value
62
+ and header_value >= self.max_waiting_time_in_seconds
63
+ ):
52
64
  raise AirbyteTracedException(
53
65
  internal_message=f"Rate limit wait time {header_value} is greater than max waiting time of {self.max_waiting_time_in_seconds} seconds. Stopping the stream...",
54
66
  message="The rate limit is greater than max waiting time has been reached.",
@@ -10,8 +10,12 @@ from typing import Any, Mapping, Optional, Union
10
10
 
11
11
  import requests
12
12
  from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
13
- from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies.header_helper import get_numeric_value_from_header
14
- from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategy import BackoffStrategy
13
+ from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies.header_helper import (
14
+ get_numeric_value_from_header,
15
+ )
16
+ from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategy import (
17
+ BackoffStrategy,
18
+ )
15
19
  from airbyte_cdk.sources.types import Config
16
20
 
17
21
 
@@ -35,12 +39,16 @@ class WaitUntilTimeFromHeaderBackoffStrategy(BackoffStrategy):
35
39
 
36
40
  def __post_init__(self, parameters: Mapping[str, Any]) -> None:
37
41
  self.header = InterpolatedString.create(self.header, parameters=parameters)
38
- self.regex = InterpolatedString.create(self.regex, parameters=parameters) if self.regex else None
42
+ self.regex = (
43
+ InterpolatedString.create(self.regex, parameters=parameters) if self.regex else None
44
+ )
39
45
  if not isinstance(self.min_wait, InterpolatedString):
40
46
  self.min_wait = InterpolatedString.create(str(self.min_wait), parameters=parameters)
41
47
 
42
48
  def backoff_time(
43
- self, response_or_exception: Optional[Union[requests.Response, requests.RequestException]], attempt_count: int
49
+ self,
50
+ response_or_exception: Optional[Union[requests.Response, requests.RequestException]],
51
+ attempt_count: int,
44
52
  ) -> Optional[float]:
45
53
  now = time.time()
46
54
  header = self.header.eval(self.config) # type: ignore # header is always cast to an interpolated string
@@ -55,7 +63,9 @@ class WaitUntilTimeFromHeaderBackoffStrategy(BackoffStrategy):
55
63
  min_wait = self.min_wait.eval(self.config) # type: ignore # header is always cast to an interpolated string
56
64
  if wait_until is None or not wait_until:
57
65
  return float(min_wait) if min_wait else None
58
- if (isinstance(wait_until, str) and wait_until.isnumeric()) or isinstance(wait_until, numbers.Number):
66
+ if (isinstance(wait_until, str) and wait_until.isnumeric()) or isinstance(
67
+ wait_until, numbers.Number
68
+ ):
59
69
  wait_time = float(wait_until) - now
60
70
  else:
61
71
  return float(min_wait)
@@ -54,7 +54,9 @@ class CompositeErrorHandler(ErrorHandler):
54
54
  def max_time(self) -> Optional[int]:
55
55
  return max([error_handler.max_time or 0 for error_handler in self.error_handlers])
56
56
 
57
- def interpret_response(self, response_or_exception: Optional[Union[requests.Response, Exception]]) -> ErrorResolution:
57
+ def interpret_response(
58
+ self, response_or_exception: Optional[Union[requests.Response, Exception]]
59
+ ) -> ErrorResolution:
58
60
  matched_error_resolution = None
59
61
  for error_handler in self.error_handlers:
60
62
  matched_error_resolution = error_handler.interpret_response(response_or_exception)
@@ -6,8 +6,12 @@ from dataclasses import InitVar, dataclass, field
6
6
  from typing import Any, List, Mapping, MutableMapping, Optional, Union
7
7
 
8
8
  import requests
9
- from airbyte_cdk.sources.declarative.requesters.error_handlers.default_http_response_filter import DefaultHttpResponseFilter
10
- from airbyte_cdk.sources.declarative.requesters.error_handlers.http_response_filter import HttpResponseFilter
9
+ from airbyte_cdk.sources.declarative.requesters.error_handlers.default_http_response_filter import (
10
+ DefaultHttpResponseFilter,
11
+ )
12
+ from airbyte_cdk.sources.declarative.requesters.error_handlers.http_response_filter import (
13
+ HttpResponseFilter,
14
+ )
11
15
  from airbyte_cdk.sources.streams.http.error_handlers import BackoffStrategy, ErrorHandler
12
16
  from airbyte_cdk.sources.streams.http.error_handlers.response_models import (
13
17
  SUCCESS_RESOLUTION,
@@ -98,17 +102,19 @@ class DefaultErrorHandler(ErrorHandler):
98
102
  backoff_strategies: Optional[List[BackoffStrategy]] = None
99
103
 
100
104
  def __post_init__(self, parameters: Mapping[str, Any]) -> None:
101
-
102
105
  if not self.response_filters:
103
106
  self.response_filters = [HttpResponseFilter(config=self.config, parameters={})]
104
107
 
105
108
  self._last_request_to_attempt_count: MutableMapping[requests.PreparedRequest, int] = {}
106
109
 
107
- def interpret_response(self, response_or_exception: Optional[Union[requests.Response, Exception]]) -> ErrorResolution:
108
-
110
+ def interpret_response(
111
+ self, response_or_exception: Optional[Union[requests.Response, Exception]]
112
+ ) -> ErrorResolution:
109
113
  if self.response_filters:
110
114
  for response_filter in self.response_filters:
111
- matched_error_resolution = response_filter.matches(response_or_exception=response_or_exception)
115
+ matched_error_resolution = response_filter.matches(
116
+ response_or_exception=response_or_exception
117
+ )
112
118
  if matched_error_resolution:
113
119
  return matched_error_resolution
114
120
  if isinstance(response_or_exception, requests.Response):
@@ -125,12 +131,16 @@ class DefaultErrorHandler(ErrorHandler):
125
131
  )
126
132
 
127
133
  def backoff_time(
128
- self, response_or_exception: Optional[Union[requests.Response, requests.RequestException]], attempt_count: int = 0
134
+ self,
135
+ response_or_exception: Optional[Union[requests.Response, requests.RequestException]],
136
+ attempt_count: int = 0,
129
137
  ) -> Optional[float]:
130
138
  backoff = None
131
139
  if self.backoff_strategies:
132
140
  for backoff_strategy in self.backoff_strategies:
133
- backoff = backoff_strategy.backoff_time(response_or_exception=response_or_exception, attempt_count=attempt_count) # type: ignore # attempt_count maintained for compatibility with low code CDK
141
+ backoff = backoff_strategy.backoff_time(
142
+ response_or_exception=response_or_exception, attempt_count=attempt_count
143
+ ) # type: ignore # attempt_count maintained for compatibility with low code CDK
134
144
  if backoff:
135
145
  return backoff
136
146
  return backoff
@@ -5,18 +5,25 @@
5
5
  from typing import Optional, Union
6
6
 
7
7
  import requests
8
- from airbyte_cdk.sources.declarative.requesters.error_handlers.http_response_filter import HttpResponseFilter
9
- from airbyte_cdk.sources.streams.http.error_handlers.default_error_mapping import DEFAULT_ERROR_MAPPING
10
- from airbyte_cdk.sources.streams.http.error_handlers.response_models import ErrorResolution, create_fallback_error_resolution
8
+ from airbyte_cdk.sources.declarative.requesters.error_handlers.http_response_filter import (
9
+ HttpResponseFilter,
10
+ )
11
+ from airbyte_cdk.sources.streams.http.error_handlers.default_error_mapping import (
12
+ DEFAULT_ERROR_MAPPING,
13
+ )
14
+ from airbyte_cdk.sources.streams.http.error_handlers.response_models import (
15
+ ErrorResolution,
16
+ create_fallback_error_resolution,
17
+ )
11
18
 
12
19
 
13
20
  class DefaultHttpResponseFilter(HttpResponseFilter):
14
- def matches(self, response_or_exception: Optional[Union[requests.Response, Exception]]) -> Optional[ErrorResolution]:
15
-
21
+ def matches(
22
+ self, response_or_exception: Optional[Union[requests.Response, Exception]]
23
+ ) -> Optional[ErrorResolution]:
16
24
  default_mapped_error_resolution = None
17
25
 
18
26
  if isinstance(response_or_exception, (requests.Response, Exception)):
19
-
20
27
  mapped_key: Union[int, type] = (
21
28
  response_or_exception.status_code
22
29
  if isinstance(response_or_exception, requests.Response)
@@ -26,5 +33,7 @@ class DefaultHttpResponseFilter(HttpResponseFilter):
26
33
  default_mapped_error_resolution = DEFAULT_ERROR_MAPPING.get(mapped_key)
27
34
 
28
35
  return (
29
- default_mapped_error_resolution if default_mapped_error_resolution else create_fallback_error_resolution(response_or_exception)
36
+ default_mapped_error_resolution
37
+ if default_mapped_error_resolution
38
+ else create_fallback_error_resolution(response_or_exception)
30
39
  )
@@ -10,8 +10,13 @@ from airbyte_cdk.models import FailureType
10
10
  from airbyte_cdk.sources.declarative.interpolation import InterpolatedString
11
11
  from airbyte_cdk.sources.declarative.interpolation.interpolated_boolean import InterpolatedBoolean
12
12
  from airbyte_cdk.sources.streams.http.error_handlers import JsonErrorMessageParser
13
- from airbyte_cdk.sources.streams.http.error_handlers.default_error_mapping import DEFAULT_ERROR_MAPPING
14
- from airbyte_cdk.sources.streams.http.error_handlers.response_models import ErrorResolution, ResponseAction
13
+ from airbyte_cdk.sources.streams.http.error_handlers.default_error_mapping import (
14
+ DEFAULT_ERROR_MAPPING,
15
+ )
16
+ from airbyte_cdk.sources.streams.http.error_handlers.response_models import (
17
+ ErrorResolution,
18
+ ResponseAction,
19
+ )
15
20
  from airbyte_cdk.sources.types import Config
16
21
 
17
22
 
@@ -42,24 +47,35 @@ class HttpResponseFilter:
42
47
  error_message: Union[InterpolatedString, str] = ""
43
48
 
44
49
  def __post_init__(self, parameters: Mapping[str, Any]) -> None:
45
-
46
50
  if self.action is not None:
47
- if self.http_codes is None and self.predicate is None and self.error_message_contains is None:
48
- raise ValueError("HttpResponseFilter requires a filter condition if an action is specified")
51
+ if (
52
+ self.http_codes is None
53
+ and self.predicate is None
54
+ and self.error_message_contains is None
55
+ ):
56
+ raise ValueError(
57
+ "HttpResponseFilter requires a filter condition if an action is specified"
58
+ )
49
59
  elif isinstance(self.action, str):
50
60
  self.action = ResponseAction[self.action]
51
61
  self.http_codes = self.http_codes or set()
52
62
  if isinstance(self.predicate, str):
53
63
  self.predicate = InterpolatedBoolean(condition=self.predicate, parameters=parameters)
54
- self.error_message = InterpolatedString.create(string_or_interpolated=self.error_message, parameters=parameters)
64
+ self.error_message = InterpolatedString.create(
65
+ string_or_interpolated=self.error_message, parameters=parameters
66
+ )
55
67
  self._error_message_parser = JsonErrorMessageParser()
56
68
  if self.failure_type and isinstance(self.failure_type, str):
57
69
  self.failure_type = FailureType[self.failure_type]
58
70
 
59
- def matches(self, response_or_exception: Optional[Union[requests.Response, Exception]]) -> Optional[ErrorResolution]:
71
+ def matches(
72
+ self, response_or_exception: Optional[Union[requests.Response, Exception]]
73
+ ) -> Optional[ErrorResolution]:
60
74
  filter_action = self._matches_filter(response_or_exception)
61
75
  mapped_key = (
62
- response_or_exception.status_code if isinstance(response_or_exception, requests.Response) else response_or_exception.__class__
76
+ response_or_exception.status_code
77
+ if isinstance(response_or_exception, requests.Response)
78
+ else response_or_exception.__class__
63
79
  )
64
80
 
65
81
  if isinstance(mapped_key, (int, Exception)):
@@ -68,7 +84,11 @@ class HttpResponseFilter:
68
84
  default_mapped_error_resolution = None
69
85
 
70
86
  if filter_action is not None:
71
- default_error_message = default_mapped_error_resolution.error_message if default_mapped_error_resolution else ""
87
+ default_error_message = (
88
+ default_mapped_error_resolution.error_message
89
+ if default_mapped_error_resolution
90
+ else ""
91
+ )
72
92
  error_message = None
73
93
  if isinstance(response_or_exception, requests.Response):
74
94
  error_message = self._create_error_message(response_or_exception)
@@ -96,10 +116,14 @@ class HttpResponseFilter:
96
116
 
97
117
  return None
98
118
 
99
- def _match_default_error_mapping(self, mapped_key: Union[int, type[Exception]]) -> Optional[ErrorResolution]:
119
+ def _match_default_error_mapping(
120
+ self, mapped_key: Union[int, type[Exception]]
121
+ ) -> Optional[ErrorResolution]:
100
122
  return DEFAULT_ERROR_MAPPING.get(mapped_key)
101
123
 
102
- def _matches_filter(self, response_or_exception: Optional[Union[requests.Response, Exception]]) -> Optional[ResponseAction]:
124
+ def _matches_filter(
125
+ self, response_or_exception: Optional[Union[requests.Response, Exception]]
126
+ ) -> Optional[ResponseAction]:
103
127
  """
104
128
  Apply the HTTP filter on the response and return the action to execute if it matches
105
129
  :param response: The HTTP response to evaluate
@@ -126,14 +150,27 @@ class HttpResponseFilter:
126
150
  :param response: The HTTP response which can be used during interpolation
127
151
  :return: The evaluated error message string to be emitted
128
152
  """
129
- return self.error_message.eval(self.config, response=self._safe_response_json(response), headers=response.headers) # type: ignore # error_message is always cast to an interpolated string
153
+ return self.error_message.eval(
154
+ self.config, response=self._safe_response_json(response), headers=response.headers
155
+ ) # type: ignore # error_message is always cast to an interpolated string
130
156
 
131
157
  def _response_matches_predicate(self, response: requests.Response) -> bool:
132
- return bool(self.predicate.condition and self.predicate.eval(None, response=self._safe_response_json(response), headers=response.headers)) if self.predicate else False # type: ignore # predicate is always cast to an interpolated string
158
+ return (
159
+ bool(
160
+ self.predicate.condition
161
+ and self.predicate.eval(
162
+ None, response=self._safe_response_json(response), headers=response.headers
163
+ )
164
+ )
165
+ if self.predicate
166
+ else False
167
+ ) # type: ignore # predicate is always cast to an interpolated string
133
168
 
134
169
  def _response_contains_error_message(self, response: requests.Response) -> bool:
135
170
  if not self.error_message_contains:
136
171
  return False
137
172
  else:
138
- error_message = self._error_message_parser.parse_response_error_message(response=response)
173
+ error_message = self._error_message_parser.parse_response_error_message(
174
+ response=response
175
+ )
139
176
  return bool(error_message and self.error_message_contains in error_message)