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,152 @@
|
|
|
1
|
+
## Behavior
|
|
2
|
+
|
|
3
|
+
The Airbyte protocol defines the actions `spec`, `discover`, `check` and `read` for a source to be compliant. Here is the high-level description of the flow for a file-based source:
|
|
4
|
+
|
|
5
|
+
- spec: calls AbstractFileBasedSpec.documentation_url and AbstractFileBasedSpec.schema to return a ConnectorSpecification.
|
|
6
|
+
- discover: calls Source.streams, and subsequently Stream.get_json_schema; this uses Source.open_file to open files during schema discovery.
|
|
7
|
+
- check: Source.check_connection is called from the entrypoint code (in the main CDK).
|
|
8
|
+
- read: Stream.read_records calls Stream.list_files which calls Source.list_matching_files, and then also uses Source.open_file to parse records from the file handle.
|
|
9
|
+
|
|
10
|
+
## How to Implement Your Own
|
|
11
|
+
|
|
12
|
+
To create a file-based source a user must extend three classes – AbstractFileBasedSource, AbstractFileBasedSpec, and AbstractStreamReader – to create an implementation for the connector’s specific storage system. They then initialize a FileBasedSource with the instance of AbstractStreamReader specific to their storage system.
|
|
13
|
+
|
|
14
|
+
The abstract classes house the vast majority of the logic required by file-based sources. For example, when extending AbstractStreamReader, users only have to implement three methods:
|
|
15
|
+
|
|
16
|
+
- list_matching_files: lists files matching the glob pattern(s) provided in the config.
|
|
17
|
+
- open_file: returns a file handle for reading.
|
|
18
|
+
- config property setter: concrete implementations of AbstractFileBasedStreamReader's config setter should assert that `value` is of the correct config type for that type of StreamReader.
|
|
19
|
+
|
|
20
|
+
The result is that an implementation of a source might look like this:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
class CustomStreamReader(AbstractStreamReader):
|
|
24
|
+
def open_file(self, remote_file: RemoteFile) -> FileHandler:
|
|
25
|
+
<...>
|
|
26
|
+
|
|
27
|
+
def get_matching_files(
|
|
28
|
+
self,
|
|
29
|
+
globs: List[str],
|
|
30
|
+
logger: logging.Logger,
|
|
31
|
+
) -> Iterable[RemoteFile]:
|
|
32
|
+
<...>
|
|
33
|
+
|
|
34
|
+
@config.setter
|
|
35
|
+
def config(self, value: Config):
|
|
36
|
+
assert isinstance(value, CustomConfig)
|
|
37
|
+
self._config = value
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CustomConfig(AbstractFileBasedSpec):
|
|
41
|
+
@classmethod
|
|
42
|
+
def documentation_url(cls) -> AnyUrl:
|
|
43
|
+
return AnyUrl("https://docs.airbyte.com/integrations/sources/s3", scheme="https")
|
|
44
|
+
|
|
45
|
+
a_spec_field: str = Field(title="A Spec Field", description="This is where you describe the fields of the spec", order=0)
|
|
46
|
+
<...>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
For more information, feel free to check the docstrings of each classes or check specific implementations (like source-s3).
|
|
50
|
+
|
|
51
|
+
## Supported File Types
|
|
52
|
+
|
|
53
|
+
### Avro
|
|
54
|
+
|
|
55
|
+
Avro is a serialization format developed by [Apache](https://avro.apache.org/docs/). Avro configuration options for the file-based CDK:
|
|
56
|
+
|
|
57
|
+
- `double_as_string`: Whether to convert double fields to strings. This is recommended if you have decimal numbers with a high degree of precision because there can be a loss precision when handling floating point numbers.
|
|
58
|
+
|
|
59
|
+
### CSV
|
|
60
|
+
|
|
61
|
+
CSV is a format loosely described by [RFC 4180](https://www.rfc-editor.org/rfc/rfc4180). The format is quite flexible which leads to a ton of options to consider:
|
|
62
|
+
|
|
63
|
+
- `delimiter`: The character delimiting individual cells in the CSV data. By name, CSV is comma separated so the default value is `,`
|
|
64
|
+
- `quote_char`: When quoted fields are used, it is possible for a field to span multiple lines, even when line breaks appear within such field. The default quote character is `"`.
|
|
65
|
+
- `escape_char`: The character used for escaping special characters.
|
|
66
|
+
- `encoding`: The character encoding of the file. By default, `UTF-8`
|
|
67
|
+
- `double_quote`: Whether two quotes in a quoted CSV value denote a single quote in the data.
|
|
68
|
+
- `quoting_behavior`: The quoting behavior determines when a value in a row should have quote marks added around it.
|
|
69
|
+
- `skip_rows_before_header`: The number of rows to skip before the header row. For example, if the header row is on the 3rd row, enter 2 in this field.
|
|
70
|
+
- `skip_rows_after_header`: The number of rows to skip after the header row.
|
|
71
|
+
- `autogenerate_column_names`: If your CSV does not have a header row, the file-based CDK will need this enable to generate column names.
|
|
72
|
+
- `null_values`: As CSV does not explicitly define a value for null values, the user can specify a set of case-sensitive strings that should be interpreted as null values.
|
|
73
|
+
- `true_values`: As CSV does not explicitly define a value for positive boolean, the user can specify a set of case-sensitive strings that should be interpreted as true values.
|
|
74
|
+
- `false_values`: As CSV does not explicitly define a value for negative boolean, the user can specify a set of case-sensitive strings that should be interpreted as false values.
|
|
75
|
+
|
|
76
|
+
### JSONL
|
|
77
|
+
|
|
78
|
+
[JSONL](https://jsonlines.org/) (or JSON Lines) is a format where each row is a JSON object. There are no configuration option for this format. For backward compatibility reasons, the JSONL parser currently supports multiline objects even though this is not part of the JSONL standard. Following some data gathering, we reserve the right to remove the support for this. Given that files have multiline JSON objects, performances will be slow.
|
|
79
|
+
|
|
80
|
+
### Parquet
|
|
81
|
+
|
|
82
|
+
Parquet is a file format defined by [Apache](https://parquet.apache.org/). Configuration options are:
|
|
83
|
+
|
|
84
|
+
- `decimal_as_float`: Whether to convert decimal fields to floats. There is a loss of precision when converting decimals to floats, so this is not recommended.
|
|
85
|
+
|
|
86
|
+
### Document file types (PDF, DOCX, Markdown)
|
|
87
|
+
|
|
88
|
+
For file share source connectors, the `unstructured` parser can be used to parse document file types. The textual content of the whole file will be parsed as a single record with a `content` field containing the text encoded as markdown.
|
|
89
|
+
|
|
90
|
+
To use the unstructured parser, the libraries `poppler` and `tesseract` need to be installed on the system running the connector. For example, on Ubuntu, you can install them with the following command:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
apt-get install -y tesseract-ocr poppler-utils
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
on Mac, you can install these via brew:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
brew install poppler
|
|
100
|
+
brew install tesseract
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Schema
|
|
104
|
+
|
|
105
|
+
Having a schema allows for the file-based CDK to take action when there is a discrepancy between a record and what are the expected types of the record fields.
|
|
106
|
+
|
|
107
|
+
Schema can be either inferred or user provided.
|
|
108
|
+
|
|
109
|
+
- If the user defines it a format using JSON types, inference will not apply. Input schemas are a key/value pair of strings describing column name and data type. Supported types are `["string", "number", "integer", "object", "array", "boolean", "null"]`. For example, `{"col1": "string", "col2": "boolean"}`.
|
|
110
|
+
- If the user enables schemaless sync, schema will `{"data": "object"}` and therefore emitted records will look like `{"data": {"col1": val1, …}}`. This is recommended if the contents between files in the stream vary significantly, and/or if data is very nested.
|
|
111
|
+
- Else, the file-based CDK will infer the schema depending on the file type. Some file formats defined the schema as part of their metadata (like Parquet), some do on the record-level (like Avro) and some don't have any explicit typing (like JSON or CSV). Note that all CSV values are inferred as strings except where we are supporting legacy configurations. Any file format that does not define their schema on a metadata level will require the file-based CDK to iterate to a number of records. There is a limit of bytes that will be consumed in order to infer the schema.
|
|
112
|
+
|
|
113
|
+
### Validation Policies
|
|
114
|
+
|
|
115
|
+
Users will be required to select one of 3 different options, in the event that records are encountered that don’t conform to the schema.
|
|
116
|
+
|
|
117
|
+
- Skip nonconforming records: check each record to see if it conforms to the user-input or inferred schema; skip the record if it doesn't conform. We keep a count of the number of records in each file that do and do not conform and emit a log message with these counts once we’re done reading the file.
|
|
118
|
+
- Emit all records: emit all records, even if they do not conform to the user-provided or inferred schema. Columns that don't exist in the configured catalog probably won't be available in the destination's table since that's the current behavior.
|
|
119
|
+
Only error if there are conflicting field types or malformed rows.
|
|
120
|
+
- Stop the sync and wait for schema re-discovery: if a record is encountered that does not conform to the configured catalog’s schema, we log a message and stop the whole sync. Note: this option is not recommended if the files have very different columns or datatypes, because the inferred schema may vary significantly at discover time.
|
|
121
|
+
|
|
122
|
+
When the `schemaless` is enabled, validation will be skipped.
|
|
123
|
+
|
|
124
|
+
## Breaking Changes (compared to previous S3 implementation)
|
|
125
|
+
|
|
126
|
+
- [CSV] Mapping of type `array` and `object`: before, they were mapped as `large_string` and hence casted as strings. Given the new changes, if `array` or `object` is specified, the value will be casted as `array` and `object` respectively.
|
|
127
|
+
- [CSV] Before, a string value would not be considered as `null_values` if the column type was a string. We will now start to cast string columns with values matching `null_values` to null.
|
|
128
|
+
- [CSV] `decimal_point` option is deprecated: It is not possible anymore to use another character than `.` to separate the integer part from non-integer part. Given that the float is format with another character than this, it will be considered as a string.
|
|
129
|
+
- [Parquet] `columns` option is deprecated: You can use Airbyte column selection in order to have the same behavior. We don't expect it, but this could have impact on the performance as payload could be bigger.
|
|
130
|
+
|
|
131
|
+
## Incremental syncs
|
|
132
|
+
|
|
133
|
+
The file-based connectors supports the following [sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes):
|
|
134
|
+
|
|
135
|
+
| Feature | Supported? |
|
|
136
|
+
| :--------------------------------------------- | :--------- |
|
|
137
|
+
| Full Refresh Sync | Yes |
|
|
138
|
+
| Incremental Sync | Yes |
|
|
139
|
+
| Replicate Incremental Deletes | No |
|
|
140
|
+
| Replicate Multiple Files \(pattern matching\) | Yes |
|
|
141
|
+
| Replicate Multiple Streams \(distinct tables\) | Yes |
|
|
142
|
+
| Namespaces | No |
|
|
143
|
+
|
|
144
|
+
We recommend you do not manually modify files that are already synced. The connector has file-level granularity, which means adding or modifying a row in a CSV file will trigger a re-sync of the content of that file.
|
|
145
|
+
|
|
146
|
+
### Incremental sync
|
|
147
|
+
|
|
148
|
+
After the initial sync, the connector only pulls files that were modified since the last sync.
|
|
149
|
+
|
|
150
|
+
The connector checkpoints the connection states when it is done syncing all files for a given timestamp. The connection's state only keeps track of the last 10 000 files synced. If more than 10 000 files are synced, the connector won't be able to rely on the connection state to deduplicate files. In this case, the connector will initialize its cursor to the minimum between the earliest file in the history, or 3 days ago.
|
|
151
|
+
|
|
152
|
+
Both the maximum number of files, and the time buffer can be configured by connector developers.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from .config.abstract_file_based_spec import AbstractFileBasedSpec
|
|
2
|
+
from .config.csv_format import CsvFormat
|
|
3
|
+
from .config.file_based_stream_config import FileBasedStreamConfig
|
|
4
|
+
from .config.jsonl_format import JsonlFormat
|
|
5
|
+
from .exceptions import CustomFileBasedException, ErrorListingFiles, FileBasedSourceError
|
|
6
|
+
from .file_based_source import DEFAULT_CONCURRENCY, FileBasedSource
|
|
7
|
+
from .file_based_stream_reader import AbstractFileBasedStreamReader, FileReadMode
|
|
8
|
+
from .remote_file import RemoteFile
|
|
9
|
+
from .stream.cursor import DefaultFileBasedCursor
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"AbstractFileBasedSpec",
|
|
13
|
+
"AbstractFileBasedStreamReader",
|
|
14
|
+
"CsvFormat",
|
|
15
|
+
"CustomFileBasedException",
|
|
16
|
+
"DefaultFileBasedCursor",
|
|
17
|
+
"ErrorListingFiles",
|
|
18
|
+
"FileBasedSource",
|
|
19
|
+
"FileBasedSourceError",
|
|
20
|
+
"FileBasedStreamConfig",
|
|
21
|
+
"FileReadMode",
|
|
22
|
+
"JsonlFormat",
|
|
23
|
+
"RemoteFile",
|
|
24
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .abstract_file_based_availability_strategy import (
|
|
2
|
+
AbstractFileBasedAvailabilityStrategy,
|
|
3
|
+
AbstractFileBasedAvailabilityStrategyWrapper,
|
|
4
|
+
)
|
|
5
|
+
from .default_file_based_availability_strategy import DefaultFileBasedAvailabilityStrategy
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"AbstractFileBasedAvailabilityStrategy",
|
|
9
|
+
"AbstractFileBasedAvailabilityStrategyWrapper",
|
|
10
|
+
"DefaultFileBasedAvailabilityStrategy",
|
|
11
|
+
]
|
airbyte_cdk/sources/file_based/availability_strategy/abstract_file_based_availability_strategy.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from abc import abstractmethod
|
|
9
|
+
from typing import TYPE_CHECKING, Optional, Tuple
|
|
10
|
+
|
|
11
|
+
from airbyte_cdk.sources import Source
|
|
12
|
+
from airbyte_cdk.sources.streams.availability_strategy import AvailabilityStrategy
|
|
13
|
+
from airbyte_cdk.sources.streams.concurrent.availability_strategy import (
|
|
14
|
+
AbstractAvailabilityStrategy,
|
|
15
|
+
StreamAvailability,
|
|
16
|
+
StreamAvailable,
|
|
17
|
+
StreamUnavailable,
|
|
18
|
+
)
|
|
19
|
+
from airbyte_cdk.sources.streams.core import Stream
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from airbyte_cdk.sources.file_based.stream import AbstractFileBasedStream
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AbstractFileBasedAvailabilityStrategy(AvailabilityStrategy):
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def check_availability( # type: ignore[override] # Signature doesn't match base class
|
|
28
|
+
self,
|
|
29
|
+
stream: Stream,
|
|
30
|
+
logger: logging.Logger,
|
|
31
|
+
_: Optional[Source],
|
|
32
|
+
) -> Tuple[bool, Optional[str]]:
|
|
33
|
+
"""
|
|
34
|
+
Perform a connection check for the stream.
|
|
35
|
+
|
|
36
|
+
Returns (True, None) if successful, otherwise (False, <error message>).
|
|
37
|
+
"""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def check_availability_and_parsability(
|
|
42
|
+
self,
|
|
43
|
+
stream: AbstractFileBasedStream,
|
|
44
|
+
logger: logging.Logger,
|
|
45
|
+
_: Optional[Source],
|
|
46
|
+
) -> Tuple[bool, Optional[str]]:
|
|
47
|
+
"""
|
|
48
|
+
Performs a connection check for the stream, as well as additional checks that
|
|
49
|
+
verify that the connection is working as expected.
|
|
50
|
+
|
|
51
|
+
Returns (True, None) if successful, otherwise (False, <error message>).
|
|
52
|
+
"""
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class AbstractFileBasedAvailabilityStrategyWrapper(AbstractAvailabilityStrategy):
|
|
57
|
+
def __init__(self, stream: AbstractFileBasedStream) -> None:
|
|
58
|
+
self.stream = stream
|
|
59
|
+
|
|
60
|
+
def check_availability(self, logger: logging.Logger) -> StreamAvailability:
|
|
61
|
+
is_available, reason = self.stream.availability_strategy.check_availability(
|
|
62
|
+
self.stream, logger, None
|
|
63
|
+
)
|
|
64
|
+
if is_available:
|
|
65
|
+
return StreamAvailable()
|
|
66
|
+
return StreamUnavailable(reason or "")
|
|
67
|
+
|
|
68
|
+
def check_availability_and_parsability(
|
|
69
|
+
self, logger: logging.Logger
|
|
70
|
+
) -> Tuple[bool, Optional[str]]:
|
|
71
|
+
return self.stream.availability_strategy.check_availability_and_parsability(
|
|
72
|
+
self.stream, logger, None
|
|
73
|
+
)
|
airbyte_cdk/sources/file_based/availability_strategy/default_file_based_availability_strategy.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import traceback
|
|
9
|
+
from typing import TYPE_CHECKING, Optional, Tuple
|
|
10
|
+
|
|
11
|
+
from airbyte_cdk import AirbyteTracedException
|
|
12
|
+
from airbyte_cdk.sources import Source
|
|
13
|
+
from airbyte_cdk.sources.file_based.availability_strategy import (
|
|
14
|
+
AbstractFileBasedAvailabilityStrategy,
|
|
15
|
+
)
|
|
16
|
+
from airbyte_cdk.sources.file_based.exceptions import (
|
|
17
|
+
CheckAvailabilityError,
|
|
18
|
+
CustomFileBasedException,
|
|
19
|
+
FileBasedSourceError,
|
|
20
|
+
)
|
|
21
|
+
from airbyte_cdk.sources.file_based.file_based_stream_reader import AbstractFileBasedStreamReader
|
|
22
|
+
from airbyte_cdk.sources.file_based.remote_file import RemoteFile
|
|
23
|
+
from airbyte_cdk.sources.file_based.schema_helpers import conforms_to_schema
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from airbyte_cdk.sources.file_based.stream import AbstractFileBasedStream
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class DefaultFileBasedAvailabilityStrategy(AbstractFileBasedAvailabilityStrategy):
|
|
30
|
+
def __init__(self, stream_reader: AbstractFileBasedStreamReader) -> None:
|
|
31
|
+
self.stream_reader = stream_reader
|
|
32
|
+
|
|
33
|
+
def check_availability( # type: ignore[override] # Signature doesn't match base class
|
|
34
|
+
self,
|
|
35
|
+
stream: AbstractFileBasedStream,
|
|
36
|
+
logger: logging.Logger,
|
|
37
|
+
_: Optional[Source],
|
|
38
|
+
) -> Tuple[bool, Optional[str]]:
|
|
39
|
+
"""
|
|
40
|
+
Perform a connection check for the stream (verify that we can list files from the stream).
|
|
41
|
+
|
|
42
|
+
Returns (True, None) if successful, otherwise (False, <error message>).
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
self._check_list_files(stream)
|
|
46
|
+
except CheckAvailabilityError:
|
|
47
|
+
return False, "".join(traceback.format_exc())
|
|
48
|
+
|
|
49
|
+
return True, None
|
|
50
|
+
|
|
51
|
+
def check_availability_and_parsability(
|
|
52
|
+
self,
|
|
53
|
+
stream: AbstractFileBasedStream,
|
|
54
|
+
logger: logging.Logger,
|
|
55
|
+
_: Optional[Source],
|
|
56
|
+
) -> Tuple[bool, Optional[str]]:
|
|
57
|
+
"""
|
|
58
|
+
Perform a connection check for the stream.
|
|
59
|
+
|
|
60
|
+
Returns (True, None) if successful, otherwise (False, <error message>).
|
|
61
|
+
|
|
62
|
+
For the stream:
|
|
63
|
+
- Verify the parser config is valid per check_config method of the parser.
|
|
64
|
+
- Verify that we can list files from the stream using the configured globs.
|
|
65
|
+
- Verify that we can read one file from the stream as long as the stream parser is not setting parser_max_n_files_for_parsability to 0.
|
|
66
|
+
|
|
67
|
+
This method will also check that the files and their contents are consistent
|
|
68
|
+
with the configured options, as follows:
|
|
69
|
+
- If the files have extensions, verify that they don't disagree with the
|
|
70
|
+
configured file type.
|
|
71
|
+
- If the user provided a schema in the config, check that a subset of records in
|
|
72
|
+
one file conform to the schema via a call to stream.conforms_to_schema(schema).
|
|
73
|
+
"""
|
|
74
|
+
parser = stream.get_parser()
|
|
75
|
+
config_check_result, config_check_error_message = parser.check_config(stream.config)
|
|
76
|
+
if config_check_result is False:
|
|
77
|
+
return False, config_check_error_message
|
|
78
|
+
try:
|
|
79
|
+
file = self._check_list_files(stream)
|
|
80
|
+
if not parser.parser_max_n_files_for_parsability == 0:
|
|
81
|
+
self._check_parse_record(stream, file, logger)
|
|
82
|
+
else:
|
|
83
|
+
# If the parser is set to not check parsability, we still want to check that we can open the file.
|
|
84
|
+
handle = stream.stream_reader.open_file(file, parser.file_read_mode, None, logger)
|
|
85
|
+
handle.close()
|
|
86
|
+
except AirbyteTracedException as ate:
|
|
87
|
+
raise ate
|
|
88
|
+
except CheckAvailabilityError:
|
|
89
|
+
return False, "".join(traceback.format_exc())
|
|
90
|
+
|
|
91
|
+
return True, None
|
|
92
|
+
|
|
93
|
+
def _check_list_files(self, stream: AbstractFileBasedStream) -> RemoteFile:
|
|
94
|
+
"""
|
|
95
|
+
Check that we can list files from the stream.
|
|
96
|
+
|
|
97
|
+
Returns the first file if successful, otherwise raises a CheckAvailabilityError.
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
file = next(iter(stream.get_files()))
|
|
101
|
+
except StopIteration:
|
|
102
|
+
raise CheckAvailabilityError(FileBasedSourceError.EMPTY_STREAM, stream=stream.name)
|
|
103
|
+
except CustomFileBasedException as exc:
|
|
104
|
+
raise CheckAvailabilityError(str(exc), stream=stream.name) from exc
|
|
105
|
+
except Exception as exc:
|
|
106
|
+
raise CheckAvailabilityError(
|
|
107
|
+
FileBasedSourceError.ERROR_LISTING_FILES, stream=stream.name
|
|
108
|
+
) from exc
|
|
109
|
+
|
|
110
|
+
return file
|
|
111
|
+
|
|
112
|
+
def _check_parse_record(
|
|
113
|
+
self,
|
|
114
|
+
stream: AbstractFileBasedStream,
|
|
115
|
+
file: RemoteFile,
|
|
116
|
+
logger: logging.Logger,
|
|
117
|
+
) -> None:
|
|
118
|
+
parser = stream.get_parser()
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
record = next(
|
|
122
|
+
iter(
|
|
123
|
+
parser.parse_records(
|
|
124
|
+
stream.config, file, self.stream_reader, logger, discovered_schema=None
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
except StopIteration:
|
|
129
|
+
# The file is empty. We've verified that we can open it, so will
|
|
130
|
+
# consider the connection check successful even though it means
|
|
131
|
+
# we skip the schema validation check.
|
|
132
|
+
return
|
|
133
|
+
except AirbyteTracedException as ate:
|
|
134
|
+
raise ate
|
|
135
|
+
except Exception as exc:
|
|
136
|
+
raise CheckAvailabilityError(
|
|
137
|
+
FileBasedSourceError.ERROR_READING_FILE, stream=stream.name, file=file.uri
|
|
138
|
+
) from exc
|
|
139
|
+
|
|
140
|
+
schema = stream.catalog_schema or stream.config.input_schema
|
|
141
|
+
if schema and stream.validation_policy.validate_schema_before_sync:
|
|
142
|
+
if not conforms_to_schema(record, schema): # type: ignore
|
|
143
|
+
raise CheckAvailabilityError(
|
|
144
|
+
FileBasedSourceError.ERROR_VALIDATING_RECORD,
|
|
145
|
+
stream=stream.name,
|
|
146
|
+
file=file.uri,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return None
|
|
File without changes
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
from abc import abstractmethod
|
|
7
|
+
from typing import Any, Dict, List, Literal, Optional, Union
|
|
8
|
+
|
|
9
|
+
import dpath
|
|
10
|
+
from pydantic.v1 import AnyUrl, BaseModel, Field
|
|
11
|
+
|
|
12
|
+
from airbyte_cdk import OneOfOptionConfig
|
|
13
|
+
from airbyte_cdk.sources.file_based.config.file_based_stream_config import FileBasedStreamConfig
|
|
14
|
+
from airbyte_cdk.sources.specs.transfer_modes import DeliverPermissions
|
|
15
|
+
from airbyte_cdk.sources.utils import schema_helpers
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DeliverRecords(BaseModel):
|
|
19
|
+
class Config(OneOfOptionConfig):
|
|
20
|
+
title = "Replicate Records"
|
|
21
|
+
description = "Recommended - Extract and load structured records into your destination of choice. This is the classic method of moving data in Airbyte. It allows for blocking and hashing individual fields or files from a structured schema. Data can be flattened, typed and deduped depending on the destination."
|
|
22
|
+
discriminator = "delivery_type"
|
|
23
|
+
|
|
24
|
+
delivery_type: Literal["use_records_transfer"] = Field("use_records_transfer", const=True)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DeliverRawFiles(BaseModel):
|
|
28
|
+
class Config(OneOfOptionConfig):
|
|
29
|
+
title = "Copy Raw Files"
|
|
30
|
+
description = "Copy raw files without parsing their contents. Bits are copied into the destination exactly as they appeared in the source. Recommended for use with unstructured text data, non-text and compressed files."
|
|
31
|
+
discriminator = "delivery_type"
|
|
32
|
+
|
|
33
|
+
delivery_type: Literal["use_file_transfer"] = Field("use_file_transfer", const=True)
|
|
34
|
+
|
|
35
|
+
preserve_directory_structure: bool = Field(
|
|
36
|
+
title="Preserve Sub-Directories in File Paths",
|
|
37
|
+
description=(
|
|
38
|
+
"If enabled, sends subdirectory folder structure "
|
|
39
|
+
"along with source file names to the destination. "
|
|
40
|
+
"Otherwise, files will be synced by their names only. "
|
|
41
|
+
"This option is ignored when file-based replication is not enabled."
|
|
42
|
+
),
|
|
43
|
+
default=True,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class AbstractFileBasedSpec(BaseModel):
|
|
48
|
+
"""
|
|
49
|
+
Used during spec; allows the developer to configure the cloud provider specific options
|
|
50
|
+
that are needed when users configure a file-based source.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
start_date: Optional[str] = Field(
|
|
54
|
+
title="Start Date",
|
|
55
|
+
description="UTC date and time in the format 2017-01-25T00:00:00.000000Z. Any file modified before this date will not be replicated.",
|
|
56
|
+
examples=["2021-01-01T00:00:00.000000Z"],
|
|
57
|
+
format="date-time",
|
|
58
|
+
pattern="^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{6}Z$",
|
|
59
|
+
pattern_descriptor="YYYY-MM-DDTHH:mm:ss.SSSSSSZ",
|
|
60
|
+
order=1,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
streams: List[FileBasedStreamConfig] = Field(
|
|
64
|
+
title="The list of streams to sync",
|
|
65
|
+
description='Each instance of this configuration defines a <a href="https://docs.airbyte.com/cloud/core-concepts#stream">stream</a>. Use this to define which files belong in the stream, their format, and how they should be parsed and validated. When sending data to warehouse destination such as Snowflake or BigQuery, each stream is a separate table.',
|
|
66
|
+
order=10,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
delivery_method: Union[DeliverRecords, DeliverRawFiles, DeliverPermissions] = Field(
|
|
70
|
+
title="Delivery Method",
|
|
71
|
+
discriminator="delivery_type",
|
|
72
|
+
type="object",
|
|
73
|
+
order=7,
|
|
74
|
+
display_type="radio",
|
|
75
|
+
group="advanced",
|
|
76
|
+
default="use_records_transfer",
|
|
77
|
+
airbyte_hidden=True,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
@abstractmethod
|
|
82
|
+
def documentation_url(cls) -> AnyUrl:
|
|
83
|
+
"""
|
|
84
|
+
:return: link to docs page for this source e.g. "https://docs.airbyte.com/integrations/sources/s3"
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def schema(cls, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
|
89
|
+
"""
|
|
90
|
+
Generates the mapping comprised of the config fields
|
|
91
|
+
"""
|
|
92
|
+
schema = super().schema(*args, **kwargs)
|
|
93
|
+
transformed_schema: Dict[str, Any] = copy.deepcopy(schema)
|
|
94
|
+
schema_helpers.expand_refs(transformed_schema)
|
|
95
|
+
cls.replace_enum_allOf_and_anyOf(transformed_schema)
|
|
96
|
+
cls.remove_discriminator(transformed_schema)
|
|
97
|
+
|
|
98
|
+
return transformed_schema
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def remove_discriminator(schema: Dict[str, Any]) -> None:
|
|
102
|
+
"""pydantic adds "discriminator" to the schema for oneOfs, which is not treated right by the platform as we inline all references"""
|
|
103
|
+
dpath.delete(schema, "properties/**/discriminator")
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def replace_enum_allOf_and_anyOf(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
107
|
+
"""
|
|
108
|
+
allOfs are not supported by the UI, but pydantic is automatically writing them for enums.
|
|
109
|
+
Unpacks the enums under allOf and moves them up a level under the enum key
|
|
110
|
+
anyOfs are also not supported by the UI, so we replace them with the similar oneOf, with the
|
|
111
|
+
additional validation that an incoming config only matches exactly one of a field's types.
|
|
112
|
+
"""
|
|
113
|
+
objects_to_check = schema["properties"]["streams"]["items"]["properties"]["format"]
|
|
114
|
+
objects_to_check["type"] = "object"
|
|
115
|
+
objects_to_check["oneOf"] = objects_to_check.pop("anyOf", [])
|
|
116
|
+
for format in objects_to_check["oneOf"]:
|
|
117
|
+
for key in format["properties"]:
|
|
118
|
+
object_property = format["properties"][key]
|
|
119
|
+
AbstractFileBasedSpec.move_enum_to_root(object_property)
|
|
120
|
+
|
|
121
|
+
properties_to_change = ["validation_policy"]
|
|
122
|
+
for property_to_change in properties_to_change:
|
|
123
|
+
property_object = schema["properties"]["streams"]["items"]["properties"][
|
|
124
|
+
property_to_change
|
|
125
|
+
]
|
|
126
|
+
if "anyOf" in property_object:
|
|
127
|
+
schema["properties"]["streams"]["items"]["properties"][property_to_change][
|
|
128
|
+
"type"
|
|
129
|
+
] = "object"
|
|
130
|
+
schema["properties"]["streams"]["items"]["properties"][property_to_change][
|
|
131
|
+
"oneOf"
|
|
132
|
+
] = property_object.pop("anyOf")
|
|
133
|
+
AbstractFileBasedSpec.move_enum_to_root(property_object)
|
|
134
|
+
|
|
135
|
+
csv_format_schemas = list(
|
|
136
|
+
filter(
|
|
137
|
+
lambda format: format["properties"]["filetype"]["default"] == "csv",
|
|
138
|
+
schema["properties"]["streams"]["items"]["properties"]["format"]["oneOf"],
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
if len(csv_format_schemas) != 1:
|
|
142
|
+
raise ValueError(f"Expecting only one CSV format but got {csv_format_schemas}")
|
|
143
|
+
csv_format_schemas[0]["properties"]["header_definition"]["oneOf"] = csv_format_schemas[0][
|
|
144
|
+
"properties"
|
|
145
|
+
]["header_definition"].pop("anyOf", [])
|
|
146
|
+
csv_format_schemas[0]["properties"]["header_definition"]["type"] = "object"
|
|
147
|
+
return schema
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def move_enum_to_root(object_property: Dict[str, Any]) -> None:
|
|
151
|
+
if "allOf" in object_property and "enum" in object_property["allOf"][0]:
|
|
152
|
+
object_property["enum"] = object_property["allOf"][0]["enum"]
|
|
153
|
+
object_property.pop("allOf")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from pydantic.v1 import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
from airbyte_cdk.utils.oneof_option_config import OneOfOptionConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AvroFormat(BaseModel):
|
|
12
|
+
class Config(OneOfOptionConfig):
|
|
13
|
+
title = "Avro Format"
|
|
14
|
+
discriminator = "filetype"
|
|
15
|
+
|
|
16
|
+
filetype: str = Field(
|
|
17
|
+
"avro",
|
|
18
|
+
const=True,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
double_as_string: bool = Field(
|
|
22
|
+
title="Convert Double Fields to Strings",
|
|
23
|
+
description="Whether to convert double fields to strings. This is recommended if you have decimal numbers with a high degree of precision because there can be a loss precision when handling floating point numbers.",
|
|
24
|
+
default=False,
|
|
25
|
+
)
|