airbyte-cdk 0.39.2__tar.gz → 0.39.4__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/PKG-INFO +1 -1
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/connector_builder/message_grouper.py +17 -19
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/connector_builder/models.py +1 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/auth/oauth.py +11 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/declarative_component_schema.yaml +40 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/models/declarative_component_schema.py +32 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +22 -2
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py +30 -46
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk.egg-info/PKG-INFO +1 -1
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/setup.py +1 -1
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/connector_builder/test_connector_builder_handler.py +4 -1
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/connector_builder/test_message_grouper.py +100 -3
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/parsers/test_model_to_component_factory.py +40 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/http/requests_native_auth/test_requests_native_auth.py +20 -22
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/LICENSE.txt +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/README.md +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/config_observation.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/connector.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/connector_builder/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/connector_builder/connector_builder_handler.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/connector_builder/main.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/destinations/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/destinations/destination.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/entrypoint.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/exception_handler.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/logger.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/models/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/models/airbyte_protocol.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/models/well_known_types.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/py.typed +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/abstract_source.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/config.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/connector_state_manager.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/auth/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/auth/declarative_authenticator.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/auth/token.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/checks/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/checks/check_stream.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/checks/connection_checker.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/create_partial.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/datetime/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/datetime/datetime_parser.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/datetime/min_max_datetime.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/declarative_source.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/declarative_stream.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/decoders/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/decoders/decoder.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/decoders/json_decoder.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/exceptions.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/extractors/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/extractors/dpath_extractor.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/extractors/http_selector.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/extractors/record_extractor.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/extractors/record_filter.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/extractors/record_selector.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/incremental/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/interpolation/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/interpolation/filters.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/interpolation/interpolated_boolean.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/interpolation/interpolated_mapping.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/interpolation/interpolated_nested_mapping.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/interpolation/interpolated_string.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/interpolation/interpolation.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/interpolation/jinja.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/interpolation/macros.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/manifest_declarative_source.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/models/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/parsers/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/parsers/class_types_registry.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/parsers/custom_exceptions.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/parsers/default_implementation_registry.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/parsers/manifest_reference_resolver.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/partition_routers/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/partition_routers/single_partition_router.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/constant_backoff_strategy.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/exponential_backoff_strategy.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/header_helper.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_time_from_header_backoff_strategy.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_until_time_from_header_backoff_strategy.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategy.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/composite_error_handler.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/error_handler.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/response_action.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/response_status.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/http_requester.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/paginators/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/paginators/paginator.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/paginators/strategies/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/request_option.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/request_options/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/request_options/interpolated_nested_request_input_provider.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_input_provider.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/request_options/request_options_provider.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/request_path.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/requester.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/retrievers/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/retrievers/retriever.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/schema/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/schema/default_schema_loader.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/schema/inline_schema_loader.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/schema/json_file_schema_loader.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/schema/schema_loader.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/spec/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/spec/spec.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/stream_slicers/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/stream_slicers/cartesian_product_stream_slicer.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/stream_slicers/stream_slicer.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/transformations/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/transformations/add_fields.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/transformations/remove_fields.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/transformations/transformation.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/types.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/yaml_declarative_source.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/deprecated/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/deprecated/base_source.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/deprecated/client.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/singer/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/singer/singer_helpers.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/singer/source.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/source.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/availability_strategy.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/core.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/auth/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/auth/core.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/auth/oauth.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/auth/token.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/availability_strategy.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/exceptions.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/http.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/rate_limiting.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/requests_native_auth/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/requests_native_auth/abstract_token.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/requests_native_auth/token.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/utils/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/utils/stream_helper.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/utils/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/utils/casing.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/utils/catalog_helpers.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/utils/record_helper.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/utils/schema_helpers.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/utils/schema_models.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/utils/transform.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/utils/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/utils/airbyte_secrets_utils.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/utils/event_timing.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/utils/schema_inferrer.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/utils/stream_status_utils.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/utils/traced_exception.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk.egg-info/SOURCES.txt +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk.egg-info/dependency_links.txt +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk.egg-info/requires.txt +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk.egg-info/top_level.txt +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/pyproject.toml +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/setup.cfg +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/source_declarative_manifest/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/source_declarative_manifest/main.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/connector_builder/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/connector_builder/utils.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/destinations/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/destinations/test_destination.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/singer/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/singer/test_singer_helpers.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/singer/test_singer_source.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/auth/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/auth/test_oauth.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/auth/test_session_token_auth.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/auth/test_token_auth.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/checks/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/checks/test_check_stream.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/decoders/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/decoders/test_json_decoder.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/external_component.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/extractors/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/extractors/test_dpath_extractor.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/extractors/test_record_filter.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/extractors/test_record_selector.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/incremental/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/incremental/test_datetime_based_cursor.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/interpolation/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/interpolation/test_filters.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/interpolation/test_interpolated_boolean.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/interpolation/test_interpolated_mapping.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/interpolation/test_interpolated_nested_mapping.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/interpolation/test_interpolated_string.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/interpolation/test_jinja.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/interpolation/test_macros.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/parsers/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/parsers/test_manifest_component_transformer.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/parsers/test_manifest_reference_resolver.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/parsers/testing_components.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/partition_routers/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/partition_routers/test_list_partition_router.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/partition_routers/test_single_partition_router.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/partition_routers/test_substream_partition_router.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_constant_backoff.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_exponential_backoff.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_header_helper.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_wait_time_from_header.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_wait_until_time_from_header.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/test_composite_error_handler.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/test_default_error_handler.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/test_http_response_filter.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/test_response_status.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/paginators/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/paginators/test_default_paginator.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/paginators/test_no_paginator.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/paginators/test_offset_increment.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/paginators/test_page_increment.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/paginators/test_request_option.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/request_options/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/request_options/test_interpolated_request_options_provider.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/test_http_requester.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/test_interpolated_request_input_provider.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/retrievers/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/retrievers/test_simple_retriever.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/schema/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/schema/source_test/SourceTest.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/schema/source_test/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/schema/test_default_schema_loader.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/schema/test_inline_schema_loader.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/schema/test_json_file_schema_loader.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/states/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/stream_slicers/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/stream_slicers/test_cartesian_product_stream_slicer.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/test_create_partial.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/test_declarative_stream.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/test_manifest_declarative_source.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/test_yaml_declarative_source.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/http/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/http/auth/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/http/auth/test_auth.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/http/requests_native_auth/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/http/test_availability_strategy.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/http/test_http.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/test_availability_strategy.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/test_streams_core.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/test_abstract_source.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/test_config.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/test_connector_state_manager.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/test_source.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/utils/__init__.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/utils/test_schema_inferrer.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/utils/test_secret_utils.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/utils/test_stream_status_utils.py +0 -0
- {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/utils/test_traced_exception.py +0 -0
@@ -16,11 +16,13 @@ from airbyte_cdk.sources.declarative.declarative_source import DeclarativeSource
|
|
16
16
|
from airbyte_cdk.utils import AirbyteTracedException
|
17
17
|
from airbyte_cdk.utils.schema_inferrer import SchemaInferrer
|
18
18
|
from airbyte_protocol.models.airbyte_protocol import (
|
19
|
+
AirbyteControlMessage,
|
19
20
|
AirbyteLogMessage,
|
20
21
|
AirbyteMessage,
|
21
22
|
AirbyteTraceMessage,
|
22
23
|
ConfiguredAirbyteCatalog,
|
23
24
|
Level,
|
25
|
+
OrchestratorType,
|
24
26
|
TraceType,
|
25
27
|
)
|
26
28
|
from airbyte_protocol.models.airbyte_protocol import Type as MessageType
|
@@ -52,6 +54,7 @@ class MessageGrouper:
|
|
52
54
|
|
53
55
|
slices = []
|
54
56
|
log_messages = []
|
57
|
+
latest_config_update: AirbyteControlMessage = None
|
55
58
|
for message_group in self._get_message_groups(
|
56
59
|
self._read_stream(source, config, configured_catalog),
|
57
60
|
schema_inferrer,
|
@@ -63,7 +66,9 @@ class MessageGrouper:
|
|
63
66
|
if message_group.type == TraceType.ERROR:
|
64
67
|
error_message = f"{message_group.error.message} - {message_group.error.stack_trace}"
|
65
68
|
log_messages.append(LogMessage(**{"message": error_message, "level": "ERROR"}))
|
66
|
-
|
69
|
+
elif isinstance(message_group, AirbyteControlMessage):
|
70
|
+
if not latest_config_update or latest_config_update.emitted_at <= message_group.emitted_at:
|
71
|
+
latest_config_update = message_group
|
67
72
|
else:
|
68
73
|
slices.append(message_group)
|
69
74
|
|
@@ -74,11 +79,12 @@ class MessageGrouper:
|
|
74
79
|
inferred_schema=schema_inferrer.get_stream_schema(
|
75
80
|
configured_catalog.streams[0].stream.name
|
76
81
|
), # The connector builder currently only supports reading from a single stream at a time
|
82
|
+
latest_config_update=latest_config_update.connectorConfig.config if latest_config_update else self._clean_config(config),
|
77
83
|
)
|
78
84
|
|
79
85
|
def _get_message_groups(
|
80
86
|
self, messages: Iterator[AirbyteMessage], schema_inferrer: SchemaInferrer, limit: int
|
81
|
-
) -> Iterable[Union[StreamReadPages, AirbyteLogMessage, AirbyteTraceMessage]]:
|
87
|
+
) -> Iterable[Union[StreamReadPages, AirbyteControlMessage, AirbyteLogMessage, AirbyteTraceMessage]]:
|
82
88
|
"""
|
83
89
|
Message groups are partitioned according to when request log messages are received. Subsequent response log messages
|
84
90
|
and record messages belong to the prior request log message and when we encounter another request, append the latest
|
@@ -135,6 +141,8 @@ class MessageGrouper:
|
|
135
141
|
current_page_records.append(message.record.data)
|
136
142
|
records_count += 1
|
137
143
|
schema_inferrer.accumulate(message.record)
|
144
|
+
elif message.type == MessageType.CONTROL and message.control.type == OrchestratorType.CONNECTOR_CONFIG:
|
145
|
+
yield message.control
|
138
146
|
else:
|
139
147
|
self._close_page(current_page_request, current_page_response, current_slice_pages, current_page_records, validate_page_complete=not had_error)
|
140
148
|
yield StreamReadSlices(pages=current_slice_pages, slice_descriptor=current_slice_descriptor)
|
@@ -217,20 +225,10 @@ class MessageGrouper:
|
|
217
225
|
def _parse_slice_description(self, log_message):
|
218
226
|
return json.loads(log_message.replace(AbstractSource.SLICE_LOG_PREFIX, "", 1))
|
219
227
|
|
220
|
-
@
|
221
|
-
def
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
"name": stream_name,
|
228
|
-
"json_schema": {},
|
229
|
-
"supported_sync_modes": ["full_refresh", "incremental"],
|
230
|
-
},
|
231
|
-
"sync_mode": "full_refresh",
|
232
|
-
"destination_sync_mode": "overwrite",
|
233
|
-
}
|
234
|
-
]
|
235
|
-
}
|
236
|
-
)
|
228
|
+
@staticmethod
|
229
|
+
def _clean_config(config: Mapping[str, Any]):
|
230
|
+
cleaned_config = deepcopy(config)
|
231
|
+
for key in config.keys():
|
232
|
+
if key.startswith("__"):
|
233
|
+
del cleaned_config[key]
|
234
|
+
return cleaned_config
|
@@ -10,6 +10,7 @@ from airbyte_cdk.sources.declarative.auth.declarative_authenticator import Decla
|
|
10
10
|
from airbyte_cdk.sources.declarative.interpolation.interpolated_mapping import InterpolatedMapping
|
11
11
|
from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
|
12
12
|
from airbyte_cdk.sources.streams.http.requests_native_auth.abstract_oauth import AbstractOauth2Authenticator
|
13
|
+
from airbyte_cdk.sources.streams.http.requests_native_auth.oauth import SingleUseRefreshTokenOauth2Authenticator
|
13
14
|
|
14
15
|
|
15
16
|
@dataclass
|
@@ -133,3 +134,13 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
|
|
133
134
|
@access_token.setter
|
134
135
|
def access_token(self, value: str):
|
135
136
|
self._access_token = value
|
137
|
+
|
138
|
+
|
139
|
+
@dataclass
|
140
|
+
class DeclarativeSingleUseRefreshTokenOauth2Authenticator(SingleUseRefreshTokenOauth2Authenticator, DeclarativeAuthenticator):
|
141
|
+
"""
|
142
|
+
Declarative version of SingleUseRefreshTokenOauth2Authenticator which can be used in declarative connectors.
|
143
|
+
"""
|
144
|
+
|
145
|
+
def __init__(self, *args, **kwargs):
|
146
|
+
super().__init__(*args, **kwargs)
|
@@ -733,6 +733,46 @@ definitions:
|
|
733
733
|
type: string
|
734
734
|
examples:
|
735
735
|
- "%Y-%m-%d %H:%M:%S.%f+00:00"
|
736
|
+
refresh_token_updater:
|
737
|
+
title: Token Updater
|
738
|
+
description: When the token updater is defined, new refresh tokens, access tokens and the access token expiry date are written back from the authentication response to the config object. This is important if the refresh token can only used once.
|
739
|
+
properties:
|
740
|
+
refresh_token_name:
|
741
|
+
title: Refresh Token Property Name
|
742
|
+
description: The name of the property which contains the updated refresh token in the response from the token refresh endpoint.
|
743
|
+
type: string
|
744
|
+
default: "refresh_token"
|
745
|
+
examples:
|
746
|
+
- "refresh_token"
|
747
|
+
access_token_config_path:
|
748
|
+
title: Config Path To Access Token
|
749
|
+
description: Config path to the access token. Make sure the field actually exists in the config.
|
750
|
+
type: array
|
751
|
+
items:
|
752
|
+
type: string
|
753
|
+
default: ["credentials", "access_token"]
|
754
|
+
examples:
|
755
|
+
- ["credentials", "access_token"]
|
756
|
+
- ["access_token"]
|
757
|
+
refresh_token_config_path:
|
758
|
+
title: Config Path To Refresh Token
|
759
|
+
description: Config path to the access token. Make sure the field actually exists in the config.
|
760
|
+
type: array
|
761
|
+
items:
|
762
|
+
type: string
|
763
|
+
default: ["credentials", "refresh_token"]
|
764
|
+
examples:
|
765
|
+
- ["credentials", "refresh_token"]
|
766
|
+
- ["refresh_token"]
|
767
|
+
token_expiry_date_config_path:
|
768
|
+
title: Config Path To Expiry Date
|
769
|
+
description: Config path to the expiry date. Make sure actually exists in the config.
|
770
|
+
type: array
|
771
|
+
items:
|
772
|
+
type: string
|
773
|
+
default: ["credentials", "token_expiry_date"]
|
774
|
+
examples:
|
775
|
+
- ["credentials", "token_expiry_date"]
|
736
776
|
$parameters:
|
737
777
|
type: object
|
738
778
|
additionalProperties: true
|
@@ -260,6 +260,33 @@ class CustomTransformation(BaseModel):
|
|
260
260
|
parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters")
|
261
261
|
|
262
262
|
|
263
|
+
class RefreshTokenUpdater(BaseModel):
|
264
|
+
refresh_token_name: Optional[str] = Field(
|
265
|
+
"refresh_token",
|
266
|
+
description="The name of the property which contains the updated refresh token in the response from the token refresh endpoint.",
|
267
|
+
examples=["refresh_token"],
|
268
|
+
title="Refresh Token Property Name",
|
269
|
+
)
|
270
|
+
access_token_config_path: Optional[List[str]] = Field(
|
271
|
+
["credentials", "access_token"],
|
272
|
+
description="Config path to the access token. Make sure the field actually exists in the config.",
|
273
|
+
examples=[["credentials", "access_token"], ["access_token"]],
|
274
|
+
title="Config Path To Access Token",
|
275
|
+
)
|
276
|
+
refresh_token_config_path: Optional[List[str]] = Field(
|
277
|
+
["credentials", "refresh_token"],
|
278
|
+
description="Config path to the access token. Make sure the field actually exists in the config.",
|
279
|
+
examples=[["credentials", "refresh_token"], ["refresh_token"]],
|
280
|
+
title="Config Path To Refresh Token",
|
281
|
+
)
|
282
|
+
token_expiry_date_config_path: Optional[List[str]] = Field(
|
283
|
+
["credentials", "token_expiry_date"],
|
284
|
+
description="Config path to the expiry date. Make sure actually exists in the config.",
|
285
|
+
examples=[["credentials", "token_expiry_date"]],
|
286
|
+
title="Config Path To Expiry Date",
|
287
|
+
)
|
288
|
+
|
289
|
+
|
263
290
|
class OAuthAuthenticator(BaseModel):
|
264
291
|
type: Literal["OAuthAuthenticator"]
|
265
292
|
client_id: str = Field(
|
@@ -340,6 +367,11 @@ class OAuthAuthenticator(BaseModel):
|
|
340
367
|
examples=["%Y-%m-%d %H:%M:%S.%f+00:00"],
|
341
368
|
title="Token Expiry Date Format",
|
342
369
|
)
|
370
|
+
refresh_token_updater: Optional[RefreshTokenUpdater] = Field(
|
371
|
+
None,
|
372
|
+
description="When the token updater is defined, new refresh tokens, access tokens and the access token expiry date are written back from the authentication response to the config object. This is important if the refresh token can only used once.",
|
373
|
+
title="Token Updater",
|
374
|
+
)
|
343
375
|
parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters")
|
344
376
|
|
345
377
|
|
@@ -9,8 +9,10 @@ import inspect
|
|
9
9
|
import re
|
10
10
|
from typing import Any, Callable, List, Literal, Mapping, Optional, Type, Union, get_args, get_origin, get_type_hints
|
11
11
|
|
12
|
+
import dpath
|
12
13
|
from airbyte_cdk.sources.declarative.auth import DeclarativeOauth2Authenticator
|
13
14
|
from airbyte_cdk.sources.declarative.auth.declarative_authenticator import NoAuth
|
15
|
+
from airbyte_cdk.sources.declarative.auth.oauth import DeclarativeSingleUseRefreshTokenOauth2Authenticator
|
14
16
|
from airbyte_cdk.sources.declarative.auth.token import (
|
15
17
|
ApiKeyAuthenticator,
|
16
18
|
BasicHttpAuthenticator,
|
@@ -24,6 +26,7 @@ from airbyte_cdk.sources.declarative.decoders import JsonDecoder
|
|
24
26
|
from airbyte_cdk.sources.declarative.extractors import DpathExtractor, RecordFilter, RecordSelector
|
25
27
|
from airbyte_cdk.sources.declarative.incremental import DatetimeBasedCursor
|
26
28
|
from airbyte_cdk.sources.declarative.interpolation import InterpolatedString
|
29
|
+
from airbyte_cdk.sources.declarative.interpolation.interpolated_mapping import InterpolatedMapping
|
27
30
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import AddedFieldDefinition as AddedFieldDefinitionModel
|
28
31
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import AddFields as AddFieldsModel
|
29
32
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import ApiKeyAuthenticator as ApiKeyAuthenticatorModel
|
@@ -659,6 +662,23 @@ class ModelToComponentFactory:
|
|
659
662
|
|
660
663
|
@staticmethod
|
661
664
|
def create_oauth_authenticator(model: OAuthAuthenticatorModel, config: Config, **kwargs) -> DeclarativeOauth2Authenticator:
|
665
|
+
if model.refresh_token_updater:
|
666
|
+
return DeclarativeSingleUseRefreshTokenOauth2Authenticator(
|
667
|
+
config,
|
668
|
+
InterpolatedString.create(model.token_refresh_endpoint, parameters=model.parameters).eval(config),
|
669
|
+
access_token_name=InterpolatedString.create(model.access_token_name, parameters=model.parameters).eval(config),
|
670
|
+
refresh_token_name=model.refresh_token_updater.refresh_token_name,
|
671
|
+
expires_in_name=InterpolatedString.create(model.expires_in_name, parameters=model.parameters).eval(config),
|
672
|
+
client_id=InterpolatedString.create(model.client_id, parameters=model.parameters).eval(config),
|
673
|
+
client_secret=InterpolatedString.create(model.client_secret, parameters=model.parameters).eval(config),
|
674
|
+
access_token_config_path=model.refresh_token_updater.access_token_config_path,
|
675
|
+
refresh_token_config_path=model.refresh_token_updater.refresh_token_config_path,
|
676
|
+
token_expiry_date_config_path=model.refresh_token_updater.token_expiry_date_config_path,
|
677
|
+
grant_type=InterpolatedString.create(model.grant_type, parameters=model.parameters).eval(config),
|
678
|
+
refresh_request_body=InterpolatedMapping(model.refresh_request_body or {}, parameters=model.parameters).eval(config),
|
679
|
+
scopes=model.scopes,
|
680
|
+
token_expiry_date_format=model.token_expiry_date_format,
|
681
|
+
)
|
662
682
|
return DeclarativeOauth2Authenticator(
|
663
683
|
access_token_name=model.access_token_name,
|
664
684
|
client_id=model.client_id,
|
@@ -685,8 +705,8 @@ class ModelToComponentFactory:
|
|
685
705
|
access_token_name=model.access_token_name,
|
686
706
|
refresh_token_name=model.refresh_token_name,
|
687
707
|
expires_in_name=model.expires_in_name,
|
688
|
-
|
689
|
-
|
708
|
+
client_id=dpath.util.get(config, model.client_id_config_path),
|
709
|
+
client_secret=dpath.util.get(config, model.client_secret_config_path),
|
690
710
|
access_token_config_path=model.access_token_config_path,
|
691
711
|
refresh_token_config_path=model.refresh_token_config_path,
|
692
712
|
token_expiry_date_config_path=model.token_expiry_date_config_path,
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
3
3
|
#
|
4
4
|
|
5
|
-
from typing import Any, List, Mapping, Sequence, Tuple, Union
|
5
|
+
from typing import Any, List, Mapping, Optional, Sequence, Tuple, Union
|
6
6
|
|
7
7
|
import dpath
|
8
8
|
import pendulum
|
@@ -109,11 +109,12 @@ class SingleUseRefreshTokenOauth2Authenticator(Oauth2Authenticator):
|
|
109
109
|
refresh_token_name: str = "refresh_token",
|
110
110
|
refresh_request_body: Mapping[str, Any] = None,
|
111
111
|
grant_type: str = "refresh_token",
|
112
|
-
|
113
|
-
|
112
|
+
client_id: Optional[str] = None,
|
113
|
+
client_secret: Optional[str] = None,
|
114
114
|
access_token_config_path: Sequence[str] = ("credentials", "access_token"),
|
115
115
|
refresh_token_config_path: Sequence[str] = ("credentials", "refresh_token"),
|
116
116
|
token_expiry_date_config_path: Sequence[str] = ("credentials", "token_expiry_date"),
|
117
|
+
token_expiry_date_format: Optional[str] = None,
|
117
118
|
):
|
118
119
|
"""
|
119
120
|
|
@@ -126,20 +127,23 @@ class SingleUseRefreshTokenOauth2Authenticator(Oauth2Authenticator):
|
|
126
127
|
refresh_token_name (str, optional): Name of the name of the refresh token field, used to parse the refresh token response. Defaults to "refresh_token".
|
127
128
|
refresh_request_body (Mapping[str, Any], optional): Custom key value pair that will be added to the refresh token request body. Defaults to None.
|
128
129
|
grant_type (str, optional): OAuth grant type. Defaults to "refresh_token".
|
129
|
-
|
130
|
-
|
130
|
+
client_id (Optional[str]): The client id to authenticate. If not specified, defaults to credentials.client_id in the config object.
|
131
|
+
client_secret (Optional[str]): The client secret to authenticate. If not specified, defaults to credentials.client_secret in the config object.
|
131
132
|
access_token_config_path (Sequence[str]): Dpath to the access_token field in the connector configuration. Defaults to ("credentials", "access_token").
|
132
133
|
refresh_token_config_path (Sequence[str]): Dpath to the refresh_token field in the connector configuration. Defaults to ("credentials", "refresh_token").
|
133
134
|
token_expiry_date_config_path (Sequence[str]): Dpath to the token_expiry_date field in the connector configuration. Defaults to ("credentials", "token_expiry_date").
|
135
|
+
token_expiry_date_format (Optional[str]): Date format of the token expiry date field (set by expires_in_name). If not specified the token expiry date is interpreted as number of seconds until expiration.
|
134
136
|
"""
|
135
|
-
self.
|
136
|
-
self.
|
137
|
+
self._client_id = client_id if client_id is not None else dpath.util.get(connector_config, ("credentials", "client_id"))
|
138
|
+
self._client_secret = (
|
139
|
+
client_secret if client_secret is not None else dpath.util.get(connector_config, ("credentials", "client_secret"))
|
140
|
+
)
|
137
141
|
self._access_token_config_path = access_token_config_path
|
138
142
|
self._refresh_token_config_path = refresh_token_config_path
|
139
143
|
self._token_expiry_date_config_path = token_expiry_date_config_path
|
144
|
+
self._token_expiry_date_format = token_expiry_date_format
|
140
145
|
self._refresh_token_name = refresh_token_name
|
141
146
|
self._connector_config = connector_config
|
142
|
-
self._validate_connector_config()
|
143
147
|
super().__init__(
|
144
148
|
token_refresh_endpoint,
|
145
149
|
self.get_client_id(),
|
@@ -151,69 +155,49 @@ class SingleUseRefreshTokenOauth2Authenticator(Oauth2Authenticator):
|
|
151
155
|
expires_in_name=expires_in_name,
|
152
156
|
refresh_request_body=refresh_request_body,
|
153
157
|
grant_type=grant_type,
|
158
|
+
token_expiry_date_format=token_expiry_date_format,
|
154
159
|
)
|
155
160
|
|
156
|
-
def _validate_connector_config(self):
|
157
|
-
"""Validates the defined getters for configuration values are returning values.
|
158
|
-
|
159
|
-
Raises:
|
160
|
-
ValueError: Raised if the defined getters are not returning a value.
|
161
|
-
"""
|
162
|
-
try:
|
163
|
-
assert self.access_token
|
164
|
-
except KeyError:
|
165
|
-
raise ValueError(
|
166
|
-
f"This authenticator expects a value under the {self._access_token_config_path} field path. Please check your configuration structure or change the access_token_config_path value at initialization of this authenticator."
|
167
|
-
)
|
168
|
-
for field_path, getter, parameter_name in [
|
169
|
-
(self._client_id_config_path, self.get_client_id, "client_id_config_path"),
|
170
|
-
(self._client_secret_config_path, self.get_client_secret, "client_secret_config_path"),
|
171
|
-
(self._refresh_token_config_path, self.get_refresh_token, "refresh_token_config_path"),
|
172
|
-
(self._token_expiry_date_config_path, self.get_token_expiry_date, "token_expiry_date_config_path"),
|
173
|
-
]:
|
174
|
-
try:
|
175
|
-
assert getter()
|
176
|
-
except KeyError:
|
177
|
-
raise ValueError(
|
178
|
-
f"This authenticator expects a value under the {field_path} field path. Please check your configuration structure or change the {parameter_name} value at initialization of this authenticator."
|
179
|
-
)
|
180
|
-
|
181
161
|
def get_refresh_token_name(self) -> str:
|
182
162
|
return self._refresh_token_name
|
183
163
|
|
184
164
|
def get_client_id(self) -> str:
|
185
|
-
return
|
165
|
+
return self._client_id
|
186
166
|
|
187
167
|
def get_client_secret(self) -> str:
|
188
|
-
return
|
168
|
+
return self._client_secret
|
189
169
|
|
190
170
|
@property
|
191
171
|
def access_token(self) -> str:
|
192
|
-
return dpath.util.get(self._connector_config, self._access_token_config_path)
|
172
|
+
return dpath.util.get(self._connector_config, self._access_token_config_path, default="")
|
193
173
|
|
194
174
|
@access_token.setter
|
195
175
|
def access_token(self, new_access_token: str):
|
196
|
-
dpath.util.
|
176
|
+
dpath.util.new(self._connector_config, self._access_token_config_path, new_access_token)
|
197
177
|
|
198
178
|
def get_refresh_token(self) -> str:
|
199
|
-
return dpath.util.get(self._connector_config, self._refresh_token_config_path)
|
179
|
+
return dpath.util.get(self._connector_config, self._refresh_token_config_path, default="")
|
200
180
|
|
201
181
|
def set_refresh_token(self, new_refresh_token: str):
|
202
|
-
dpath.util.
|
182
|
+
dpath.util.new(self._connector_config, self._refresh_token_config_path, new_refresh_token)
|
203
183
|
|
204
184
|
def get_token_expiry_date(self) -> pendulum.DateTime:
|
205
|
-
|
185
|
+
expiry_date = dpath.util.get(self._connector_config, self._token_expiry_date_config_path, default="")
|
186
|
+
return pendulum.now().subtract(days=1) if expiry_date == "" else pendulum.parse(expiry_date)
|
206
187
|
|
207
188
|
def set_token_expiry_date(self, new_token_expiry_date):
|
208
|
-
dpath.util.
|
189
|
+
dpath.util.new(self._connector_config, self._token_expiry_date_config_path, str(new_token_expiry_date))
|
209
190
|
|
210
191
|
def token_has_expired(self) -> bool:
|
211
192
|
"""Returns True if the token is expired"""
|
212
193
|
return pendulum.now("UTC") > self.get_token_expiry_date()
|
213
194
|
|
214
195
|
@staticmethod
|
215
|
-
def get_new_token_expiry_date(access_token_expires_in:
|
216
|
-
|
196
|
+
def get_new_token_expiry_date(access_token_expires_in: str, token_expiry_date_format: str = None) -> pendulum.DateTime:
|
197
|
+
if token_expiry_date_format:
|
198
|
+
return pendulum.from_format(access_token_expires_in, token_expiry_date_format)
|
199
|
+
else:
|
200
|
+
return pendulum.now("UTC").add(seconds=int(access_token_expires_in))
|
217
201
|
|
218
202
|
def get_access_token(self) -> str:
|
219
203
|
"""Retrieve new access and refresh token if the access token has expired.
|
@@ -223,17 +207,17 @@ class SingleUseRefreshTokenOauth2Authenticator(Oauth2Authenticator):
|
|
223
207
|
"""
|
224
208
|
if self.token_has_expired():
|
225
209
|
new_access_token, access_token_expires_in, new_refresh_token = self.refresh_access_token()
|
226
|
-
new_token_expiry_date = self.get_new_token_expiry_date(access_token_expires_in)
|
210
|
+
new_token_expiry_date = self.get_new_token_expiry_date(access_token_expires_in, self._token_expiry_date_format)
|
227
211
|
self.access_token = new_access_token
|
228
212
|
self.set_refresh_token(new_refresh_token)
|
229
213
|
self.set_token_expiry_date(new_token_expiry_date)
|
230
214
|
emit_configuration_as_airbyte_control_message(self._connector_config)
|
231
215
|
return self.access_token
|
232
216
|
|
233
|
-
def refresh_access_token(self) -> Tuple[str,
|
217
|
+
def refresh_access_token(self) -> Tuple[str, str, str]:
|
234
218
|
response_json = self._get_refresh_access_token_response()
|
235
219
|
return (
|
236
220
|
response_json[self.get_access_token_name()],
|
237
|
-
|
221
|
+
response_json[self.get_expires_in_name()],
|
238
222
|
response_json[self.get_refresh_token_name()],
|
239
223
|
)
|
@@ -17,7 +17,7 @@ setup(
|
|
17
17
|
name="airbyte-cdk",
|
18
18
|
# The version of the airbyte-cdk package is used at runtime to validate manifests. That validation must be
|
19
19
|
# updated if our semver format changes such as using release candidate versions.
|
20
|
-
version="0.39.
|
20
|
+
version="0.39.4",
|
21
21
|
description="A framework for writing Airbyte Connectors.",
|
22
22
|
long_description=README,
|
23
23
|
long_description_content_type="text/markdown",
|
@@ -354,6 +354,7 @@ def test_read():
|
|
354
354
|
],
|
355
355
|
test_read_limit_reached=False,
|
356
356
|
inferred_schema=None,
|
357
|
+
latest_config_update={}
|
357
358
|
)
|
358
359
|
|
359
360
|
expected_airbyte_message = AirbyteMessage(
|
@@ -367,6 +368,7 @@ def test_read():
|
|
367
368
|
],
|
368
369
|
"test_read_limit_reached": False,
|
369
370
|
"inferred_schema": None,
|
371
|
+
"latest_config_update": {}
|
370
372
|
},
|
371
373
|
emitted_at=1,
|
372
374
|
),
|
@@ -407,7 +409,8 @@ def test_read_returns_error_response(mock_from_exception):
|
|
407
409
|
pages=[StreamReadPages(records=[], request=None, response=None)],
|
408
410
|
slice_descriptor=None, state=None)],
|
409
411
|
test_read_limit_reached=False,
|
410
|
-
inferred_schema=None
|
412
|
+
inferred_schema=None,
|
413
|
+
latest_config_update={})
|
411
414
|
|
412
415
|
expected_message = AirbyteMessage(
|
413
416
|
type=MessageType.RECORD,
|
{airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/connector_builder/test_message_grouper.py
RENAMED
@@ -9,7 +9,15 @@ from unittest.mock import MagicMock, patch
|
|
9
9
|
import pytest
|
10
10
|
from airbyte_cdk.connector_builder.message_grouper import MessageGrouper
|
11
11
|
from airbyte_cdk.connector_builder.models import HttpRequest, HttpResponse, LogMessage, StreamRead, StreamReadPages
|
12
|
-
from airbyte_cdk.models import
|
12
|
+
from airbyte_cdk.models import (
|
13
|
+
AirbyteControlConnectorConfigMessage,
|
14
|
+
AirbyteControlMessage,
|
15
|
+
AirbyteLogMessage,
|
16
|
+
AirbyteMessage,
|
17
|
+
AirbyteRecordMessage,
|
18
|
+
Level,
|
19
|
+
OrchestratorType,
|
20
|
+
)
|
13
21
|
from airbyte_cdk.models import Type as MessageType
|
14
22
|
from unit_tests.connector_builder.utils import create_configured_catalog
|
15
23
|
|
@@ -463,9 +471,9 @@ def test_get_grouped_messages_with_many_slices(mock_entrypoint_read):
|
|
463
471
|
)
|
464
472
|
)
|
465
473
|
|
466
|
-
|
474
|
+
connector_builder_handler = MessageGrouper(MAX_PAGES_PER_SLICE, MAX_SLICES)
|
467
475
|
|
468
|
-
stream_read: StreamRead =
|
476
|
+
stream_read: StreamRead = connector_builder_handler.get_message_groups(
|
469
477
|
source=mock_source, config=CONFIG, configured_catalog=create_configured_catalog("hashiras")
|
470
478
|
)
|
471
479
|
|
@@ -530,6 +538,76 @@ def test_read_stream_returns_error_if_stream_does_not_exist():
|
|
530
538
|
assert "ERROR" in actual_response.logs[0].level
|
531
539
|
|
532
540
|
|
541
|
+
@patch('airbyte_cdk.connector_builder.message_grouper.AirbyteEntrypoint.read')
|
542
|
+
def test_given_control_message_then_stream_read_has_config_update(mock_entrypoint_read):
|
543
|
+
updated_config = {"x": 1}
|
544
|
+
mock_source = make_mock_source(mock_entrypoint_read, iter(
|
545
|
+
any_request_and_response_with_a_record() + [connector_configuration_control_message(1, updated_config)]
|
546
|
+
))
|
547
|
+
connector_builder_handler = MessageGrouper(MAX_PAGES_PER_SLICE, MAX_SLICES)
|
548
|
+
stream_read: StreamRead = connector_builder_handler.get_message_groups(
|
549
|
+
source=mock_source, config=CONFIG, configured_catalog=create_configured_catalog("hashiras")
|
550
|
+
)
|
551
|
+
|
552
|
+
assert stream_read.latest_config_update == updated_config
|
553
|
+
|
554
|
+
|
555
|
+
@patch('airbyte_cdk.connector_builder.message_grouper.AirbyteEntrypoint.read')
|
556
|
+
def test_given_no_control_message_then_use_in_memory_config_change_as_update(mock_entrypoint_read):
|
557
|
+
mock_source = make_mock_source(mock_entrypoint_read, iter(any_request_and_response_with_a_record()))
|
558
|
+
connector_builder_handler = MessageGrouper(MAX_PAGES_PER_SLICE, MAX_SLICES)
|
559
|
+
full_config = {**CONFIG, **{"__injected_declarative_manifest": MANIFEST}}
|
560
|
+
stream_read: StreamRead = connector_builder_handler.get_message_groups(
|
561
|
+
source=mock_source, config=full_config, configured_catalog=create_configured_catalog("hashiras")
|
562
|
+
)
|
563
|
+
|
564
|
+
assert stream_read.latest_config_update == CONFIG
|
565
|
+
|
566
|
+
|
567
|
+
@patch('airbyte_cdk.connector_builder.message_grouper.AirbyteEntrypoint.read')
|
568
|
+
def test_given_multiple_control_messages_then_stream_read_has_latest_based_on_emitted_at(mock_entrypoint_read):
|
569
|
+
earliest = 0
|
570
|
+
earliest_config = {"earliest": 0}
|
571
|
+
latest = 1
|
572
|
+
latest_config = {"latest": 1}
|
573
|
+
mock_source = make_mock_source(mock_entrypoint_read, iter(
|
574
|
+
any_request_and_response_with_a_record() +
|
575
|
+
[
|
576
|
+
# here, we test that even if messages are emitted in a different order, we still rely on `emitted_at`
|
577
|
+
connector_configuration_control_message(latest, latest_config),
|
578
|
+
connector_configuration_control_message(earliest, earliest_config),
|
579
|
+
]
|
580
|
+
)
|
581
|
+
)
|
582
|
+
connector_builder_handler = MessageGrouper(MAX_PAGES_PER_SLICE, MAX_SLICES)
|
583
|
+
stream_read: StreamRead = connector_builder_handler.get_message_groups(
|
584
|
+
source=mock_source, config=CONFIG, configured_catalog=create_configured_catalog("hashiras")
|
585
|
+
)
|
586
|
+
|
587
|
+
assert stream_read.latest_config_update == latest_config
|
588
|
+
|
589
|
+
|
590
|
+
@patch('airbyte_cdk.connector_builder.message_grouper.AirbyteEntrypoint.read')
|
591
|
+
def test_given_multiple_control_messages_with_same_timestamp_then_stream_read_has_latest_based_on_message_order(mock_entrypoint_read):
|
592
|
+
emitted_at = 0
|
593
|
+
earliest_config = {"earliest": 0}
|
594
|
+
latest_config = {"latest": 1}
|
595
|
+
mock_source = make_mock_source(mock_entrypoint_read, iter(
|
596
|
+
any_request_and_response_with_a_record() +
|
597
|
+
[
|
598
|
+
connector_configuration_control_message(emitted_at, earliest_config),
|
599
|
+
connector_configuration_control_message(emitted_at, latest_config),
|
600
|
+
]
|
601
|
+
)
|
602
|
+
)
|
603
|
+
connector_builder_handler = MessageGrouper(MAX_PAGES_PER_SLICE, MAX_SLICES)
|
604
|
+
stream_read: StreamRead = connector_builder_handler.get_message_groups(
|
605
|
+
source=mock_source, config=CONFIG, configured_catalog=create_configured_catalog("hashiras")
|
606
|
+
)
|
607
|
+
|
608
|
+
assert stream_read.latest_config_update == latest_config
|
609
|
+
|
610
|
+
|
533
611
|
def make_mock_source(mock_entrypoint_read, return_value: Iterator) -> MagicMock:
|
534
612
|
mock_source = MagicMock()
|
535
613
|
mock_entrypoint_read.return_value = return_value
|
@@ -550,3 +628,22 @@ def record_message(stream: str, data: dict) -> AirbyteMessage:
|
|
550
628
|
|
551
629
|
def slice_message(slice_descriptor: str = '{"key": "value"}') -> AirbyteMessage:
|
552
630
|
return AirbyteMessage(type=MessageType.LOG, log=AirbyteLogMessage(level=Level.INFO, message="slice:" + slice_descriptor))
|
631
|
+
|
632
|
+
|
633
|
+
def connector_configuration_control_message(emitted_at: float, config: dict) -> AirbyteMessage:
|
634
|
+
return AirbyteMessage(
|
635
|
+
type=MessageType.CONTROL,
|
636
|
+
control=AirbyteControlMessage(
|
637
|
+
type=OrchestratorType.CONNECTOR_CONFIG,
|
638
|
+
emitted_at=emitted_at,
|
639
|
+
connectorConfig=AirbyteControlConnectorConfigMessage(config=config),
|
640
|
+
)
|
641
|
+
)
|
642
|
+
|
643
|
+
|
644
|
+
def any_request_and_response_with_a_record():
|
645
|
+
return [
|
646
|
+
request_log_message({"request": 1}),
|
647
|
+
response_log_message({"response": 2}),
|
648
|
+
record_message("hashiras", {"name": "Shinobu Kocho"}),
|
649
|
+
]
|