airbyte-cdk 0.72.0__py3-none-any.whl → 6.13.1.dev4106__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- airbyte_cdk/__init__.py +355 -6
- airbyte_cdk/cli/__init__.py +1 -0
- airbyte_cdk/cli/source_declarative_manifest/__init__.py +5 -0
- airbyte_cdk/cli/source_declarative_manifest/_run.py +230 -0
- airbyte_cdk/cli/source_declarative_manifest/spec.json +17 -0
- airbyte_cdk/config_observation.py +29 -10
- airbyte_cdk/connector.py +24 -24
- airbyte_cdk/connector_builder/README.md +53 -0
- airbyte_cdk/connector_builder/connector_builder_handler.py +37 -11
- airbyte_cdk/connector_builder/main.py +45 -13
- airbyte_cdk/connector_builder/message_grouper.py +189 -50
- airbyte_cdk/connector_builder/models.py +3 -2
- airbyte_cdk/destinations/__init__.py +4 -3
- airbyte_cdk/destinations/destination.py +54 -20
- airbyte_cdk/destinations/vector_db_based/README.md +37 -0
- airbyte_cdk/destinations/vector_db_based/config.py +40 -17
- airbyte_cdk/destinations/vector_db_based/document_processor.py +56 -17
- airbyte_cdk/destinations/vector_db_based/embedder.py +57 -15
- airbyte_cdk/destinations/vector_db_based/test_utils.py +14 -4
- airbyte_cdk/destinations/vector_db_based/utils.py +8 -2
- airbyte_cdk/destinations/vector_db_based/writer.py +24 -5
- airbyte_cdk/entrypoint.py +153 -44
- airbyte_cdk/exception_handler.py +21 -3
- airbyte_cdk/logger.py +30 -44
- airbyte_cdk/models/__init__.py +13 -2
- airbyte_cdk/models/airbyte_protocol.py +86 -1
- airbyte_cdk/models/airbyte_protocol_serializers.py +44 -0
- airbyte_cdk/models/file_transfer_record_message.py +13 -0
- airbyte_cdk/models/well_known_types.py +1 -1
- airbyte_cdk/sources/__init__.py +5 -1
- airbyte_cdk/sources/abstract_source.py +125 -79
- airbyte_cdk/sources/concurrent_source/__init__.py +7 -2
- airbyte_cdk/sources/concurrent_source/concurrent_read_processor.py +102 -36
- airbyte_cdk/sources/concurrent_source/concurrent_source.py +29 -36
- airbyte_cdk/sources/concurrent_source/concurrent_source_adapter.py +94 -10
- airbyte_cdk/sources/concurrent_source/stream_thread_exception.py +25 -0
- airbyte_cdk/sources/concurrent_source/thread_pool_manager.py +20 -14
- airbyte_cdk/sources/config.py +3 -2
- airbyte_cdk/sources/connector_state_manager.py +49 -83
- airbyte_cdk/sources/declarative/async_job/job.py +52 -0
- airbyte_cdk/sources/declarative/async_job/job_orchestrator.py +497 -0
- airbyte_cdk/sources/declarative/async_job/job_tracker.py +75 -0
- airbyte_cdk/sources/declarative/async_job/repository.py +35 -0
- airbyte_cdk/sources/declarative/async_job/status.py +24 -0
- airbyte_cdk/sources/declarative/async_job/timer.py +39 -0
- airbyte_cdk/sources/declarative/auth/__init__.py +2 -3
- airbyte_cdk/sources/declarative/auth/declarative_authenticator.py +3 -1
- airbyte_cdk/sources/declarative/auth/jwt.py +191 -0
- airbyte_cdk/sources/declarative/auth/oauth.py +60 -20
- airbyte_cdk/sources/declarative/auth/selective_authenticator.py +10 -2
- airbyte_cdk/sources/declarative/auth/token.py +28 -10
- airbyte_cdk/sources/declarative/auth/token_provider.py +9 -8
- airbyte_cdk/sources/declarative/checks/check_stream.py +16 -8
- airbyte_cdk/sources/declarative/checks/connection_checker.py +4 -2
- airbyte_cdk/sources/declarative/concurrency_level/__init__.py +7 -0
- airbyte_cdk/sources/declarative/concurrency_level/concurrency_level.py +50 -0
- airbyte_cdk/sources/declarative/concurrent_declarative_source.py +421 -0
- airbyte_cdk/sources/declarative/datetime/datetime_parser.py +4 -0
- airbyte_cdk/sources/declarative/datetime/min_max_datetime.py +26 -6
- airbyte_cdk/sources/declarative/declarative_component_schema.yaml +1213 -88
- airbyte_cdk/sources/declarative/declarative_source.py +5 -2
- airbyte_cdk/sources/declarative/declarative_stream.py +95 -9
- airbyte_cdk/sources/declarative/decoders/__init__.py +23 -2
- airbyte_cdk/sources/declarative/decoders/composite_raw_decoder.py +97 -0
- airbyte_cdk/sources/declarative/decoders/decoder.py +11 -4
- airbyte_cdk/sources/declarative/decoders/json_decoder.py +92 -5
- airbyte_cdk/sources/declarative/decoders/noop_decoder.py +21 -0
- airbyte_cdk/sources/declarative/decoders/pagination_decoder_decorator.py +39 -0
- airbyte_cdk/sources/declarative/decoders/xml_decoder.py +98 -0
- airbyte_cdk/sources/declarative/extractors/__init__.py +12 -1
- airbyte_cdk/sources/declarative/extractors/dpath_extractor.py +29 -24
- airbyte_cdk/sources/declarative/extractors/http_selector.py +4 -5
- airbyte_cdk/sources/declarative/extractors/record_extractor.py +2 -3
- airbyte_cdk/sources/declarative/extractors/record_filter.py +65 -8
- airbyte_cdk/sources/declarative/extractors/record_selector.py +85 -26
- airbyte_cdk/sources/declarative/extractors/response_to_file_extractor.py +177 -0
- airbyte_cdk/sources/declarative/extractors/type_transformer.py +55 -0
- airbyte_cdk/sources/declarative/incremental/__init__.py +25 -3
- airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py +156 -48
- airbyte_cdk/sources/declarative/incremental/declarative_cursor.py +13 -0
- airbyte_cdk/sources/declarative/incremental/global_substream_cursor.py +350 -0
- airbyte_cdk/sources/declarative/incremental/per_partition_cursor.py +159 -74
- airbyte_cdk/sources/declarative/incremental/per_partition_with_global.py +200 -0
- airbyte_cdk/sources/declarative/incremental/resumable_full_refresh_cursor.py +122 -0
- airbyte_cdk/sources/declarative/interpolation/filters.py +27 -1
- airbyte_cdk/sources/declarative/interpolation/interpolated_boolean.py +23 -5
- airbyte_cdk/sources/declarative/interpolation/interpolated_mapping.py +12 -8
- airbyte_cdk/sources/declarative/interpolation/interpolated_nested_mapping.py +13 -6
- airbyte_cdk/sources/declarative/interpolation/interpolated_string.py +21 -6
- airbyte_cdk/sources/declarative/interpolation/interpolation.py +9 -3
- airbyte_cdk/sources/declarative/interpolation/jinja.py +72 -37
- airbyte_cdk/sources/declarative/interpolation/macros.py +72 -17
- airbyte_cdk/sources/declarative/manifest_declarative_source.py +193 -52
- airbyte_cdk/sources/declarative/migrations/legacy_to_per_partition_state_migration.py +98 -0
- airbyte_cdk/sources/declarative/migrations/state_migration.py +24 -0
- airbyte_cdk/sources/declarative/models/__init__.py +1 -1
- airbyte_cdk/sources/declarative/models/declarative_component_schema.py +1329 -595
- airbyte_cdk/sources/declarative/parsers/custom_exceptions.py +2 -2
- airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py +26 -4
- airbyte_cdk/sources/declarative/parsers/manifest_reference_resolver.py +26 -15
- airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +1699 -226
- airbyte_cdk/sources/declarative/partition_routers/__init__.py +24 -4
- airbyte_cdk/sources/declarative/partition_routers/async_job_partition_router.py +65 -0
- airbyte_cdk/sources/declarative/partition_routers/cartesian_product_stream_slicer.py +176 -0
- airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py +39 -9
- airbyte_cdk/sources/declarative/partition_routers/partition_router.py +62 -0
- airbyte_cdk/sources/declarative/partition_routers/single_partition_router.py +15 -3
- airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py +222 -39
- airbyte_cdk/sources/declarative/requesters/error_handlers/__init__.py +19 -5
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/__init__.py +3 -1
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/constant_backoff_strategy.py +19 -7
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/exponential_backoff_strategy.py +19 -7
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/header_helper.py +4 -2
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_time_from_header_backoff_strategy.py +41 -9
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_until_time_from_header_backoff_strategy.py +29 -14
- airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategy.py +5 -13
- airbyte_cdk/sources/declarative/requesters/error_handlers/composite_error_handler.py +32 -16
- airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py +46 -56
- airbyte_cdk/sources/declarative/requesters/error_handlers/default_http_response_filter.py +40 -0
- airbyte_cdk/sources/declarative/requesters/error_handlers/error_handler.py +6 -32
- airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py +119 -41
- airbyte_cdk/sources/declarative/requesters/http_job_repository.py +228 -0
- airbyte_cdk/sources/declarative/requesters/http_requester.py +98 -344
- airbyte_cdk/sources/declarative/requesters/paginators/__init__.py +14 -3
- airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +105 -46
- airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py +14 -8
- airbyte_cdk/sources/declarative/requesters/paginators/paginator.py +19 -8
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/__init__.py +9 -3
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py +53 -21
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py +42 -19
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py +25 -12
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py +13 -10
- airbyte_cdk/sources/declarative/requesters/paginators/strategies/stop_condition.py +26 -13
- airbyte_cdk/sources/declarative/requesters/request_options/__init__.py +15 -2
- airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py +91 -0
- airbyte_cdk/sources/declarative/requesters/request_options/default_request_options_provider.py +60 -0
- airbyte_cdk/sources/declarative/requesters/request_options/interpolated_nested_request_input_provider.py +31 -14
- airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_input_provider.py +27 -15
- airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py +63 -10
- airbyte_cdk/sources/declarative/requesters/request_options/request_options_provider.py +1 -1
- airbyte_cdk/sources/declarative/requesters/requester.py +9 -17
- airbyte_cdk/sources/declarative/resolvers/__init__.py +41 -0
- airbyte_cdk/sources/declarative/resolvers/components_resolver.py +55 -0
- airbyte_cdk/sources/declarative/resolvers/config_components_resolver.py +136 -0
- airbyte_cdk/sources/declarative/resolvers/http_components_resolver.py +112 -0
- airbyte_cdk/sources/declarative/retrievers/__init__.py +6 -2
- airbyte_cdk/sources/declarative/retrievers/async_retriever.py +100 -0
- airbyte_cdk/sources/declarative/retrievers/retriever.py +1 -3
- airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +228 -72
- airbyte_cdk/sources/declarative/schema/__init__.py +14 -1
- airbyte_cdk/sources/declarative/schema/default_schema_loader.py +5 -3
- airbyte_cdk/sources/declarative/schema/dynamic_schema_loader.py +236 -0
- airbyte_cdk/sources/declarative/schema/json_file_schema_loader.py +8 -8
- airbyte_cdk/sources/declarative/spec/spec.py +12 -5
- airbyte_cdk/sources/declarative/stream_slicers/__init__.py +1 -2
- airbyte_cdk/sources/declarative/stream_slicers/declarative_partition_generator.py +88 -0
- airbyte_cdk/sources/declarative/stream_slicers/stream_slicer.py +9 -14
- airbyte_cdk/sources/declarative/transformations/add_fields.py +19 -11
- airbyte_cdk/sources/declarative/transformations/flatten_fields.py +52 -0
- airbyte_cdk/sources/declarative/transformations/keys_replace_transformation.py +61 -0
- airbyte_cdk/sources/declarative/transformations/keys_to_lower_transformation.py +22 -0
- airbyte_cdk/sources/declarative/transformations/keys_to_snake_transformation.py +68 -0
- airbyte_cdk/sources/declarative/transformations/remove_fields.py +13 -10
- airbyte_cdk/sources/declarative/transformations/transformation.py +5 -5
- airbyte_cdk/sources/declarative/types.py +19 -110
- airbyte_cdk/sources/declarative/yaml_declarative_source.py +31 -10
- airbyte_cdk/sources/embedded/base_integration.py +16 -5
- airbyte_cdk/sources/embedded/catalog.py +16 -4
- airbyte_cdk/sources/embedded/runner.py +19 -3
- airbyte_cdk/sources/embedded/tools.py +5 -2
- airbyte_cdk/sources/file_based/README.md +152 -0
- airbyte_cdk/sources/file_based/__init__.py +24 -0
- airbyte_cdk/sources/file_based/availability_strategy/__init__.py +9 -2
- airbyte_cdk/sources/file_based/availability_strategy/abstract_file_based_availability_strategy.py +22 -6
- airbyte_cdk/sources/file_based/availability_strategy/default_file_based_availability_strategy.py +46 -10
- airbyte_cdk/sources/file_based/config/abstract_file_based_spec.py +58 -10
- airbyte_cdk/sources/file_based/config/avro_format.py +2 -1
- airbyte_cdk/sources/file_based/config/csv_format.py +29 -10
- airbyte_cdk/sources/file_based/config/excel_format.py +18 -0
- airbyte_cdk/sources/file_based/config/file_based_stream_config.py +16 -4
- airbyte_cdk/sources/file_based/config/jsonl_format.py +2 -1
- airbyte_cdk/sources/file_based/config/parquet_format.py +2 -1
- airbyte_cdk/sources/file_based/config/unstructured_format.py +13 -5
- airbyte_cdk/sources/file_based/discovery_policy/__init__.py +6 -2
- airbyte_cdk/sources/file_based/discovery_policy/abstract_discovery_policy.py +2 -4
- airbyte_cdk/sources/file_based/discovery_policy/default_discovery_policy.py +7 -2
- airbyte_cdk/sources/file_based/exceptions.py +52 -15
- airbyte_cdk/sources/file_based/file_based_source.py +163 -33
- airbyte_cdk/sources/file_based/file_based_stream_reader.py +83 -5
- airbyte_cdk/sources/file_based/file_types/__init__.py +14 -1
- airbyte_cdk/sources/file_based/file_types/avro_parser.py +75 -24
- airbyte_cdk/sources/file_based/file_types/csv_parser.py +116 -34
- airbyte_cdk/sources/file_based/file_types/excel_parser.py +196 -0
- airbyte_cdk/sources/file_based/file_types/file_transfer.py +37 -0
- airbyte_cdk/sources/file_based/file_types/file_type_parser.py +4 -1
- airbyte_cdk/sources/file_based/file_types/jsonl_parser.py +24 -8
- airbyte_cdk/sources/file_based/file_types/parquet_parser.py +60 -18
- airbyte_cdk/sources/file_based/file_types/unstructured_parser.py +145 -41
- airbyte_cdk/sources/file_based/remote_file.py +1 -1
- airbyte_cdk/sources/file_based/schema_helpers.py +38 -10
- airbyte_cdk/sources/file_based/schema_validation_policies/__init__.py +3 -1
- airbyte_cdk/sources/file_based/schema_validation_policies/abstract_schema_validation_policy.py +3 -1
- airbyte_cdk/sources/file_based/schema_validation_policies/default_schema_validation_policies.py +16 -5
- airbyte_cdk/sources/file_based/stream/abstract_file_based_stream.py +50 -13
- airbyte_cdk/sources/file_based/stream/concurrent/adapters.py +67 -27
- airbyte_cdk/sources/file_based/stream/concurrent/cursor/__init__.py +5 -1
- airbyte_cdk/sources/file_based/stream/concurrent/cursor/abstract_concurrent_file_based_cursor.py +14 -23
- airbyte_cdk/sources/file_based/stream/concurrent/cursor/file_based_concurrent_cursor.py +54 -18
- airbyte_cdk/sources/file_based/stream/concurrent/cursor/file_based_final_state_cursor.py +21 -9
- airbyte_cdk/sources/file_based/stream/cursor/abstract_file_based_cursor.py +3 -1
- airbyte_cdk/sources/file_based/stream/cursor/default_file_based_cursor.py +27 -10
- airbyte_cdk/sources/file_based/stream/default_file_based_stream.py +175 -45
- airbyte_cdk/sources/http_logger.py +8 -3
- airbyte_cdk/sources/message/__init__.py +7 -1
- airbyte_cdk/sources/message/repository.py +18 -4
- airbyte_cdk/sources/source.py +42 -38
- airbyte_cdk/sources/streams/__init__.py +2 -2
- airbyte_cdk/sources/streams/availability_strategy.py +54 -3
- airbyte_cdk/sources/streams/call_rate.py +64 -21
- airbyte_cdk/sources/streams/checkpoint/__init__.py +26 -0
- airbyte_cdk/sources/streams/checkpoint/checkpoint_reader.py +335 -0
- airbyte_cdk/sources/{declarative/incremental → streams/checkpoint}/cursor.py +17 -14
- airbyte_cdk/sources/streams/checkpoint/per_partition_key_serializer.py +22 -0
- airbyte_cdk/sources/streams/checkpoint/resumable_full_refresh_cursor.py +51 -0
- airbyte_cdk/sources/streams/checkpoint/substream_resumable_full_refresh_cursor.py +110 -0
- airbyte_cdk/sources/streams/concurrent/README.md +7 -0
- airbyte_cdk/sources/streams/concurrent/abstract_stream.py +7 -2
- airbyte_cdk/sources/streams/concurrent/adapters.py +84 -75
- airbyte_cdk/sources/streams/concurrent/availability_strategy.py +30 -2
- airbyte_cdk/sources/streams/concurrent/cursor.py +298 -42
- airbyte_cdk/sources/streams/concurrent/default_stream.py +12 -3
- airbyte_cdk/sources/streams/concurrent/exceptions.py +3 -0
- airbyte_cdk/sources/streams/concurrent/helpers.py +14 -3
- airbyte_cdk/sources/streams/concurrent/partition_enqueuer.py +12 -3
- airbyte_cdk/sources/streams/concurrent/partition_reader.py +10 -3
- airbyte_cdk/sources/streams/concurrent/partitions/partition.py +1 -16
- airbyte_cdk/sources/streams/concurrent/partitions/stream_slicer.py +21 -0
- airbyte_cdk/sources/streams/concurrent/partitions/types.py +15 -5
- airbyte_cdk/sources/streams/concurrent/state_converters/abstract_stream_state_converter.py +109 -17
- airbyte_cdk/sources/streams/concurrent/state_converters/datetime_stream_state_converter.py +90 -72
- airbyte_cdk/sources/streams/core.py +412 -87
- airbyte_cdk/sources/streams/http/__init__.py +2 -1
- airbyte_cdk/sources/streams/http/availability_strategy.py +12 -101
- airbyte_cdk/sources/streams/http/error_handlers/__init__.py +22 -0
- airbyte_cdk/sources/streams/http/error_handlers/backoff_strategy.py +28 -0
- airbyte_cdk/sources/streams/http/error_handlers/default_backoff_strategy.py +17 -0
- airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py +86 -0
- airbyte_cdk/sources/streams/http/error_handlers/error_handler.py +42 -0
- airbyte_cdk/sources/streams/http/error_handlers/error_message_parser.py +19 -0
- airbyte_cdk/sources/streams/http/error_handlers/http_status_error_handler.py +110 -0
- airbyte_cdk/sources/streams/http/error_handlers/json_error_message_parser.py +52 -0
- airbyte_cdk/sources/streams/http/error_handlers/response_models.py +65 -0
- airbyte_cdk/sources/streams/http/exceptions.py +27 -7
- airbyte_cdk/sources/streams/http/http.py +369 -246
- airbyte_cdk/sources/streams/http/http_client.py +531 -0
- airbyte_cdk/sources/streams/http/rate_limiting.py +76 -12
- airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +28 -9
- airbyte_cdk/sources/streams/http/requests_native_auth/abstract_token.py +2 -1
- airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py +90 -35
- airbyte_cdk/sources/streams/http/requests_native_auth/token.py +13 -3
- airbyte_cdk/sources/types.py +154 -0
- airbyte_cdk/sources/utils/record_helper.py +36 -21
- airbyte_cdk/sources/utils/schema_helpers.py +13 -6
- airbyte_cdk/sources/utils/slice_logger.py +4 -1
- airbyte_cdk/sources/utils/transform.py +54 -20
- airbyte_cdk/sql/_util/hashing.py +34 -0
- airbyte_cdk/sql/_util/name_normalizers.py +92 -0
- airbyte_cdk/sql/constants.py +32 -0
- airbyte_cdk/sql/exceptions.py +235 -0
- airbyte_cdk/sql/secrets.py +123 -0
- airbyte_cdk/sql/shared/__init__.py +15 -0
- airbyte_cdk/sql/shared/catalog_providers.py +145 -0
- airbyte_cdk/sql/shared/sql_processor.py +786 -0
- airbyte_cdk/sql/types.py +160 -0
- airbyte_cdk/test/catalog_builder.py +70 -18
- airbyte_cdk/test/entrypoint_wrapper.py +117 -42
- airbyte_cdk/test/mock_http/__init__.py +1 -1
- airbyte_cdk/test/mock_http/matcher.py +6 -0
- airbyte_cdk/test/mock_http/mocker.py +57 -10
- airbyte_cdk/test/mock_http/request.py +19 -3
- airbyte_cdk/test/mock_http/response.py +3 -1
- airbyte_cdk/test/mock_http/response_builder.py +32 -16
- airbyte_cdk/test/state_builder.py +18 -10
- airbyte_cdk/test/utils/__init__.py +1 -0
- airbyte_cdk/test/utils/data.py +24 -0
- airbyte_cdk/test/utils/http_mocking.py +16 -0
- airbyte_cdk/test/utils/manifest_only_fixtures.py +60 -0
- airbyte_cdk/test/utils/reading.py +26 -0
- airbyte_cdk/utils/__init__.py +2 -1
- airbyte_cdk/utils/airbyte_secrets_utils.py +5 -3
- airbyte_cdk/utils/analytics_message.py +10 -2
- airbyte_cdk/utils/datetime_format_inferrer.py +4 -1
- airbyte_cdk/utils/event_timing.py +10 -10
- airbyte_cdk/utils/mapping_helpers.py +3 -1
- airbyte_cdk/utils/message_utils.py +20 -11
- airbyte_cdk/utils/print_buffer.py +75 -0
- airbyte_cdk/utils/schema_inferrer.py +198 -28
- airbyte_cdk/utils/slice_hasher.py +30 -0
- airbyte_cdk/utils/spec_schema_transformations.py +6 -3
- airbyte_cdk/utils/stream_status_utils.py +8 -1
- airbyte_cdk/utils/traced_exception.py +61 -21
- airbyte_cdk-6.13.1.dev4106.dist-info/METADATA +109 -0
- airbyte_cdk-6.13.1.dev4106.dist-info/RECORD +349 -0
- {airbyte_cdk-0.72.0.dist-info → airbyte_cdk-6.13.1.dev4106.dist-info}/WHEEL +1 -2
- airbyte_cdk-6.13.1.dev4106.dist-info/entry_points.txt +3 -0
- airbyte_cdk/sources/declarative/create_partial.py +0 -92
- airbyte_cdk/sources/declarative/parsers/class_types_registry.py +0 -102
- airbyte_cdk/sources/declarative/parsers/default_implementation_registry.py +0 -64
- airbyte_cdk/sources/declarative/requesters/error_handlers/response_action.py +0 -16
- airbyte_cdk/sources/declarative/requesters/error_handlers/response_status.py +0 -68
- airbyte_cdk/sources/declarative/stream_slicers/cartesian_product_stream_slicer.py +0 -114
- airbyte_cdk/sources/deprecated/base_source.py +0 -94
- airbyte_cdk/sources/deprecated/client.py +0 -99
- airbyte_cdk/sources/singer/__init__.py +0 -8
- airbyte_cdk/sources/singer/singer_helpers.py +0 -304
- airbyte_cdk/sources/singer/source.py +0 -186
- airbyte_cdk/sources/streams/concurrent/partitions/record.py +0 -23
- airbyte_cdk/sources/streams/http/auth/__init__.py +0 -17
- airbyte_cdk/sources/streams/http/auth/core.py +0 -29
- airbyte_cdk/sources/streams/http/auth/oauth.py +0 -113
- airbyte_cdk/sources/streams/http/auth/token.py +0 -47
- airbyte_cdk/sources/streams/utils/stream_helper.py +0 -40
- airbyte_cdk/sources/utils/catalog_helpers.py +0 -22
- airbyte_cdk/sources/utils/schema_models.py +0 -84
- airbyte_cdk-0.72.0.dist-info/METADATA +0 -243
- airbyte_cdk-0.72.0.dist-info/RECORD +0 -466
- airbyte_cdk-0.72.0.dist-info/top_level.txt +0 -3
- source_declarative_manifest/main.py +0 -29
- unit_tests/connector_builder/__init__.py +0 -3
- unit_tests/connector_builder/test_connector_builder_handler.py +0 -871
- unit_tests/connector_builder/test_message_grouper.py +0 -713
- unit_tests/connector_builder/utils.py +0 -27
- unit_tests/destinations/test_destination.py +0 -243
- unit_tests/singer/test_singer_helpers.py +0 -56
- unit_tests/singer/test_singer_source.py +0 -112
- unit_tests/sources/__init__.py +0 -0
- unit_tests/sources/concurrent_source/__init__.py +0 -3
- unit_tests/sources/concurrent_source/test_concurrent_source_adapter.py +0 -106
- unit_tests/sources/declarative/__init__.py +0 -3
- unit_tests/sources/declarative/auth/__init__.py +0 -3
- unit_tests/sources/declarative/auth/test_oauth.py +0 -331
- unit_tests/sources/declarative/auth/test_selective_authenticator.py +0 -39
- unit_tests/sources/declarative/auth/test_session_token_auth.py +0 -182
- unit_tests/sources/declarative/auth/test_token_auth.py +0 -200
- unit_tests/sources/declarative/auth/test_token_provider.py +0 -73
- unit_tests/sources/declarative/checks/__init__.py +0 -3
- unit_tests/sources/declarative/checks/test_check_stream.py +0 -146
- unit_tests/sources/declarative/decoders/__init__.py +0 -0
- unit_tests/sources/declarative/decoders/test_json_decoder.py +0 -16
- unit_tests/sources/declarative/external_component.py +0 -13
- unit_tests/sources/declarative/extractors/__init__.py +0 -3
- unit_tests/sources/declarative/extractors/test_dpath_extractor.py +0 -55
- unit_tests/sources/declarative/extractors/test_record_filter.py +0 -55
- unit_tests/sources/declarative/extractors/test_record_selector.py +0 -179
- unit_tests/sources/declarative/incremental/__init__.py +0 -0
- unit_tests/sources/declarative/incremental/test_datetime_based_cursor.py +0 -860
- unit_tests/sources/declarative/incremental/test_per_partition_cursor.py +0 -406
- unit_tests/sources/declarative/incremental/test_per_partition_cursor_integration.py +0 -332
- unit_tests/sources/declarative/interpolation/__init__.py +0 -3
- unit_tests/sources/declarative/interpolation/test_filters.py +0 -80
- unit_tests/sources/declarative/interpolation/test_interpolated_boolean.py +0 -40
- unit_tests/sources/declarative/interpolation/test_interpolated_mapping.py +0 -35
- unit_tests/sources/declarative/interpolation/test_interpolated_nested_mapping.py +0 -45
- unit_tests/sources/declarative/interpolation/test_interpolated_string.py +0 -25
- unit_tests/sources/declarative/interpolation/test_jinja.py +0 -240
- unit_tests/sources/declarative/interpolation/test_macros.py +0 -73
- unit_tests/sources/declarative/parsers/__init__.py +0 -3
- unit_tests/sources/declarative/parsers/test_manifest_component_transformer.py +0 -406
- unit_tests/sources/declarative/parsers/test_manifest_reference_resolver.py +0 -139
- unit_tests/sources/declarative/parsers/test_model_to_component_factory.py +0 -1841
- unit_tests/sources/declarative/parsers/testing_components.py +0 -36
- unit_tests/sources/declarative/partition_routers/__init__.py +0 -3
- unit_tests/sources/declarative/partition_routers/test_list_partition_router.py +0 -155
- unit_tests/sources/declarative/partition_routers/test_single_partition_router.py +0 -14
- unit_tests/sources/declarative/partition_routers/test_substream_partition_router.py +0 -404
- unit_tests/sources/declarative/requesters/__init__.py +0 -3
- unit_tests/sources/declarative/requesters/error_handlers/__init__.py +0 -3
- unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/__init__.py +0 -3
- unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_constant_backoff.py +0 -34
- unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_exponential_backoff.py +0 -36
- unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_header_helper.py +0 -38
- unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_wait_time_from_header.py +0 -35
- unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_wait_until_time_from_header.py +0 -64
- unit_tests/sources/declarative/requesters/error_handlers/test_composite_error_handler.py +0 -213
- unit_tests/sources/declarative/requesters/error_handlers/test_default_error_handler.py +0 -178
- unit_tests/sources/declarative/requesters/error_handlers/test_http_response_filter.py +0 -121
- unit_tests/sources/declarative/requesters/error_handlers/test_response_status.py +0 -44
- unit_tests/sources/declarative/requesters/paginators/__init__.py +0 -3
- unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py +0 -64
- unit_tests/sources/declarative/requesters/paginators/test_default_paginator.py +0 -313
- unit_tests/sources/declarative/requesters/paginators/test_no_paginator.py +0 -12
- unit_tests/sources/declarative/requesters/paginators/test_offset_increment.py +0 -58
- unit_tests/sources/declarative/requesters/paginators/test_page_increment.py +0 -70
- unit_tests/sources/declarative/requesters/paginators/test_request_option.py +0 -43
- unit_tests/sources/declarative/requesters/paginators/test_stop_condition.py +0 -105
- unit_tests/sources/declarative/requesters/request_options/__init__.py +0 -3
- unit_tests/sources/declarative/requesters/request_options/test_interpolated_request_options_provider.py +0 -101
- unit_tests/sources/declarative/requesters/test_http_requester.py +0 -974
- unit_tests/sources/declarative/requesters/test_interpolated_request_input_provider.py +0 -32
- unit_tests/sources/declarative/retrievers/__init__.py +0 -3
- unit_tests/sources/declarative/retrievers/test_simple_retriever.py +0 -542
- unit_tests/sources/declarative/schema/__init__.py +0 -6
- unit_tests/sources/declarative/schema/source_test/SourceTest.py +0 -8
- unit_tests/sources/declarative/schema/source_test/__init__.py +0 -3
- unit_tests/sources/declarative/schema/test_default_schema_loader.py +0 -32
- unit_tests/sources/declarative/schema/test_inline_schema_loader.py +0 -19
- unit_tests/sources/declarative/schema/test_json_file_schema_loader.py +0 -26
- unit_tests/sources/declarative/states/__init__.py +0 -3
- unit_tests/sources/declarative/stream_slicers/__init__.py +0 -3
- unit_tests/sources/declarative/stream_slicers/test_cartesian_product_stream_slicer.py +0 -225
- unit_tests/sources/declarative/test_create_partial.py +0 -83
- unit_tests/sources/declarative/test_declarative_stream.py +0 -103
- unit_tests/sources/declarative/test_manifest_declarative_source.py +0 -1260
- unit_tests/sources/declarative/test_types.py +0 -39
- unit_tests/sources/declarative/test_yaml_declarative_source.py +0 -148
- unit_tests/sources/file_based/__init__.py +0 -0
- unit_tests/sources/file_based/availability_strategy/__init__.py +0 -0
- unit_tests/sources/file_based/availability_strategy/test_default_file_based_availability_strategy.py +0 -100
- unit_tests/sources/file_based/config/__init__.py +0 -0
- unit_tests/sources/file_based/config/test_abstract_file_based_spec.py +0 -28
- unit_tests/sources/file_based/config/test_csv_format.py +0 -34
- unit_tests/sources/file_based/config/test_file_based_stream_config.py +0 -84
- unit_tests/sources/file_based/discovery_policy/__init__.py +0 -0
- unit_tests/sources/file_based/discovery_policy/test_default_discovery_policy.py +0 -31
- unit_tests/sources/file_based/file_types/__init__.py +0 -0
- unit_tests/sources/file_based/file_types/test_avro_parser.py +0 -243
- unit_tests/sources/file_based/file_types/test_csv_parser.py +0 -546
- unit_tests/sources/file_based/file_types/test_jsonl_parser.py +0 -158
- unit_tests/sources/file_based/file_types/test_parquet_parser.py +0 -274
- unit_tests/sources/file_based/file_types/test_unstructured_parser.py +0 -593
- unit_tests/sources/file_based/helpers.py +0 -70
- unit_tests/sources/file_based/in_memory_files_source.py +0 -211
- unit_tests/sources/file_based/scenarios/__init__.py +0 -0
- unit_tests/sources/file_based/scenarios/avro_scenarios.py +0 -744
- unit_tests/sources/file_based/scenarios/check_scenarios.py +0 -220
- unit_tests/sources/file_based/scenarios/concurrent_incremental_scenarios.py +0 -2844
- unit_tests/sources/file_based/scenarios/csv_scenarios.py +0 -3105
- unit_tests/sources/file_based/scenarios/file_based_source_builder.py +0 -91
- unit_tests/sources/file_based/scenarios/incremental_scenarios.py +0 -1926
- unit_tests/sources/file_based/scenarios/jsonl_scenarios.py +0 -930
- unit_tests/sources/file_based/scenarios/parquet_scenarios.py +0 -754
- unit_tests/sources/file_based/scenarios/scenario_builder.py +0 -234
- unit_tests/sources/file_based/scenarios/unstructured_scenarios.py +0 -608
- unit_tests/sources/file_based/scenarios/user_input_schema_scenarios.py +0 -746
- unit_tests/sources/file_based/scenarios/validation_policy_scenarios.py +0 -726
- unit_tests/sources/file_based/stream/__init__.py +0 -0
- unit_tests/sources/file_based/stream/concurrent/__init__.py +0 -0
- unit_tests/sources/file_based/stream/concurrent/test_adapters.py +0 -362
- unit_tests/sources/file_based/stream/concurrent/test_file_based_concurrent_cursor.py +0 -458
- unit_tests/sources/file_based/stream/test_default_file_based_cursor.py +0 -310
- unit_tests/sources/file_based/stream/test_default_file_based_stream.py +0 -244
- unit_tests/sources/file_based/test_file_based_scenarios.py +0 -320
- unit_tests/sources/file_based/test_file_based_stream_reader.py +0 -272
- unit_tests/sources/file_based/test_scenarios.py +0 -253
- unit_tests/sources/file_based/test_schema_helpers.py +0 -346
- unit_tests/sources/fixtures/__init__.py +0 -3
- unit_tests/sources/fixtures/source_test_fixture.py +0 -153
- unit_tests/sources/message/__init__.py +0 -0
- unit_tests/sources/message/test_repository.py +0 -153
- unit_tests/sources/streams/__init__.py +0 -0
- unit_tests/sources/streams/concurrent/__init__.py +0 -3
- unit_tests/sources/streams/concurrent/scenarios/__init__.py +0 -3
- unit_tests/sources/streams/concurrent/scenarios/incremental_scenarios.py +0 -250
- unit_tests/sources/streams/concurrent/scenarios/stream_facade_builder.py +0 -140
- unit_tests/sources/streams/concurrent/scenarios/stream_facade_scenarios.py +0 -452
- unit_tests/sources/streams/concurrent/scenarios/test_concurrent_scenarios.py +0 -76
- unit_tests/sources/streams/concurrent/scenarios/thread_based_concurrent_stream_scenarios.py +0 -418
- unit_tests/sources/streams/concurrent/scenarios/thread_based_concurrent_stream_source_builder.py +0 -142
- unit_tests/sources/streams/concurrent/scenarios/utils.py +0 -55
- unit_tests/sources/streams/concurrent/test_adapters.py +0 -380
- unit_tests/sources/streams/concurrent/test_concurrent_read_processor.py +0 -684
- unit_tests/sources/streams/concurrent/test_cursor.py +0 -139
- unit_tests/sources/streams/concurrent/test_datetime_state_converter.py +0 -369
- unit_tests/sources/streams/concurrent/test_default_stream.py +0 -197
- unit_tests/sources/streams/concurrent/test_partition_enqueuer.py +0 -90
- unit_tests/sources/streams/concurrent/test_partition_reader.py +0 -67
- unit_tests/sources/streams/concurrent/test_thread_pool_manager.py +0 -106
- unit_tests/sources/streams/http/__init__.py +0 -0
- unit_tests/sources/streams/http/auth/__init__.py +0 -0
- unit_tests/sources/streams/http/auth/test_auth.py +0 -173
- unit_tests/sources/streams/http/requests_native_auth/__init__.py +0 -0
- unit_tests/sources/streams/http/requests_native_auth/test_requests_native_auth.py +0 -423
- unit_tests/sources/streams/http/test_availability_strategy.py +0 -180
- unit_tests/sources/streams/http/test_http.py +0 -635
- unit_tests/sources/streams/test_availability_strategy.py +0 -70
- unit_tests/sources/streams/test_call_rate.py +0 -300
- unit_tests/sources/streams/test_stream_read.py +0 -405
- unit_tests/sources/streams/test_streams_core.py +0 -184
- unit_tests/sources/test_abstract_source.py +0 -1442
- unit_tests/sources/test_concurrent_source.py +0 -112
- unit_tests/sources/test_config.py +0 -92
- unit_tests/sources/test_connector_state_manager.py +0 -482
- unit_tests/sources/test_http_logger.py +0 -252
- unit_tests/sources/test_integration_source.py +0 -86
- unit_tests/sources/test_source.py +0 -684
- unit_tests/sources/test_source_read.py +0 -460
- unit_tests/test/__init__.py +0 -0
- unit_tests/test/mock_http/__init__.py +0 -0
- unit_tests/test/mock_http/test_matcher.py +0 -53
- unit_tests/test/mock_http/test_mocker.py +0 -214
- unit_tests/test/mock_http/test_request.py +0 -117
- unit_tests/test/mock_http/test_response_builder.py +0 -177
- unit_tests/test/test_entrypoint_wrapper.py +0 -240
- unit_tests/utils/__init__.py +0 -0
- unit_tests/utils/test_datetime_format_inferrer.py +0 -60
- unit_tests/utils/test_mapping_helpers.py +0 -54
- unit_tests/utils/test_message_utils.py +0 -91
- unit_tests/utils/test_rate_limiting.py +0 -26
- unit_tests/utils/test_schema_inferrer.py +0 -202
- unit_tests/utils/test_secret_utils.py +0 -135
- unit_tests/utils/test_stream_status_utils.py +0 -61
- unit_tests/utils/test_traced_exception.py +0 -107
- /airbyte_cdk/sources/{deprecated → declarative/async_job}/__init__.py +0 -0
- {source_declarative_manifest → airbyte_cdk/sources/declarative/migrations}/__init__.py +0 -0
- {unit_tests/destinations → airbyte_cdk/sql}/__init__.py +0 -0
- {unit_tests/singer → airbyte_cdk/sql/_util}/__init__.py +0 -0
- {airbyte_cdk-0.72.0.dist-info → airbyte_cdk-6.13.1.dev4106.dist-info}/LICENSE.txt +0 -0
@@ -27,7 +27,10 @@ class SliceLogger(ABC):
|
|
27
27
|
printable_slice = dict(_slice) if _slice else _slice
|
28
28
|
return AirbyteMessage(
|
29
29
|
type=MessageType.LOG,
|
30
|
-
log=AirbyteLogMessage(
|
30
|
+
log=AirbyteLogMessage(
|
31
|
+
level=Level.INFO,
|
32
|
+
message=f"{SliceLogger.SLICE_LOG_PREFIX}{json.dumps(printable_slice, default=str)}",
|
33
|
+
),
|
31
34
|
)
|
32
35
|
|
33
36
|
@abstractmethod
|
@@ -5,11 +5,17 @@
|
|
5
5
|
import logging
|
6
6
|
from distutils.util import strtobool
|
7
7
|
from enum import Flag, auto
|
8
|
-
from typing import Any, Callable, Dict, Mapping, Optional
|
8
|
+
from typing import Any, Callable, Dict, Generator, Mapping, Optional, cast
|
9
9
|
|
10
|
-
from jsonschema import Draft7Validator, ValidationError, validators
|
10
|
+
from jsonschema import Draft7Validator, RefResolver, ValidationError, Validator, validators
|
11
11
|
|
12
|
-
json_to_python_simple = {
|
12
|
+
json_to_python_simple = {
|
13
|
+
"string": str,
|
14
|
+
"number": float,
|
15
|
+
"integer": int,
|
16
|
+
"boolean": bool,
|
17
|
+
"null": type(None),
|
18
|
+
}
|
13
19
|
json_to_python = {**json_to_python_simple, **{"object": dict, "array": list}}
|
14
20
|
python_to_json = {v: k for k, v in json_to_python.items()}
|
15
21
|
|
@@ -24,7 +30,7 @@ class TransformConfig(Flag):
|
|
24
30
|
```
|
25
31
|
"""
|
26
32
|
|
27
|
-
# No action taken, default
|
33
|
+
# No action taken, default behavior. Cannot be combined with any other options.
|
28
34
|
NoTransform = auto()
|
29
35
|
# Applies default type casting with default_convert method which converts
|
30
36
|
# values by applying simple type casting to specified jsonschema type.
|
@@ -56,19 +62,25 @@ class TypeTransformer:
|
|
56
62
|
# Do not validate field we do not transform for maximum performance.
|
57
63
|
if key in ["type", "array", "$ref", "properties", "items"]
|
58
64
|
}
|
59
|
-
self._normalizer = validators.create(
|
65
|
+
self._normalizer = validators.create(
|
66
|
+
meta_schema=Draft7Validator.META_SCHEMA, validators=all_validators
|
67
|
+
)
|
60
68
|
|
61
|
-
def registerCustomTransform(
|
69
|
+
def registerCustomTransform(
|
70
|
+
self, normalization_callback: Callable[[Any, dict[str, Any]], Any]
|
71
|
+
) -> Callable[[Any, dict[str, Any]], Any]:
|
62
72
|
"""
|
63
73
|
Register custom normalization callback.
|
64
74
|
:param normalization_callback function to be used for value
|
65
75
|
normalization. Takes original value and part type schema. Should return
|
66
76
|
normalized value. See docs/connector-development/cdk-python/schemas.md
|
67
77
|
for details.
|
68
|
-
:return Same
|
78
|
+
:return Same callback, this is useful for using registerCustomTransform function as decorator.
|
69
79
|
"""
|
70
80
|
if TransformConfig.CustomSchemaNormalization not in self._config:
|
71
|
-
raise Exception(
|
81
|
+
raise Exception(
|
82
|
+
"Please set TransformConfig.CustomSchemaNormalization config before registering custom normalizer"
|
83
|
+
)
|
72
84
|
self._custom_normalizer = normalization_callback
|
73
85
|
return normalization_callback
|
74
86
|
|
@@ -120,20 +132,32 @@ class TypeTransformer:
|
|
120
132
|
return bool(original_item)
|
121
133
|
elif target_type == "array":
|
122
134
|
item_types = set(subschema.get("items", {}).get("type", set()))
|
123
|
-
if
|
135
|
+
if (
|
136
|
+
item_types.issubset(json_to_python_simple)
|
137
|
+
and type(original_item) in json_to_python_simple.values()
|
138
|
+
):
|
124
139
|
return [original_item]
|
125
140
|
except (ValueError, TypeError):
|
126
141
|
return original_item
|
127
142
|
return original_item
|
128
143
|
|
129
|
-
def __get_normalizer(
|
144
|
+
def __get_normalizer(
|
145
|
+
self,
|
146
|
+
schema_key: str,
|
147
|
+
original_validator: Callable, # type: ignore[type-arg]
|
148
|
+
) -> Callable[[Any, Any, Any, dict[str, Any]], Generator[Any, Any, None]]:
|
130
149
|
"""
|
131
150
|
Traverse through object fields using native jsonschema validator and apply normalization function.
|
132
151
|
:param schema_key related json schema key that currently being validated/normalized.
|
133
152
|
:original_validator: native jsonschema validator callback.
|
134
153
|
"""
|
135
154
|
|
136
|
-
def normalizator(
|
155
|
+
def normalizator(
|
156
|
+
validator_instance: Validator,
|
157
|
+
property_value: Any,
|
158
|
+
instance: Any,
|
159
|
+
schema: Dict[str, Any],
|
160
|
+
) -> Generator[Any, Any, None]:
|
137
161
|
"""
|
138
162
|
Jsonschema validator callable it uses for validating instance. We
|
139
163
|
override default Draft7Validator to perform value transformation
|
@@ -146,10 +170,13 @@ class TypeTransformer:
|
|
146
170
|
:
|
147
171
|
"""
|
148
172
|
|
149
|
-
def resolve(subschema):
|
173
|
+
def resolve(subschema: dict[str, Any]) -> dict[str, Any]:
|
150
174
|
if "$ref" in subschema:
|
151
|
-
_, resolved =
|
152
|
-
|
175
|
+
_, resolved = cast(
|
176
|
+
RefResolver,
|
177
|
+
validator_instance.resolver,
|
178
|
+
).resolve(subschema["$ref"])
|
179
|
+
return cast(dict[str, Any], resolved)
|
153
180
|
return subschema
|
154
181
|
|
155
182
|
# Transform object and array values before running json schema type checking for each element.
|
@@ -168,11 +195,20 @@ class TypeTransformer:
|
|
168
195
|
instance[index] = self.__normalize(item, subschema)
|
169
196
|
|
170
197
|
# Running native jsonschema traverse algorithm after field normalization is done.
|
171
|
-
yield from original_validator(
|
198
|
+
yield from original_validator(
|
199
|
+
validator_instance,
|
200
|
+
property_value,
|
201
|
+
instance,
|
202
|
+
schema,
|
203
|
+
)
|
172
204
|
|
173
205
|
return normalizator
|
174
206
|
|
175
|
-
def transform(
|
207
|
+
def transform(
|
208
|
+
self,
|
209
|
+
record: Dict[str, Any],
|
210
|
+
schema: Mapping[str, Any],
|
211
|
+
) -> None:
|
176
212
|
"""
|
177
213
|
Normalize and validate according to config.
|
178
214
|
:param record: record instance for normalization/transformation. All modification are done by modifying existent object.
|
@@ -184,13 +220,11 @@ class TypeTransformer:
|
|
184
220
|
for e in normalizer.iter_errors(record):
|
185
221
|
"""
|
186
222
|
just calling normalizer.validate() would throw an exception on
|
187
|
-
first validation
|
223
|
+
first validation occurrences and stop processing rest of schema.
|
188
224
|
"""
|
189
225
|
logger.warning(self.get_error_message(e))
|
190
226
|
|
191
227
|
def get_error_message(self, e: ValidationError) -> str:
|
192
228
|
instance_json_type = python_to_json[type(e.instance)]
|
193
229
|
key_path = "." + ".".join(map(str, e.path))
|
194
|
-
return (
|
195
|
-
f"Failed to transform value {repr(e.instance)} of type '{instance_json_type}' to '{e.validator_value}', key path: '{key_path}'"
|
196
|
-
)
|
230
|
+
return f"Failed to transform value {repr(e.instance)} of type '{instance_json_type}' to '{e.validator_value}', key path: '{key_path}'"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
2
|
+
"""Hashing utils for Airbyte."""
|
3
|
+
|
4
|
+
from __future__ import annotations
|
5
|
+
|
6
|
+
import hashlib
|
7
|
+
from collections.abc import Mapping
|
8
|
+
|
9
|
+
HASH_SEED = "Airbyte:"
|
10
|
+
"""Additional seed for randomizing one-way hashed strings."""
|
11
|
+
|
12
|
+
|
13
|
+
def one_way_hash(
|
14
|
+
obj: Mapping[str, str] | list[str] | object,
|
15
|
+
/,
|
16
|
+
) -> str:
|
17
|
+
"""Return a one-way hash of the given string.
|
18
|
+
|
19
|
+
To ensure a unique domain of hashes, we prepend a seed to the string before hashing.
|
20
|
+
"""
|
21
|
+
string_to_hash: str
|
22
|
+
if isinstance(obj, Mapping):
|
23
|
+
# Recursively sort and convert nested dictionaries to tuples of key-value pairs
|
24
|
+
string_to_hash = str(sorted((k, one_way_hash(v)) for k, v in obj.items()))
|
25
|
+
|
26
|
+
elif isinstance(obj, list):
|
27
|
+
# Recursively hash elements of the list
|
28
|
+
string_to_hash = str([one_way_hash(item) for item in obj])
|
29
|
+
|
30
|
+
else:
|
31
|
+
# Convert the object to a string
|
32
|
+
string_to_hash = str(obj)
|
33
|
+
|
34
|
+
return hashlib.sha256((HASH_SEED + str(string_to_hash)).encode()).hexdigest()
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
2
|
+
"""Name normalizer classes."""
|
3
|
+
|
4
|
+
from __future__ import annotations
|
5
|
+
|
6
|
+
import abc
|
7
|
+
import functools
|
8
|
+
import re
|
9
|
+
from typing import TYPE_CHECKING
|
10
|
+
|
11
|
+
from airbyte_cdk.sql import exceptions as exc
|
12
|
+
|
13
|
+
if TYPE_CHECKING:
|
14
|
+
from collections.abc import Iterable
|
15
|
+
|
16
|
+
|
17
|
+
class NameNormalizerBase(abc.ABC):
|
18
|
+
"""Abstract base class for name normalizers."""
|
19
|
+
|
20
|
+
@staticmethod
|
21
|
+
@abc.abstractmethod
|
22
|
+
def normalize(name: str) -> str:
|
23
|
+
"""Return the normalized name."""
|
24
|
+
...
|
25
|
+
|
26
|
+
@classmethod
|
27
|
+
def normalize_set(cls, str_iter: Iterable[str]) -> set[str]:
|
28
|
+
"""Converts string iterable to a set of lower case strings."""
|
29
|
+
return {cls.normalize(s) for s in str_iter}
|
30
|
+
|
31
|
+
@classmethod
|
32
|
+
def normalize_list(cls, str_iter: Iterable[str]) -> list[str]:
|
33
|
+
"""Converts string iterable to a list of lower case strings."""
|
34
|
+
return [cls.normalize(s) for s in str_iter]
|
35
|
+
|
36
|
+
@classmethod
|
37
|
+
def check_matched(cls, name1: str, name2: str) -> bool:
|
38
|
+
"""Return True if the two names match after each is normalized."""
|
39
|
+
return cls.normalize(name1) == cls.normalize(name2)
|
40
|
+
|
41
|
+
@classmethod
|
42
|
+
def check_normalized(cls, name: str) -> bool:
|
43
|
+
"""Return True if the name is already normalized."""
|
44
|
+
return cls.normalize(name) == name
|
45
|
+
|
46
|
+
|
47
|
+
class LowerCaseNormalizer(NameNormalizerBase):
|
48
|
+
"""A name normalizer that converts names to lower case."""
|
49
|
+
|
50
|
+
@staticmethod
|
51
|
+
@functools.cache
|
52
|
+
def normalize(name: str) -> str:
|
53
|
+
"""Return the normalized name.
|
54
|
+
|
55
|
+
- All non-alphanumeric characters are replaced with underscores.
|
56
|
+
- Any names that start with a numeric ("1", "2", "123", "1b" etc.) are prefixed
|
57
|
+
with and underscore ("_1", "_2", "_123", "_1b" etc.)
|
58
|
+
|
59
|
+
Examples:
|
60
|
+
- "Hello World!" -> "hello_world"
|
61
|
+
- "Hello, World!" -> "hello__world"
|
62
|
+
- "Hello - World" -> "hello___world"
|
63
|
+
- "___Hello, World___" -> "___hello__world___"
|
64
|
+
- "Average Sales (%)" -> "average_sales____"
|
65
|
+
- "Average Sales (#)" -> "average_sales____"
|
66
|
+
- "+1" -> "_1"
|
67
|
+
- "-1" -> "_1"
|
68
|
+
"""
|
69
|
+
result = name
|
70
|
+
|
71
|
+
# Replace all non-alphanumeric characters with underscores.
|
72
|
+
result = re.sub("[^A-Za-z0-9]", "_", result.lower())
|
73
|
+
|
74
|
+
# Check if name starts with a number and prepend "_" if it does.
|
75
|
+
if result and result[0].isdigit():
|
76
|
+
# Most databases do not allow identifiers to start with a number.
|
77
|
+
result = f"_{result}"
|
78
|
+
|
79
|
+
if not result.replace("_", ""):
|
80
|
+
raise exc.AirbyteNameNormalizationError(
|
81
|
+
message="Name cannot be empty after normalization.",
|
82
|
+
raw_name=name,
|
83
|
+
normalization_result=result,
|
84
|
+
)
|
85
|
+
|
86
|
+
return result
|
87
|
+
|
88
|
+
|
89
|
+
__all__ = [
|
90
|
+
"NameNormalizerBase",
|
91
|
+
"LowerCaseNormalizer",
|
92
|
+
]
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
|
2
|
+
"""Constants shared across the Airbyte codebase."""
|
3
|
+
|
4
|
+
from __future__ import annotations
|
5
|
+
|
6
|
+
DEBUG_MODE = False # Set to True to enable additional debug logging.
|
7
|
+
|
8
|
+
AB_EXTRACTED_AT_COLUMN = "_airbyte_extracted_at"
|
9
|
+
"""A column that stores the timestamp when the record was extracted."""
|
10
|
+
|
11
|
+
AB_META_COLUMN = "_airbyte_meta"
|
12
|
+
"""A column that stores metadata about the record."""
|
13
|
+
|
14
|
+
AB_RAW_ID_COLUMN = "_airbyte_raw_id"
|
15
|
+
"""A column that stores a unique identifier for each row in the source data.
|
16
|
+
|
17
|
+
Note: The interpretation of this column is slightly different from in Airbyte Dv2 destinations.
|
18
|
+
In Airbyte Dv2 destinations, this column points to a row in a separate 'raw' table. In Airbyte,
|
19
|
+
this column is simply used as a unique identifier for each record as it is received.
|
20
|
+
|
21
|
+
Airbyte uses ULIDs for this column, which are identifiers that can be sorted by time
|
22
|
+
received. This allows us to determine the debug the order of records as they are received, even if
|
23
|
+
the source provides records that are tied or received out of order from the perspective of their
|
24
|
+
`emitted_at` (`_airbyte_extracted_at`) timestamps.
|
25
|
+
"""
|
26
|
+
|
27
|
+
AB_INTERNAL_COLUMNS = {
|
28
|
+
AB_RAW_ID_COLUMN,
|
29
|
+
AB_EXTRACTED_AT_COLUMN,
|
30
|
+
AB_META_COLUMN,
|
31
|
+
}
|
32
|
+
"""A set of internal columns that are reserved for Airbyte's internal use."""
|
@@ -0,0 +1,235 @@
|
|
1
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
2
|
+
|
3
|
+
"""All exceptions used in Airbyte.
|
4
|
+
|
5
|
+
This design is modeled after structlog's exceptions, in that we bias towards auto-generated
|
6
|
+
property prints rather than sentence-like string concatenation.
|
7
|
+
|
8
|
+
E.g. Instead of this:
|
9
|
+
|
10
|
+
> `Subprocess failed with exit code '1'`
|
11
|
+
|
12
|
+
We do this:
|
13
|
+
|
14
|
+
> `Subprocess failed. (exit_code=1)`
|
15
|
+
|
16
|
+
The benefit of this approach is that we can easily support structured logging, and we can
|
17
|
+
easily add new properties to exceptions without having to update all the places where they
|
18
|
+
are raised. We can also support any arbitrary number of properties in exceptions, without spending
|
19
|
+
time on building sentence-like string constructions with optional inputs.
|
20
|
+
|
21
|
+
|
22
|
+
In addition, the following principles are applied for exception class design:
|
23
|
+
|
24
|
+
- All exceptions inherit from a common base class.
|
25
|
+
- All exceptions have a message attribute.
|
26
|
+
- The first line of the docstring is used as the default message.
|
27
|
+
- The default message can be overridden by explicitly setting the message attribute.
|
28
|
+
- Exceptions may optionally have a guidance attribute.
|
29
|
+
- Exceptions may optionally have a help_url attribute.
|
30
|
+
- Rendering is automatically handled by the base class.
|
31
|
+
- Any helpful context not defined by the exception class can be passed in the `context` dict arg.
|
32
|
+
- Within reason, avoid sending PII to the exception constructor.
|
33
|
+
- Exceptions are dataclasses, so they can be instantiated with keyword arguments.
|
34
|
+
- Use the 'from' syntax to chain exceptions when it is helpful to do so.
|
35
|
+
E.g. `raise AirbyteConnectorNotFoundError(...) from FileNotFoundError(connector_path)`
|
36
|
+
- Any exception that adds a new property should also be decorated as `@dataclass`.
|
37
|
+
"""
|
38
|
+
|
39
|
+
from __future__ import annotations
|
40
|
+
|
41
|
+
import logging
|
42
|
+
from dataclasses import dataclass
|
43
|
+
from pathlib import Path
|
44
|
+
from textwrap import indent
|
45
|
+
from typing import Any
|
46
|
+
|
47
|
+
NEW_ISSUE_URL = "https://github.com/airbytehq/airbyte/issues/new/choose"
|
48
|
+
DOCS_URL_BASE = "https://https://docs.airbyte.com/"
|
49
|
+
DOCS_URL = f"{DOCS_URL_BASE}/airbyte.html"
|
50
|
+
|
51
|
+
VERTICAL_SEPARATOR = "\n" + "-" * 60
|
52
|
+
|
53
|
+
|
54
|
+
# Base error class
|
55
|
+
|
56
|
+
|
57
|
+
@dataclass
|
58
|
+
class AirbyteError(Exception):
|
59
|
+
"""Base class for exceptions in Airbyte."""
|
60
|
+
|
61
|
+
guidance: str | None = None
|
62
|
+
help_url: str | None = None
|
63
|
+
log_text: str | list[str] | None = None
|
64
|
+
log_file: Path | None = None
|
65
|
+
context: dict[str, Any] | None = None
|
66
|
+
message: str | None = None
|
67
|
+
original_exception: Exception | None = None
|
68
|
+
|
69
|
+
def get_message(self) -> str:
|
70
|
+
"""Return the best description for the exception.
|
71
|
+
|
72
|
+
We resolve the following in order:
|
73
|
+
1. The message sent to the exception constructor (if provided).
|
74
|
+
2. The first line of the class's docstring.
|
75
|
+
"""
|
76
|
+
if self.message:
|
77
|
+
return self.message
|
78
|
+
|
79
|
+
return self.__doc__.split("\n")[0] if self.__doc__ else ""
|
80
|
+
|
81
|
+
def __str__(self) -> str:
|
82
|
+
"""Return a string representation of the exception."""
|
83
|
+
special_properties = [
|
84
|
+
"message",
|
85
|
+
"guidance",
|
86
|
+
"help_url",
|
87
|
+
"log_text",
|
88
|
+
"context",
|
89
|
+
"log_file",
|
90
|
+
"original_exception",
|
91
|
+
]
|
92
|
+
display_properties = {
|
93
|
+
k: v
|
94
|
+
for k, v in self.__dict__.items()
|
95
|
+
if k not in special_properties and not k.startswith("_") and v is not None
|
96
|
+
}
|
97
|
+
display_properties.update(self.context or {})
|
98
|
+
context_str = "\n ".join(
|
99
|
+
f"{str(k).replace('_', ' ').title()}: {v!r}" for k, v in display_properties.items()
|
100
|
+
)
|
101
|
+
exception_str = (
|
102
|
+
f"{self.get_message()} ({self.__class__.__name__})"
|
103
|
+
+ VERTICAL_SEPARATOR
|
104
|
+
+ f"\n{self.__class__.__name__}: {self.get_message()}"
|
105
|
+
)
|
106
|
+
|
107
|
+
if self.guidance:
|
108
|
+
exception_str += f"\n {self.guidance}"
|
109
|
+
|
110
|
+
if self.help_url:
|
111
|
+
exception_str += f"\n More info: {self.help_url}"
|
112
|
+
|
113
|
+
if context_str:
|
114
|
+
exception_str += "\n " + context_str
|
115
|
+
|
116
|
+
if self.log_file:
|
117
|
+
exception_str += f"\n Log file: {self.log_file.absolute()!s}"
|
118
|
+
|
119
|
+
if self.log_text:
|
120
|
+
if isinstance(self.log_text, list):
|
121
|
+
self.log_text = "\n".join(self.log_text)
|
122
|
+
|
123
|
+
exception_str += f"\n Log output: \n {indent(self.log_text, ' ')}"
|
124
|
+
|
125
|
+
if self.original_exception:
|
126
|
+
exception_str += VERTICAL_SEPARATOR + f"\nCaused by: {self.original_exception!s}"
|
127
|
+
|
128
|
+
return exception_str
|
129
|
+
|
130
|
+
def __repr__(self) -> str:
|
131
|
+
"""Return a string representation of the exception."""
|
132
|
+
class_name = self.__class__.__name__
|
133
|
+
properties_str = ", ".join(
|
134
|
+
f"{k}={v!r}" for k, v in self.__dict__.items() if not k.startswith("_")
|
135
|
+
)
|
136
|
+
return f"{class_name}({properties_str})"
|
137
|
+
|
138
|
+
def safe_logging_dict(self) -> dict[str, Any]:
|
139
|
+
"""Return a dictionary of the exception's properties which is safe for logging.
|
140
|
+
|
141
|
+
We avoid any properties which could potentially contain PII.
|
142
|
+
"""
|
143
|
+
result = {
|
144
|
+
# The class name is safe to log:
|
145
|
+
"class": self.__class__.__name__,
|
146
|
+
# We discourage interpolated strings in 'message' so that this should never contain PII:
|
147
|
+
"message": self.get_message(),
|
148
|
+
}
|
149
|
+
safe_attrs = ["connector_name", "stream_name", "violation", "exit_code"]
|
150
|
+
for attr in safe_attrs:
|
151
|
+
if hasattr(self, attr):
|
152
|
+
result[attr] = getattr(self, attr)
|
153
|
+
|
154
|
+
return result
|
155
|
+
|
156
|
+
|
157
|
+
# Airbyte Internal Errors (these are probably bugs)
|
158
|
+
|
159
|
+
|
160
|
+
@dataclass
|
161
|
+
class AirbyteInternalError(AirbyteError):
|
162
|
+
"""An internal error occurred in Airbyte."""
|
163
|
+
|
164
|
+
guidance = "Please consider reporting this error to the Airbyte team."
|
165
|
+
help_url = NEW_ISSUE_URL
|
166
|
+
|
167
|
+
|
168
|
+
# Airbyte Input Errors (replaces ValueError for user input)
|
169
|
+
|
170
|
+
|
171
|
+
@dataclass
|
172
|
+
class AirbyteInputError(AirbyteError, ValueError):
|
173
|
+
"""The input provided to Airbyte did not match expected validation rules.
|
174
|
+
|
175
|
+
This inherits from ValueError so that it can be used as a drop-in replacement for
|
176
|
+
ValueError in the Airbyte API.
|
177
|
+
"""
|
178
|
+
|
179
|
+
guidance = "Please check the provided value and try again."
|
180
|
+
help_url = DOCS_URL
|
181
|
+
input_value: str | None = None
|
182
|
+
|
183
|
+
|
184
|
+
# Normalization Errors
|
185
|
+
|
186
|
+
|
187
|
+
@dataclass
|
188
|
+
class AirbyteNameNormalizationError(AirbyteError, ValueError):
|
189
|
+
"""Error occurred while normalizing a table or column name."""
|
190
|
+
|
191
|
+
guidance = (
|
192
|
+
"Please consider renaming the source object if possible, or "
|
193
|
+
"raise an issue in GitHub if not."
|
194
|
+
)
|
195
|
+
help_url = NEW_ISSUE_URL
|
196
|
+
|
197
|
+
raw_name: str | None = None
|
198
|
+
normalization_result: str | None = None
|
199
|
+
|
200
|
+
|
201
|
+
@dataclass
|
202
|
+
class AirbyteConnectorError(AirbyteError):
|
203
|
+
"""Error when running the connector."""
|
204
|
+
|
205
|
+
connector_name: str | None = None
|
206
|
+
|
207
|
+
def __post_init__(self) -> None:
|
208
|
+
"""Set the log file path for the connector."""
|
209
|
+
self.log_file = self._get_log_file()
|
210
|
+
if not self.guidance and self.log_file:
|
211
|
+
self.guidance = "Please review the log file for more information."
|
212
|
+
|
213
|
+
def _get_log_file(self) -> Path | None:
|
214
|
+
"""Return the log file path for the connector."""
|
215
|
+
if self.connector_name:
|
216
|
+
logger = logging.getLogger(f"airbyte.{self.connector_name}")
|
217
|
+
|
218
|
+
log_paths: list[Path] = [
|
219
|
+
Path(handler.baseFilename).absolute()
|
220
|
+
for handler in logger.handlers
|
221
|
+
if isinstance(handler, logging.FileHandler)
|
222
|
+
]
|
223
|
+
|
224
|
+
if log_paths:
|
225
|
+
return log_paths[0]
|
226
|
+
|
227
|
+
return None
|
228
|
+
|
229
|
+
|
230
|
+
@dataclass
|
231
|
+
class AirbyteStreamNotFoundError(AirbyteConnectorError):
|
232
|
+
"""Connector stream not found."""
|
233
|
+
|
234
|
+
stream_name: str | None = None
|
235
|
+
available_streams: list[str] | None = None
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
|
2
|
+
"""Base classes and methods for working with secrets in Airbyte."""
|
3
|
+
|
4
|
+
from __future__ import annotations
|
5
|
+
|
6
|
+
import json
|
7
|
+
from typing import TYPE_CHECKING, Any
|
8
|
+
|
9
|
+
from pydantic_core import CoreSchema, core_schema
|
10
|
+
|
11
|
+
from airbyte_cdk.sql import exceptions as exc
|
12
|
+
|
13
|
+
if TYPE_CHECKING:
|
14
|
+
from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler, ValidationInfo
|
15
|
+
from pydantic.json_schema import JsonSchemaValue
|
16
|
+
|
17
|
+
|
18
|
+
class SecretString(str):
|
19
|
+
"""A string that represents a secret.
|
20
|
+
|
21
|
+
This class is used to mark a string as a secret. When a secret is printed, it
|
22
|
+
will be masked to prevent accidental exposure of sensitive information when debugging
|
23
|
+
or when printing containing objects like dictionaries.
|
24
|
+
|
25
|
+
To create a secret string, simply instantiate the class with any string value:
|
26
|
+
|
27
|
+
```python
|
28
|
+
secret = SecretString("my_secret_password")
|
29
|
+
```
|
30
|
+
|
31
|
+
"""
|
32
|
+
|
33
|
+
__slots__ = ()
|
34
|
+
|
35
|
+
def __repr__(self) -> str:
|
36
|
+
"""Override the representation of the secret string to return a masked value.
|
37
|
+
|
38
|
+
The secret string is always masked with `****` to prevent accidental exposure, unless
|
39
|
+
explicitly converted to a string. For instance, printing a config dictionary that contains
|
40
|
+
a secret will automatically mask the secret value instead of printing it in plain text.
|
41
|
+
|
42
|
+
However, if you explicitly convert the cast the secret as a string, such as when used
|
43
|
+
in an f-string, the secret will be exposed. This is the desired behavior to allow
|
44
|
+
secrets to be used in a controlled manner.
|
45
|
+
"""
|
46
|
+
return "<SecretString: ****>"
|
47
|
+
|
48
|
+
def is_empty(self) -> bool:
|
49
|
+
"""Check if the secret is an empty string."""
|
50
|
+
return len(self) == 0
|
51
|
+
|
52
|
+
def is_json(self) -> bool:
|
53
|
+
"""Check if the secret string is a valid JSON string."""
|
54
|
+
try:
|
55
|
+
json.loads(self)
|
56
|
+
except (json.JSONDecodeError, Exception):
|
57
|
+
return False
|
58
|
+
|
59
|
+
return True
|
60
|
+
|
61
|
+
def __bool__(self) -> bool:
|
62
|
+
"""Override the boolean value of the secret string.
|
63
|
+
|
64
|
+
Always returns `True` without inspecting contents.
|
65
|
+
"""
|
66
|
+
return True
|
67
|
+
|
68
|
+
def parse_json(self) -> Any:
|
69
|
+
"""Parse the secret string as JSON."""
|
70
|
+
try:
|
71
|
+
return json.loads(self)
|
72
|
+
except json.JSONDecodeError as ex:
|
73
|
+
raise exc.AirbyteInputError(
|
74
|
+
message="Failed to parse secret as JSON.",
|
75
|
+
context={
|
76
|
+
"Message": ex.msg,
|
77
|
+
"Position": ex.pos,
|
78
|
+
"SecretString_Length": len(self), # Debug secret blank or an unexpected format.
|
79
|
+
},
|
80
|
+
) from None
|
81
|
+
|
82
|
+
# Pydantic compatibility
|
83
|
+
|
84
|
+
@classmethod
|
85
|
+
def validate(
|
86
|
+
cls,
|
87
|
+
v: Any, # noqa: ANN401 # Must allow `Any` to match Pydantic signature
|
88
|
+
info: ValidationInfo,
|
89
|
+
) -> SecretString:
|
90
|
+
"""Validate the input value is valid as a secret string."""
|
91
|
+
_ = info # Unused
|
92
|
+
if not isinstance(v, str):
|
93
|
+
raise exc.AirbyteInputError(
|
94
|
+
message="A valid `str` or `SecretString` object is required.",
|
95
|
+
)
|
96
|
+
return cls(v)
|
97
|
+
|
98
|
+
@classmethod
|
99
|
+
def __get_pydantic_core_schema__( # noqa: PLW3201 # Pydantic dunder
|
100
|
+
cls,
|
101
|
+
source_type: Any, # noqa: ANN401 # Must allow `Any` to match Pydantic signature
|
102
|
+
handler: GetCoreSchemaHandler,
|
103
|
+
) -> CoreSchema:
|
104
|
+
"""Return a modified core schema for the secret string."""
|
105
|
+
return core_schema.with_info_after_validator_function(
|
106
|
+
function=cls.validate, schema=handler(str), field_name=handler.field_name
|
107
|
+
)
|
108
|
+
|
109
|
+
@classmethod
|
110
|
+
def __get_pydantic_json_schema__( # noqa: PLW3201 # Pydantic dunder method
|
111
|
+
cls, _core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
|
112
|
+
) -> JsonSchemaValue:
|
113
|
+
"""Return a modified JSON schema for the secret string.
|
114
|
+
|
115
|
+
- `writeOnly=True` is the official way to prevent secrets from being exposed inadvertently.
|
116
|
+
- `Format=password` is a popular and readable convention to indicate the field is sensitive.
|
117
|
+
"""
|
118
|
+
_ = _core_schema, handler # Unused
|
119
|
+
return {
|
120
|
+
"type": "string",
|
121
|
+
"format": "password",
|
122
|
+
"writeOnly": True,
|
123
|
+
}
|