airbyte-cdk 0.72.1__py3-none-any.whl → 6.13.1.dev4107__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- airbyte_cdk/__init__.py +355 -6
- airbyte_cdk/cli/__init__.py +1 -0
- airbyte_cdk/cli/source_declarative_manifest/__init__.py +5 -0
- airbyte_cdk/cli/source_declarative_manifest/_run.py +230 -0
- airbyte_cdk/cli/source_declarative_manifest/spec.json +17 -0
- airbyte_cdk/config_observation.py +29 -10
- airbyte_cdk/connector.py +24 -24
- airbyte_cdk/connector_builder/README.md +53 -0
- airbyte_cdk/connector_builder/connector_builder_handler.py +37 -11
- airbyte_cdk/connector_builder/main.py +45 -13
- airbyte_cdk/connector_builder/message_grouper.py +189 -50
- airbyte_cdk/connector_builder/models.py +3 -2
- airbyte_cdk/destinations/__init__.py +4 -3
- airbyte_cdk/destinations/destination.py +54 -20
- airbyte_cdk/destinations/vector_db_based/README.md +37 -0
- airbyte_cdk/destinations/vector_db_based/config.py +40 -17
- airbyte_cdk/destinations/vector_db_based/document_processor.py +56 -17
- airbyte_cdk/destinations/vector_db_based/embedder.py +57 -15
- 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 +24 -5
- airbyte_cdk/entrypoint.py +153 -44
- airbyte_cdk/exception_handler.py +21 -3
- airbyte_cdk/logger.py +30 -44
- airbyte_cdk/models/__init__.py +13 -2
- airbyte_cdk/models/airbyte_protocol.py +86 -1
- airbyte_cdk/models/airbyte_protocol_serializers.py +44 -0
- airbyte_cdk/models/file_transfer_record_message.py +13 -0
- airbyte_cdk/models/well_known_types.py +1 -1
- airbyte_cdk/sources/__init__.py +5 -1
- airbyte_cdk/sources/abstract_source.py +125 -79
- airbyte_cdk/sources/concurrent_source/__init__.py +7 -2
- airbyte_cdk/sources/concurrent_source/concurrent_read_processor.py +102 -36
- airbyte_cdk/sources/concurrent_source/concurrent_source.py +29 -36
- airbyte_cdk/sources/concurrent_source/concurrent_source_adapter.py +94 -10
- airbyte_cdk/sources/concurrent_source/stream_thread_exception.py +25 -0
- airbyte_cdk/sources/concurrent_source/thread_pool_manager.py +20 -14
- airbyte_cdk/sources/config.py +3 -2
- airbyte_cdk/sources/connector_state_manager.py +49 -83
- airbyte_cdk/sources/declarative/async_job/job.py +52 -0
- airbyte_cdk/sources/declarative/async_job/job_orchestrator.py +497 -0
- airbyte_cdk/sources/declarative/async_job/job_tracker.py +75 -0
- airbyte_cdk/sources/declarative/async_job/repository.py +35 -0
- airbyte_cdk/sources/declarative/async_job/status.py +24 -0
- airbyte_cdk/sources/declarative/async_job/timer.py +39 -0
- airbyte_cdk/sources/declarative/auth/__init__.py +2 -3
- airbyte_cdk/sources/declarative/auth/declarative_authenticator.py +3 -1
- airbyte_cdk/sources/declarative/auth/jwt.py +191 -0
- airbyte_cdk/sources/declarative/auth/oauth.py +60 -20
- airbyte_cdk/sources/declarative/auth/selective_authenticator.py +10 -2
- airbyte_cdk/sources/declarative/auth/token.py +28 -10
- airbyte_cdk/sources/declarative/auth/token_provider.py +9 -8
- airbyte_cdk/sources/declarative/checks/check_stream.py +16 -8
- airbyte_cdk/sources/declarative/checks/connection_checker.py +4 -2
- airbyte_cdk/sources/declarative/concurrency_level/__init__.py +7 -0
- airbyte_cdk/sources/declarative/concurrency_level/concurrency_level.py +50 -0
- airbyte_cdk/sources/declarative/concurrent_declarative_source.py +421 -0
- airbyte_cdk/sources/declarative/datetime/datetime_parser.py +4 -0
- airbyte_cdk/sources/declarative/datetime/min_max_datetime.py +26 -6
- airbyte_cdk/sources/declarative/declarative_component_schema.yaml +1185 -85
- airbyte_cdk/sources/declarative/declarative_source.py +5 -2
- airbyte_cdk/sources/declarative/declarative_stream.py +95 -9
- airbyte_cdk/sources/declarative/decoders/__init__.py +23 -2
- airbyte_cdk/sources/declarative/decoders/composite_raw_decoder.py +97 -0
- airbyte_cdk/sources/declarative/decoders/decoder.py +11 -4
- airbyte_cdk/sources/declarative/decoders/json_decoder.py +92 -5
- airbyte_cdk/sources/declarative/decoders/noop_decoder.py +21 -0
- airbyte_cdk/sources/declarative/decoders/pagination_decoder_decorator.py +39 -0
- airbyte_cdk/sources/declarative/decoders/xml_decoder.py +98 -0
- airbyte_cdk/sources/declarative/extractors/__init__.py +12 -1
- airbyte_cdk/sources/declarative/extractors/dpath_extractor.py +29 -24
- airbyte_cdk/sources/declarative/extractors/http_selector.py +4 -5
- airbyte_cdk/sources/declarative/extractors/record_extractor.py +2 -3
- airbyte_cdk/sources/declarative/extractors/record_filter.py +65 -8
- airbyte_cdk/sources/declarative/extractors/record_selector.py +85 -26
- airbyte_cdk/sources/declarative/extractors/response_to_file_extractor.py +177 -0
- airbyte_cdk/sources/declarative/extractors/type_transformer.py +55 -0
- airbyte_cdk/sources/declarative/incremental/__init__.py +25 -3
- airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py +156 -48
- airbyte_cdk/sources/declarative/incremental/declarative_cursor.py +13 -0
- airbyte_cdk/sources/declarative/incremental/global_substream_cursor.py +350 -0
- airbyte_cdk/sources/declarative/incremental/per_partition_cursor.py +159 -74
- airbyte_cdk/sources/declarative/incremental/per_partition_with_global.py +200 -0
- airbyte_cdk/sources/declarative/incremental/resumable_full_refresh_cursor.py +122 -0
- airbyte_cdk/sources/declarative/interpolation/filters.py +27 -1
- airbyte_cdk/sources/declarative/interpolation/interpolated_boolean.py +23 -5
- airbyte_cdk/sources/declarative/interpolation/interpolated_mapping.py +12 -8
- airbyte_cdk/sources/declarative/interpolation/interpolated_nested_mapping.py +13 -6
- airbyte_cdk/sources/declarative/interpolation/interpolated_string.py +21 -6
- airbyte_cdk/sources/declarative/interpolation/interpolation.py +9 -3
- airbyte_cdk/sources/declarative/interpolation/jinja.py +72 -37
- airbyte_cdk/sources/declarative/interpolation/macros.py +72 -17
- airbyte_cdk/sources/declarative/manifest_declarative_source.py +193 -52
- airbyte_cdk/sources/declarative/migrations/legacy_to_per_partition_state_migration.py +98 -0
- airbyte_cdk/sources/declarative/migrations/state_migration.py +24 -0
- airbyte_cdk/sources/declarative/models/__init__.py +1 -1
- airbyte_cdk/sources/declarative/models/declarative_component_schema.py +1319 -603
- airbyte_cdk/sources/declarative/parsers/custom_exceptions.py +2 -2
- airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py +26 -4
- airbyte_cdk/sources/declarative/parsers/manifest_reference_resolver.py +26 -15
- airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +1695 -225
- airbyte_cdk/sources/declarative/partition_routers/__init__.py +24 -4
- airbyte_cdk/sources/declarative/partition_routers/async_job_partition_router.py +65 -0
- airbyte_cdk/sources/declarative/partition_routers/cartesian_product_stream_slicer.py +176 -0
- airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py +39 -9
- airbyte_cdk/sources/declarative/partition_routers/partition_router.py +62 -0
- airbyte_cdk/sources/declarative/partition_routers/single_partition_router.py +15 -3
- airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py +222 -39
- airbyte_cdk/sources/declarative/requesters/error_handlers/__init__.py +19 -5
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/__init__.py +3 -1
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/constant_backoff_strategy.py +19 -7
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/exponential_backoff_strategy.py +19 -7
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/header_helper.py +4 -2
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_time_from_header_backoff_strategy.py +41 -9
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_until_time_from_header_backoff_strategy.py +29 -14
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategy.py +5 -13
- airbyte_cdk/sources/declarative/requesters/error_handlers/composite_error_handler.py +32 -16
- airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py +46 -56
- airbyte_cdk/sources/declarative/requesters/error_handlers/default_http_response_filter.py +40 -0
- airbyte_cdk/sources/declarative/requesters/error_handlers/error_handler.py +6 -32
- airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py +119 -41
- airbyte_cdk/sources/declarative/requesters/http_job_repository.py +228 -0
- airbyte_cdk/sources/declarative/requesters/http_requester.py +98 -344
- airbyte_cdk/sources/declarative/requesters/paginators/__init__.py +14 -3
- airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +105 -46
- airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py +14 -8
- airbyte_cdk/sources/declarative/requesters/paginators/paginator.py +19 -8
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/__init__.py +9 -3
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py +53 -21
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py +42 -19
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py +25 -12
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py +13 -10
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/stop_condition.py +26 -13
- airbyte_cdk/sources/declarative/requesters/request_options/__init__.py +15 -2
- airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py +91 -0
- airbyte_cdk/sources/declarative/requesters/request_options/default_request_options_provider.py +60 -0
- airbyte_cdk/sources/declarative/requesters/request_options/interpolated_nested_request_input_provider.py +31 -14
- airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_input_provider.py +27 -15
- airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py +63 -10
- airbyte_cdk/sources/declarative/requesters/request_options/request_options_provider.py +1 -1
- airbyte_cdk/sources/declarative/requesters/requester.py +9 -17
- airbyte_cdk/sources/declarative/resolvers/__init__.py +41 -0
- airbyte_cdk/sources/declarative/resolvers/components_resolver.py +55 -0
- airbyte_cdk/sources/declarative/resolvers/config_components_resolver.py +136 -0
- airbyte_cdk/sources/declarative/resolvers/http_components_resolver.py +112 -0
- airbyte_cdk/sources/declarative/retrievers/__init__.py +6 -2
- airbyte_cdk/sources/declarative/retrievers/async_retriever.py +100 -0
- airbyte_cdk/sources/declarative/retrievers/retriever.py +1 -3
- airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +228 -72
- airbyte_cdk/sources/declarative/schema/__init__.py +14 -1
- airbyte_cdk/sources/declarative/schema/default_schema_loader.py +5 -3
- airbyte_cdk/sources/declarative/schema/dynamic_schema_loader.py +236 -0
- airbyte_cdk/sources/declarative/schema/json_file_schema_loader.py +8 -8
- airbyte_cdk/sources/declarative/spec/spec.py +12 -5
- airbyte_cdk/sources/declarative/stream_slicers/__init__.py +1 -2
- airbyte_cdk/sources/declarative/stream_slicers/declarative_partition_generator.py +88 -0
- airbyte_cdk/sources/declarative/stream_slicers/stream_slicer.py +9 -14
- airbyte_cdk/sources/declarative/transformations/add_fields.py +19 -11
- airbyte_cdk/sources/declarative/transformations/flatten_fields.py +52 -0
- airbyte_cdk/sources/declarative/transformations/keys_replace_transformation.py +61 -0
- airbyte_cdk/sources/declarative/transformations/keys_to_lower_transformation.py +22 -0
- airbyte_cdk/sources/declarative/transformations/keys_to_snake_transformation.py +68 -0
- airbyte_cdk/sources/declarative/transformations/remove_fields.py +13 -10
- airbyte_cdk/sources/declarative/transformations/transformation.py +5 -5
- airbyte_cdk/sources/declarative/types.py +19 -110
- airbyte_cdk/sources/declarative/yaml_declarative_source.py +31 -10
- airbyte_cdk/sources/embedded/base_integration.py +16 -5
- airbyte_cdk/sources/embedded/catalog.py +16 -4
- airbyte_cdk/sources/embedded/runner.py +19 -3
- airbyte_cdk/sources/embedded/tools.py +5 -2
- airbyte_cdk/sources/file_based/README.md +152 -0
- airbyte_cdk/sources/file_based/__init__.py +24 -0
- airbyte_cdk/sources/file_based/availability_strategy/__init__.py +9 -2
- airbyte_cdk/sources/file_based/availability_strategy/abstract_file_based_availability_strategy.py +22 -6
- airbyte_cdk/sources/file_based/availability_strategy/default_file_based_availability_strategy.py +46 -10
- airbyte_cdk/sources/file_based/config/abstract_file_based_spec.py +58 -10
- airbyte_cdk/sources/file_based/config/avro_format.py +2 -1
- airbyte_cdk/sources/file_based/config/csv_format.py +29 -10
- airbyte_cdk/sources/file_based/config/excel_format.py +18 -0
- airbyte_cdk/sources/file_based/config/file_based_stream_config.py +16 -4
- airbyte_cdk/sources/file_based/config/jsonl_format.py +2 -1
- airbyte_cdk/sources/file_based/config/parquet_format.py +2 -1
- airbyte_cdk/sources/file_based/config/unstructured_format.py +13 -5
- airbyte_cdk/sources/file_based/discovery_policy/__init__.py +6 -2
- 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 +52 -15
- airbyte_cdk/sources/file_based/file_based_source.py +163 -33
- airbyte_cdk/sources/file_based/file_based_stream_reader.py +83 -5
- airbyte_cdk/sources/file_based/file_types/__init__.py +14 -1
- airbyte_cdk/sources/file_based/file_types/avro_parser.py +75 -24
- airbyte_cdk/sources/file_based/file_types/csv_parser.py +116 -34
- airbyte_cdk/sources/file_based/file_types/excel_parser.py +196 -0
- airbyte_cdk/sources/file_based/file_types/file_transfer.py +37 -0
- airbyte_cdk/sources/file_based/file_types/file_type_parser.py +4 -1
- airbyte_cdk/sources/file_based/file_types/jsonl_parser.py +24 -8
- airbyte_cdk/sources/file_based/file_types/parquet_parser.py +60 -18
- airbyte_cdk/sources/file_based/file_types/unstructured_parser.py +147 -41
- airbyte_cdk/sources/file_based/remote_file.py +1 -1
- airbyte_cdk/sources/file_based/schema_helpers.py +38 -10
- airbyte_cdk/sources/file_based/schema_validation_policies/__init__.py +3 -1
- 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 +50 -13
- airbyte_cdk/sources/file_based/stream/concurrent/adapters.py +67 -27
- airbyte_cdk/sources/file_based/stream/concurrent/cursor/__init__.py +5 -1
- airbyte_cdk/sources/file_based/stream/concurrent/cursor/abstract_concurrent_file_based_cursor.py +14 -23
- airbyte_cdk/sources/file_based/stream/concurrent/cursor/file_based_concurrent_cursor.py +54 -18
- airbyte_cdk/sources/file_based/stream/concurrent/cursor/file_based_final_state_cursor.py +21 -9
- 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 +27 -10
- airbyte_cdk/sources/file_based/stream/default_file_based_stream.py +175 -45
- airbyte_cdk/sources/http_logger.py +8 -3
- airbyte_cdk/sources/message/__init__.py +7 -1
- airbyte_cdk/sources/message/repository.py +18 -4
- airbyte_cdk/sources/source.py +42 -38
- airbyte_cdk/sources/streams/__init__.py +2 -2
- airbyte_cdk/sources/streams/availability_strategy.py +54 -3
- airbyte_cdk/sources/streams/call_rate.py +64 -21
- airbyte_cdk/sources/streams/checkpoint/__init__.py +26 -0
- airbyte_cdk/sources/streams/checkpoint/checkpoint_reader.py +335 -0
- airbyte_cdk/sources/{declarative/incremental → streams/checkpoint}/cursor.py +17 -14
- airbyte_cdk/sources/streams/checkpoint/per_partition_key_serializer.py +22 -0
- airbyte_cdk/sources/streams/checkpoint/resumable_full_refresh_cursor.py +51 -0
- airbyte_cdk/sources/streams/checkpoint/substream_resumable_full_refresh_cursor.py +110 -0
- airbyte_cdk/sources/streams/concurrent/README.md +7 -0
- airbyte_cdk/sources/streams/concurrent/abstract_stream.py +7 -2
- airbyte_cdk/sources/streams/concurrent/adapters.py +84 -75
- airbyte_cdk/sources/streams/concurrent/availability_strategy.py +30 -2
- airbyte_cdk/sources/streams/concurrent/cursor.py +298 -42
- airbyte_cdk/sources/streams/concurrent/default_stream.py +12 -3
- airbyte_cdk/sources/streams/concurrent/exceptions.py +3 -0
- airbyte_cdk/sources/streams/concurrent/helpers.py +14 -3
- airbyte_cdk/sources/streams/concurrent/partition_enqueuer.py +12 -3
- airbyte_cdk/sources/streams/concurrent/partition_reader.py +10 -3
- airbyte_cdk/sources/streams/concurrent/partitions/partition.py +1 -16
- airbyte_cdk/sources/streams/concurrent/partitions/stream_slicer.py +21 -0
- airbyte_cdk/sources/streams/concurrent/partitions/types.py +15 -5
- airbyte_cdk/sources/streams/concurrent/state_converters/abstract_stream_state_converter.py +109 -17
- airbyte_cdk/sources/streams/concurrent/state_converters/datetime_stream_state_converter.py +90 -72
- airbyte_cdk/sources/streams/core.py +412 -87
- airbyte_cdk/sources/streams/http/__init__.py +2 -1
- airbyte_cdk/sources/streams/http/availability_strategy.py +12 -101
- airbyte_cdk/sources/streams/http/error_handlers/__init__.py +22 -0
- airbyte_cdk/sources/streams/http/error_handlers/backoff_strategy.py +28 -0
- airbyte_cdk/sources/streams/http/error_handlers/default_backoff_strategy.py +17 -0
- airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py +86 -0
- airbyte_cdk/sources/streams/http/error_handlers/error_handler.py +42 -0
- airbyte_cdk/sources/streams/http/error_handlers/error_message_parser.py +19 -0
- airbyte_cdk/sources/streams/http/error_handlers/http_status_error_handler.py +110 -0
- airbyte_cdk/sources/streams/http/error_handlers/json_error_message_parser.py +52 -0
- airbyte_cdk/sources/streams/http/error_handlers/response_models.py +65 -0
- airbyte_cdk/sources/streams/http/exceptions.py +27 -7
- airbyte_cdk/sources/streams/http/http.py +369 -246
- airbyte_cdk/sources/streams/http/http_client.py +531 -0
- airbyte_cdk/sources/streams/http/rate_limiting.py +76 -12
- airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +28 -9
- airbyte_cdk/sources/streams/http/requests_native_auth/abstract_token.py +2 -1
- airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py +90 -35
- airbyte_cdk/sources/streams/http/requests_native_auth/token.py +13 -3
- airbyte_cdk/sources/types.py +154 -0
- airbyte_cdk/sources/utils/record_helper.py +36 -21
- airbyte_cdk/sources/utils/schema_helpers.py +13 -6
- airbyte_cdk/sources/utils/slice_logger.py +4 -1
- airbyte_cdk/sources/utils/transform.py +54 -20
- airbyte_cdk/sql/_util/hashing.py +34 -0
- airbyte_cdk/sql/_util/name_normalizers.py +92 -0
- airbyte_cdk/sql/constants.py +32 -0
- airbyte_cdk/sql/exceptions.py +235 -0
- airbyte_cdk/sql/secrets.py +123 -0
- airbyte_cdk/sql/shared/__init__.py +15 -0
- airbyte_cdk/sql/shared/catalog_providers.py +145 -0
- airbyte_cdk/sql/shared/sql_processor.py +786 -0
- airbyte_cdk/sql/types.py +160 -0
- airbyte_cdk/test/catalog_builder.py +70 -18
- airbyte_cdk/test/entrypoint_wrapper.py +117 -42
- airbyte_cdk/test/mock_http/__init__.py +1 -1
- airbyte_cdk/test/mock_http/matcher.py +6 -0
- airbyte_cdk/test/mock_http/mocker.py +57 -10
- airbyte_cdk/test/mock_http/request.py +19 -3
- airbyte_cdk/test/mock_http/response.py +3 -1
- airbyte_cdk/test/mock_http/response_builder.py +32 -16
- airbyte_cdk/test/state_builder.py +18 -10
- airbyte_cdk/test/utils/__init__.py +1 -0
- airbyte_cdk/test/utils/data.py +24 -0
- airbyte_cdk/test/utils/http_mocking.py +16 -0
- airbyte_cdk/test/utils/manifest_only_fixtures.py +60 -0
- airbyte_cdk/test/utils/reading.py +26 -0
- airbyte_cdk/utils/__init__.py +2 -1
- airbyte_cdk/utils/airbyte_secrets_utils.py +5 -3
- airbyte_cdk/utils/analytics_message.py +10 -2
- airbyte_cdk/utils/datetime_format_inferrer.py +4 -1
- airbyte_cdk/utils/event_timing.py +10 -10
- airbyte_cdk/utils/mapping_helpers.py +3 -1
- airbyte_cdk/utils/message_utils.py +20 -11
- airbyte_cdk/utils/print_buffer.py +75 -0
- airbyte_cdk/utils/schema_inferrer.py +198 -28
- airbyte_cdk/utils/slice_hasher.py +30 -0
- airbyte_cdk/utils/spec_schema_transformations.py +6 -3
- airbyte_cdk/utils/stream_status_utils.py +8 -1
- airbyte_cdk/utils/traced_exception.py +61 -21
- airbyte_cdk-6.13.1.dev4107.dist-info/METADATA +109 -0
- airbyte_cdk-6.13.1.dev4107.dist-info/RECORD +349 -0
- {airbyte_cdk-0.72.1.dist-info → airbyte_cdk-6.13.1.dev4107.dist-info}/WHEEL +1 -2
- airbyte_cdk-6.13.1.dev4107.dist-info/entry_points.txt +3 -0
- airbyte_cdk/sources/declarative/create_partial.py +0 -92
- airbyte_cdk/sources/declarative/parsers/class_types_registry.py +0 -102
- airbyte_cdk/sources/declarative/parsers/default_implementation_registry.py +0 -64
- airbyte_cdk/sources/declarative/requesters/error_handlers/response_action.py +0 -16
- airbyte_cdk/sources/declarative/requesters/error_handlers/response_status.py +0 -68
- airbyte_cdk/sources/declarative/stream_slicers/cartesian_product_stream_slicer.py +0 -114
- airbyte_cdk/sources/deprecated/base_source.py +0 -94
- airbyte_cdk/sources/deprecated/client.py +0 -99
- airbyte_cdk/sources/singer/__init__.py +0 -8
- airbyte_cdk/sources/singer/singer_helpers.py +0 -304
- airbyte_cdk/sources/singer/source.py +0 -186
- airbyte_cdk/sources/streams/concurrent/partitions/record.py +0 -23
- airbyte_cdk/sources/streams/http/auth/__init__.py +0 -17
- airbyte_cdk/sources/streams/http/auth/core.py +0 -29
- airbyte_cdk/sources/streams/http/auth/oauth.py +0 -113
- airbyte_cdk/sources/streams/http/auth/token.py +0 -47
- airbyte_cdk/sources/streams/utils/stream_helper.py +0 -40
- airbyte_cdk/sources/utils/catalog_helpers.py +0 -22
- airbyte_cdk/sources/utils/schema_models.py +0 -84
- airbyte_cdk-0.72.1.dist-info/METADATA +0 -243
- airbyte_cdk-0.72.1.dist-info/RECORD +0 -466
- airbyte_cdk-0.72.1.dist-info/top_level.txt +0 -3
- source_declarative_manifest/main.py +0 -29
- unit_tests/connector_builder/__init__.py +0 -3
- unit_tests/connector_builder/test_connector_builder_handler.py +0 -871
- unit_tests/connector_builder/test_message_grouper.py +0 -713
- unit_tests/connector_builder/utils.py +0 -27
- unit_tests/destinations/test_destination.py +0 -243
- unit_tests/singer/test_singer_helpers.py +0 -56
- unit_tests/singer/test_singer_source.py +0 -112
- unit_tests/sources/__init__.py +0 -0
- unit_tests/sources/concurrent_source/__init__.py +0 -3
- unit_tests/sources/concurrent_source/test_concurrent_source_adapter.py +0 -106
- unit_tests/sources/declarative/__init__.py +0 -3
- unit_tests/sources/declarative/auth/__init__.py +0 -3
- unit_tests/sources/declarative/auth/test_oauth.py +0 -331
- unit_tests/sources/declarative/auth/test_selective_authenticator.py +0 -39
- unit_tests/sources/declarative/auth/test_session_token_auth.py +0 -182
- unit_tests/sources/declarative/auth/test_token_auth.py +0 -200
- unit_tests/sources/declarative/auth/test_token_provider.py +0 -73
- unit_tests/sources/declarative/checks/__init__.py +0 -3
- unit_tests/sources/declarative/checks/test_check_stream.py +0 -146
- unit_tests/sources/declarative/decoders/__init__.py +0 -0
- unit_tests/sources/declarative/decoders/test_json_decoder.py +0 -16
- unit_tests/sources/declarative/external_component.py +0 -13
- unit_tests/sources/declarative/extractors/__init__.py +0 -3
- unit_tests/sources/declarative/extractors/test_dpath_extractor.py +0 -55
- unit_tests/sources/declarative/extractors/test_record_filter.py +0 -55
- unit_tests/sources/declarative/extractors/test_record_selector.py +0 -179
- unit_tests/sources/declarative/incremental/__init__.py +0 -0
- unit_tests/sources/declarative/incremental/test_datetime_based_cursor.py +0 -860
- unit_tests/sources/declarative/incremental/test_per_partition_cursor.py +0 -406
- unit_tests/sources/declarative/incremental/test_per_partition_cursor_integration.py +0 -332
- unit_tests/sources/declarative/interpolation/__init__.py +0 -3
- unit_tests/sources/declarative/interpolation/test_filters.py +0 -80
- unit_tests/sources/declarative/interpolation/test_interpolated_boolean.py +0 -40
- unit_tests/sources/declarative/interpolation/test_interpolated_mapping.py +0 -35
- unit_tests/sources/declarative/interpolation/test_interpolated_nested_mapping.py +0 -45
- unit_tests/sources/declarative/interpolation/test_interpolated_string.py +0 -25
- unit_tests/sources/declarative/interpolation/test_jinja.py +0 -240
- unit_tests/sources/declarative/interpolation/test_macros.py +0 -73
- unit_tests/sources/declarative/parsers/__init__.py +0 -3
- unit_tests/sources/declarative/parsers/test_manifest_component_transformer.py +0 -406
- unit_tests/sources/declarative/parsers/test_manifest_reference_resolver.py +0 -139
- unit_tests/sources/declarative/parsers/test_model_to_component_factory.py +0 -1847
- unit_tests/sources/declarative/parsers/testing_components.py +0 -36
- unit_tests/sources/declarative/partition_routers/__init__.py +0 -3
- unit_tests/sources/declarative/partition_routers/test_list_partition_router.py +0 -155
- unit_tests/sources/declarative/partition_routers/test_single_partition_router.py +0 -14
- unit_tests/sources/declarative/partition_routers/test_substream_partition_router.py +0 -404
- unit_tests/sources/declarative/requesters/__init__.py +0 -3
- unit_tests/sources/declarative/requesters/error_handlers/__init__.py +0 -3
- unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/__init__.py +0 -3
- unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_constant_backoff.py +0 -34
- unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_exponential_backoff.py +0 -36
- unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_header_helper.py +0 -38
- unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_wait_time_from_header.py +0 -35
- unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_wait_until_time_from_header.py +0 -64
- unit_tests/sources/declarative/requesters/error_handlers/test_composite_error_handler.py +0 -213
- unit_tests/sources/declarative/requesters/error_handlers/test_default_error_handler.py +0 -178
- unit_tests/sources/declarative/requesters/error_handlers/test_http_response_filter.py +0 -121
- unit_tests/sources/declarative/requesters/error_handlers/test_response_status.py +0 -44
- unit_tests/sources/declarative/requesters/paginators/__init__.py +0 -3
- unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py +0 -64
- unit_tests/sources/declarative/requesters/paginators/test_default_paginator.py +0 -313
- unit_tests/sources/declarative/requesters/paginators/test_no_paginator.py +0 -12
- unit_tests/sources/declarative/requesters/paginators/test_offset_increment.py +0 -58
- unit_tests/sources/declarative/requesters/paginators/test_page_increment.py +0 -70
- unit_tests/sources/declarative/requesters/paginators/test_request_option.py +0 -43
- unit_tests/sources/declarative/requesters/paginators/test_stop_condition.py +0 -105
- unit_tests/sources/declarative/requesters/request_options/__init__.py +0 -3
- unit_tests/sources/declarative/requesters/request_options/test_interpolated_request_options_provider.py +0 -101
- unit_tests/sources/declarative/requesters/test_http_requester.py +0 -974
- unit_tests/sources/declarative/requesters/test_interpolated_request_input_provider.py +0 -32
- unit_tests/sources/declarative/retrievers/__init__.py +0 -3
- unit_tests/sources/declarative/retrievers/test_simple_retriever.py +0 -542
- unit_tests/sources/declarative/schema/__init__.py +0 -6
- unit_tests/sources/declarative/schema/source_test/SourceTest.py +0 -8
- unit_tests/sources/declarative/schema/source_test/__init__.py +0 -3
- unit_tests/sources/declarative/schema/test_default_schema_loader.py +0 -32
- unit_tests/sources/declarative/schema/test_inline_schema_loader.py +0 -19
- unit_tests/sources/declarative/schema/test_json_file_schema_loader.py +0 -26
- unit_tests/sources/declarative/states/__init__.py +0 -3
- unit_tests/sources/declarative/stream_slicers/__init__.py +0 -3
- unit_tests/sources/declarative/stream_slicers/test_cartesian_product_stream_slicer.py +0 -225
- unit_tests/sources/declarative/test_create_partial.py +0 -83
- unit_tests/sources/declarative/test_declarative_stream.py +0 -103
- unit_tests/sources/declarative/test_manifest_declarative_source.py +0 -1260
- unit_tests/sources/declarative/test_types.py +0 -39
- unit_tests/sources/declarative/test_yaml_declarative_source.py +0 -148
- unit_tests/sources/file_based/__init__.py +0 -0
- unit_tests/sources/file_based/availability_strategy/__init__.py +0 -0
- unit_tests/sources/file_based/availability_strategy/test_default_file_based_availability_strategy.py +0 -100
- unit_tests/sources/file_based/config/__init__.py +0 -0
- unit_tests/sources/file_based/config/test_abstract_file_based_spec.py +0 -28
- unit_tests/sources/file_based/config/test_csv_format.py +0 -34
- unit_tests/sources/file_based/config/test_file_based_stream_config.py +0 -84
- unit_tests/sources/file_based/discovery_policy/__init__.py +0 -0
- unit_tests/sources/file_based/discovery_policy/test_default_discovery_policy.py +0 -31
- unit_tests/sources/file_based/file_types/__init__.py +0 -0
- unit_tests/sources/file_based/file_types/test_avro_parser.py +0 -243
- unit_tests/sources/file_based/file_types/test_csv_parser.py +0 -546
- unit_tests/sources/file_based/file_types/test_jsonl_parser.py +0 -158
- unit_tests/sources/file_based/file_types/test_parquet_parser.py +0 -274
- unit_tests/sources/file_based/file_types/test_unstructured_parser.py +0 -593
- unit_tests/sources/file_based/helpers.py +0 -70
- unit_tests/sources/file_based/in_memory_files_source.py +0 -211
- unit_tests/sources/file_based/scenarios/__init__.py +0 -0
- unit_tests/sources/file_based/scenarios/avro_scenarios.py +0 -744
- unit_tests/sources/file_based/scenarios/check_scenarios.py +0 -220
- unit_tests/sources/file_based/scenarios/concurrent_incremental_scenarios.py +0 -2844
- unit_tests/sources/file_based/scenarios/csv_scenarios.py +0 -3105
- unit_tests/sources/file_based/scenarios/file_based_source_builder.py +0 -91
- unit_tests/sources/file_based/scenarios/incremental_scenarios.py +0 -1926
- unit_tests/sources/file_based/scenarios/jsonl_scenarios.py +0 -930
- unit_tests/sources/file_based/scenarios/parquet_scenarios.py +0 -754
- unit_tests/sources/file_based/scenarios/scenario_builder.py +0 -234
- unit_tests/sources/file_based/scenarios/unstructured_scenarios.py +0 -608
- unit_tests/sources/file_based/scenarios/user_input_schema_scenarios.py +0 -746
- unit_tests/sources/file_based/scenarios/validation_policy_scenarios.py +0 -726
- unit_tests/sources/file_based/stream/__init__.py +0 -0
- unit_tests/sources/file_based/stream/concurrent/__init__.py +0 -0
- unit_tests/sources/file_based/stream/concurrent/test_adapters.py +0 -362
- unit_tests/sources/file_based/stream/concurrent/test_file_based_concurrent_cursor.py +0 -458
- unit_tests/sources/file_based/stream/test_default_file_based_cursor.py +0 -310
- unit_tests/sources/file_based/stream/test_default_file_based_stream.py +0 -244
- unit_tests/sources/file_based/test_file_based_scenarios.py +0 -320
- unit_tests/sources/file_based/test_file_based_stream_reader.py +0 -272
- unit_tests/sources/file_based/test_scenarios.py +0 -253
- unit_tests/sources/file_based/test_schema_helpers.py +0 -346
- unit_tests/sources/fixtures/__init__.py +0 -3
- unit_tests/sources/fixtures/source_test_fixture.py +0 -153
- unit_tests/sources/message/__init__.py +0 -0
- unit_tests/sources/message/test_repository.py +0 -153
- unit_tests/sources/streams/__init__.py +0 -0
- unit_tests/sources/streams/concurrent/__init__.py +0 -3
- unit_tests/sources/streams/concurrent/scenarios/__init__.py +0 -3
- unit_tests/sources/streams/concurrent/scenarios/incremental_scenarios.py +0 -250
- unit_tests/sources/streams/concurrent/scenarios/stream_facade_builder.py +0 -140
- unit_tests/sources/streams/concurrent/scenarios/stream_facade_scenarios.py +0 -452
- unit_tests/sources/streams/concurrent/scenarios/test_concurrent_scenarios.py +0 -76
- unit_tests/sources/streams/concurrent/scenarios/thread_based_concurrent_stream_scenarios.py +0 -418
- unit_tests/sources/streams/concurrent/scenarios/thread_based_concurrent_stream_source_builder.py +0 -142
- unit_tests/sources/streams/concurrent/scenarios/utils.py +0 -55
- unit_tests/sources/streams/concurrent/test_adapters.py +0 -380
- unit_tests/sources/streams/concurrent/test_concurrent_read_processor.py +0 -684
- unit_tests/sources/streams/concurrent/test_cursor.py +0 -139
- unit_tests/sources/streams/concurrent/test_datetime_state_converter.py +0 -369
- unit_tests/sources/streams/concurrent/test_default_stream.py +0 -197
- unit_tests/sources/streams/concurrent/test_partition_enqueuer.py +0 -90
- unit_tests/sources/streams/concurrent/test_partition_reader.py +0 -67
- unit_tests/sources/streams/concurrent/test_thread_pool_manager.py +0 -106
- unit_tests/sources/streams/http/__init__.py +0 -0
- unit_tests/sources/streams/http/auth/__init__.py +0 -0
- unit_tests/sources/streams/http/auth/test_auth.py +0 -173
- unit_tests/sources/streams/http/requests_native_auth/__init__.py +0 -0
- unit_tests/sources/streams/http/requests_native_auth/test_requests_native_auth.py +0 -423
- unit_tests/sources/streams/http/test_availability_strategy.py +0 -180
- unit_tests/sources/streams/http/test_http.py +0 -635
- unit_tests/sources/streams/test_availability_strategy.py +0 -70
- unit_tests/sources/streams/test_call_rate.py +0 -300
- unit_tests/sources/streams/test_stream_read.py +0 -405
- unit_tests/sources/streams/test_streams_core.py +0 -184
- unit_tests/sources/test_abstract_source.py +0 -1442
- unit_tests/sources/test_concurrent_source.py +0 -112
- unit_tests/sources/test_config.py +0 -92
- unit_tests/sources/test_connector_state_manager.py +0 -482
- unit_tests/sources/test_http_logger.py +0 -252
- unit_tests/sources/test_integration_source.py +0 -86
- unit_tests/sources/test_source.py +0 -684
- unit_tests/sources/test_source_read.py +0 -460
- unit_tests/test/__init__.py +0 -0
- unit_tests/test/mock_http/__init__.py +0 -0
- unit_tests/test/mock_http/test_matcher.py +0 -53
- unit_tests/test/mock_http/test_mocker.py +0 -214
- unit_tests/test/mock_http/test_request.py +0 -117
- unit_tests/test/mock_http/test_response_builder.py +0 -177
- unit_tests/test/test_entrypoint_wrapper.py +0 -240
- unit_tests/utils/__init__.py +0 -0
- unit_tests/utils/test_datetime_format_inferrer.py +0 -60
- unit_tests/utils/test_mapping_helpers.py +0 -54
- unit_tests/utils/test_message_utils.py +0 -91
- unit_tests/utils/test_rate_limiting.py +0 -26
- unit_tests/utils/test_schema_inferrer.py +0 -202
- unit_tests/utils/test_secret_utils.py +0 -135
- unit_tests/utils/test_stream_status_utils.py +0 -61
- unit_tests/utils/test_traced_exception.py +0 -107
- /airbyte_cdk/sources/{deprecated → declarative/async_job}/__init__.py +0 -0
- {source_declarative_manifest → airbyte_cdk/sources/declarative/migrations}/__init__.py +0 -0
- {unit_tests/destinations → airbyte_cdk/sql}/__init__.py +0 -0
- {unit_tests/singer → airbyte_cdk/sql/_util}/__init__.py +0 -0
- {airbyte_cdk-0.72.1.dist-info → airbyte_cdk-6.13.1.dev4107.dist-info}/LICENSE.txt +0 -0
@@ -0,0 +1,531 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
3
|
+
#
|
4
|
+
|
5
|
+
import logging
|
6
|
+
import os
|
7
|
+
import urllib
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Union
|
10
|
+
|
11
|
+
import orjson
|
12
|
+
import requests
|
13
|
+
import requests_cache
|
14
|
+
from requests.auth import AuthBase
|
15
|
+
|
16
|
+
from airbyte_cdk.models import (
|
17
|
+
AirbyteMessageSerializer,
|
18
|
+
AirbyteStreamStatus,
|
19
|
+
AirbyteStreamStatusReason,
|
20
|
+
AirbyteStreamStatusReasonType,
|
21
|
+
Level,
|
22
|
+
StreamDescriptor,
|
23
|
+
)
|
24
|
+
from airbyte_cdk.sources.http_config import MAX_CONNECTION_POOL_SIZE
|
25
|
+
from airbyte_cdk.sources.message import MessageRepository
|
26
|
+
from airbyte_cdk.sources.streams.call_rate import APIBudget, CachedLimiterSession, LimiterSession
|
27
|
+
from airbyte_cdk.sources.streams.http.error_handlers import (
|
28
|
+
BackoffStrategy,
|
29
|
+
DefaultBackoffStrategy,
|
30
|
+
ErrorHandler,
|
31
|
+
ErrorMessageParser,
|
32
|
+
ErrorResolution,
|
33
|
+
HttpStatusErrorHandler,
|
34
|
+
JsonErrorMessageParser,
|
35
|
+
ResponseAction,
|
36
|
+
)
|
37
|
+
from airbyte_cdk.sources.streams.http.exceptions import (
|
38
|
+
DefaultBackoffException,
|
39
|
+
RateLimitBackoffException,
|
40
|
+
RequestBodyException,
|
41
|
+
UserDefinedBackoffException,
|
42
|
+
)
|
43
|
+
from airbyte_cdk.sources.streams.http.rate_limiting import (
|
44
|
+
http_client_default_backoff_handler,
|
45
|
+
rate_limit_default_backoff_handler,
|
46
|
+
user_defined_backoff_handler,
|
47
|
+
)
|
48
|
+
from airbyte_cdk.sources.utils.types import JsonType
|
49
|
+
from airbyte_cdk.utils.airbyte_secrets_utils import filter_secrets
|
50
|
+
from airbyte_cdk.utils.constants import ENV_REQUEST_CACHE_PATH
|
51
|
+
from airbyte_cdk.utils.stream_status_utils import (
|
52
|
+
as_airbyte_message as stream_status_as_airbyte_message,
|
53
|
+
)
|
54
|
+
from airbyte_cdk.utils.traced_exception import AirbyteTracedException
|
55
|
+
|
56
|
+
BODY_REQUEST_METHODS = ("GET", "POST", "PUT", "PATCH")
|
57
|
+
|
58
|
+
|
59
|
+
class MessageRepresentationAirbyteTracedErrors(AirbyteTracedException):
|
60
|
+
"""
|
61
|
+
Before the migration to the HttpClient in low-code, the exception raised was
|
62
|
+
[ReadException](https://github.com/airbytehq/airbyte/blob/8fdd9818ec16e653ba3dd2b167a74b7c07459861/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/http_requester.py#L566).
|
63
|
+
This has been moved to a AirbyteTracedException. The printing on this is questionable (AirbyteTracedException string representation
|
64
|
+
shows the internal_message and not the message). We have already discussed moving the AirbyteTracedException string representation to
|
65
|
+
`message` but the impact is unclear and hard to quantify so we will do it here only for now.
|
66
|
+
"""
|
67
|
+
|
68
|
+
def __str__(self) -> str:
|
69
|
+
if self.message:
|
70
|
+
return self.message
|
71
|
+
elif self.internal_message:
|
72
|
+
return self.internal_message
|
73
|
+
return ""
|
74
|
+
|
75
|
+
|
76
|
+
class HttpClient:
|
77
|
+
_DEFAULT_MAX_RETRY: int = 5
|
78
|
+
_DEFAULT_MAX_TIME: int = 60 * 10
|
79
|
+
_ACTIONS_TO_RETRY_ON = {ResponseAction.RETRY, ResponseAction.RATE_LIMITED}
|
80
|
+
|
81
|
+
def __init__(
|
82
|
+
self,
|
83
|
+
name: str,
|
84
|
+
logger: logging.Logger,
|
85
|
+
error_handler: Optional[ErrorHandler] = None,
|
86
|
+
api_budget: Optional[APIBudget] = None,
|
87
|
+
session: Optional[Union[requests.Session, requests_cache.CachedSession]] = None,
|
88
|
+
authenticator: Optional[AuthBase] = None,
|
89
|
+
use_cache: bool = False,
|
90
|
+
backoff_strategy: Optional[Union[BackoffStrategy, List[BackoffStrategy]]] = None,
|
91
|
+
error_message_parser: Optional[ErrorMessageParser] = None,
|
92
|
+
disable_retries: bool = False,
|
93
|
+
message_repository: Optional[MessageRepository] = None,
|
94
|
+
):
|
95
|
+
self._name = name
|
96
|
+
self._api_budget: APIBudget = api_budget or APIBudget(policies=[])
|
97
|
+
if session:
|
98
|
+
self._session = session
|
99
|
+
else:
|
100
|
+
self._use_cache = use_cache
|
101
|
+
self._session = self._request_session()
|
102
|
+
self._session.mount(
|
103
|
+
"https://",
|
104
|
+
requests.adapters.HTTPAdapter(
|
105
|
+
pool_connections=MAX_CONNECTION_POOL_SIZE, pool_maxsize=MAX_CONNECTION_POOL_SIZE
|
106
|
+
),
|
107
|
+
)
|
108
|
+
if isinstance(authenticator, AuthBase):
|
109
|
+
self._session.auth = authenticator
|
110
|
+
self._logger = logger
|
111
|
+
self._error_handler = error_handler or HttpStatusErrorHandler(self._logger)
|
112
|
+
if backoff_strategy is not None:
|
113
|
+
if isinstance(backoff_strategy, list):
|
114
|
+
self._backoff_strategies = backoff_strategy
|
115
|
+
else:
|
116
|
+
self._backoff_strategies = [backoff_strategy]
|
117
|
+
else:
|
118
|
+
self._backoff_strategies = [DefaultBackoffStrategy()]
|
119
|
+
self._error_message_parser = error_message_parser or JsonErrorMessageParser()
|
120
|
+
self._request_attempt_count: Dict[requests.PreparedRequest, int] = {}
|
121
|
+
self._disable_retries = disable_retries
|
122
|
+
self._message_repository = message_repository
|
123
|
+
|
124
|
+
@property
|
125
|
+
def cache_filename(self) -> str:
|
126
|
+
"""
|
127
|
+
Override if needed. Return the name of cache file
|
128
|
+
Note that if the environment variable REQUEST_CACHE_PATH is not set, the cache will be in-memory only.
|
129
|
+
"""
|
130
|
+
return f"{self._name}.sqlite"
|
131
|
+
|
132
|
+
def _request_session(self) -> requests.Session:
|
133
|
+
"""
|
134
|
+
Session factory based on use_cache property and call rate limits (api_budget parameter)
|
135
|
+
:return: instance of request-based session
|
136
|
+
"""
|
137
|
+
if self._use_cache:
|
138
|
+
cache_dir = os.getenv(ENV_REQUEST_CACHE_PATH)
|
139
|
+
# Use in-memory cache if cache_dir is not set
|
140
|
+
# This is a non-obvious interface, but it ensures we don't write sql files when running unit tests
|
141
|
+
# Use in-memory cache if cache_dir is not set
|
142
|
+
# This is a non-obvious interface, but it ensures we don't write sql files when running unit tests
|
143
|
+
sqlite_path = (
|
144
|
+
str(Path(cache_dir) / self.cache_filename)
|
145
|
+
if cache_dir
|
146
|
+
else "file::memory:?cache=shared"
|
147
|
+
)
|
148
|
+
# By using `PRAGMA synchronous=OFF` and `PRAGMA journal_mode=WAL`, we reduce the possible occurrences of `database table is locked` errors.
|
149
|
+
# Note that those were blindly added at the same time and one or the other might be sufficient to prevent the issues but we have seen good results with both. Feel free to revisit given more information.
|
150
|
+
# There are strong signals that `fast_save` might create problems but if the sync crashes, we start back from the beginning in terms of sqlite anyway so the impact should be minimal. Signals are:
|
151
|
+
# * https://github.com/requests-cache/requests-cache/commit/7fa89ffda300331c37d8fad7f773348a3b5b0236#diff-f43db4a5edf931647c32dec28ea7557aae4cae8444af4b26c8ecbe88d8c925aaR238
|
152
|
+
# * https://github.com/requests-cache/requests-cache/commit/7fa89ffda300331c37d8fad7f773348a3b5b0236#diff-2e7f95b7d7be270ff1a8118f817ea3e6663cdad273592e536a116c24e6d23c18R164-R168
|
153
|
+
# * `If the application running SQLite crashes, the data will be safe, but the database [might become corrupted](https://www.sqlite.org/howtocorrupt.html#cfgerr) if the operating system crashes or the computer loses power before that data has been written to the disk surface.` in [this description](https://www.sqlite.org/pragma.html#pragma_synchronous).
|
154
|
+
backend = requests_cache.SQLiteCache(sqlite_path, fast_save=True, wal=True)
|
155
|
+
return CachedLimiterSession(
|
156
|
+
sqlite_path, backend=backend, api_budget=self._api_budget, match_headers=True
|
157
|
+
)
|
158
|
+
else:
|
159
|
+
return LimiterSession(api_budget=self._api_budget)
|
160
|
+
|
161
|
+
def clear_cache(self) -> None:
|
162
|
+
"""
|
163
|
+
Clear cached requests for current session, can be called any time
|
164
|
+
"""
|
165
|
+
if isinstance(self._session, requests_cache.CachedSession):
|
166
|
+
self._session.cache.clear() # type: ignore # cache.clear is not typed
|
167
|
+
|
168
|
+
def _dedupe_query_params(
|
169
|
+
self, url: str, params: Optional[Mapping[str, str]]
|
170
|
+
) -> Mapping[str, str]:
|
171
|
+
"""
|
172
|
+
Remove query parameters from params mapping if they are already encoded in the URL.
|
173
|
+
:param url: URL with
|
174
|
+
:param params:
|
175
|
+
:return:
|
176
|
+
"""
|
177
|
+
if params is None:
|
178
|
+
params = {}
|
179
|
+
query_string = urllib.parse.urlparse(url).query
|
180
|
+
query_dict = {k: v[0] for k, v in urllib.parse.parse_qs(query_string).items()}
|
181
|
+
|
182
|
+
duplicate_keys_with_same_value = {
|
183
|
+
k for k in query_dict.keys() if str(params.get(k)) == str(query_dict[k])
|
184
|
+
}
|
185
|
+
return {k: v for k, v in params.items() if k not in duplicate_keys_with_same_value}
|
186
|
+
|
187
|
+
def _create_prepared_request(
|
188
|
+
self,
|
189
|
+
http_method: str,
|
190
|
+
url: str,
|
191
|
+
dedupe_query_params: bool = False,
|
192
|
+
headers: Optional[Mapping[str, str]] = None,
|
193
|
+
params: Optional[Mapping[str, str]] = None,
|
194
|
+
json: Optional[Mapping[str, Any]] = None,
|
195
|
+
data: Optional[Union[str, Mapping[str, Any]]] = None,
|
196
|
+
) -> requests.PreparedRequest:
|
197
|
+
if dedupe_query_params:
|
198
|
+
query_params = self._dedupe_query_params(url, params)
|
199
|
+
else:
|
200
|
+
query_params = params or {}
|
201
|
+
args = {"method": http_method, "url": url, "headers": headers, "params": query_params}
|
202
|
+
if http_method.upper() in BODY_REQUEST_METHODS:
|
203
|
+
if json and data:
|
204
|
+
raise RequestBodyException(
|
205
|
+
"At the same time only one of the 'request_body_data' and 'request_body_json' functions can return data"
|
206
|
+
)
|
207
|
+
elif json:
|
208
|
+
args["json"] = json
|
209
|
+
elif data:
|
210
|
+
args["data"] = data
|
211
|
+
prepared_request: requests.PreparedRequest = self._session.prepare_request(
|
212
|
+
requests.Request(**args)
|
213
|
+
)
|
214
|
+
|
215
|
+
return prepared_request
|
216
|
+
|
217
|
+
@property
|
218
|
+
def _max_retries(self) -> int:
|
219
|
+
"""
|
220
|
+
Determines the max retries based on the provided error handler.
|
221
|
+
"""
|
222
|
+
max_retries = None
|
223
|
+
if self._disable_retries:
|
224
|
+
max_retries = 0
|
225
|
+
else:
|
226
|
+
max_retries = self._error_handler.max_retries
|
227
|
+
return max_retries if max_retries is not None else self._DEFAULT_MAX_RETRY
|
228
|
+
|
229
|
+
@property
|
230
|
+
def _max_time(self) -> int:
|
231
|
+
"""
|
232
|
+
Determines the max time based on the provided error handler.
|
233
|
+
"""
|
234
|
+
return (
|
235
|
+
self._error_handler.max_time
|
236
|
+
if self._error_handler.max_time is not None
|
237
|
+
else self._DEFAULT_MAX_TIME
|
238
|
+
)
|
239
|
+
|
240
|
+
def _send_with_retry(
|
241
|
+
self,
|
242
|
+
request: requests.PreparedRequest,
|
243
|
+
request_kwargs: Mapping[str, Any],
|
244
|
+
log_formatter: Optional[Callable[[requests.Response], Any]] = None,
|
245
|
+
exit_on_rate_limit: Optional[bool] = False,
|
246
|
+
) -> requests.Response:
|
247
|
+
"""
|
248
|
+
Sends a request with retry logic.
|
249
|
+
|
250
|
+
Args:
|
251
|
+
request (requests.PreparedRequest): The prepared HTTP request to send.
|
252
|
+
request_kwargs (Mapping[str, Any]): Additional keyword arguments for the request.
|
253
|
+
|
254
|
+
Returns:
|
255
|
+
requests.Response: The HTTP response received from the server after retries.
|
256
|
+
"""
|
257
|
+
|
258
|
+
max_retries = self._max_retries
|
259
|
+
max_tries = max(0, max_retries) + 1
|
260
|
+
max_time = self._max_time
|
261
|
+
|
262
|
+
user_backoff_handler = user_defined_backoff_handler(max_tries=max_tries, max_time=max_time)(
|
263
|
+
self._send
|
264
|
+
)
|
265
|
+
rate_limit_backoff_handler = rate_limit_default_backoff_handler(max_tries=max_tries)
|
266
|
+
backoff_handler = http_client_default_backoff_handler(
|
267
|
+
max_tries=max_tries, max_time=max_time
|
268
|
+
)
|
269
|
+
# backoff handlers wrap _send, so it will always return a response
|
270
|
+
response = backoff_handler(rate_limit_backoff_handler(user_backoff_handler))(
|
271
|
+
request,
|
272
|
+
request_kwargs,
|
273
|
+
log_formatter=log_formatter,
|
274
|
+
exit_on_rate_limit=exit_on_rate_limit,
|
275
|
+
) # type: ignore # mypy can't infer that backoff_handler wraps _send
|
276
|
+
|
277
|
+
return response
|
278
|
+
|
279
|
+
def _send(
|
280
|
+
self,
|
281
|
+
request: requests.PreparedRequest,
|
282
|
+
request_kwargs: Mapping[str, Any],
|
283
|
+
log_formatter: Optional[Callable[[requests.Response], Any]] = None,
|
284
|
+
exit_on_rate_limit: Optional[bool] = False,
|
285
|
+
) -> requests.Response:
|
286
|
+
if request not in self._request_attempt_count:
|
287
|
+
self._request_attempt_count[request] = 1
|
288
|
+
else:
|
289
|
+
self._request_attempt_count[request] += 1
|
290
|
+
if hasattr(self._session, "auth") and isinstance(self._session.auth, AuthBase):
|
291
|
+
self._session.auth(request)
|
292
|
+
|
293
|
+
self._logger.debug(
|
294
|
+
"Making outbound API request",
|
295
|
+
extra={"headers": request.headers, "url": request.url, "request_body": request.body},
|
296
|
+
)
|
297
|
+
|
298
|
+
response: Optional[requests.Response] = None
|
299
|
+
exc: Optional[requests.RequestException] = None
|
300
|
+
|
301
|
+
try:
|
302
|
+
response = self._session.send(request, **request_kwargs)
|
303
|
+
except requests.RequestException as e:
|
304
|
+
exc = e
|
305
|
+
|
306
|
+
error_resolution: ErrorResolution = self._error_handler.interpret_response(
|
307
|
+
response if response is not None else exc
|
308
|
+
)
|
309
|
+
|
310
|
+
# Evaluation of response.text can be heavy, for example, if streaming a large response
|
311
|
+
# Do it only in debug mode
|
312
|
+
if self._logger.isEnabledFor(logging.DEBUG) and response is not None:
|
313
|
+
if request_kwargs.get("stream"):
|
314
|
+
self._logger.debug(
|
315
|
+
"Receiving response, but not logging it as the response is streamed",
|
316
|
+
extra={"headers": response.headers, "status": response.status_code},
|
317
|
+
)
|
318
|
+
else:
|
319
|
+
self._logger.debug(
|
320
|
+
"Receiving response",
|
321
|
+
extra={
|
322
|
+
"headers": response.headers,
|
323
|
+
"status": response.status_code,
|
324
|
+
"body": response.text,
|
325
|
+
},
|
326
|
+
)
|
327
|
+
|
328
|
+
# Request/response logging for declarative cdk
|
329
|
+
if (
|
330
|
+
log_formatter is not None
|
331
|
+
and response is not None
|
332
|
+
and self._message_repository is not None
|
333
|
+
):
|
334
|
+
formatter = log_formatter
|
335
|
+
self._message_repository.log_message(
|
336
|
+
Level.DEBUG,
|
337
|
+
lambda: formatter(response),
|
338
|
+
)
|
339
|
+
|
340
|
+
self._handle_error_resolution(
|
341
|
+
response=response,
|
342
|
+
exc=exc,
|
343
|
+
request=request,
|
344
|
+
error_resolution=error_resolution,
|
345
|
+
exit_on_rate_limit=exit_on_rate_limit,
|
346
|
+
)
|
347
|
+
|
348
|
+
return response # type: ignore # will either return a valid response of type requests.Response or raise an exception
|
349
|
+
|
350
|
+
def _get_response_body(self, response: requests.Response) -> Optional[JsonType]:
|
351
|
+
"""
|
352
|
+
Extracts and returns the body of an HTTP response.
|
353
|
+
|
354
|
+
This method attempts to parse the response body as JSON. If the response
|
355
|
+
body is not valid JSON, it falls back to decoding the response content
|
356
|
+
as a UTF-8 string. If both attempts fail, it returns None.
|
357
|
+
|
358
|
+
Args:
|
359
|
+
response (requests.Response): The HTTP response object.
|
360
|
+
|
361
|
+
Returns:
|
362
|
+
Optional[JsonType]: The parsed JSON object as a string, the decoded
|
363
|
+
response content as a string, or None if both parsing attempts fail.
|
364
|
+
"""
|
365
|
+
try:
|
366
|
+
return str(response.json())
|
367
|
+
except requests.exceptions.JSONDecodeError:
|
368
|
+
try:
|
369
|
+
return response.content.decode("utf-8")
|
370
|
+
except Exception:
|
371
|
+
return "The Content of the Response couldn't be decoded."
|
372
|
+
|
373
|
+
def _evict_key(self, prepared_request: requests.PreparedRequest) -> None:
|
374
|
+
"""
|
375
|
+
Addresses high memory consumption when enabling concurrency in https://github.com/airbytehq/oncall/issues/6821.
|
376
|
+
|
377
|
+
The `_request_attempt_count` attribute keeps growing as multiple requests are made using the same `http_client`.
|
378
|
+
To mitigate this issue, we evict keys for completed requests once we confirm that no further retries are needed.
|
379
|
+
This helps manage memory usage more efficiently while maintaining the necessary logic for retry attempts.
|
380
|
+
"""
|
381
|
+
if prepared_request in self._request_attempt_count:
|
382
|
+
del self._request_attempt_count[prepared_request]
|
383
|
+
|
384
|
+
def _handle_error_resolution(
|
385
|
+
self,
|
386
|
+
response: Optional[requests.Response],
|
387
|
+
exc: Optional[requests.RequestException],
|
388
|
+
request: requests.PreparedRequest,
|
389
|
+
error_resolution: ErrorResolution,
|
390
|
+
exit_on_rate_limit: Optional[bool] = False,
|
391
|
+
) -> None:
|
392
|
+
if error_resolution.response_action not in self._ACTIONS_TO_RETRY_ON:
|
393
|
+
self._evict_key(request)
|
394
|
+
|
395
|
+
# Emit stream status RUNNING with the reason RATE_LIMITED to log that the rate limit has been reached
|
396
|
+
if error_resolution.response_action == ResponseAction.RATE_LIMITED:
|
397
|
+
# TODO: Update to handle with message repository when concurrent message repository is ready
|
398
|
+
reasons = [AirbyteStreamStatusReason(type=AirbyteStreamStatusReasonType.RATE_LIMITED)]
|
399
|
+
message = orjson.dumps(
|
400
|
+
AirbyteMessageSerializer.dump(
|
401
|
+
stream_status_as_airbyte_message(
|
402
|
+
StreamDescriptor(name=self._name), AirbyteStreamStatus.RUNNING, reasons
|
403
|
+
)
|
404
|
+
)
|
405
|
+
).decode()
|
406
|
+
|
407
|
+
# Simply printing the stream status is a temporary solution and can cause future issues. Currently, the _send method is
|
408
|
+
# wrapped with backoff decorators, and we can only emit messages by iterating record_iterator in the abstract source at the
|
409
|
+
# end of the retry decorator behavior. This approach does not allow us to emit messages in the queue before exiting the
|
410
|
+
# backoff retry loop. Adding `\n` to the message and ignore 'end' ensure that few messages are printed at the same time.
|
411
|
+
print(f"{message}\n", end="", flush=True)
|
412
|
+
|
413
|
+
if error_resolution.response_action == ResponseAction.FAIL:
|
414
|
+
if response is not None:
|
415
|
+
filtered_response_message = filter_secrets(
|
416
|
+
f"Request (body): '{str(request.body)}'. Response (body): '{self._get_response_body(response)}'. Response (headers): '{response.headers}'."
|
417
|
+
)
|
418
|
+
error_message = f"'{request.method}' request to '{request.url}' failed with status code '{response.status_code}' and error message: '{self._error_message_parser.parse_response_error_message(response)}'. {filtered_response_message}"
|
419
|
+
else:
|
420
|
+
error_message = (
|
421
|
+
f"'{request.method}' request to '{request.url}' failed with exception: '{exc}'"
|
422
|
+
)
|
423
|
+
|
424
|
+
# ensure the exception message is emitted before raised
|
425
|
+
self._logger.error(error_message)
|
426
|
+
|
427
|
+
raise MessageRepresentationAirbyteTracedErrors(
|
428
|
+
internal_message=error_message,
|
429
|
+
message=error_resolution.error_message or error_message,
|
430
|
+
failure_type=error_resolution.failure_type,
|
431
|
+
)
|
432
|
+
|
433
|
+
elif error_resolution.response_action == ResponseAction.IGNORE:
|
434
|
+
if response is not None:
|
435
|
+
log_message = f"Ignoring response for '{request.method}' request to '{request.url}' with response code '{response.status_code}'"
|
436
|
+
else:
|
437
|
+
log_message = f"Ignoring response for '{request.method}' request to '{request.url}' with error '{exc}'"
|
438
|
+
|
439
|
+
self._logger.info(error_resolution.error_message or log_message)
|
440
|
+
|
441
|
+
# TODO: Consider dynamic retry count depending on subsequent error codes
|
442
|
+
elif (
|
443
|
+
error_resolution.response_action == ResponseAction.RETRY
|
444
|
+
or error_resolution.response_action == ResponseAction.RATE_LIMITED
|
445
|
+
):
|
446
|
+
user_defined_backoff_time = None
|
447
|
+
for backoff_strategy in self._backoff_strategies:
|
448
|
+
backoff_time = backoff_strategy.backoff_time(
|
449
|
+
response_or_exception=response if response is not None else exc,
|
450
|
+
attempt_count=self._request_attempt_count[request],
|
451
|
+
)
|
452
|
+
if backoff_time:
|
453
|
+
user_defined_backoff_time = backoff_time
|
454
|
+
break
|
455
|
+
error_message = (
|
456
|
+
error_resolution.error_message
|
457
|
+
or f"Request to {request.url} failed with failure type {error_resolution.failure_type}, response action {error_resolution.response_action}."
|
458
|
+
)
|
459
|
+
|
460
|
+
retry_endlessly = (
|
461
|
+
error_resolution.response_action == ResponseAction.RATE_LIMITED
|
462
|
+
and not exit_on_rate_limit
|
463
|
+
)
|
464
|
+
|
465
|
+
if user_defined_backoff_time:
|
466
|
+
raise UserDefinedBackoffException(
|
467
|
+
backoff=user_defined_backoff_time,
|
468
|
+
request=request,
|
469
|
+
response=(response if response is not None else exc),
|
470
|
+
error_message=error_message,
|
471
|
+
)
|
472
|
+
|
473
|
+
elif retry_endlessly:
|
474
|
+
raise RateLimitBackoffException(
|
475
|
+
request=request,
|
476
|
+
response=(response if response is not None else exc),
|
477
|
+
error_message=error_message,
|
478
|
+
)
|
479
|
+
|
480
|
+
raise DefaultBackoffException(
|
481
|
+
request=request,
|
482
|
+
response=(response if response is not None else exc),
|
483
|
+
error_message=error_message,
|
484
|
+
)
|
485
|
+
|
486
|
+
elif response:
|
487
|
+
try:
|
488
|
+
response.raise_for_status()
|
489
|
+
except requests.HTTPError as e:
|
490
|
+
self._logger.error(response.text)
|
491
|
+
raise e
|
492
|
+
|
493
|
+
@property
|
494
|
+
def name(self) -> str:
|
495
|
+
return self._name
|
496
|
+
|
497
|
+
def send_request(
|
498
|
+
self,
|
499
|
+
http_method: str,
|
500
|
+
url: str,
|
501
|
+
request_kwargs: Mapping[str, Any],
|
502
|
+
headers: Optional[Mapping[str, str]] = None,
|
503
|
+
params: Optional[Mapping[str, str]] = None,
|
504
|
+
json: Optional[Mapping[str, Any]] = None,
|
505
|
+
data: Optional[Union[str, Mapping[str, Any]]] = None,
|
506
|
+
dedupe_query_params: bool = False,
|
507
|
+
log_formatter: Optional[Callable[[requests.Response], Any]] = None,
|
508
|
+
exit_on_rate_limit: Optional[bool] = False,
|
509
|
+
) -> Tuple[requests.PreparedRequest, requests.Response]:
|
510
|
+
"""
|
511
|
+
Prepares and sends request and return request and response objects.
|
512
|
+
"""
|
513
|
+
|
514
|
+
request: requests.PreparedRequest = self._create_prepared_request(
|
515
|
+
http_method=http_method,
|
516
|
+
url=url,
|
517
|
+
dedupe_query_params=dedupe_query_params,
|
518
|
+
headers=headers,
|
519
|
+
params=params,
|
520
|
+
json=json,
|
521
|
+
data=data,
|
522
|
+
)
|
523
|
+
|
524
|
+
response: requests.Response = self._send_with_retry(
|
525
|
+
request=request,
|
526
|
+
request_kwargs=request_kwargs,
|
527
|
+
log_formatter=log_formatter,
|
528
|
+
exit_on_rate_limit=exit_on_rate_limit,
|
529
|
+
)
|
530
|
+
|
531
|
+
return request, response
|
@@ -10,7 +10,11 @@ from typing import Any, Callable, Mapping, Optional
|
|
10
10
|
import backoff
|
11
11
|
from requests import PreparedRequest, RequestException, Response, codes, exceptions
|
12
12
|
|
13
|
-
from .exceptions import
|
13
|
+
from .exceptions import (
|
14
|
+
DefaultBackoffException,
|
15
|
+
RateLimitBackoffException,
|
16
|
+
UserDefinedBackoffException,
|
17
|
+
)
|
14
18
|
|
15
19
|
TRANSIENT_EXCEPTIONS = (
|
16
20
|
DefaultBackoffException,
|
@@ -32,7 +36,9 @@ def default_backoff_handler(
|
|
32
36
|
def log_retry_attempt(details: Mapping[str, Any]) -> None:
|
33
37
|
_, exc, _ = sys.exc_info()
|
34
38
|
if isinstance(exc, RequestException) and exc.response:
|
35
|
-
logger.info(
|
39
|
+
logger.info(
|
40
|
+
f"Status code: {exc.response.status_code!r}, Response Content: {exc.response.content!r}"
|
41
|
+
)
|
36
42
|
logger.info(
|
37
43
|
f"Caught retryable error '{str(exc)}' after {details['tries']} tries. Waiting {details['wait']} seconds then retrying..."
|
38
44
|
)
|
@@ -40,16 +46,19 @@ def default_backoff_handler(
|
|
40
46
|
def should_give_up(exc: Exception) -> bool:
|
41
47
|
# If a non-rate-limiting related 4XX error makes it this far, it means it was unexpected and probably consistent, so we shouldn't back off
|
42
48
|
if isinstance(exc, RequestException):
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
+
if exc.response is not None:
|
50
|
+
give_up: bool = (
|
51
|
+
exc.response is not None
|
52
|
+
and exc.response.status_code != codes.too_many_requests
|
53
|
+
and 400 <= exc.response.status_code < 500
|
54
|
+
)
|
55
|
+
if give_up:
|
56
|
+
logger.info(f"Giving up for returned HTTP status: {exc.response.status_code!r}")
|
57
|
+
return give_up
|
49
58
|
# Only RequestExceptions are retryable, so if we get here, it's not retryable
|
50
59
|
return False
|
51
60
|
|
52
|
-
return backoff.on_exception(
|
61
|
+
return backoff.on_exception( # type: ignore # Decorator function returns a function with a different signature than the input function, so mypy can't infer the type of the returned function
|
53
62
|
backoff.expo,
|
54
63
|
TRANSIENT_EXCEPTIONS,
|
55
64
|
jitter=None,
|
@@ -62,6 +71,35 @@ def default_backoff_handler(
|
|
62
71
|
)
|
63
72
|
|
64
73
|
|
74
|
+
def http_client_default_backoff_handler(
|
75
|
+
max_tries: Optional[int], max_time: Optional[int] = None, **kwargs: Any
|
76
|
+
) -> Callable[[SendRequestCallableType], SendRequestCallableType]:
|
77
|
+
def log_retry_attempt(details: Mapping[str, Any]) -> None:
|
78
|
+
_, exc, _ = sys.exc_info()
|
79
|
+
if isinstance(exc, RequestException) and exc.response:
|
80
|
+
logger.info(
|
81
|
+
f"Status code: {exc.response.status_code!r}, Response Content: {exc.response.content!r}"
|
82
|
+
)
|
83
|
+
logger.info(
|
84
|
+
f"Caught retryable error '{str(exc)}' after {details['tries']} tries. Waiting {details['wait']} seconds then retrying..."
|
85
|
+
)
|
86
|
+
|
87
|
+
def should_give_up(exc: Exception) -> bool:
|
88
|
+
# If made it here, the ResponseAction was RETRY and therefore should not give up
|
89
|
+
return False
|
90
|
+
|
91
|
+
return backoff.on_exception( # type: ignore # Decorator function returns a function with a different signature than the input function, so mypy can't infer the type of the returned function
|
92
|
+
backoff.expo,
|
93
|
+
TRANSIENT_EXCEPTIONS,
|
94
|
+
jitter=None,
|
95
|
+
on_backoff=log_retry_attempt,
|
96
|
+
giveup=should_give_up,
|
97
|
+
max_tries=max_tries,
|
98
|
+
max_time=max_time,
|
99
|
+
**kwargs,
|
100
|
+
)
|
101
|
+
|
102
|
+
|
65
103
|
def user_defined_backoff_handler(
|
66
104
|
max_tries: Optional[int], max_time: Optional[int] = None, **kwargs: Any
|
67
105
|
) -> Callable[[SendRequestCallableType], SendRequestCallableType]:
|
@@ -69,7 +107,9 @@ def user_defined_backoff_handler(
|
|
69
107
|
_, exc, _ = sys.exc_info()
|
70
108
|
if isinstance(exc, UserDefinedBackoffException):
|
71
109
|
if exc.response:
|
72
|
-
logger.info(
|
110
|
+
logger.info(
|
111
|
+
f"Status code: {exc.response.status_code!r}, Response Content: {exc.response.content!r}"
|
112
|
+
)
|
73
113
|
retry_after = exc.backoff
|
74
114
|
logger.info(f"Retrying. Sleeping for {retry_after} seconds")
|
75
115
|
time.sleep(retry_after + 1) # extra second to cover any fractions of second
|
@@ -77,11 +117,13 @@ def user_defined_backoff_handler(
|
|
77
117
|
def log_give_up(details: Mapping[str, Any]) -> None:
|
78
118
|
_, exc, _ = sys.exc_info()
|
79
119
|
if isinstance(exc, RequestException):
|
80
|
-
logger.error(
|
120
|
+
logger.error(
|
121
|
+
f"Max retry limit reached in {details['elapsed']}s. Request: {exc.request}, Response: {exc.response}"
|
122
|
+
)
|
81
123
|
else:
|
82
124
|
logger.error("Max retry limit reached for unknown request and response")
|
83
125
|
|
84
|
-
return backoff.on_exception(
|
126
|
+
return backoff.on_exception( # type: ignore # Decorator function returns a function with a different signature than the input function, so mypy can't infer the type of the returned function
|
85
127
|
backoff.constant,
|
86
128
|
UserDefinedBackoffException,
|
87
129
|
interval=0, # skip waiting, we'll wait in on_backoff handler
|
@@ -92,3 +134,25 @@ def user_defined_backoff_handler(
|
|
92
134
|
max_time=max_time,
|
93
135
|
**kwargs,
|
94
136
|
)
|
137
|
+
|
138
|
+
|
139
|
+
def rate_limit_default_backoff_handler(
|
140
|
+
**kwargs: Any,
|
141
|
+
) -> Callable[[SendRequestCallableType], SendRequestCallableType]:
|
142
|
+
def log_retry_attempt(details: Mapping[str, Any]) -> None:
|
143
|
+
_, exc, _ = sys.exc_info()
|
144
|
+
if isinstance(exc, RequestException) and exc.response:
|
145
|
+
logger.info(
|
146
|
+
f"Status code: {exc.response.status_code!r}, Response Content: {exc.response.content!r}"
|
147
|
+
)
|
148
|
+
logger.info(
|
149
|
+
f"Caught retryable error '{str(exc)}' after {details['tries']} tries. Waiting {details['wait']} seconds then retrying..."
|
150
|
+
)
|
151
|
+
|
152
|
+
return backoff.on_exception( # type: ignore # Decorator function returns a function with a different signature than the input function, so mypy can't infer the type of the returned function
|
153
|
+
backoff.expo,
|
154
|
+
RateLimitBackoffException,
|
155
|
+
jitter=None,
|
156
|
+
on_backoff=log_retry_attempt,
|
157
|
+
**kwargs,
|
158
|
+
)
|