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,39 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, Generator, MutableMapping
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
|
|
11
|
+
from airbyte_cdk.sources.declarative.decoders import Decoder
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("airbyte")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class PaginationDecoderDecorator(Decoder):
|
|
18
|
+
"""
|
|
19
|
+
Decoder to wrap other decoders when instantiating a DefaultPaginator in order to bypass decoding if the response is streamed.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, decoder: Decoder):
|
|
23
|
+
self._decoder = decoder
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def decoder(self) -> Decoder:
|
|
27
|
+
return self._decoder
|
|
28
|
+
|
|
29
|
+
def is_stream_response(self) -> bool:
|
|
30
|
+
return self._decoder.is_stream_response()
|
|
31
|
+
|
|
32
|
+
def decode(
|
|
33
|
+
self, response: requests.Response
|
|
34
|
+
) -> Generator[MutableMapping[str, Any], None, None]:
|
|
35
|
+
if self._decoder.is_stream_response():
|
|
36
|
+
logger.warning("Response is streamed and therefore will not be decoded for pagination.")
|
|
37
|
+
yield {}
|
|
38
|
+
else:
|
|
39
|
+
yield from self._decoder.decode(response)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from dataclasses import InitVar, dataclass
|
|
7
|
+
from typing import Any, Generator, Mapping, MutableMapping
|
|
8
|
+
from xml.parsers.expat import ExpatError
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
import xmltodict
|
|
12
|
+
|
|
13
|
+
from airbyte_cdk.sources.declarative.decoders.decoder import Decoder
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger("airbyte")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class XmlDecoder(Decoder):
|
|
20
|
+
"""
|
|
21
|
+
XmlDecoder is a decoder strategy that parses the XML content of the resopnse, and converts it to a dict.
|
|
22
|
+
|
|
23
|
+
This class handles XML attributes by prefixing them with an '@' symbol and represents XML text content by using the '#text' key if the element has attributes or the element name/tag. It does not currently support XML namespace declarations.
|
|
24
|
+
|
|
25
|
+
Example XML Input:
|
|
26
|
+
<root>
|
|
27
|
+
<location id="123">
|
|
28
|
+
San Francisco
|
|
29
|
+
</location>
|
|
30
|
+
<item id="1" category="books">
|
|
31
|
+
<name>Book Title 1</name>
|
|
32
|
+
<price>10.99</price>
|
|
33
|
+
</item>
|
|
34
|
+
<item id="2" category="electronics">
|
|
35
|
+
<name>Gadget</name>
|
|
36
|
+
<price>299.99</price>
|
|
37
|
+
<description>A useful gadget</description>
|
|
38
|
+
</item>
|
|
39
|
+
</root>
|
|
40
|
+
|
|
41
|
+
Converted Output:
|
|
42
|
+
{
|
|
43
|
+
"root": {
|
|
44
|
+
"location: {
|
|
45
|
+
"@id": "123,
|
|
46
|
+
"#text": "San Francisco"
|
|
47
|
+
},
|
|
48
|
+
"item": [
|
|
49
|
+
{
|
|
50
|
+
"@id": "1",
|
|
51
|
+
"@category": "books",
|
|
52
|
+
"name": "Book Title 1",
|
|
53
|
+
"price": "10.99"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"@id": "2",
|
|
57
|
+
"@category": "electronics",
|
|
58
|
+
"name": "Gadget",
|
|
59
|
+
"price": "299.99",
|
|
60
|
+
"description": "A useful gadget"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
Notes:
|
|
67
|
+
- Attributes of an XML element are prefixed with an '@' symbol in the dictionary output.
|
|
68
|
+
- Text content of an XML element is handled in two different ways, depending on whether
|
|
69
|
+
the element has attributes.
|
|
70
|
+
- If the element has attributes, the text content will be
|
|
71
|
+
represented by the "#text" key.
|
|
72
|
+
- If the element does not have any attributes, the text content will be
|
|
73
|
+
represented by element name.
|
|
74
|
+
- Namespace declarations are not supported in the current implementation.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
parameters: InitVar[Mapping[str, Any]]
|
|
78
|
+
|
|
79
|
+
def is_stream_response(self) -> bool:
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
def decode(
|
|
83
|
+
self, response: requests.Response
|
|
84
|
+
) -> Generator[MutableMapping[str, Any], None, None]:
|
|
85
|
+
body_xml = response.text
|
|
86
|
+
try:
|
|
87
|
+
body_json = xmltodict.parse(body_xml)
|
|
88
|
+
if not isinstance(body_json, list):
|
|
89
|
+
body_json = [body_json]
|
|
90
|
+
if len(body_json) == 0:
|
|
91
|
+
yield {}
|
|
92
|
+
else:
|
|
93
|
+
yield from body_json
|
|
94
|
+
except ExpatError as exc:
|
|
95
|
+
logger.warning(
|
|
96
|
+
f"Response cannot be parsed from XML: {response.status_code=}, {response.text=}, {exc=}"
|
|
97
|
+
)
|
|
98
|
+
yield {}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import zipfile
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from io import BytesIO
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
from airbyte_cdk.models import FailureType
|
|
13
|
+
from airbyte_cdk.sources.declarative.decoders import Decoder
|
|
14
|
+
from airbyte_cdk.sources.declarative.decoders.composite_raw_decoder import Parser
|
|
15
|
+
from airbyte_cdk.sources.declarative.decoders.decoder import DECODER_OUTPUT_TYPE
|
|
16
|
+
from airbyte_cdk.utils import AirbyteTracedException
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger("airbyte")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ZipfileDecoder(Decoder):
|
|
23
|
+
parser: Parser
|
|
24
|
+
|
|
25
|
+
def is_stream_response(self) -> bool:
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
def decode(self, response: requests.Response) -> DECODER_OUTPUT_TYPE:
|
|
29
|
+
try:
|
|
30
|
+
with zipfile.ZipFile(BytesIO(response.content)) as zip_file:
|
|
31
|
+
for file_name in zip_file.namelist():
|
|
32
|
+
unzipped_content = zip_file.read(file_name)
|
|
33
|
+
buffered_content = BytesIO(unzipped_content)
|
|
34
|
+
try:
|
|
35
|
+
yield from self.parser.parse(
|
|
36
|
+
buffered_content,
|
|
37
|
+
)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
logger.error(
|
|
40
|
+
f"Failed to parse file: {file_name} from zip file: {response.request.url} with exception {e}."
|
|
41
|
+
)
|
|
42
|
+
raise AirbyteTracedException(
|
|
43
|
+
message=f"Failed to parse file: {file_name} from zip file.",
|
|
44
|
+
internal_message=f"Failed to parse file: {file_name} from zip file: {response.request.url}.",
|
|
45
|
+
failure_type=FailureType.system_error,
|
|
46
|
+
) from e
|
|
47
|
+
except zipfile.BadZipFile as e:
|
|
48
|
+
logger.error(
|
|
49
|
+
f"Received an invalid zip file in response to URL: {response.request.url}. "
|
|
50
|
+
f"The size of the response body is: {len(response.content)}"
|
|
51
|
+
)
|
|
52
|
+
raise AirbyteTracedException(
|
|
53
|
+
message="Received an invalid zip file in response.",
|
|
54
|
+
internal_message=f"Received an invalid zip file in response to URL: {response.request.url}.",
|
|
55
|
+
failure_type=FailureType.system_error,
|
|
56
|
+
) from e
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
from airbyte_cdk.sources.declarative.extractors.dpath_extractor import DpathExtractor
|
|
6
|
+
from airbyte_cdk.sources.declarative.extractors.http_selector import HttpSelector
|
|
7
|
+
from airbyte_cdk.sources.declarative.extractors.record_filter import RecordFilter
|
|
8
|
+
from airbyte_cdk.sources.declarative.extractors.record_selector import RecordSelector
|
|
9
|
+
from airbyte_cdk.sources.declarative.extractors.response_to_file_extractor import (
|
|
10
|
+
ResponseToFileExtractor,
|
|
11
|
+
)
|
|
12
|
+
from airbyte_cdk.sources.declarative.extractors.type_transformer import TypeTransformer
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"TypeTransformer",
|
|
16
|
+
"HttpSelector",
|
|
17
|
+
"DpathExtractor",
|
|
18
|
+
"RecordFilter",
|
|
19
|
+
"RecordSelector",
|
|
20
|
+
"ResponseToFileExtractor",
|
|
21
|
+
]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
from dataclasses import InitVar, dataclass, field
|
|
6
|
+
from typing import Any, Iterable, List, Mapping, MutableMapping, Union
|
|
7
|
+
|
|
8
|
+
import dpath
|
|
9
|
+
import requests
|
|
10
|
+
|
|
11
|
+
from airbyte_cdk.sources.declarative.decoders import Decoder, JsonDecoder
|
|
12
|
+
from airbyte_cdk.sources.declarative.extractors.record_extractor import RecordExtractor
|
|
13
|
+
from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
|
|
14
|
+
from airbyte_cdk.sources.types import Config
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class DpathExtractor(RecordExtractor):
|
|
19
|
+
"""
|
|
20
|
+
Record extractor that searches a decoded response over a path defined as an array of fields.
|
|
21
|
+
|
|
22
|
+
If the field path points to an array, that array is returned.
|
|
23
|
+
If the field path points to an object, that object is returned wrapped as an array.
|
|
24
|
+
If the field path points to an empty object, an empty array is returned.
|
|
25
|
+
If the field path points to a non-existing path, an empty array is returned.
|
|
26
|
+
|
|
27
|
+
Examples of instantiating this transform:
|
|
28
|
+
```
|
|
29
|
+
extractor:
|
|
30
|
+
type: DpathExtractor
|
|
31
|
+
field_path:
|
|
32
|
+
- "root"
|
|
33
|
+
- "data"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
extractor:
|
|
38
|
+
type: DpathExtractor
|
|
39
|
+
field_path:
|
|
40
|
+
- "root"
|
|
41
|
+
- "{{ parameters['field'] }}"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
extractor:
|
|
46
|
+
type: DpathExtractor
|
|
47
|
+
field_path: []
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Attributes:
|
|
51
|
+
field_path (Union[InterpolatedString, str]): Path to the field that should be extracted
|
|
52
|
+
config (Config): The user-provided configuration as specified by the source's spec
|
|
53
|
+
decoder (Decoder): The decoder responsible to transfom the response in a Mapping
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
field_path: List[Union[InterpolatedString, str]]
|
|
57
|
+
config: Config
|
|
58
|
+
parameters: InitVar[Mapping[str, Any]]
|
|
59
|
+
decoder: Decoder = field(default_factory=lambda: JsonDecoder(parameters={}))
|
|
60
|
+
|
|
61
|
+
def __post_init__(self, parameters: Mapping[str, Any]) -> None:
|
|
62
|
+
self._field_path = [
|
|
63
|
+
InterpolatedString.create(path, parameters=parameters) for path in self.field_path
|
|
64
|
+
]
|
|
65
|
+
for path_index in range(len(self.field_path)):
|
|
66
|
+
if isinstance(self.field_path[path_index], str):
|
|
67
|
+
self._field_path[path_index] = InterpolatedString.create(
|
|
68
|
+
self.field_path[path_index], parameters=parameters
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def extract_records(self, response: requests.Response) -> Iterable[MutableMapping[Any, Any]]:
|
|
72
|
+
for body in self.decoder.decode(response):
|
|
73
|
+
if len(self._field_path) == 0:
|
|
74
|
+
extracted = body
|
|
75
|
+
else:
|
|
76
|
+
path = [path.eval(self.config) for path in self._field_path]
|
|
77
|
+
if "*" in path:
|
|
78
|
+
extracted = dpath.values(body, path)
|
|
79
|
+
else:
|
|
80
|
+
extracted = dpath.get(body, path, default=[]) # type: ignore # extracted will be a MutableMapping, given input data structure
|
|
81
|
+
if isinstance(extracted, list):
|
|
82
|
+
yield from extracted
|
|
83
|
+
elif extracted:
|
|
84
|
+
yield extracted
|
|
85
|
+
else:
|
|
86
|
+
yield from []
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
from abc import abstractmethod
|
|
6
|
+
from typing import Any, Iterable, Mapping, Optional
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
from airbyte_cdk.sources.types import Record, StreamSlice, StreamState
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HttpSelector:
|
|
14
|
+
"""
|
|
15
|
+
Responsible for translating an HTTP response into a list of records by extracting records from the response and optionally filtering
|
|
16
|
+
records based on a heuristic.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def select_records(
|
|
21
|
+
self,
|
|
22
|
+
response: requests.Response,
|
|
23
|
+
stream_state: StreamState,
|
|
24
|
+
records_schema: Mapping[str, Any],
|
|
25
|
+
stream_slice: Optional[StreamSlice] = None,
|
|
26
|
+
next_page_token: Optional[Mapping[str, Any]] = None,
|
|
27
|
+
) -> Iterable[Record]:
|
|
28
|
+
"""
|
|
29
|
+
Selects records from the response
|
|
30
|
+
:param response: The response to select the records from
|
|
31
|
+
:param stream_state: The stream state
|
|
32
|
+
:param records_schema: json schema of records to return
|
|
33
|
+
:param stream_slice: The stream slice
|
|
34
|
+
:param next_page_token: The paginator token
|
|
35
|
+
:return: List of Records selected from the response
|
|
36
|
+
"""
|
|
37
|
+
pass
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
from abc import abstractmethod
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Iterable, Mapping
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class RecordExtractor:
|
|
13
|
+
"""
|
|
14
|
+
Responsible for translating an HTTP response into a list of records by extracting records from the response.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def extract_records(
|
|
19
|
+
self,
|
|
20
|
+
response: requests.Response,
|
|
21
|
+
) -> Iterable[Mapping[str, Any]]:
|
|
22
|
+
"""
|
|
23
|
+
Selects records from the response
|
|
24
|
+
:param response: The response to extract the records from
|
|
25
|
+
:return: List of Records extracted from the response
|
|
26
|
+
"""
|
|
27
|
+
pass
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
from dataclasses import InitVar, dataclass
|
|
5
|
+
from typing import Any, Iterable, Mapping, Optional, Union
|
|
6
|
+
|
|
7
|
+
from airbyte_cdk.sources.declarative.incremental import (
|
|
8
|
+
DatetimeBasedCursor,
|
|
9
|
+
GlobalSubstreamCursor,
|
|
10
|
+
PerPartitionWithGlobalCursor,
|
|
11
|
+
)
|
|
12
|
+
from airbyte_cdk.sources.declarative.interpolation.interpolated_boolean import InterpolatedBoolean
|
|
13
|
+
from airbyte_cdk.sources.types import Config, Record, StreamSlice, StreamState
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class RecordFilter:
|
|
18
|
+
"""
|
|
19
|
+
Filter applied on a list of Records
|
|
20
|
+
|
|
21
|
+
config (Config): The user-provided configuration as specified by the source's spec
|
|
22
|
+
condition (str): The string representing the predicate to filter a record. Records will be removed if evaluated to False
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
parameters: InitVar[Mapping[str, Any]]
|
|
26
|
+
config: Config
|
|
27
|
+
condition: str = ""
|
|
28
|
+
|
|
29
|
+
def __post_init__(self, parameters: Mapping[str, Any]) -> None:
|
|
30
|
+
self._filter_interpolator = InterpolatedBoolean(
|
|
31
|
+
condition=self.condition, parameters=parameters
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def filter_records(
|
|
35
|
+
self,
|
|
36
|
+
records: Iterable[Mapping[str, Any]],
|
|
37
|
+
stream_state: StreamState,
|
|
38
|
+
stream_slice: Optional[StreamSlice] = None,
|
|
39
|
+
next_page_token: Optional[Mapping[str, Any]] = None,
|
|
40
|
+
) -> Iterable[Mapping[str, Any]]:
|
|
41
|
+
kwargs = {
|
|
42
|
+
"stream_state": stream_state,
|
|
43
|
+
"stream_slice": stream_slice,
|
|
44
|
+
"next_page_token": next_page_token,
|
|
45
|
+
"stream_slice.extra_fields": stream_slice.extra_fields if stream_slice else {},
|
|
46
|
+
}
|
|
47
|
+
for record in records:
|
|
48
|
+
if self._filter_interpolator.eval(self.config, record=record, **kwargs):
|
|
49
|
+
yield record
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ClientSideIncrementalRecordFilterDecorator(RecordFilter):
|
|
53
|
+
"""
|
|
54
|
+
Applies a filter to a list of records to exclude those that are older than the stream_state/start_date.
|
|
55
|
+
|
|
56
|
+
:param DatetimeBasedCursor date_time_based_cursor: Cursor used to extract datetime values
|
|
57
|
+
:param PerPartitionCursor per_partition_cursor: Optional Cursor used for mapping cursor value in nested stream_state
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
cursor: Union[DatetimeBasedCursor, PerPartitionWithGlobalCursor, GlobalSubstreamCursor],
|
|
63
|
+
**kwargs: Any,
|
|
64
|
+
):
|
|
65
|
+
super().__init__(**kwargs)
|
|
66
|
+
self._cursor = cursor
|
|
67
|
+
|
|
68
|
+
def filter_records(
|
|
69
|
+
self,
|
|
70
|
+
records: Iterable[Mapping[str, Any]],
|
|
71
|
+
stream_state: StreamState,
|
|
72
|
+
stream_slice: Optional[StreamSlice] = None,
|
|
73
|
+
next_page_token: Optional[Mapping[str, Any]] = None,
|
|
74
|
+
) -> Iterable[Mapping[str, Any]]:
|
|
75
|
+
records = (
|
|
76
|
+
record
|
|
77
|
+
for record in records
|
|
78
|
+
if self._cursor.should_be_synced(
|
|
79
|
+
# Record is created on the fly to align with cursors interface; stream name is ignored as we don't need it here
|
|
80
|
+
# Record stream name is empty cause it is not used durig the filtering
|
|
81
|
+
Record(data=record, associated_slice=stream_slice, stream_name="")
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
if self.condition:
|
|
85
|
+
records = super().filter_records(
|
|
86
|
+
records=records,
|
|
87
|
+
stream_state=stream_state,
|
|
88
|
+
stream_slice=stream_slice,
|
|
89
|
+
next_page_token=next_page_token,
|
|
90
|
+
)
|
|
91
|
+
yield from records
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
from dataclasses import InitVar, dataclass, field
|
|
6
|
+
from typing import Any, Iterable, List, Mapping, Optional, Union
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
from airbyte_cdk.sources.declarative.extractors.http_selector import HttpSelector
|
|
11
|
+
from airbyte_cdk.sources.declarative.extractors.record_extractor import RecordExtractor
|
|
12
|
+
from airbyte_cdk.sources.declarative.extractors.record_filter import RecordFilter
|
|
13
|
+
from airbyte_cdk.sources.declarative.extractors.type_transformer import (
|
|
14
|
+
TypeTransformer as DeclarativeTypeTransformer,
|
|
15
|
+
)
|
|
16
|
+
from airbyte_cdk.sources.declarative.interpolation import InterpolatedString
|
|
17
|
+
from airbyte_cdk.sources.declarative.models import SchemaNormalization
|
|
18
|
+
from airbyte_cdk.sources.declarative.retrievers.file_uploader import FileUploader
|
|
19
|
+
from airbyte_cdk.sources.declarative.transformations import RecordTransformation
|
|
20
|
+
from airbyte_cdk.sources.types import Config, Record, StreamSlice, StreamState
|
|
21
|
+
from airbyte_cdk.sources.utils.transform import TypeTransformer
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class RecordSelector(HttpSelector):
|
|
26
|
+
"""
|
|
27
|
+
Responsible for translating an HTTP response into a list of records by extracting records from the response and optionally filtering
|
|
28
|
+
records based on a heuristic.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
extractor (RecordExtractor): The record extractor responsible for extracting records from a response
|
|
32
|
+
schema_normalization (TypeTransformer): The record normalizer responsible for casting record values to stream schema types
|
|
33
|
+
record_filter (RecordFilter): The record filter responsible for filtering extracted records
|
|
34
|
+
transformations (List[RecordTransformation]): The transformations to be done on the records
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
extractor: RecordExtractor
|
|
38
|
+
config: Config
|
|
39
|
+
parameters: InitVar[Mapping[str, Any]]
|
|
40
|
+
schema_normalization: Union[TypeTransformer, DeclarativeTypeTransformer]
|
|
41
|
+
name: str
|
|
42
|
+
_name: Union[InterpolatedString, str] = field(init=False, repr=False, default="")
|
|
43
|
+
record_filter: Optional[RecordFilter] = None
|
|
44
|
+
transformations: List[RecordTransformation] = field(default_factory=lambda: [])
|
|
45
|
+
transform_before_filtering: bool = False
|
|
46
|
+
file_uploader: Optional[FileUploader] = None
|
|
47
|
+
|
|
48
|
+
def __post_init__(self, parameters: Mapping[str, Any]) -> None:
|
|
49
|
+
self._parameters = parameters
|
|
50
|
+
self._name = (
|
|
51
|
+
InterpolatedString(self._name, parameters=parameters)
|
|
52
|
+
if isinstance(self._name, str)
|
|
53
|
+
else self._name
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
@property # type: ignore
|
|
57
|
+
def name(self) -> str:
|
|
58
|
+
"""
|
|
59
|
+
:return: Stream name
|
|
60
|
+
"""
|
|
61
|
+
return (
|
|
62
|
+
str(self._name.eval(self.config))
|
|
63
|
+
if isinstance(self._name, InterpolatedString)
|
|
64
|
+
else self._name
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@name.setter
|
|
68
|
+
def name(self, value: str) -> None:
|
|
69
|
+
if not isinstance(value, property):
|
|
70
|
+
self._name = value
|
|
71
|
+
|
|
72
|
+
def select_records(
|
|
73
|
+
self,
|
|
74
|
+
response: requests.Response,
|
|
75
|
+
stream_state: StreamState,
|
|
76
|
+
records_schema: Mapping[str, Any],
|
|
77
|
+
stream_slice: Optional[StreamSlice] = None,
|
|
78
|
+
next_page_token: Optional[Mapping[str, Any]] = None,
|
|
79
|
+
) -> Iterable[Record]:
|
|
80
|
+
"""
|
|
81
|
+
Selects records from the response
|
|
82
|
+
:param response: The response to select the records from
|
|
83
|
+
:param stream_state: The stream state
|
|
84
|
+
:param records_schema: json schema of records to return
|
|
85
|
+
:param stream_slice: The stream slice
|
|
86
|
+
:param next_page_token: The paginator token
|
|
87
|
+
:return: List of Records selected from the response
|
|
88
|
+
"""
|
|
89
|
+
all_data: Iterable[Mapping[str, Any]] = self.extractor.extract_records(response)
|
|
90
|
+
yield from self.filter_and_transform(
|
|
91
|
+
all_data, stream_state, records_schema, stream_slice, next_page_token
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def filter_and_transform(
|
|
95
|
+
self,
|
|
96
|
+
all_data: Iterable[Mapping[str, Any]],
|
|
97
|
+
stream_state: StreamState,
|
|
98
|
+
records_schema: Mapping[str, Any],
|
|
99
|
+
stream_slice: Optional[StreamSlice] = None,
|
|
100
|
+
next_page_token: Optional[Mapping[str, Any]] = None,
|
|
101
|
+
) -> Iterable[Record]:
|
|
102
|
+
"""
|
|
103
|
+
There is an issue with the selector as of 2024-08-30: it does technology-agnostic processing like filtering, transformation and
|
|
104
|
+
normalization with an API that is technology-specific (as requests.Response is only for HTTP communication using the requests
|
|
105
|
+
library).
|
|
106
|
+
|
|
107
|
+
Until we decide to move this logic away from the selector, we made this method public so that users like AsyncJobRetriever could
|
|
108
|
+
share the logic of doing transformations on a set of records.
|
|
109
|
+
"""
|
|
110
|
+
if self.transform_before_filtering:
|
|
111
|
+
transformed_data = self._transform(all_data, stream_state, stream_slice)
|
|
112
|
+
transformed_filtered_data = self._filter(
|
|
113
|
+
transformed_data, stream_state, stream_slice, next_page_token
|
|
114
|
+
)
|
|
115
|
+
else:
|
|
116
|
+
filtered_data = self._filter(all_data, stream_state, stream_slice, next_page_token)
|
|
117
|
+
transformed_filtered_data = self._transform(filtered_data, stream_state, stream_slice)
|
|
118
|
+
normalized_data = self._normalize_by_schema(
|
|
119
|
+
transformed_filtered_data, schema=records_schema
|
|
120
|
+
)
|
|
121
|
+
for data in normalized_data:
|
|
122
|
+
record = Record(data=data, stream_name=self.name, associated_slice=stream_slice)
|
|
123
|
+
if self.file_uploader:
|
|
124
|
+
self.file_uploader.upload(record)
|
|
125
|
+
yield record
|
|
126
|
+
|
|
127
|
+
def _normalize_by_schema(
|
|
128
|
+
self, records: Iterable[Mapping[str, Any]], schema: Optional[Mapping[str, Any]]
|
|
129
|
+
) -> Iterable[Mapping[str, Any]]:
|
|
130
|
+
if schema:
|
|
131
|
+
# record has type Mapping[str, Any], but dict[str, Any] expected
|
|
132
|
+
for record in records:
|
|
133
|
+
normalized_record = dict(record)
|
|
134
|
+
self.schema_normalization.transform(normalized_record, schema)
|
|
135
|
+
yield normalized_record
|
|
136
|
+
else:
|
|
137
|
+
yield from records
|
|
138
|
+
|
|
139
|
+
def _filter(
|
|
140
|
+
self,
|
|
141
|
+
records: Iterable[Mapping[str, Any]],
|
|
142
|
+
stream_state: StreamState,
|
|
143
|
+
stream_slice: Optional[StreamSlice],
|
|
144
|
+
next_page_token: Optional[Mapping[str, Any]],
|
|
145
|
+
) -> Iterable[Mapping[str, Any]]:
|
|
146
|
+
if self.record_filter:
|
|
147
|
+
yield from self.record_filter.filter_records(
|
|
148
|
+
records,
|
|
149
|
+
stream_state=stream_state,
|
|
150
|
+
stream_slice=stream_slice,
|
|
151
|
+
next_page_token=next_page_token,
|
|
152
|
+
)
|
|
153
|
+
else:
|
|
154
|
+
yield from records
|
|
155
|
+
|
|
156
|
+
def _transform(
|
|
157
|
+
self,
|
|
158
|
+
records: Iterable[Mapping[str, Any]],
|
|
159
|
+
stream_state: StreamState,
|
|
160
|
+
stream_slice: Optional[StreamSlice] = None,
|
|
161
|
+
) -> Iterable[Mapping[str, Any]]:
|
|
162
|
+
for record in records:
|
|
163
|
+
for transformation in self.transformations:
|
|
164
|
+
transformation.transform(
|
|
165
|
+
record, # type: ignore # record has type Mapping[str, Any], but Dict[str, Any] expected
|
|
166
|
+
config=self.config,
|
|
167
|
+
stream_state=stream_state,
|
|
168
|
+
stream_slice=stream_slice,
|
|
169
|
+
)
|
|
170
|
+
yield record
|