airbyte-cdk 6.5.3rc2__py3-none-any.whl → 6.5.5__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.
- airbyte_cdk/__init__.py +17 -2
- airbyte_cdk/config_observation.py +10 -3
- airbyte_cdk/connector.py +19 -9
- airbyte_cdk/connector_builder/connector_builder_handler.py +28 -8
- airbyte_cdk/connector_builder/main.py +26 -6
- airbyte_cdk/connector_builder/message_grouper.py +95 -25
- airbyte_cdk/destinations/destination.py +47 -14
- airbyte_cdk/destinations/vector_db_based/config.py +36 -14
- airbyte_cdk/destinations/vector_db_based/document_processor.py +49 -11
- airbyte_cdk/destinations/vector_db_based/embedder.py +52 -11
- airbyte_cdk/destinations/vector_db_based/test_utils.py +14 -4
- airbyte_cdk/destinations/vector_db_based/utils.py +8 -2
- airbyte_cdk/destinations/vector_db_based/writer.py +15 -4
- airbyte_cdk/entrypoint.py +82 -26
- airbyte_cdk/exception_handler.py +13 -3
- airbyte_cdk/logger.py +10 -2
- airbyte_cdk/models/airbyte_protocol.py +11 -5
- airbyte_cdk/models/airbyte_protocol_serializers.py +9 -3
- airbyte_cdk/models/well_known_types.py +1 -1
- airbyte_cdk/sources/abstract_source.py +63 -17
- airbyte_cdk/sources/concurrent_source/concurrent_read_processor.py +47 -14
- airbyte_cdk/sources/concurrent_source/concurrent_source.py +25 -7
- airbyte_cdk/sources/concurrent_source/concurrent_source_adapter.py +27 -6
- airbyte_cdk/sources/concurrent_source/thread_pool_manager.py +9 -3
- airbyte_cdk/sources/connector_state_manager.py +32 -10
- airbyte_cdk/sources/declarative/async_job/job.py +3 -1
- airbyte_cdk/sources/declarative/async_job/job_orchestrator.py +68 -14
- airbyte_cdk/sources/declarative/async_job/job_tracker.py +24 -6
- airbyte_cdk/sources/declarative/async_job/repository.py +3 -1
- airbyte_cdk/sources/declarative/auth/declarative_authenticator.py +3 -1
- airbyte_cdk/sources/declarative/auth/jwt.py +27 -7
- airbyte_cdk/sources/declarative/auth/oauth.py +35 -11
- airbyte_cdk/sources/declarative/auth/selective_authenticator.py +3 -1
- airbyte_cdk/sources/declarative/auth/token.py +25 -8
- airbyte_cdk/sources/declarative/checks/check_stream.py +12 -4
- airbyte_cdk/sources/declarative/checks/connection_checker.py +3 -1
- airbyte_cdk/sources/declarative/concurrency_level/concurrency_level.py +11 -3
- airbyte_cdk/sources/declarative/concurrent_declarative_source.py +106 -50
- airbyte_cdk/sources/declarative/datetime/min_max_datetime.py +20 -6
- airbyte_cdk/sources/declarative/declarative_source.py +3 -1
- airbyte_cdk/sources/declarative/declarative_stream.py +27 -6
- airbyte_cdk/sources/declarative/decoders/decoder.py +3 -1
- airbyte_cdk/sources/declarative/decoders/json_decoder.py +3 -1
- airbyte_cdk/sources/declarative/decoders/pagination_decoder_decorator.py +3 -1
- airbyte_cdk/sources/declarative/decoders/xml_decoder.py +6 -2
- airbyte_cdk/sources/declarative/extractors/dpath_extractor.py +6 -2
- airbyte_cdk/sources/declarative/extractors/record_filter.py +24 -7
- airbyte_cdk/sources/declarative/extractors/record_selector.py +10 -3
- airbyte_cdk/sources/declarative/extractors/response_to_file_extractor.py +15 -5
- airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py +96 -31
- airbyte_cdk/sources/declarative/incremental/global_substream_cursor.py +22 -8
- airbyte_cdk/sources/declarative/incremental/per_partition_cursor.py +46 -15
- airbyte_cdk/sources/declarative/incremental/per_partition_with_global.py +19 -5
- airbyte_cdk/sources/declarative/incremental/resumable_full_refresh_cursor.py +3 -1
- airbyte_cdk/sources/declarative/interpolation/interpolated_boolean.py +20 -2
- airbyte_cdk/sources/declarative/interpolation/interpolated_mapping.py +5 -1
- airbyte_cdk/sources/declarative/interpolation/interpolated_nested_mapping.py +10 -3
- airbyte_cdk/sources/declarative/interpolation/interpolated_string.py +6 -2
- airbyte_cdk/sources/declarative/interpolation/interpolation.py +7 -1
- airbyte_cdk/sources/declarative/interpolation/jinja.py +6 -2
- airbyte_cdk/sources/declarative/interpolation/macros.py +19 -4
- airbyte_cdk/sources/declarative/manifest_declarative_source.py +106 -24
- airbyte_cdk/sources/declarative/migrations/legacy_to_per_partition_state_migration.py +7 -2
- airbyte_cdk/sources/declarative/models/declarative_component_schema.py +656 -678
- airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py +13 -4
- airbyte_cdk/sources/declarative/parsers/manifest_reference_resolver.py +9 -2
- airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +782 -232
- airbyte_cdk/sources/declarative/partition_routers/cartesian_product_stream_slicer.py +29 -7
- airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py +25 -7
- airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py +54 -15
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/constant_backoff_strategy.py +6 -2
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/header_helper.py +3 -1
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_time_from_header_backoff_strategy.py +17 -5
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_until_time_from_header_backoff_strategy.py +15 -5
- airbyte_cdk/sources/declarative/requesters/error_handlers/composite_error_handler.py +3 -1
- airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py +18 -8
- airbyte_cdk/sources/declarative/requesters/error_handlers/default_http_response_filter.py +16 -7
- airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py +51 -14
- airbyte_cdk/sources/declarative/requesters/http_job_repository.py +29 -8
- airbyte_cdk/sources/declarative/requesters/http_requester.py +58 -16
- airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +49 -14
- airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py +3 -1
- airbyte_cdk/sources/declarative/requesters/paginators/paginator.py +3 -1
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py +17 -5
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py +24 -7
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py +9 -3
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py +3 -1
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/stop_condition.py +6 -2
- airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py +19 -6
- airbyte_cdk/sources/declarative/requesters/request_options/default_request_options_provider.py +3 -1
- airbyte_cdk/sources/declarative/requesters/request_options/interpolated_nested_request_input_provider.py +21 -7
- airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_input_provider.py +18 -6
- airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py +27 -8
- airbyte_cdk/sources/declarative/requesters/requester.py +3 -1
- airbyte_cdk/sources/declarative/retrievers/async_retriever.py +12 -5
- airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +105 -24
- airbyte_cdk/sources/declarative/schema/default_schema_loader.py +3 -1
- airbyte_cdk/sources/declarative/spec/spec.py +8 -2
- airbyte_cdk/sources/declarative/stream_slicers/stream_slicer.py +3 -1
- airbyte_cdk/sources/declarative/transformations/add_fields.py +12 -3
- airbyte_cdk/sources/declarative/transformations/remove_fields.py +6 -2
- airbyte_cdk/sources/declarative/types.py +8 -1
- airbyte_cdk/sources/declarative/yaml_declarative_source.py +3 -1
- airbyte_cdk/sources/embedded/base_integration.py +14 -4
- airbyte_cdk/sources/embedded/catalog.py +16 -4
- airbyte_cdk/sources/embedded/runner.py +19 -3
- airbyte_cdk/sources/embedded/tools.py +3 -1
- airbyte_cdk/sources/file_based/availability_strategy/abstract_file_based_availability_strategy.py +12 -4
- airbyte_cdk/sources/file_based/availability_strategy/default_file_based_availability_strategy.py +27 -7
- airbyte_cdk/sources/file_based/config/abstract_file_based_spec.py +12 -6
- airbyte_cdk/sources/file_based/config/csv_format.py +21 -9
- airbyte_cdk/sources/file_based/config/file_based_stream_config.py +6 -2
- airbyte_cdk/sources/file_based/config/unstructured_format.py +10 -3
- airbyte_cdk/sources/file_based/discovery_policy/abstract_discovery_policy.py +2 -4
- airbyte_cdk/sources/file_based/discovery_policy/default_discovery_policy.py +7 -2
- airbyte_cdk/sources/file_based/exceptions.py +13 -15
- airbyte_cdk/sources/file_based/file_based_source.py +82 -24
- airbyte_cdk/sources/file_based/file_based_stream_reader.py +16 -5
- airbyte_cdk/sources/file_based/file_types/avro_parser.py +58 -17
- airbyte_cdk/sources/file_based/file_types/csv_parser.py +89 -26
- airbyte_cdk/sources/file_based/file_types/excel_parser.py +25 -7
- airbyte_cdk/sources/file_based/file_types/file_transfer.py +8 -2
- airbyte_cdk/sources/file_based/file_types/file_type_parser.py +4 -1
- airbyte_cdk/sources/file_based/file_types/jsonl_parser.py +20 -6
- airbyte_cdk/sources/file_based/file_types/parquet_parser.py +57 -16
- airbyte_cdk/sources/file_based/file_types/unstructured_parser.py +64 -15
- airbyte_cdk/sources/file_based/schema_helpers.py +33 -10
- airbyte_cdk/sources/file_based/schema_validation_policies/abstract_schema_validation_policy.py +3 -1
- airbyte_cdk/sources/file_based/schema_validation_policies/default_schema_validation_policies.py +16 -5
- airbyte_cdk/sources/file_based/stream/abstract_file_based_stream.py +33 -10
- airbyte_cdk/sources/file_based/stream/concurrent/adapters.py +47 -11
- airbyte_cdk/sources/file_based/stream/concurrent/cursor/abstract_concurrent_file_based_cursor.py +13 -22
- airbyte_cdk/sources/file_based/stream/concurrent/cursor/file_based_concurrent_cursor.py +53 -17
- airbyte_cdk/sources/file_based/stream/concurrent/cursor/file_based_final_state_cursor.py +17 -5
- airbyte_cdk/sources/file_based/stream/cursor/abstract_file_based_cursor.py +3 -1
- airbyte_cdk/sources/file_based/stream/cursor/default_file_based_cursor.py +26 -9
- airbyte_cdk/sources/file_based/stream/default_file_based_stream.py +67 -21
- airbyte_cdk/sources/http_logger.py +5 -1
- airbyte_cdk/sources/message/repository.py +18 -4
- airbyte_cdk/sources/source.py +17 -7
- airbyte_cdk/sources/streams/availability_strategy.py +9 -3
- airbyte_cdk/sources/streams/call_rate.py +63 -19
- airbyte_cdk/sources/streams/checkpoint/checkpoint_reader.py +31 -7
- airbyte_cdk/sources/streams/checkpoint/substream_resumable_full_refresh_cursor.py +6 -2
- airbyte_cdk/sources/streams/concurrent/adapters.py +77 -22
- airbyte_cdk/sources/streams/concurrent/cursor.py +56 -20
- airbyte_cdk/sources/streams/concurrent/default_stream.py +9 -2
- airbyte_cdk/sources/streams/concurrent/helpers.py +6 -2
- airbyte_cdk/sources/streams/concurrent/partition_enqueuer.py +9 -2
- airbyte_cdk/sources/streams/concurrent/partition_reader.py +4 -1
- airbyte_cdk/sources/streams/concurrent/partitions/record.py +10 -2
- airbyte_cdk/sources/streams/concurrent/partitions/types.py +6 -2
- airbyte_cdk/sources/streams/concurrent/state_converters/abstract_stream_state_converter.py +25 -10
- airbyte_cdk/sources/streams/concurrent/state_converters/datetime_stream_state_converter.py +32 -16
- airbyte_cdk/sources/streams/core.py +77 -22
- airbyte_cdk/sources/streams/http/availability_strategy.py +3 -1
- airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py +4 -1
- airbyte_cdk/sources/streams/http/error_handlers/error_handler.py +3 -1
- airbyte_cdk/sources/streams/http/error_handlers/http_status_error_handler.py +16 -5
- airbyte_cdk/sources/streams/http/error_handlers/response_models.py +9 -3
- airbyte_cdk/sources/streams/http/exceptions.py +2 -2
- airbyte_cdk/sources/streams/http/http.py +133 -33
- airbyte_cdk/sources/streams/http/http_client.py +91 -29
- airbyte_cdk/sources/streams/http/rate_limiting.py +23 -7
- airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +19 -6
- airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py +38 -11
- airbyte_cdk/sources/streams/http/requests_native_auth/token.py +13 -3
- airbyte_cdk/sources/types.py +5 -1
- airbyte_cdk/sources/utils/record_helper.py +12 -3
- airbyte_cdk/sources/utils/schema_helpers.py +9 -3
- airbyte_cdk/sources/utils/slice_logger.py +4 -1
- airbyte_cdk/sources/utils/transform.py +24 -9
- airbyte_cdk/sql/exceptions.py +19 -6
- airbyte_cdk/sql/secrets.py +3 -1
- airbyte_cdk/sql/shared/catalog_providers.py +13 -4
- airbyte_cdk/sql/shared/sql_processor.py +44 -14
- airbyte_cdk/test/catalog_builder.py +19 -8
- airbyte_cdk/test/entrypoint_wrapper.py +27 -8
- airbyte_cdk/test/mock_http/mocker.py +41 -11
- airbyte_cdk/test/mock_http/request.py +9 -3
- airbyte_cdk/test/mock_http/response.py +3 -1
- airbyte_cdk/test/mock_http/response_builder.py +29 -7
- airbyte_cdk/test/state_builder.py +10 -2
- airbyte_cdk/test/utils/data.py +6 -2
- airbyte_cdk/test/utils/http_mocking.py +3 -1
- airbyte_cdk/utils/airbyte_secrets_utils.py +3 -1
- airbyte_cdk/utils/analytics_message.py +10 -2
- airbyte_cdk/utils/datetime_format_inferrer.py +4 -1
- airbyte_cdk/utils/mapping_helpers.py +3 -1
- airbyte_cdk/utils/message_utils.py +11 -4
- airbyte_cdk/utils/print_buffer.py +6 -1
- airbyte_cdk/utils/schema_inferrer.py +30 -9
- airbyte_cdk/utils/spec_schema_transformations.py +3 -1
- airbyte_cdk/utils/traced_exception.py +35 -9
- {airbyte_cdk-6.5.3rc2.dist-info → airbyte_cdk-6.5.5.dist-info}/METADATA +7 -6
- {airbyte_cdk-6.5.3rc2.dist-info → airbyte_cdk-6.5.5.dist-info}/RECORD +198 -198
- {airbyte_cdk-6.5.3rc2.dist-info → airbyte_cdk-6.5.5.dist-info}/LICENSE.txt +0 -0
- {airbyte_cdk-6.5.3rc2.dist-info → airbyte_cdk-6.5.5.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
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
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(
|
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)
|
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 [
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
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]]]] =
|
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(
|
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]
|
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(
|
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
|
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(
|
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 = [
|
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 =
|
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(
|
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={
|
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,
|
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(
|
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(
|
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] = {
|
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(
|
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(
|
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,
|
airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/header_helper.py
CHANGED
@@ -9,7 +9,9 @@ from typing import Optional
|
|
9
9
|
import requests
|
10
10
|
|
11
11
|
|
12
|
-
def get_numeric_value_from_header(
|
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
|
13
|
-
|
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 =
|
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,
|
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
|
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
|
14
|
-
|
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 =
|
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,
|
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(
|
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(
|
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
|
10
|
-
|
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(
|
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(
|
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,
|
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(
|
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
|
9
|
-
|
10
|
-
|
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(
|
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
|
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
|
14
|
-
|
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
|
48
|
-
|
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(
|
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(
|
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
|
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 =
|
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(
|
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(
|
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(
|
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
|
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(
|
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)
|