airbyte-cdk 0.0.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- airbyte_cdk/__init__.py +358 -0
- 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 +236 -0
- airbyte_cdk/cli/source_declarative_manifest/spec.json +17 -0
- airbyte_cdk/config_observation.py +104 -0
- airbyte_cdk/connector.py +123 -0
- airbyte_cdk/connector_builder/README.md +53 -0
- airbyte_cdk/connector_builder/__init__.py +3 -0
- airbyte_cdk/connector_builder/connector_builder_handler.py +121 -0
- airbyte_cdk/connector_builder/main.py +107 -0
- airbyte_cdk/connector_builder/models.py +73 -0
- airbyte_cdk/connector_builder/test_reader/__init__.py +7 -0
- airbyte_cdk/connector_builder/test_reader/helpers.py +689 -0
- airbyte_cdk/connector_builder/test_reader/message_grouper.py +173 -0
- airbyte_cdk/connector_builder/test_reader/reader.py +441 -0
- airbyte_cdk/connector_builder/test_reader/types.py +83 -0
- airbyte_cdk/destinations/__init__.py +8 -0
- airbyte_cdk/destinations/destination.py +154 -0
- airbyte_cdk/destinations/vector_db_based/README.md +37 -0
- airbyte_cdk/destinations/vector_db_based/__init__.py +38 -0
- airbyte_cdk/destinations/vector_db_based/config.py +298 -0
- airbyte_cdk/destinations/vector_db_based/document_processor.py +223 -0
- airbyte_cdk/destinations/vector_db_based/embedder.py +303 -0
- airbyte_cdk/destinations/vector_db_based/indexer.py +78 -0
- airbyte_cdk/destinations/vector_db_based/test_utils.py +63 -0
- airbyte_cdk/destinations/vector_db_based/utils.py +35 -0
- airbyte_cdk/destinations/vector_db_based/writer.py +104 -0
- airbyte_cdk/entrypoint.py +414 -0
- airbyte_cdk/exception_handler.py +56 -0
- airbyte_cdk/logger.py +109 -0
- airbyte_cdk/models/__init__.py +72 -0
- airbyte_cdk/models/airbyte_protocol.py +88 -0
- airbyte_cdk/models/airbyte_protocol_serializers.py +44 -0
- airbyte_cdk/models/well_known_types.py +5 -0
- airbyte_cdk/py.typed +0 -0
- airbyte_cdk/sources/__init__.py +26 -0
- airbyte_cdk/sources/abstract_source.py +326 -0
- airbyte_cdk/sources/concurrent_source/__init__.py +8 -0
- airbyte_cdk/sources/concurrent_source/concurrent_read_processor.py +255 -0
- airbyte_cdk/sources/concurrent_source/concurrent_source.py +165 -0
- airbyte_cdk/sources/concurrent_source/concurrent_source_adapter.py +147 -0
- airbyte_cdk/sources/concurrent_source/partition_generation_completed_sentinel.py +24 -0
- airbyte_cdk/sources/concurrent_source/stream_thread_exception.py +25 -0
- airbyte_cdk/sources/concurrent_source/thread_pool_manager.py +115 -0
- airbyte_cdk/sources/config.py +27 -0
- airbyte_cdk/sources/connector_state_manager.py +161 -0
- airbyte_cdk/sources/declarative/__init__.py +3 -0
- airbyte_cdk/sources/declarative/async_job/__init__.py +0 -0
- airbyte_cdk/sources/declarative/async_job/job.py +52 -0
- airbyte_cdk/sources/declarative/async_job/job_orchestrator.py +525 -0
- airbyte_cdk/sources/declarative/async_job/job_tracker.py +79 -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 +8 -0
- airbyte_cdk/sources/declarative/auth/declarative_authenticator.py +42 -0
- airbyte_cdk/sources/declarative/auth/jwt.py +197 -0
- airbyte_cdk/sources/declarative/auth/oauth.py +293 -0
- airbyte_cdk/sources/declarative/auth/selective_authenticator.py +45 -0
- airbyte_cdk/sources/declarative/auth/token.py +267 -0
- airbyte_cdk/sources/declarative/auth/token_provider.py +82 -0
- airbyte_cdk/sources/declarative/checks/__init__.py +24 -0
- airbyte_cdk/sources/declarative/checks/check_dynamic_stream.py +61 -0
- airbyte_cdk/sources/declarative/checks/check_stream.py +56 -0
- airbyte_cdk/sources/declarative/checks/connection_checker.py +35 -0
- 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 +526 -0
- airbyte_cdk/sources/declarative/datetime/__init__.py +3 -0
- airbyte_cdk/sources/declarative/datetime/datetime_parser.py +65 -0
- airbyte_cdk/sources/declarative/datetime/min_max_datetime.py +118 -0
- airbyte_cdk/sources/declarative/declarative_component_schema.yaml +3975 -0
- airbyte_cdk/sources/declarative/declarative_source.py +36 -0
- airbyte_cdk/sources/declarative/declarative_stream.py +241 -0
- airbyte_cdk/sources/declarative/decoders/__init__.py +33 -0
- airbyte_cdk/sources/declarative/decoders/composite_raw_decoder.py +218 -0
- airbyte_cdk/sources/declarative/decoders/decoder.py +32 -0
- airbyte_cdk/sources/declarative/decoders/decoder_parser.py +30 -0
- airbyte_cdk/sources/declarative/decoders/json_decoder.py +65 -0
- 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/decoders/zipfile_decoder.py +56 -0
- airbyte_cdk/sources/declarative/exceptions.py +9 -0
- airbyte_cdk/sources/declarative/extractors/__init__.py +21 -0
- airbyte_cdk/sources/declarative/extractors/dpath_extractor.py +86 -0
- airbyte_cdk/sources/declarative/extractors/http_selector.py +37 -0
- airbyte_cdk/sources/declarative/extractors/record_extractor.py +27 -0
- airbyte_cdk/sources/declarative/extractors/record_filter.py +91 -0
- airbyte_cdk/sources/declarative/extractors/record_selector.py +170 -0
- airbyte_cdk/sources/declarative/extractors/response_to_file_extractor.py +176 -0
- airbyte_cdk/sources/declarative/extractors/type_transformer.py +55 -0
- airbyte_cdk/sources/declarative/incremental/__init__.py +37 -0
- airbyte_cdk/sources/declarative/incremental/concurrent_partition_cursor.py +497 -0
- airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py +459 -0
- airbyte_cdk/sources/declarative/incremental/declarative_cursor.py +13 -0
- airbyte_cdk/sources/declarative/incremental/global_substream_cursor.py +357 -0
- airbyte_cdk/sources/declarative/incremental/per_partition_cursor.py +380 -0
- 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/__init__.py +9 -0
- airbyte_cdk/sources/declarative/interpolation/filters.py +139 -0
- airbyte_cdk/sources/declarative/interpolation/interpolated_boolean.py +66 -0
- airbyte_cdk/sources/declarative/interpolation/interpolated_mapping.py +56 -0
- airbyte_cdk/sources/declarative/interpolation/interpolated_nested_mapping.py +52 -0
- airbyte_cdk/sources/declarative/interpolation/interpolated_string.py +79 -0
- airbyte_cdk/sources/declarative/interpolation/interpolation.py +34 -0
- airbyte_cdk/sources/declarative/interpolation/jinja.py +161 -0
- airbyte_cdk/sources/declarative/interpolation/macros.py +191 -0
- airbyte_cdk/sources/declarative/manifest_declarative_source.py +421 -0
- airbyte_cdk/sources/declarative/migrations/__init__.py +0 -0
- 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 +2 -0
- airbyte_cdk/sources/declarative/models/declarative_component_schema.py +2503 -0
- airbyte_cdk/sources/declarative/parsers/__init__.py +3 -0
- airbyte_cdk/sources/declarative/parsers/custom_code_compiler.py +157 -0
- airbyte_cdk/sources/declarative/parsers/custom_exceptions.py +21 -0
- airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py +172 -0
- airbyte_cdk/sources/declarative/parsers/manifest_reference_resolver.py +213 -0
- airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +3407 -0
- airbyte_cdk/sources/declarative/partition_routers/__init__.py +29 -0
- 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 +121 -0
- airbyte_cdk/sources/declarative/partition_routers/partition_router.py +62 -0
- airbyte_cdk/sources/declarative/partition_routers/single_partition_router.py +63 -0
- airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py +437 -0
- airbyte_cdk/sources/declarative/requesters/README.md +56 -0
- airbyte_cdk/sources/declarative/requesters/__init__.py +9 -0
- airbyte_cdk/sources/declarative/requesters/error_handlers/__init__.py +25 -0
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/__init__.py +23 -0
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/constant_backoff_strategy.py +45 -0
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/exponential_backoff_strategy.py +45 -0
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/header_helper.py +41 -0
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_time_from_header_backoff_strategy.py +70 -0
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_until_time_from_header_backoff_strategy.py +77 -0
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategy.py +17 -0
- airbyte_cdk/sources/declarative/requesters/error_handlers/composite_error_handler.py +101 -0
- airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py +147 -0
- airbyte_cdk/sources/declarative/requesters/error_handlers/default_http_response_filter.py +40 -0
- airbyte_cdk/sources/declarative/requesters/error_handlers/error_handler.py +17 -0
- airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py +179 -0
- airbyte_cdk/sources/declarative/requesters/http_job_repository.py +350 -0
- airbyte_cdk/sources/declarative/requesters/http_requester.py +433 -0
- airbyte_cdk/sources/declarative/requesters/paginators/__init__.py +21 -0
- airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +327 -0
- airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py +76 -0
- airbyte_cdk/sources/declarative/requesters/paginators/paginator.py +65 -0
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/__init__.py +25 -0
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py +98 -0
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py +102 -0
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py +71 -0
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py +48 -0
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/stop_condition.py +66 -0
- airbyte_cdk/sources/declarative/requesters/request_option.py +117 -0
- airbyte_cdk/sources/declarative/requesters/request_options/__init__.py +23 -0
- airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py +92 -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 +59 -0
- airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_input_provider.py +68 -0
- airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py +119 -0
- airbyte_cdk/sources/declarative/requesters/request_options/request_options_provider.py +79 -0
- airbyte_cdk/sources/declarative/requesters/request_path.py +15 -0
- airbyte_cdk/sources/declarative/requesters/requester.py +144 -0
- 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 +19 -0
- airbyte_cdk/sources/declarative/retrievers/async_retriever.py +124 -0
- airbyte_cdk/sources/declarative/retrievers/file_uploader.py +89 -0
- airbyte_cdk/sources/declarative/retrievers/retriever.py +54 -0
- airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +702 -0
- airbyte_cdk/sources/declarative/schema/__init__.py +25 -0
- airbyte_cdk/sources/declarative/schema/default_schema_loader.py +47 -0
- airbyte_cdk/sources/declarative/schema/dynamic_schema_loader.py +285 -0
- airbyte_cdk/sources/declarative/schema/inline_schema_loader.py +19 -0
- airbyte_cdk/sources/declarative/schema/json_file_schema_loader.py +92 -0
- airbyte_cdk/sources/declarative/schema/schema_loader.py +17 -0
- airbyte_cdk/sources/declarative/spec/__init__.py +7 -0
- airbyte_cdk/sources/declarative/spec/spec.py +48 -0
- airbyte_cdk/sources/declarative/stream_slicers/__init__.py +7 -0
- airbyte_cdk/sources/declarative/stream_slicers/declarative_partition_generator.py +93 -0
- airbyte_cdk/sources/declarative/stream_slicers/stream_slicer.py +25 -0
- airbyte_cdk/sources/declarative/transformations/__init__.py +17 -0
- airbyte_cdk/sources/declarative/transformations/add_fields.py +146 -0
- airbyte_cdk/sources/declarative/transformations/dpath_flatten_fields.py +61 -0
- 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 +75 -0
- airbyte_cdk/sources/declarative/transformations/transformation.py +37 -0
- airbyte_cdk/sources/declarative/types.py +25 -0
- airbyte_cdk/sources/declarative/yaml_declarative_source.py +67 -0
- 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 +11 -0
- airbyte_cdk/sources/file_based/availability_strategy/abstract_file_based_availability_strategy.py +73 -0
- airbyte_cdk/sources/file_based/availability_strategy/default_file_based_availability_strategy.py +149 -0
- airbyte_cdk/sources/file_based/config/__init__.py +0 -0
- airbyte_cdk/sources/file_based/config/abstract_file_based_spec.py +153 -0
- airbyte_cdk/sources/file_based/config/avro_format.py +25 -0
- airbyte_cdk/sources/file_based/config/csv_format.py +210 -0
- airbyte_cdk/sources/file_based/config/excel_format.py +18 -0
- airbyte_cdk/sources/file_based/config/file_based_stream_config.py +99 -0
- airbyte_cdk/sources/file_based/config/jsonl_format.py +18 -0
- airbyte_cdk/sources/file_based/config/parquet_format.py +25 -0
- airbyte_cdk/sources/file_based/config/unstructured_format.py +102 -0
- airbyte_cdk/sources/file_based/config/validate_config_transfer_modes.py +81 -0
- airbyte_cdk/sources/file_based/discovery_policy/__init__.py +8 -0
- airbyte_cdk/sources/file_based/discovery_policy/abstract_discovery_policy.py +21 -0
- airbyte_cdk/sources/file_based/discovery_policy/default_discovery_policy.py +33 -0
- airbyte_cdk/sources/file_based/exceptions.py +159 -0
- airbyte_cdk/sources/file_based/file_based_source.py +466 -0
- airbyte_cdk/sources/file_based/file_based_stream_permissions_reader.py +123 -0
- airbyte_cdk/sources/file_based/file_based_stream_reader.py +209 -0
- airbyte_cdk/sources/file_based/file_record_data.py +22 -0
- airbyte_cdk/sources/file_based/file_types/__init__.py +37 -0
- airbyte_cdk/sources/file_based/file_types/avro_parser.py +233 -0
- airbyte_cdk/sources/file_based/file_types/csv_parser.py +527 -0
- airbyte_cdk/sources/file_based/file_types/excel_parser.py +196 -0
- airbyte_cdk/sources/file_based/file_types/file_transfer.py +30 -0
- airbyte_cdk/sources/file_based/file_types/file_type_parser.py +86 -0
- airbyte_cdk/sources/file_based/file_types/jsonl_parser.py +145 -0
- airbyte_cdk/sources/file_based/file_types/parquet_parser.py +275 -0
- airbyte_cdk/sources/file_based/file_types/unstructured_parser.py +480 -0
- airbyte_cdk/sources/file_based/remote_file.py +18 -0
- airbyte_cdk/sources/file_based/schema_helpers.py +281 -0
- airbyte_cdk/sources/file_based/schema_validation_policies/__init__.py +17 -0
- airbyte_cdk/sources/file_based/schema_validation_policies/abstract_schema_validation_policy.py +20 -0
- airbyte_cdk/sources/file_based/schema_validation_policies/default_schema_validation_policies.py +52 -0
- airbyte_cdk/sources/file_based/stream/__init__.py +13 -0
- airbyte_cdk/sources/file_based/stream/abstract_file_based_stream.py +197 -0
- airbyte_cdk/sources/file_based/stream/concurrent/__init__.py +0 -0
- airbyte_cdk/sources/file_based/stream/concurrent/adapters.py +343 -0
- airbyte_cdk/sources/file_based/stream/concurrent/cursor/__init__.py +9 -0
- airbyte_cdk/sources/file_based/stream/concurrent/cursor/abstract_concurrent_file_based_cursor.py +59 -0
- airbyte_cdk/sources/file_based/stream/concurrent/cursor/file_based_concurrent_cursor.py +313 -0
- airbyte_cdk/sources/file_based/stream/concurrent/cursor/file_based_final_state_cursor.py +83 -0
- airbyte_cdk/sources/file_based/stream/cursor/__init__.py +4 -0
- airbyte_cdk/sources/file_based/stream/cursor/abstract_file_based_cursor.py +66 -0
- airbyte_cdk/sources/file_based/stream/cursor/default_file_based_cursor.py +149 -0
- airbyte_cdk/sources/file_based/stream/default_file_based_stream.py +396 -0
- airbyte_cdk/sources/file_based/stream/identities_stream.py +49 -0
- airbyte_cdk/sources/file_based/stream/permissions_file_based_stream.py +92 -0
- airbyte_cdk/sources/file_based/types.py +10 -0
- airbyte_cdk/sources/http_config.py +10 -0
- airbyte_cdk/sources/http_logger.py +55 -0
- airbyte_cdk/sources/message/__init__.py +19 -0
- airbyte_cdk/sources/message/repository.py +137 -0
- airbyte_cdk/sources/source.py +95 -0
- airbyte_cdk/sources/specs/transfer_modes.py +26 -0
- airbyte_cdk/sources/streams/__init__.py +8 -0
- airbyte_cdk/sources/streams/availability_strategy.py +84 -0
- airbyte_cdk/sources/streams/call_rate.py +704 -0
- airbyte_cdk/sources/streams/checkpoint/__init__.py +26 -0
- airbyte_cdk/sources/streams/checkpoint/checkpoint_reader.py +335 -0
- airbyte_cdk/sources/streams/checkpoint/cursor.py +77 -0
- 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/__init__.py +3 -0
- airbyte_cdk/sources/streams/concurrent/abstract_stream.py +96 -0
- airbyte_cdk/sources/streams/concurrent/abstract_stream_facade.py +37 -0
- airbyte_cdk/sources/streams/concurrent/adapters.py +397 -0
- airbyte_cdk/sources/streams/concurrent/availability_strategy.py +94 -0
- airbyte_cdk/sources/streams/concurrent/clamping.py +99 -0
- airbyte_cdk/sources/streams/concurrent/cursor.py +481 -0
- airbyte_cdk/sources/streams/concurrent/cursor_types.py +32 -0
- airbyte_cdk/sources/streams/concurrent/default_stream.py +102 -0
- airbyte_cdk/sources/streams/concurrent/exceptions.py +18 -0
- airbyte_cdk/sources/streams/concurrent/helpers.py +42 -0
- airbyte_cdk/sources/streams/concurrent/partition_enqueuer.py +64 -0
- airbyte_cdk/sources/streams/concurrent/partition_reader.py +45 -0
- airbyte_cdk/sources/streams/concurrent/partitions/__init__.py +3 -0
- airbyte_cdk/sources/streams/concurrent/partitions/partition.py +48 -0
- airbyte_cdk/sources/streams/concurrent/partitions/partition_generator.py +18 -0
- airbyte_cdk/sources/streams/concurrent/partitions/stream_slicer.py +21 -0
- airbyte_cdk/sources/streams/concurrent/partitions/types.py +38 -0
- airbyte_cdk/sources/streams/concurrent/state_converters/__init__.py +0 -0
- airbyte_cdk/sources/streams/concurrent/state_converters/abstract_stream_state_converter.py +182 -0
- airbyte_cdk/sources/streams/concurrent/state_converters/datetime_stream_state_converter.py +223 -0
- airbyte_cdk/sources/streams/concurrent/state_converters/incrementing_count_stream_state_converter.py +92 -0
- airbyte_cdk/sources/streams/core.py +703 -0
- airbyte_cdk/sources/streams/http/__init__.py +10 -0
- airbyte_cdk/sources/streams/http/availability_strategy.py +54 -0
- 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 +61 -0
- airbyte_cdk/sources/streams/http/http.py +673 -0
- airbyte_cdk/sources/streams/http/http_client.py +531 -0
- airbyte_cdk/sources/streams/http/rate_limiting.py +158 -0
- airbyte_cdk/sources/streams/http/requests_native_auth/__init__.py +14 -0
- airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +479 -0
- airbyte_cdk/sources/streams/http/requests_native_auth/abstract_token.py +34 -0
- airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py +436 -0
- airbyte_cdk/sources/streams/http/requests_native_auth/token.py +83 -0
- airbyte_cdk/sources/streams/permissions/identities_stream.py +75 -0
- airbyte_cdk/sources/streams/utils/__init__.py +3 -0
- airbyte_cdk/sources/types.py +169 -0
- airbyte_cdk/sources/utils/__init__.py +7 -0
- airbyte_cdk/sources/utils/casing.py +12 -0
- airbyte_cdk/sources/utils/files_directory.py +15 -0
- airbyte_cdk/sources/utils/record_helper.py +53 -0
- airbyte_cdk/sources/utils/schema_helpers.py +230 -0
- airbyte_cdk/sources/utils/slice_logger.py +57 -0
- airbyte_cdk/sources/utils/transform.py +277 -0
- airbyte_cdk/sources/utils/types.py +7 -0
- airbyte_cdk/sql/__init__.py +0 -0
- airbyte_cdk/sql/_util/__init__.py +0 -0
- 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/__init__.py +7 -0
- airbyte_cdk/test/catalog_builder.py +81 -0
- airbyte_cdk/test/entrypoint_wrapper.py +250 -0
- airbyte_cdk/test/mock_http/__init__.py +6 -0
- airbyte_cdk/test/mock_http/matcher.py +41 -0
- airbyte_cdk/test/mock_http/mocker.py +185 -0
- airbyte_cdk/test/mock_http/request.py +103 -0
- airbyte_cdk/test/mock_http/response.py +28 -0
- airbyte_cdk/test/mock_http/response_builder.py +237 -0
- airbyte_cdk/test/state_builder.py +33 -0
- 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 +59 -0
- airbyte_cdk/test/utils/reading.py +26 -0
- airbyte_cdk/utils/__init__.py +10 -0
- airbyte_cdk/utils/airbyte_secrets_utils.py +80 -0
- airbyte_cdk/utils/analytics_message.py +25 -0
- airbyte_cdk/utils/constants.py +5 -0
- airbyte_cdk/utils/datetime_format_inferrer.py +94 -0
- airbyte_cdk/utils/datetime_helpers.py +499 -0
- airbyte_cdk/utils/event_timing.py +85 -0
- airbyte_cdk/utils/is_cloud_environment.py +18 -0
- airbyte_cdk/utils/mapping_helpers.py +162 -0
- airbyte_cdk/utils/message_utils.py +26 -0
- airbyte_cdk/utils/oneof_option_config.py +33 -0
- airbyte_cdk/utils/print_buffer.py +75 -0
- airbyte_cdk/utils/schema_inferrer.py +270 -0
- airbyte_cdk/utils/slice_hasher.py +37 -0
- airbyte_cdk/utils/spec_schema_transformations.py +26 -0
- airbyte_cdk/utils/stream_status_utils.py +43 -0
- airbyte_cdk/utils/traced_exception.py +145 -0
- airbyte_cdk-0.0.0.dev0.dist-info/LICENSE.txt +19 -0
- airbyte_cdk-0.0.0.dev0.dist-info/LICENSE_SHORT +1 -0
- airbyte_cdk-0.0.0.dev0.dist-info/METADATA +111 -0
- airbyte_cdk-0.0.0.dev0.dist-info/RECORD +368 -0
- airbyte_cdk-0.0.0.dev0.dist-info/WHEEL +4 -0
- airbyte_cdk-0.0.0.dev0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Generic, Iterator, List, Mapping, MutableMapping, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
from airbyte_cdk.models import (
|
|
9
|
+
AirbyteCatalog,
|
|
10
|
+
AirbyteMessage,
|
|
11
|
+
AirbyteStateMessage,
|
|
12
|
+
ConfiguredAirbyteCatalog,
|
|
13
|
+
)
|
|
14
|
+
from airbyte_cdk.sources.concurrent_source.concurrent_source import ConcurrentSource
|
|
15
|
+
from airbyte_cdk.sources.connector_state_manager import ConnectorStateManager
|
|
16
|
+
from airbyte_cdk.sources.declarative.concurrency_level import ConcurrencyLevel
|
|
17
|
+
from airbyte_cdk.sources.declarative.declarative_stream import DeclarativeStream
|
|
18
|
+
from airbyte_cdk.sources.declarative.extractors import RecordSelector
|
|
19
|
+
from airbyte_cdk.sources.declarative.extractors.record_filter import (
|
|
20
|
+
ClientSideIncrementalRecordFilterDecorator,
|
|
21
|
+
)
|
|
22
|
+
from airbyte_cdk.sources.declarative.incremental import ConcurrentPerPartitionCursor
|
|
23
|
+
from airbyte_cdk.sources.declarative.incremental.datetime_based_cursor import DatetimeBasedCursor
|
|
24
|
+
from airbyte_cdk.sources.declarative.incremental.per_partition_with_global import (
|
|
25
|
+
PerPartitionWithGlobalCursor,
|
|
26
|
+
)
|
|
27
|
+
from airbyte_cdk.sources.declarative.manifest_declarative_source import ManifestDeclarativeSource
|
|
28
|
+
from airbyte_cdk.sources.declarative.models import FileUploader
|
|
29
|
+
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
|
30
|
+
ConcurrencyLevel as ConcurrencyLevelModel,
|
|
31
|
+
)
|
|
32
|
+
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
|
33
|
+
DatetimeBasedCursor as DatetimeBasedCursorModel,
|
|
34
|
+
)
|
|
35
|
+
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
|
36
|
+
IncrementingCountCursor as IncrementingCountCursorModel,
|
|
37
|
+
)
|
|
38
|
+
from airbyte_cdk.sources.declarative.parsers.model_to_component_factory import (
|
|
39
|
+
ModelToComponentFactory,
|
|
40
|
+
)
|
|
41
|
+
from airbyte_cdk.sources.declarative.partition_routers import AsyncJobPartitionRouter
|
|
42
|
+
from airbyte_cdk.sources.declarative.retrievers import AsyncRetriever, Retriever, SimpleRetriever
|
|
43
|
+
from airbyte_cdk.sources.declarative.stream_slicers.declarative_partition_generator import (
|
|
44
|
+
DeclarativePartitionFactory,
|
|
45
|
+
StreamSlicerPartitionGenerator,
|
|
46
|
+
)
|
|
47
|
+
from airbyte_cdk.sources.declarative.types import ConnectionDefinition
|
|
48
|
+
from airbyte_cdk.sources.source import TState
|
|
49
|
+
from airbyte_cdk.sources.streams import Stream
|
|
50
|
+
from airbyte_cdk.sources.streams.concurrent.abstract_stream import AbstractStream
|
|
51
|
+
from airbyte_cdk.sources.streams.concurrent.abstract_stream_facade import AbstractStreamFacade
|
|
52
|
+
from airbyte_cdk.sources.streams.concurrent.availability_strategy import (
|
|
53
|
+
AlwaysAvailableAvailabilityStrategy,
|
|
54
|
+
)
|
|
55
|
+
from airbyte_cdk.sources.streams.concurrent.cursor import ConcurrentCursor, FinalStateCursor
|
|
56
|
+
from airbyte_cdk.sources.streams.concurrent.default_stream import DefaultStream
|
|
57
|
+
from airbyte_cdk.sources.streams.concurrent.helpers import get_primary_key_from_stream
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ConcurrentDeclarativeSource(ManifestDeclarativeSource, Generic[TState]):
|
|
61
|
+
# By default, we defer to a value of 2. A value lower than than could cause a PartitionEnqueuer to be stuck in a state of deadlock
|
|
62
|
+
# because it has hit the limit of futures but not partition reader is consuming them.
|
|
63
|
+
_LOWEST_SAFE_CONCURRENCY_LEVEL = 2
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
catalog: Optional[ConfiguredAirbyteCatalog],
|
|
68
|
+
config: Optional[Mapping[str, Any]],
|
|
69
|
+
state: TState,
|
|
70
|
+
source_config: ConnectionDefinition,
|
|
71
|
+
debug: bool = False,
|
|
72
|
+
emit_connector_builder_messages: bool = False,
|
|
73
|
+
component_factory: Optional[ModelToComponentFactory] = None,
|
|
74
|
+
**kwargs: Any,
|
|
75
|
+
) -> None:
|
|
76
|
+
# todo: We could remove state from initialization. Now that streams are grouped during the read(), a source
|
|
77
|
+
# no longer needs to store the original incoming state. But maybe there's an edge case?
|
|
78
|
+
self._connector_state_manager = ConnectorStateManager(state=state) # type: ignore # state is always in the form of List[AirbyteStateMessage]. The ConnectorStateManager should use generics, but this can be done later
|
|
79
|
+
|
|
80
|
+
# To reduce the complexity of the concurrent framework, we are not enabling RFR with synthetic
|
|
81
|
+
# cursors. We do this by no longer automatically instantiating RFR cursors when converting
|
|
82
|
+
# the declarative models into runtime components. Concurrent sources will continue to checkpoint
|
|
83
|
+
# incremental streams running in full refresh.
|
|
84
|
+
component_factory = component_factory or ModelToComponentFactory(
|
|
85
|
+
emit_connector_builder_messages=emit_connector_builder_messages,
|
|
86
|
+
disable_resumable_full_refresh=True,
|
|
87
|
+
connector_state_manager=self._connector_state_manager,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
super().__init__(
|
|
91
|
+
source_config=source_config,
|
|
92
|
+
config=config,
|
|
93
|
+
debug=debug,
|
|
94
|
+
emit_connector_builder_messages=emit_connector_builder_messages,
|
|
95
|
+
component_factory=component_factory,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
concurrency_level_from_manifest = self._source_config.get("concurrency_level")
|
|
99
|
+
if concurrency_level_from_manifest:
|
|
100
|
+
concurrency_level_component = self._constructor.create_component(
|
|
101
|
+
model_type=ConcurrencyLevelModel,
|
|
102
|
+
component_definition=concurrency_level_from_manifest,
|
|
103
|
+
config=config or {},
|
|
104
|
+
)
|
|
105
|
+
if not isinstance(concurrency_level_component, ConcurrencyLevel):
|
|
106
|
+
raise ValueError(
|
|
107
|
+
f"Expected to generate a ConcurrencyLevel component, but received {concurrency_level_component.__class__}"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
concurrency_level = concurrency_level_component.get_concurrency_level()
|
|
111
|
+
initial_number_of_partitions_to_generate = max(
|
|
112
|
+
concurrency_level // 2, 1
|
|
113
|
+
) # Partition_generation iterates using range based on this value. If this is floored to zero we end up in a dead lock during start up
|
|
114
|
+
else:
|
|
115
|
+
concurrency_level = self._LOWEST_SAFE_CONCURRENCY_LEVEL
|
|
116
|
+
initial_number_of_partitions_to_generate = self._LOWEST_SAFE_CONCURRENCY_LEVEL // 2
|
|
117
|
+
|
|
118
|
+
self._concurrent_source = ConcurrentSource.create(
|
|
119
|
+
num_workers=concurrency_level,
|
|
120
|
+
initial_number_of_partitions_to_generate=initial_number_of_partitions_to_generate,
|
|
121
|
+
logger=self.logger,
|
|
122
|
+
slice_logger=self._slice_logger,
|
|
123
|
+
message_repository=self.message_repository,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# TODO: Remove this. This property is necessary to safely migrate Stripe during the transition state.
|
|
127
|
+
@property
|
|
128
|
+
def is_partially_declarative(self) -> bool:
|
|
129
|
+
"""This flag used to avoid unexpected AbstractStreamFacade processing as concurrent streams."""
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
def read(
|
|
133
|
+
self,
|
|
134
|
+
logger: logging.Logger,
|
|
135
|
+
config: Mapping[str, Any],
|
|
136
|
+
catalog: ConfiguredAirbyteCatalog,
|
|
137
|
+
state: Optional[List[AirbyteStateMessage]] = None,
|
|
138
|
+
) -> Iterator[AirbyteMessage]:
|
|
139
|
+
concurrent_streams, _ = self._group_streams(config=config)
|
|
140
|
+
|
|
141
|
+
# ConcurrentReadProcessor pops streams that are finished being read so before syncing, the names of
|
|
142
|
+
# the concurrent streams must be saved so that they can be removed from the catalog before starting
|
|
143
|
+
# synchronous streams
|
|
144
|
+
if len(concurrent_streams) > 0:
|
|
145
|
+
concurrent_stream_names = set(
|
|
146
|
+
[concurrent_stream.name for concurrent_stream in concurrent_streams]
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
selected_concurrent_streams = self._select_streams(
|
|
150
|
+
streams=concurrent_streams, configured_catalog=catalog
|
|
151
|
+
)
|
|
152
|
+
# It would appear that passing in an empty set of streams causes an infinite loop in ConcurrentReadProcessor.
|
|
153
|
+
# This is also evident in concurrent_source_adapter.py so I'll leave this out of scope to fix for now
|
|
154
|
+
if selected_concurrent_streams:
|
|
155
|
+
yield from self._concurrent_source.read(selected_concurrent_streams)
|
|
156
|
+
|
|
157
|
+
# Sync all streams that are not concurrent compatible. We filter out concurrent streams because the
|
|
158
|
+
# existing AbstractSource.read() implementation iterates over the catalog when syncing streams. Many
|
|
159
|
+
# of which were already synced using the Concurrent CDK
|
|
160
|
+
filtered_catalog = self._remove_concurrent_streams_from_catalog(
|
|
161
|
+
catalog=catalog, concurrent_stream_names=concurrent_stream_names
|
|
162
|
+
)
|
|
163
|
+
else:
|
|
164
|
+
filtered_catalog = catalog
|
|
165
|
+
|
|
166
|
+
# It is no need run read for synchronous streams if they are not exists.
|
|
167
|
+
if not filtered_catalog.streams:
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
yield from super().read(logger, config, filtered_catalog, state)
|
|
171
|
+
|
|
172
|
+
def discover(self, logger: logging.Logger, config: Mapping[str, Any]) -> AirbyteCatalog:
|
|
173
|
+
concurrent_streams, synchronous_streams = self._group_streams(config=config)
|
|
174
|
+
return AirbyteCatalog(
|
|
175
|
+
streams=[
|
|
176
|
+
stream.as_airbyte_stream() for stream in concurrent_streams + synchronous_streams
|
|
177
|
+
]
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def streams(self, config: Mapping[str, Any]) -> List[Stream]:
|
|
181
|
+
"""
|
|
182
|
+
The `streams` method is used as part of the AbstractSource in the following cases:
|
|
183
|
+
* ConcurrentDeclarativeSource.check -> ManifestDeclarativeSource.check -> AbstractSource.check -> DeclarativeSource.check_connection -> CheckStream.check_connection -> streams
|
|
184
|
+
* ConcurrentDeclarativeSource.read -> AbstractSource.read -> streams (note that we filter for a specific catalog which excludes concurrent streams so not all streams actually read from all the streams returned by `streams`)
|
|
185
|
+
Note that `super.streams(config)` is also called when splitting the streams between concurrent or not in `_group_streams`.
|
|
186
|
+
|
|
187
|
+
In both case, we will assume that calling the DeclarativeStream is perfectly fine as the result for these is the same regardless of if it is a DeclarativeStream or a DefaultStream (concurrent). This should simply be removed once we have moved away from the mentioned code paths above.
|
|
188
|
+
"""
|
|
189
|
+
return super().streams(config)
|
|
190
|
+
|
|
191
|
+
def _group_streams(
|
|
192
|
+
self, config: Mapping[str, Any]
|
|
193
|
+
) -> Tuple[List[AbstractStream], List[Stream]]:
|
|
194
|
+
concurrent_streams: List[AbstractStream] = []
|
|
195
|
+
synchronous_streams: List[Stream] = []
|
|
196
|
+
|
|
197
|
+
# Combine streams and dynamic_streams. Note: both cannot be empty at the same time,
|
|
198
|
+
# and this is validated during the initialization of the source.
|
|
199
|
+
streams = self._stream_configs(self._source_config) + self._dynamic_stream_configs(
|
|
200
|
+
self._source_config, config
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
name_to_stream_mapping = {stream["name"]: stream for stream in streams}
|
|
204
|
+
|
|
205
|
+
for declarative_stream in self.streams(config=config):
|
|
206
|
+
# Some low-code sources use a combination of DeclarativeStream and regular Python streams. We can't inspect
|
|
207
|
+
# these legacy Python streams the way we do low-code streams to determine if they are concurrent compatible,
|
|
208
|
+
# so we need to treat them as synchronous
|
|
209
|
+
|
|
210
|
+
supports_file_transfer = (
|
|
211
|
+
"file_uploader" in name_to_stream_mapping[declarative_stream.name]
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
if (
|
|
215
|
+
isinstance(declarative_stream, DeclarativeStream)
|
|
216
|
+
and name_to_stream_mapping[declarative_stream.name]["type"]
|
|
217
|
+
== "StateDelegatingStream"
|
|
218
|
+
):
|
|
219
|
+
stream_state = self._connector_state_manager.get_stream_state(
|
|
220
|
+
stream_name=declarative_stream.name, namespace=declarative_stream.namespace
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
name_to_stream_mapping[declarative_stream.name] = (
|
|
224
|
+
name_to_stream_mapping[declarative_stream.name]["incremental_stream"]
|
|
225
|
+
if stream_state
|
|
226
|
+
else name_to_stream_mapping[declarative_stream.name]["full_refresh_stream"]
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
if isinstance(declarative_stream, DeclarativeStream) and (
|
|
230
|
+
name_to_stream_mapping[declarative_stream.name]["retriever"]["type"]
|
|
231
|
+
== "SimpleRetriever"
|
|
232
|
+
or name_to_stream_mapping[declarative_stream.name]["retriever"]["type"]
|
|
233
|
+
== "AsyncRetriever"
|
|
234
|
+
):
|
|
235
|
+
incremental_sync_component_definition = name_to_stream_mapping[
|
|
236
|
+
declarative_stream.name
|
|
237
|
+
].get("incremental_sync")
|
|
238
|
+
|
|
239
|
+
partition_router_component_definition = (
|
|
240
|
+
name_to_stream_mapping[declarative_stream.name]
|
|
241
|
+
.get("retriever", {})
|
|
242
|
+
.get("partition_router")
|
|
243
|
+
)
|
|
244
|
+
is_without_partition_router_or_cursor = not bool(
|
|
245
|
+
incremental_sync_component_definition
|
|
246
|
+
) and not bool(partition_router_component_definition)
|
|
247
|
+
|
|
248
|
+
is_substream_without_incremental = (
|
|
249
|
+
partition_router_component_definition
|
|
250
|
+
and not incremental_sync_component_definition
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
if self._is_concurrent_cursor_incremental_without_partition_routing(
|
|
254
|
+
declarative_stream, incremental_sync_component_definition
|
|
255
|
+
):
|
|
256
|
+
stream_state = self._connector_state_manager.get_stream_state(
|
|
257
|
+
stream_name=declarative_stream.name, namespace=declarative_stream.namespace
|
|
258
|
+
)
|
|
259
|
+
stream_state = self._migrate_state(declarative_stream, stream_state)
|
|
260
|
+
|
|
261
|
+
retriever = self._get_retriever(declarative_stream, stream_state)
|
|
262
|
+
|
|
263
|
+
if isinstance(declarative_stream.retriever, AsyncRetriever) and isinstance(
|
|
264
|
+
declarative_stream.retriever.stream_slicer, AsyncJobPartitionRouter
|
|
265
|
+
):
|
|
266
|
+
cursor = declarative_stream.retriever.stream_slicer.stream_slicer
|
|
267
|
+
|
|
268
|
+
if not isinstance(cursor, ConcurrentCursor | ConcurrentPerPartitionCursor):
|
|
269
|
+
# This should never happen since we instantiate ConcurrentCursor in
|
|
270
|
+
# model_to_component_factory.py
|
|
271
|
+
raise ValueError(
|
|
272
|
+
f"Expected AsyncJobPartitionRouter stream_slicer to be of type ConcurrentCursor, but received{cursor.__class__}"
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
partition_generator = StreamSlicerPartitionGenerator(
|
|
276
|
+
partition_factory=DeclarativePartitionFactory(
|
|
277
|
+
declarative_stream.name,
|
|
278
|
+
declarative_stream.get_json_schema(),
|
|
279
|
+
retriever,
|
|
280
|
+
self.message_repository,
|
|
281
|
+
),
|
|
282
|
+
stream_slicer=declarative_stream.retriever.stream_slicer,
|
|
283
|
+
)
|
|
284
|
+
else:
|
|
285
|
+
if (
|
|
286
|
+
incremental_sync_component_definition
|
|
287
|
+
and incremental_sync_component_definition.get("type")
|
|
288
|
+
== IncrementingCountCursorModel.__name__
|
|
289
|
+
):
|
|
290
|
+
cursor = self._constructor.create_concurrent_cursor_from_incrementing_count_cursor(
|
|
291
|
+
model_type=IncrementingCountCursorModel,
|
|
292
|
+
component_definition=incremental_sync_component_definition, # type: ignore # Not None because of the if condition above
|
|
293
|
+
stream_name=declarative_stream.name,
|
|
294
|
+
stream_namespace=declarative_stream.namespace,
|
|
295
|
+
config=config or {},
|
|
296
|
+
)
|
|
297
|
+
else:
|
|
298
|
+
cursor = self._constructor.create_concurrent_cursor_from_datetime_based_cursor(
|
|
299
|
+
model_type=DatetimeBasedCursorModel,
|
|
300
|
+
component_definition=incremental_sync_component_definition, # type: ignore # Not None because of the if condition above
|
|
301
|
+
stream_name=declarative_stream.name,
|
|
302
|
+
stream_namespace=declarative_stream.namespace,
|
|
303
|
+
config=config or {},
|
|
304
|
+
)
|
|
305
|
+
partition_generator = StreamSlicerPartitionGenerator(
|
|
306
|
+
partition_factory=DeclarativePartitionFactory(
|
|
307
|
+
declarative_stream.name,
|
|
308
|
+
declarative_stream.get_json_schema(),
|
|
309
|
+
retriever,
|
|
310
|
+
self.message_repository,
|
|
311
|
+
),
|
|
312
|
+
stream_slicer=cursor,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
concurrent_streams.append(
|
|
316
|
+
DefaultStream(
|
|
317
|
+
partition_generator=partition_generator,
|
|
318
|
+
name=declarative_stream.name,
|
|
319
|
+
json_schema=declarative_stream.get_json_schema(),
|
|
320
|
+
availability_strategy=AlwaysAvailableAvailabilityStrategy(),
|
|
321
|
+
primary_key=get_primary_key_from_stream(declarative_stream.primary_key),
|
|
322
|
+
cursor_field=cursor.cursor_field.cursor_field_key
|
|
323
|
+
if hasattr(cursor, "cursor_field")
|
|
324
|
+
and hasattr(
|
|
325
|
+
cursor.cursor_field, "cursor_field_key"
|
|
326
|
+
) # FIXME this will need to be updated once we do the per partition
|
|
327
|
+
else None,
|
|
328
|
+
logger=self.logger,
|
|
329
|
+
cursor=cursor,
|
|
330
|
+
supports_file_transfer=supports_file_transfer,
|
|
331
|
+
)
|
|
332
|
+
)
|
|
333
|
+
elif (
|
|
334
|
+
is_substream_without_incremental or is_without_partition_router_or_cursor
|
|
335
|
+
) and hasattr(declarative_stream.retriever, "stream_slicer"):
|
|
336
|
+
partition_generator = StreamSlicerPartitionGenerator(
|
|
337
|
+
DeclarativePartitionFactory(
|
|
338
|
+
declarative_stream.name,
|
|
339
|
+
declarative_stream.get_json_schema(),
|
|
340
|
+
declarative_stream.retriever,
|
|
341
|
+
self.message_repository,
|
|
342
|
+
),
|
|
343
|
+
declarative_stream.retriever.stream_slicer,
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
final_state_cursor = FinalStateCursor(
|
|
347
|
+
stream_name=declarative_stream.name,
|
|
348
|
+
stream_namespace=declarative_stream.namespace,
|
|
349
|
+
message_repository=self.message_repository,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
concurrent_streams.append(
|
|
353
|
+
DefaultStream(
|
|
354
|
+
partition_generator=partition_generator,
|
|
355
|
+
name=declarative_stream.name,
|
|
356
|
+
json_schema=declarative_stream.get_json_schema(),
|
|
357
|
+
availability_strategy=AlwaysAvailableAvailabilityStrategy(),
|
|
358
|
+
primary_key=get_primary_key_from_stream(declarative_stream.primary_key),
|
|
359
|
+
cursor_field=None,
|
|
360
|
+
logger=self.logger,
|
|
361
|
+
cursor=final_state_cursor,
|
|
362
|
+
supports_file_transfer=supports_file_transfer,
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
elif (
|
|
366
|
+
incremental_sync_component_definition
|
|
367
|
+
and incremental_sync_component_definition.get("type", "")
|
|
368
|
+
== DatetimeBasedCursorModel.__name__
|
|
369
|
+
and hasattr(declarative_stream.retriever, "stream_slicer")
|
|
370
|
+
and isinstance(
|
|
371
|
+
declarative_stream.retriever.stream_slicer, PerPartitionWithGlobalCursor
|
|
372
|
+
)
|
|
373
|
+
):
|
|
374
|
+
stream_state = self._connector_state_manager.get_stream_state(
|
|
375
|
+
stream_name=declarative_stream.name, namespace=declarative_stream.namespace
|
|
376
|
+
)
|
|
377
|
+
stream_state = self._migrate_state(declarative_stream, stream_state)
|
|
378
|
+
|
|
379
|
+
partition_router = declarative_stream.retriever.stream_slicer._partition_router
|
|
380
|
+
|
|
381
|
+
perpartition_cursor = (
|
|
382
|
+
self._constructor.create_concurrent_cursor_from_perpartition_cursor(
|
|
383
|
+
state_manager=self._connector_state_manager,
|
|
384
|
+
model_type=DatetimeBasedCursorModel,
|
|
385
|
+
component_definition=incremental_sync_component_definition,
|
|
386
|
+
stream_name=declarative_stream.name,
|
|
387
|
+
stream_namespace=declarative_stream.namespace,
|
|
388
|
+
config=config or {},
|
|
389
|
+
stream_state=stream_state,
|
|
390
|
+
partition_router=partition_router,
|
|
391
|
+
)
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
retriever = self._get_retriever(declarative_stream, stream_state)
|
|
395
|
+
|
|
396
|
+
partition_generator = StreamSlicerPartitionGenerator(
|
|
397
|
+
DeclarativePartitionFactory(
|
|
398
|
+
declarative_stream.name,
|
|
399
|
+
declarative_stream.get_json_schema(),
|
|
400
|
+
retriever,
|
|
401
|
+
self.message_repository,
|
|
402
|
+
),
|
|
403
|
+
perpartition_cursor,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
concurrent_streams.append(
|
|
407
|
+
DefaultStream(
|
|
408
|
+
partition_generator=partition_generator,
|
|
409
|
+
name=declarative_stream.name,
|
|
410
|
+
json_schema=declarative_stream.get_json_schema(),
|
|
411
|
+
availability_strategy=AlwaysAvailableAvailabilityStrategy(),
|
|
412
|
+
primary_key=get_primary_key_from_stream(declarative_stream.primary_key),
|
|
413
|
+
cursor_field=perpartition_cursor.cursor_field.cursor_field_key,
|
|
414
|
+
logger=self.logger,
|
|
415
|
+
cursor=perpartition_cursor,
|
|
416
|
+
supports_file_transfer=supports_file_transfer,
|
|
417
|
+
)
|
|
418
|
+
)
|
|
419
|
+
else:
|
|
420
|
+
synchronous_streams.append(declarative_stream)
|
|
421
|
+
# TODO: Remove this. This check is necessary to safely migrate Stripe during the transition state.
|
|
422
|
+
# Condition below needs to ensure that concurrent support is not lost for sources that already support
|
|
423
|
+
# it before migration, but now are only partially migrated to declarative implementation (e.g., Stripe).
|
|
424
|
+
elif (
|
|
425
|
+
isinstance(declarative_stream, AbstractStreamFacade)
|
|
426
|
+
and self.is_partially_declarative
|
|
427
|
+
):
|
|
428
|
+
concurrent_streams.append(declarative_stream.get_underlying_stream())
|
|
429
|
+
else:
|
|
430
|
+
synchronous_streams.append(declarative_stream)
|
|
431
|
+
|
|
432
|
+
return concurrent_streams, synchronous_streams
|
|
433
|
+
|
|
434
|
+
def _is_concurrent_cursor_incremental_without_partition_routing(
|
|
435
|
+
self,
|
|
436
|
+
declarative_stream: DeclarativeStream,
|
|
437
|
+
incremental_sync_component_definition: Mapping[str, Any] | None,
|
|
438
|
+
) -> bool:
|
|
439
|
+
return (
|
|
440
|
+
incremental_sync_component_definition is not None
|
|
441
|
+
and bool(incremental_sync_component_definition)
|
|
442
|
+
and (
|
|
443
|
+
incremental_sync_component_definition.get("type", "")
|
|
444
|
+
in (DatetimeBasedCursorModel.__name__, IncrementingCountCursorModel.__name__)
|
|
445
|
+
)
|
|
446
|
+
and hasattr(declarative_stream.retriever, "stream_slicer")
|
|
447
|
+
and (
|
|
448
|
+
isinstance(declarative_stream.retriever.stream_slicer, DatetimeBasedCursor)
|
|
449
|
+
# IncrementingCountCursorModel is hardcoded to be of type DatetimeBasedCursor
|
|
450
|
+
# add isintance check here if we want to create a Declarative IncrementingCountCursor
|
|
451
|
+
# or isinstance(
|
|
452
|
+
# declarative_stream.retriever.stream_slicer, IncrementingCountCursor
|
|
453
|
+
# )
|
|
454
|
+
or isinstance(declarative_stream.retriever.stream_slicer, AsyncJobPartitionRouter)
|
|
455
|
+
)
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
@staticmethod
|
|
459
|
+
def _get_retriever(
|
|
460
|
+
declarative_stream: DeclarativeStream, stream_state: Mapping[str, Any]
|
|
461
|
+
) -> Retriever:
|
|
462
|
+
retriever = declarative_stream.retriever
|
|
463
|
+
|
|
464
|
+
# This is an optimization so that we don't invoke any cursor or state management flows within the
|
|
465
|
+
# low-code framework because state management is handled through the ConcurrentCursor.
|
|
466
|
+
if declarative_stream and isinstance(retriever, SimpleRetriever):
|
|
467
|
+
# Also a temporary hack. In the legacy Stream implementation, as part of the read,
|
|
468
|
+
# set_initial_state() is called to instantiate incoming state on the cursor. Although we no
|
|
469
|
+
# longer rely on the legacy low-code cursor for concurrent checkpointing, low-code components
|
|
470
|
+
# like StopConditionPaginationStrategyDecorator still rely on a DatetimeBasedCursor that is
|
|
471
|
+
# properly initialized with state.
|
|
472
|
+
if retriever.cursor:
|
|
473
|
+
retriever.cursor.set_initial_state(stream_state=stream_state)
|
|
474
|
+
|
|
475
|
+
# Similar to above, the ClientSideIncrementalRecordFilterDecorator cursor is a separate instance
|
|
476
|
+
# from the one initialized on the SimpleRetriever, so it also must also have state initialized
|
|
477
|
+
# for semi-incremental streams using is_client_side_incremental to filter properly
|
|
478
|
+
if isinstance(retriever.record_selector, RecordSelector) and isinstance(
|
|
479
|
+
retriever.record_selector.record_filter, ClientSideIncrementalRecordFilterDecorator
|
|
480
|
+
):
|
|
481
|
+
retriever.record_selector.record_filter._cursor.set_initial_state(
|
|
482
|
+
stream_state=stream_state
|
|
483
|
+
) # type: ignore # After non-concurrent cursors are deprecated we can remove these cursor workarounds
|
|
484
|
+
|
|
485
|
+
# We zero it out here, but since this is a cursor reference, the state is still properly
|
|
486
|
+
# instantiated for the other components that reference it
|
|
487
|
+
retriever.cursor = None
|
|
488
|
+
|
|
489
|
+
return retriever
|
|
490
|
+
|
|
491
|
+
@staticmethod
|
|
492
|
+
def _select_streams(
|
|
493
|
+
streams: List[AbstractStream], configured_catalog: ConfiguredAirbyteCatalog
|
|
494
|
+
) -> List[AbstractStream]:
|
|
495
|
+
stream_name_to_instance: Mapping[str, AbstractStream] = {s.name: s for s in streams}
|
|
496
|
+
abstract_streams: List[AbstractStream] = []
|
|
497
|
+
for configured_stream in configured_catalog.streams:
|
|
498
|
+
stream_instance = stream_name_to_instance.get(configured_stream.stream.name)
|
|
499
|
+
if stream_instance:
|
|
500
|
+
abstract_streams.append(stream_instance)
|
|
501
|
+
|
|
502
|
+
return abstract_streams
|
|
503
|
+
|
|
504
|
+
@staticmethod
|
|
505
|
+
def _remove_concurrent_streams_from_catalog(
|
|
506
|
+
catalog: ConfiguredAirbyteCatalog,
|
|
507
|
+
concurrent_stream_names: set[str],
|
|
508
|
+
) -> ConfiguredAirbyteCatalog:
|
|
509
|
+
return ConfiguredAirbyteCatalog(
|
|
510
|
+
streams=[
|
|
511
|
+
stream
|
|
512
|
+
for stream in catalog.streams
|
|
513
|
+
if stream.stream.name not in concurrent_stream_names
|
|
514
|
+
]
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
@staticmethod
|
|
518
|
+
def _migrate_state(
|
|
519
|
+
declarative_stream: DeclarativeStream, stream_state: MutableMapping[str, Any]
|
|
520
|
+
) -> MutableMapping[str, Any]:
|
|
521
|
+
for state_migration in declarative_stream.state_migrations:
|
|
522
|
+
if state_migration.should_migrate(stream_state):
|
|
523
|
+
# The state variable is expected to be mutable but the migrate method returns an immutable mapping.
|
|
524
|
+
stream_state = dict(state_migration.migrate(stream_state))
|
|
525
|
+
|
|
526
|
+
return stream_state
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
import datetime
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DatetimeParser:
|
|
10
|
+
"""
|
|
11
|
+
Parses and formats datetime objects according to a specified format.
|
|
12
|
+
|
|
13
|
+
This class mainly acts as a wrapper to properly handling timestamp formatting through the "%s" directive.
|
|
14
|
+
|
|
15
|
+
%s is part of the list of format codes required by the 1989 C standard, but it is unreliable because it always return a datetime in the system's timezone.
|
|
16
|
+
Instead of using the directive directly, we can use datetime.fromtimestamp and dt.timestamp()
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
_UNIX_EPOCH = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
|
|
20
|
+
|
|
21
|
+
def parse(self, date: Union[str, int], format: str) -> datetime.datetime:
|
|
22
|
+
# "%s" is a valid (but unreliable) directive for formatting, but not for parsing
|
|
23
|
+
# It is defined as
|
|
24
|
+
# The number of seconds since the Epoch, 1970-01-01 00:00:00+0000 (UTC). https://man7.org/linux/man-pages/man3/strptime.3.html
|
|
25
|
+
#
|
|
26
|
+
# The recommended way to parse a date from its timestamp representation is to use datetime.fromtimestamp
|
|
27
|
+
# See https://stackoverflow.com/a/4974930
|
|
28
|
+
if format == "%s":
|
|
29
|
+
return datetime.datetime.fromtimestamp(int(date), tz=datetime.timezone.utc)
|
|
30
|
+
elif format == "%s_as_float":
|
|
31
|
+
return datetime.datetime.fromtimestamp(float(date), tz=datetime.timezone.utc)
|
|
32
|
+
elif format == "%epoch_microseconds":
|
|
33
|
+
return self._UNIX_EPOCH + datetime.timedelta(microseconds=int(date))
|
|
34
|
+
elif format == "%ms":
|
|
35
|
+
return self._UNIX_EPOCH + datetime.timedelta(milliseconds=int(date))
|
|
36
|
+
elif "%_ms" in format:
|
|
37
|
+
format = format.replace("%_ms", "%f")
|
|
38
|
+
parsed_datetime = datetime.datetime.strptime(str(date), format)
|
|
39
|
+
if self._is_naive(parsed_datetime):
|
|
40
|
+
return parsed_datetime.replace(tzinfo=datetime.timezone.utc)
|
|
41
|
+
return parsed_datetime
|
|
42
|
+
|
|
43
|
+
def format(self, dt: datetime.datetime, format: str) -> str:
|
|
44
|
+
# strftime("%s") is unreliable because it ignores the time zone information and assumes the time zone of the system it's running on
|
|
45
|
+
# It's safer to use the timestamp() method than the %s directive
|
|
46
|
+
# See https://stackoverflow.com/a/4974930
|
|
47
|
+
if format == "%s":
|
|
48
|
+
return str(int(dt.timestamp()))
|
|
49
|
+
if format == "%s_as_float":
|
|
50
|
+
return str(float(dt.timestamp()))
|
|
51
|
+
if format == "%epoch_microseconds":
|
|
52
|
+
return str(int(dt.timestamp() * 1_000_000))
|
|
53
|
+
if format == "%ms":
|
|
54
|
+
# timstamp() returns a float representing the number of seconds since the unix epoch
|
|
55
|
+
return str(int(dt.timestamp() * 1000))
|
|
56
|
+
if "%_ms" in format:
|
|
57
|
+
_format = format.replace("%_ms", "%f")
|
|
58
|
+
milliseconds = int(dt.microsecond / 1000)
|
|
59
|
+
formatted_dt = dt.strftime(_format).replace(dt.strftime("%f"), "%03d" % milliseconds)
|
|
60
|
+
return formatted_dt
|
|
61
|
+
else:
|
|
62
|
+
return dt.strftime(format)
|
|
63
|
+
|
|
64
|
+
def _is_naive(self, dt: datetime.datetime) -> bool:
|
|
65
|
+
return dt.tzinfo is None or dt.tzinfo.utcoffset(dt) is None
|