airbyte-cdk 0.72.1__py3-none-any.whl → 6.13.1.dev4107__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (517) hide show
  1. airbyte_cdk/__init__.py +355 -6
  2. airbyte_cdk/cli/__init__.py +1 -0
  3. airbyte_cdk/cli/source_declarative_manifest/__init__.py +5 -0
  4. airbyte_cdk/cli/source_declarative_manifest/_run.py +230 -0
  5. airbyte_cdk/cli/source_declarative_manifest/spec.json +17 -0
  6. airbyte_cdk/config_observation.py +29 -10
  7. airbyte_cdk/connector.py +24 -24
  8. airbyte_cdk/connector_builder/README.md +53 -0
  9. airbyte_cdk/connector_builder/connector_builder_handler.py +37 -11
  10. airbyte_cdk/connector_builder/main.py +45 -13
  11. airbyte_cdk/connector_builder/message_grouper.py +189 -50
  12. airbyte_cdk/connector_builder/models.py +3 -2
  13. airbyte_cdk/destinations/__init__.py +4 -3
  14. airbyte_cdk/destinations/destination.py +54 -20
  15. airbyte_cdk/destinations/vector_db_based/README.md +37 -0
  16. airbyte_cdk/destinations/vector_db_based/config.py +40 -17
  17. airbyte_cdk/destinations/vector_db_based/document_processor.py +56 -17
  18. airbyte_cdk/destinations/vector_db_based/embedder.py +57 -15
  19. airbyte_cdk/destinations/vector_db_based/test_utils.py +14 -4
  20. airbyte_cdk/destinations/vector_db_based/utils.py +8 -2
  21. airbyte_cdk/destinations/vector_db_based/writer.py +24 -5
  22. airbyte_cdk/entrypoint.py +153 -44
  23. airbyte_cdk/exception_handler.py +21 -3
  24. airbyte_cdk/logger.py +30 -44
  25. airbyte_cdk/models/__init__.py +13 -2
  26. airbyte_cdk/models/airbyte_protocol.py +86 -1
  27. airbyte_cdk/models/airbyte_protocol_serializers.py +44 -0
  28. airbyte_cdk/models/file_transfer_record_message.py +13 -0
  29. airbyte_cdk/models/well_known_types.py +1 -1
  30. airbyte_cdk/sources/__init__.py +5 -1
  31. airbyte_cdk/sources/abstract_source.py +125 -79
  32. airbyte_cdk/sources/concurrent_source/__init__.py +7 -2
  33. airbyte_cdk/sources/concurrent_source/concurrent_read_processor.py +102 -36
  34. airbyte_cdk/sources/concurrent_source/concurrent_source.py +29 -36
  35. airbyte_cdk/sources/concurrent_source/concurrent_source_adapter.py +94 -10
  36. airbyte_cdk/sources/concurrent_source/stream_thread_exception.py +25 -0
  37. airbyte_cdk/sources/concurrent_source/thread_pool_manager.py +20 -14
  38. airbyte_cdk/sources/config.py +3 -2
  39. airbyte_cdk/sources/connector_state_manager.py +49 -83
  40. airbyte_cdk/sources/declarative/async_job/job.py +52 -0
  41. airbyte_cdk/sources/declarative/async_job/job_orchestrator.py +497 -0
  42. airbyte_cdk/sources/declarative/async_job/job_tracker.py +75 -0
  43. airbyte_cdk/sources/declarative/async_job/repository.py +35 -0
  44. airbyte_cdk/sources/declarative/async_job/status.py +24 -0
  45. airbyte_cdk/sources/declarative/async_job/timer.py +39 -0
  46. airbyte_cdk/sources/declarative/auth/__init__.py +2 -3
  47. airbyte_cdk/sources/declarative/auth/declarative_authenticator.py +3 -1
  48. airbyte_cdk/sources/declarative/auth/jwt.py +191 -0
  49. airbyte_cdk/sources/declarative/auth/oauth.py +60 -20
  50. airbyte_cdk/sources/declarative/auth/selective_authenticator.py +10 -2
  51. airbyte_cdk/sources/declarative/auth/token.py +28 -10
  52. airbyte_cdk/sources/declarative/auth/token_provider.py +9 -8
  53. airbyte_cdk/sources/declarative/checks/check_stream.py +16 -8
  54. airbyte_cdk/sources/declarative/checks/connection_checker.py +4 -2
  55. airbyte_cdk/sources/declarative/concurrency_level/__init__.py +7 -0
  56. airbyte_cdk/sources/declarative/concurrency_level/concurrency_level.py +50 -0
  57. airbyte_cdk/sources/declarative/concurrent_declarative_source.py +421 -0
  58. airbyte_cdk/sources/declarative/datetime/datetime_parser.py +4 -0
  59. airbyte_cdk/sources/declarative/datetime/min_max_datetime.py +26 -6
  60. airbyte_cdk/sources/declarative/declarative_component_schema.yaml +1185 -85
  61. airbyte_cdk/sources/declarative/declarative_source.py +5 -2
  62. airbyte_cdk/sources/declarative/declarative_stream.py +95 -9
  63. airbyte_cdk/sources/declarative/decoders/__init__.py +23 -2
  64. airbyte_cdk/sources/declarative/decoders/composite_raw_decoder.py +97 -0
  65. airbyte_cdk/sources/declarative/decoders/decoder.py +11 -4
  66. airbyte_cdk/sources/declarative/decoders/json_decoder.py +92 -5
  67. airbyte_cdk/sources/declarative/decoders/noop_decoder.py +21 -0
  68. airbyte_cdk/sources/declarative/decoders/pagination_decoder_decorator.py +39 -0
  69. airbyte_cdk/sources/declarative/decoders/xml_decoder.py +98 -0
  70. airbyte_cdk/sources/declarative/extractors/__init__.py +12 -1
  71. airbyte_cdk/sources/declarative/extractors/dpath_extractor.py +29 -24
  72. airbyte_cdk/sources/declarative/extractors/http_selector.py +4 -5
  73. airbyte_cdk/sources/declarative/extractors/record_extractor.py +2 -3
  74. airbyte_cdk/sources/declarative/extractors/record_filter.py +65 -8
  75. airbyte_cdk/sources/declarative/extractors/record_selector.py +85 -26
  76. airbyte_cdk/sources/declarative/extractors/response_to_file_extractor.py +177 -0
  77. airbyte_cdk/sources/declarative/extractors/type_transformer.py +55 -0
  78. airbyte_cdk/sources/declarative/incremental/__init__.py +25 -3
  79. airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py +156 -48
  80. airbyte_cdk/sources/declarative/incremental/declarative_cursor.py +13 -0
  81. airbyte_cdk/sources/declarative/incremental/global_substream_cursor.py +350 -0
  82. airbyte_cdk/sources/declarative/incremental/per_partition_cursor.py +159 -74
  83. airbyte_cdk/sources/declarative/incremental/per_partition_with_global.py +200 -0
  84. airbyte_cdk/sources/declarative/incremental/resumable_full_refresh_cursor.py +122 -0
  85. airbyte_cdk/sources/declarative/interpolation/filters.py +27 -1
  86. airbyte_cdk/sources/declarative/interpolation/interpolated_boolean.py +23 -5
  87. airbyte_cdk/sources/declarative/interpolation/interpolated_mapping.py +12 -8
  88. airbyte_cdk/sources/declarative/interpolation/interpolated_nested_mapping.py +13 -6
  89. airbyte_cdk/sources/declarative/interpolation/interpolated_string.py +21 -6
  90. airbyte_cdk/sources/declarative/interpolation/interpolation.py +9 -3
  91. airbyte_cdk/sources/declarative/interpolation/jinja.py +72 -37
  92. airbyte_cdk/sources/declarative/interpolation/macros.py +72 -17
  93. airbyte_cdk/sources/declarative/manifest_declarative_source.py +193 -52
  94. airbyte_cdk/sources/declarative/migrations/legacy_to_per_partition_state_migration.py +98 -0
  95. airbyte_cdk/sources/declarative/migrations/state_migration.py +24 -0
  96. airbyte_cdk/sources/declarative/models/__init__.py +1 -1
  97. airbyte_cdk/sources/declarative/models/declarative_component_schema.py +1319 -603
  98. airbyte_cdk/sources/declarative/parsers/custom_exceptions.py +2 -2
  99. airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py +26 -4
  100. airbyte_cdk/sources/declarative/parsers/manifest_reference_resolver.py +26 -15
  101. airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +1695 -225
  102. airbyte_cdk/sources/declarative/partition_routers/__init__.py +24 -4
  103. airbyte_cdk/sources/declarative/partition_routers/async_job_partition_router.py +65 -0
  104. airbyte_cdk/sources/declarative/partition_routers/cartesian_product_stream_slicer.py +176 -0
  105. airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py +39 -9
  106. airbyte_cdk/sources/declarative/partition_routers/partition_router.py +62 -0
  107. airbyte_cdk/sources/declarative/partition_routers/single_partition_router.py +15 -3
  108. airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py +222 -39
  109. airbyte_cdk/sources/declarative/requesters/error_handlers/__init__.py +19 -5
  110. airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/__init__.py +3 -1
  111. airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/constant_backoff_strategy.py +19 -7
  112. airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/exponential_backoff_strategy.py +19 -7
  113. airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/header_helper.py +4 -2
  114. airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_time_from_header_backoff_strategy.py +41 -9
  115. airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_until_time_from_header_backoff_strategy.py +29 -14
  116. airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategy.py +5 -13
  117. airbyte_cdk/sources/declarative/requesters/error_handlers/composite_error_handler.py +32 -16
  118. airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py +46 -56
  119. airbyte_cdk/sources/declarative/requesters/error_handlers/default_http_response_filter.py +40 -0
  120. airbyte_cdk/sources/declarative/requesters/error_handlers/error_handler.py +6 -32
  121. airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py +119 -41
  122. airbyte_cdk/sources/declarative/requesters/http_job_repository.py +228 -0
  123. airbyte_cdk/sources/declarative/requesters/http_requester.py +98 -344
  124. airbyte_cdk/sources/declarative/requesters/paginators/__init__.py +14 -3
  125. airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +105 -46
  126. airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py +14 -8
  127. airbyte_cdk/sources/declarative/requesters/paginators/paginator.py +19 -8
  128. airbyte_cdk/sources/declarative/requesters/paginators/strategies/__init__.py +9 -3
  129. airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py +53 -21
  130. airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py +42 -19
  131. airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py +25 -12
  132. airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py +13 -10
  133. airbyte_cdk/sources/declarative/requesters/paginators/strategies/stop_condition.py +26 -13
  134. airbyte_cdk/sources/declarative/requesters/request_options/__init__.py +15 -2
  135. airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py +91 -0
  136. airbyte_cdk/sources/declarative/requesters/request_options/default_request_options_provider.py +60 -0
  137. airbyte_cdk/sources/declarative/requesters/request_options/interpolated_nested_request_input_provider.py +31 -14
  138. airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_input_provider.py +27 -15
  139. airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py +63 -10
  140. airbyte_cdk/sources/declarative/requesters/request_options/request_options_provider.py +1 -1
  141. airbyte_cdk/sources/declarative/requesters/requester.py +9 -17
  142. airbyte_cdk/sources/declarative/resolvers/__init__.py +41 -0
  143. airbyte_cdk/sources/declarative/resolvers/components_resolver.py +55 -0
  144. airbyte_cdk/sources/declarative/resolvers/config_components_resolver.py +136 -0
  145. airbyte_cdk/sources/declarative/resolvers/http_components_resolver.py +112 -0
  146. airbyte_cdk/sources/declarative/retrievers/__init__.py +6 -2
  147. airbyte_cdk/sources/declarative/retrievers/async_retriever.py +100 -0
  148. airbyte_cdk/sources/declarative/retrievers/retriever.py +1 -3
  149. airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +228 -72
  150. airbyte_cdk/sources/declarative/schema/__init__.py +14 -1
  151. airbyte_cdk/sources/declarative/schema/default_schema_loader.py +5 -3
  152. airbyte_cdk/sources/declarative/schema/dynamic_schema_loader.py +236 -0
  153. airbyte_cdk/sources/declarative/schema/json_file_schema_loader.py +8 -8
  154. airbyte_cdk/sources/declarative/spec/spec.py +12 -5
  155. airbyte_cdk/sources/declarative/stream_slicers/__init__.py +1 -2
  156. airbyte_cdk/sources/declarative/stream_slicers/declarative_partition_generator.py +88 -0
  157. airbyte_cdk/sources/declarative/stream_slicers/stream_slicer.py +9 -14
  158. airbyte_cdk/sources/declarative/transformations/add_fields.py +19 -11
  159. airbyte_cdk/sources/declarative/transformations/flatten_fields.py +52 -0
  160. airbyte_cdk/sources/declarative/transformations/keys_replace_transformation.py +61 -0
  161. airbyte_cdk/sources/declarative/transformations/keys_to_lower_transformation.py +22 -0
  162. airbyte_cdk/sources/declarative/transformations/keys_to_snake_transformation.py +68 -0
  163. airbyte_cdk/sources/declarative/transformations/remove_fields.py +13 -10
  164. airbyte_cdk/sources/declarative/transformations/transformation.py +5 -5
  165. airbyte_cdk/sources/declarative/types.py +19 -110
  166. airbyte_cdk/sources/declarative/yaml_declarative_source.py +31 -10
  167. airbyte_cdk/sources/embedded/base_integration.py +16 -5
  168. airbyte_cdk/sources/embedded/catalog.py +16 -4
  169. airbyte_cdk/sources/embedded/runner.py +19 -3
  170. airbyte_cdk/sources/embedded/tools.py +5 -2
  171. airbyte_cdk/sources/file_based/README.md +152 -0
  172. airbyte_cdk/sources/file_based/__init__.py +24 -0
  173. airbyte_cdk/sources/file_based/availability_strategy/__init__.py +9 -2
  174. airbyte_cdk/sources/file_based/availability_strategy/abstract_file_based_availability_strategy.py +22 -6
  175. airbyte_cdk/sources/file_based/availability_strategy/default_file_based_availability_strategy.py +46 -10
  176. airbyte_cdk/sources/file_based/config/abstract_file_based_spec.py +58 -10
  177. airbyte_cdk/sources/file_based/config/avro_format.py +2 -1
  178. airbyte_cdk/sources/file_based/config/csv_format.py +29 -10
  179. airbyte_cdk/sources/file_based/config/excel_format.py +18 -0
  180. airbyte_cdk/sources/file_based/config/file_based_stream_config.py +16 -4
  181. airbyte_cdk/sources/file_based/config/jsonl_format.py +2 -1
  182. airbyte_cdk/sources/file_based/config/parquet_format.py +2 -1
  183. airbyte_cdk/sources/file_based/config/unstructured_format.py +13 -5
  184. airbyte_cdk/sources/file_based/discovery_policy/__init__.py +6 -2
  185. airbyte_cdk/sources/file_based/discovery_policy/abstract_discovery_policy.py +2 -4
  186. airbyte_cdk/sources/file_based/discovery_policy/default_discovery_policy.py +7 -2
  187. airbyte_cdk/sources/file_based/exceptions.py +52 -15
  188. airbyte_cdk/sources/file_based/file_based_source.py +163 -33
  189. airbyte_cdk/sources/file_based/file_based_stream_reader.py +83 -5
  190. airbyte_cdk/sources/file_based/file_types/__init__.py +14 -1
  191. airbyte_cdk/sources/file_based/file_types/avro_parser.py +75 -24
  192. airbyte_cdk/sources/file_based/file_types/csv_parser.py +116 -34
  193. airbyte_cdk/sources/file_based/file_types/excel_parser.py +196 -0
  194. airbyte_cdk/sources/file_based/file_types/file_transfer.py +37 -0
  195. airbyte_cdk/sources/file_based/file_types/file_type_parser.py +4 -1
  196. airbyte_cdk/sources/file_based/file_types/jsonl_parser.py +24 -8
  197. airbyte_cdk/sources/file_based/file_types/parquet_parser.py +60 -18
  198. airbyte_cdk/sources/file_based/file_types/unstructured_parser.py +147 -41
  199. airbyte_cdk/sources/file_based/remote_file.py +1 -1
  200. airbyte_cdk/sources/file_based/schema_helpers.py +38 -10
  201. airbyte_cdk/sources/file_based/schema_validation_policies/__init__.py +3 -1
  202. airbyte_cdk/sources/file_based/schema_validation_policies/abstract_schema_validation_policy.py +3 -1
  203. airbyte_cdk/sources/file_based/schema_validation_policies/default_schema_validation_policies.py +16 -5
  204. airbyte_cdk/sources/file_based/stream/abstract_file_based_stream.py +50 -13
  205. airbyte_cdk/sources/file_based/stream/concurrent/adapters.py +67 -27
  206. airbyte_cdk/sources/file_based/stream/concurrent/cursor/__init__.py +5 -1
  207. airbyte_cdk/sources/file_based/stream/concurrent/cursor/abstract_concurrent_file_based_cursor.py +14 -23
  208. airbyte_cdk/sources/file_based/stream/concurrent/cursor/file_based_concurrent_cursor.py +54 -18
  209. airbyte_cdk/sources/file_based/stream/concurrent/cursor/file_based_final_state_cursor.py +21 -9
  210. airbyte_cdk/sources/file_based/stream/cursor/abstract_file_based_cursor.py +3 -1
  211. airbyte_cdk/sources/file_based/stream/cursor/default_file_based_cursor.py +27 -10
  212. airbyte_cdk/sources/file_based/stream/default_file_based_stream.py +175 -45
  213. airbyte_cdk/sources/http_logger.py +8 -3
  214. airbyte_cdk/sources/message/__init__.py +7 -1
  215. airbyte_cdk/sources/message/repository.py +18 -4
  216. airbyte_cdk/sources/source.py +42 -38
  217. airbyte_cdk/sources/streams/__init__.py +2 -2
  218. airbyte_cdk/sources/streams/availability_strategy.py +54 -3
  219. airbyte_cdk/sources/streams/call_rate.py +64 -21
  220. airbyte_cdk/sources/streams/checkpoint/__init__.py +26 -0
  221. airbyte_cdk/sources/streams/checkpoint/checkpoint_reader.py +335 -0
  222. airbyte_cdk/sources/{declarative/incremental → streams/checkpoint}/cursor.py +17 -14
  223. airbyte_cdk/sources/streams/checkpoint/per_partition_key_serializer.py +22 -0
  224. airbyte_cdk/sources/streams/checkpoint/resumable_full_refresh_cursor.py +51 -0
  225. airbyte_cdk/sources/streams/checkpoint/substream_resumable_full_refresh_cursor.py +110 -0
  226. airbyte_cdk/sources/streams/concurrent/README.md +7 -0
  227. airbyte_cdk/sources/streams/concurrent/abstract_stream.py +7 -2
  228. airbyte_cdk/sources/streams/concurrent/adapters.py +84 -75
  229. airbyte_cdk/sources/streams/concurrent/availability_strategy.py +30 -2
  230. airbyte_cdk/sources/streams/concurrent/cursor.py +298 -42
  231. airbyte_cdk/sources/streams/concurrent/default_stream.py +12 -3
  232. airbyte_cdk/sources/streams/concurrent/exceptions.py +3 -0
  233. airbyte_cdk/sources/streams/concurrent/helpers.py +14 -3
  234. airbyte_cdk/sources/streams/concurrent/partition_enqueuer.py +12 -3
  235. airbyte_cdk/sources/streams/concurrent/partition_reader.py +10 -3
  236. airbyte_cdk/sources/streams/concurrent/partitions/partition.py +1 -16
  237. airbyte_cdk/sources/streams/concurrent/partitions/stream_slicer.py +21 -0
  238. airbyte_cdk/sources/streams/concurrent/partitions/types.py +15 -5
  239. airbyte_cdk/sources/streams/concurrent/state_converters/abstract_stream_state_converter.py +109 -17
  240. airbyte_cdk/sources/streams/concurrent/state_converters/datetime_stream_state_converter.py +90 -72
  241. airbyte_cdk/sources/streams/core.py +412 -87
  242. airbyte_cdk/sources/streams/http/__init__.py +2 -1
  243. airbyte_cdk/sources/streams/http/availability_strategy.py +12 -101
  244. airbyte_cdk/sources/streams/http/error_handlers/__init__.py +22 -0
  245. airbyte_cdk/sources/streams/http/error_handlers/backoff_strategy.py +28 -0
  246. airbyte_cdk/sources/streams/http/error_handlers/default_backoff_strategy.py +17 -0
  247. airbyte_cdk/sources/streams/http/error_handlers/default_error_mapping.py +86 -0
  248. airbyte_cdk/sources/streams/http/error_handlers/error_handler.py +42 -0
  249. airbyte_cdk/sources/streams/http/error_handlers/error_message_parser.py +19 -0
  250. airbyte_cdk/sources/streams/http/error_handlers/http_status_error_handler.py +110 -0
  251. airbyte_cdk/sources/streams/http/error_handlers/json_error_message_parser.py +52 -0
  252. airbyte_cdk/sources/streams/http/error_handlers/response_models.py +65 -0
  253. airbyte_cdk/sources/streams/http/exceptions.py +27 -7
  254. airbyte_cdk/sources/streams/http/http.py +369 -246
  255. airbyte_cdk/sources/streams/http/http_client.py +531 -0
  256. airbyte_cdk/sources/streams/http/rate_limiting.py +76 -12
  257. airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +28 -9
  258. airbyte_cdk/sources/streams/http/requests_native_auth/abstract_token.py +2 -1
  259. airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py +90 -35
  260. airbyte_cdk/sources/streams/http/requests_native_auth/token.py +13 -3
  261. airbyte_cdk/sources/types.py +154 -0
  262. airbyte_cdk/sources/utils/record_helper.py +36 -21
  263. airbyte_cdk/sources/utils/schema_helpers.py +13 -6
  264. airbyte_cdk/sources/utils/slice_logger.py +4 -1
  265. airbyte_cdk/sources/utils/transform.py +54 -20
  266. airbyte_cdk/sql/_util/hashing.py +34 -0
  267. airbyte_cdk/sql/_util/name_normalizers.py +92 -0
  268. airbyte_cdk/sql/constants.py +32 -0
  269. airbyte_cdk/sql/exceptions.py +235 -0
  270. airbyte_cdk/sql/secrets.py +123 -0
  271. airbyte_cdk/sql/shared/__init__.py +15 -0
  272. airbyte_cdk/sql/shared/catalog_providers.py +145 -0
  273. airbyte_cdk/sql/shared/sql_processor.py +786 -0
  274. airbyte_cdk/sql/types.py +160 -0
  275. airbyte_cdk/test/catalog_builder.py +70 -18
  276. airbyte_cdk/test/entrypoint_wrapper.py +117 -42
  277. airbyte_cdk/test/mock_http/__init__.py +1 -1
  278. airbyte_cdk/test/mock_http/matcher.py +6 -0
  279. airbyte_cdk/test/mock_http/mocker.py +57 -10
  280. airbyte_cdk/test/mock_http/request.py +19 -3
  281. airbyte_cdk/test/mock_http/response.py +3 -1
  282. airbyte_cdk/test/mock_http/response_builder.py +32 -16
  283. airbyte_cdk/test/state_builder.py +18 -10
  284. airbyte_cdk/test/utils/__init__.py +1 -0
  285. airbyte_cdk/test/utils/data.py +24 -0
  286. airbyte_cdk/test/utils/http_mocking.py +16 -0
  287. airbyte_cdk/test/utils/manifest_only_fixtures.py +60 -0
  288. airbyte_cdk/test/utils/reading.py +26 -0
  289. airbyte_cdk/utils/__init__.py +2 -1
  290. airbyte_cdk/utils/airbyte_secrets_utils.py +5 -3
  291. airbyte_cdk/utils/analytics_message.py +10 -2
  292. airbyte_cdk/utils/datetime_format_inferrer.py +4 -1
  293. airbyte_cdk/utils/event_timing.py +10 -10
  294. airbyte_cdk/utils/mapping_helpers.py +3 -1
  295. airbyte_cdk/utils/message_utils.py +20 -11
  296. airbyte_cdk/utils/print_buffer.py +75 -0
  297. airbyte_cdk/utils/schema_inferrer.py +198 -28
  298. airbyte_cdk/utils/slice_hasher.py +30 -0
  299. airbyte_cdk/utils/spec_schema_transformations.py +6 -3
  300. airbyte_cdk/utils/stream_status_utils.py +8 -1
  301. airbyte_cdk/utils/traced_exception.py +61 -21
  302. airbyte_cdk-6.13.1.dev4107.dist-info/METADATA +109 -0
  303. airbyte_cdk-6.13.1.dev4107.dist-info/RECORD +349 -0
  304. {airbyte_cdk-0.72.1.dist-info → airbyte_cdk-6.13.1.dev4107.dist-info}/WHEEL +1 -2
  305. airbyte_cdk-6.13.1.dev4107.dist-info/entry_points.txt +3 -0
  306. airbyte_cdk/sources/declarative/create_partial.py +0 -92
  307. airbyte_cdk/sources/declarative/parsers/class_types_registry.py +0 -102
  308. airbyte_cdk/sources/declarative/parsers/default_implementation_registry.py +0 -64
  309. airbyte_cdk/sources/declarative/requesters/error_handlers/response_action.py +0 -16
  310. airbyte_cdk/sources/declarative/requesters/error_handlers/response_status.py +0 -68
  311. airbyte_cdk/sources/declarative/stream_slicers/cartesian_product_stream_slicer.py +0 -114
  312. airbyte_cdk/sources/deprecated/base_source.py +0 -94
  313. airbyte_cdk/sources/deprecated/client.py +0 -99
  314. airbyte_cdk/sources/singer/__init__.py +0 -8
  315. airbyte_cdk/sources/singer/singer_helpers.py +0 -304
  316. airbyte_cdk/sources/singer/source.py +0 -186
  317. airbyte_cdk/sources/streams/concurrent/partitions/record.py +0 -23
  318. airbyte_cdk/sources/streams/http/auth/__init__.py +0 -17
  319. airbyte_cdk/sources/streams/http/auth/core.py +0 -29
  320. airbyte_cdk/sources/streams/http/auth/oauth.py +0 -113
  321. airbyte_cdk/sources/streams/http/auth/token.py +0 -47
  322. airbyte_cdk/sources/streams/utils/stream_helper.py +0 -40
  323. airbyte_cdk/sources/utils/catalog_helpers.py +0 -22
  324. airbyte_cdk/sources/utils/schema_models.py +0 -84
  325. airbyte_cdk-0.72.1.dist-info/METADATA +0 -243
  326. airbyte_cdk-0.72.1.dist-info/RECORD +0 -466
  327. airbyte_cdk-0.72.1.dist-info/top_level.txt +0 -3
  328. source_declarative_manifest/main.py +0 -29
  329. unit_tests/connector_builder/__init__.py +0 -3
  330. unit_tests/connector_builder/test_connector_builder_handler.py +0 -871
  331. unit_tests/connector_builder/test_message_grouper.py +0 -713
  332. unit_tests/connector_builder/utils.py +0 -27
  333. unit_tests/destinations/test_destination.py +0 -243
  334. unit_tests/singer/test_singer_helpers.py +0 -56
  335. unit_tests/singer/test_singer_source.py +0 -112
  336. unit_tests/sources/__init__.py +0 -0
  337. unit_tests/sources/concurrent_source/__init__.py +0 -3
  338. unit_tests/sources/concurrent_source/test_concurrent_source_adapter.py +0 -106
  339. unit_tests/sources/declarative/__init__.py +0 -3
  340. unit_tests/sources/declarative/auth/__init__.py +0 -3
  341. unit_tests/sources/declarative/auth/test_oauth.py +0 -331
  342. unit_tests/sources/declarative/auth/test_selective_authenticator.py +0 -39
  343. unit_tests/sources/declarative/auth/test_session_token_auth.py +0 -182
  344. unit_tests/sources/declarative/auth/test_token_auth.py +0 -200
  345. unit_tests/sources/declarative/auth/test_token_provider.py +0 -73
  346. unit_tests/sources/declarative/checks/__init__.py +0 -3
  347. unit_tests/sources/declarative/checks/test_check_stream.py +0 -146
  348. unit_tests/sources/declarative/decoders/__init__.py +0 -0
  349. unit_tests/sources/declarative/decoders/test_json_decoder.py +0 -16
  350. unit_tests/sources/declarative/external_component.py +0 -13
  351. unit_tests/sources/declarative/extractors/__init__.py +0 -3
  352. unit_tests/sources/declarative/extractors/test_dpath_extractor.py +0 -55
  353. unit_tests/sources/declarative/extractors/test_record_filter.py +0 -55
  354. unit_tests/sources/declarative/extractors/test_record_selector.py +0 -179
  355. unit_tests/sources/declarative/incremental/__init__.py +0 -0
  356. unit_tests/sources/declarative/incremental/test_datetime_based_cursor.py +0 -860
  357. unit_tests/sources/declarative/incremental/test_per_partition_cursor.py +0 -406
  358. unit_tests/sources/declarative/incremental/test_per_partition_cursor_integration.py +0 -332
  359. unit_tests/sources/declarative/interpolation/__init__.py +0 -3
  360. unit_tests/sources/declarative/interpolation/test_filters.py +0 -80
  361. unit_tests/sources/declarative/interpolation/test_interpolated_boolean.py +0 -40
  362. unit_tests/sources/declarative/interpolation/test_interpolated_mapping.py +0 -35
  363. unit_tests/sources/declarative/interpolation/test_interpolated_nested_mapping.py +0 -45
  364. unit_tests/sources/declarative/interpolation/test_interpolated_string.py +0 -25
  365. unit_tests/sources/declarative/interpolation/test_jinja.py +0 -240
  366. unit_tests/sources/declarative/interpolation/test_macros.py +0 -73
  367. unit_tests/sources/declarative/parsers/__init__.py +0 -3
  368. unit_tests/sources/declarative/parsers/test_manifest_component_transformer.py +0 -406
  369. unit_tests/sources/declarative/parsers/test_manifest_reference_resolver.py +0 -139
  370. unit_tests/sources/declarative/parsers/test_model_to_component_factory.py +0 -1847
  371. unit_tests/sources/declarative/parsers/testing_components.py +0 -36
  372. unit_tests/sources/declarative/partition_routers/__init__.py +0 -3
  373. unit_tests/sources/declarative/partition_routers/test_list_partition_router.py +0 -155
  374. unit_tests/sources/declarative/partition_routers/test_single_partition_router.py +0 -14
  375. unit_tests/sources/declarative/partition_routers/test_substream_partition_router.py +0 -404
  376. unit_tests/sources/declarative/requesters/__init__.py +0 -3
  377. unit_tests/sources/declarative/requesters/error_handlers/__init__.py +0 -3
  378. unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/__init__.py +0 -3
  379. unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_constant_backoff.py +0 -34
  380. unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_exponential_backoff.py +0 -36
  381. unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_header_helper.py +0 -38
  382. unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_wait_time_from_header.py +0 -35
  383. unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_wait_until_time_from_header.py +0 -64
  384. unit_tests/sources/declarative/requesters/error_handlers/test_composite_error_handler.py +0 -213
  385. unit_tests/sources/declarative/requesters/error_handlers/test_default_error_handler.py +0 -178
  386. unit_tests/sources/declarative/requesters/error_handlers/test_http_response_filter.py +0 -121
  387. unit_tests/sources/declarative/requesters/error_handlers/test_response_status.py +0 -44
  388. unit_tests/sources/declarative/requesters/paginators/__init__.py +0 -3
  389. unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py +0 -64
  390. unit_tests/sources/declarative/requesters/paginators/test_default_paginator.py +0 -313
  391. unit_tests/sources/declarative/requesters/paginators/test_no_paginator.py +0 -12
  392. unit_tests/sources/declarative/requesters/paginators/test_offset_increment.py +0 -58
  393. unit_tests/sources/declarative/requesters/paginators/test_page_increment.py +0 -70
  394. unit_tests/sources/declarative/requesters/paginators/test_request_option.py +0 -43
  395. unit_tests/sources/declarative/requesters/paginators/test_stop_condition.py +0 -105
  396. unit_tests/sources/declarative/requesters/request_options/__init__.py +0 -3
  397. unit_tests/sources/declarative/requesters/request_options/test_interpolated_request_options_provider.py +0 -101
  398. unit_tests/sources/declarative/requesters/test_http_requester.py +0 -974
  399. unit_tests/sources/declarative/requesters/test_interpolated_request_input_provider.py +0 -32
  400. unit_tests/sources/declarative/retrievers/__init__.py +0 -3
  401. unit_tests/sources/declarative/retrievers/test_simple_retriever.py +0 -542
  402. unit_tests/sources/declarative/schema/__init__.py +0 -6
  403. unit_tests/sources/declarative/schema/source_test/SourceTest.py +0 -8
  404. unit_tests/sources/declarative/schema/source_test/__init__.py +0 -3
  405. unit_tests/sources/declarative/schema/test_default_schema_loader.py +0 -32
  406. unit_tests/sources/declarative/schema/test_inline_schema_loader.py +0 -19
  407. unit_tests/sources/declarative/schema/test_json_file_schema_loader.py +0 -26
  408. unit_tests/sources/declarative/states/__init__.py +0 -3
  409. unit_tests/sources/declarative/stream_slicers/__init__.py +0 -3
  410. unit_tests/sources/declarative/stream_slicers/test_cartesian_product_stream_slicer.py +0 -225
  411. unit_tests/sources/declarative/test_create_partial.py +0 -83
  412. unit_tests/sources/declarative/test_declarative_stream.py +0 -103
  413. unit_tests/sources/declarative/test_manifest_declarative_source.py +0 -1260
  414. unit_tests/sources/declarative/test_types.py +0 -39
  415. unit_tests/sources/declarative/test_yaml_declarative_source.py +0 -148
  416. unit_tests/sources/file_based/__init__.py +0 -0
  417. unit_tests/sources/file_based/availability_strategy/__init__.py +0 -0
  418. unit_tests/sources/file_based/availability_strategy/test_default_file_based_availability_strategy.py +0 -100
  419. unit_tests/sources/file_based/config/__init__.py +0 -0
  420. unit_tests/sources/file_based/config/test_abstract_file_based_spec.py +0 -28
  421. unit_tests/sources/file_based/config/test_csv_format.py +0 -34
  422. unit_tests/sources/file_based/config/test_file_based_stream_config.py +0 -84
  423. unit_tests/sources/file_based/discovery_policy/__init__.py +0 -0
  424. unit_tests/sources/file_based/discovery_policy/test_default_discovery_policy.py +0 -31
  425. unit_tests/sources/file_based/file_types/__init__.py +0 -0
  426. unit_tests/sources/file_based/file_types/test_avro_parser.py +0 -243
  427. unit_tests/sources/file_based/file_types/test_csv_parser.py +0 -546
  428. unit_tests/sources/file_based/file_types/test_jsonl_parser.py +0 -158
  429. unit_tests/sources/file_based/file_types/test_parquet_parser.py +0 -274
  430. unit_tests/sources/file_based/file_types/test_unstructured_parser.py +0 -593
  431. unit_tests/sources/file_based/helpers.py +0 -70
  432. unit_tests/sources/file_based/in_memory_files_source.py +0 -211
  433. unit_tests/sources/file_based/scenarios/__init__.py +0 -0
  434. unit_tests/sources/file_based/scenarios/avro_scenarios.py +0 -744
  435. unit_tests/sources/file_based/scenarios/check_scenarios.py +0 -220
  436. unit_tests/sources/file_based/scenarios/concurrent_incremental_scenarios.py +0 -2844
  437. unit_tests/sources/file_based/scenarios/csv_scenarios.py +0 -3105
  438. unit_tests/sources/file_based/scenarios/file_based_source_builder.py +0 -91
  439. unit_tests/sources/file_based/scenarios/incremental_scenarios.py +0 -1926
  440. unit_tests/sources/file_based/scenarios/jsonl_scenarios.py +0 -930
  441. unit_tests/sources/file_based/scenarios/parquet_scenarios.py +0 -754
  442. unit_tests/sources/file_based/scenarios/scenario_builder.py +0 -234
  443. unit_tests/sources/file_based/scenarios/unstructured_scenarios.py +0 -608
  444. unit_tests/sources/file_based/scenarios/user_input_schema_scenarios.py +0 -746
  445. unit_tests/sources/file_based/scenarios/validation_policy_scenarios.py +0 -726
  446. unit_tests/sources/file_based/stream/__init__.py +0 -0
  447. unit_tests/sources/file_based/stream/concurrent/__init__.py +0 -0
  448. unit_tests/sources/file_based/stream/concurrent/test_adapters.py +0 -362
  449. unit_tests/sources/file_based/stream/concurrent/test_file_based_concurrent_cursor.py +0 -458
  450. unit_tests/sources/file_based/stream/test_default_file_based_cursor.py +0 -310
  451. unit_tests/sources/file_based/stream/test_default_file_based_stream.py +0 -244
  452. unit_tests/sources/file_based/test_file_based_scenarios.py +0 -320
  453. unit_tests/sources/file_based/test_file_based_stream_reader.py +0 -272
  454. unit_tests/sources/file_based/test_scenarios.py +0 -253
  455. unit_tests/sources/file_based/test_schema_helpers.py +0 -346
  456. unit_tests/sources/fixtures/__init__.py +0 -3
  457. unit_tests/sources/fixtures/source_test_fixture.py +0 -153
  458. unit_tests/sources/message/__init__.py +0 -0
  459. unit_tests/sources/message/test_repository.py +0 -153
  460. unit_tests/sources/streams/__init__.py +0 -0
  461. unit_tests/sources/streams/concurrent/__init__.py +0 -3
  462. unit_tests/sources/streams/concurrent/scenarios/__init__.py +0 -3
  463. unit_tests/sources/streams/concurrent/scenarios/incremental_scenarios.py +0 -250
  464. unit_tests/sources/streams/concurrent/scenarios/stream_facade_builder.py +0 -140
  465. unit_tests/sources/streams/concurrent/scenarios/stream_facade_scenarios.py +0 -452
  466. unit_tests/sources/streams/concurrent/scenarios/test_concurrent_scenarios.py +0 -76
  467. unit_tests/sources/streams/concurrent/scenarios/thread_based_concurrent_stream_scenarios.py +0 -418
  468. unit_tests/sources/streams/concurrent/scenarios/thread_based_concurrent_stream_source_builder.py +0 -142
  469. unit_tests/sources/streams/concurrent/scenarios/utils.py +0 -55
  470. unit_tests/sources/streams/concurrent/test_adapters.py +0 -380
  471. unit_tests/sources/streams/concurrent/test_concurrent_read_processor.py +0 -684
  472. unit_tests/sources/streams/concurrent/test_cursor.py +0 -139
  473. unit_tests/sources/streams/concurrent/test_datetime_state_converter.py +0 -369
  474. unit_tests/sources/streams/concurrent/test_default_stream.py +0 -197
  475. unit_tests/sources/streams/concurrent/test_partition_enqueuer.py +0 -90
  476. unit_tests/sources/streams/concurrent/test_partition_reader.py +0 -67
  477. unit_tests/sources/streams/concurrent/test_thread_pool_manager.py +0 -106
  478. unit_tests/sources/streams/http/__init__.py +0 -0
  479. unit_tests/sources/streams/http/auth/__init__.py +0 -0
  480. unit_tests/sources/streams/http/auth/test_auth.py +0 -173
  481. unit_tests/sources/streams/http/requests_native_auth/__init__.py +0 -0
  482. unit_tests/sources/streams/http/requests_native_auth/test_requests_native_auth.py +0 -423
  483. unit_tests/sources/streams/http/test_availability_strategy.py +0 -180
  484. unit_tests/sources/streams/http/test_http.py +0 -635
  485. unit_tests/sources/streams/test_availability_strategy.py +0 -70
  486. unit_tests/sources/streams/test_call_rate.py +0 -300
  487. unit_tests/sources/streams/test_stream_read.py +0 -405
  488. unit_tests/sources/streams/test_streams_core.py +0 -184
  489. unit_tests/sources/test_abstract_source.py +0 -1442
  490. unit_tests/sources/test_concurrent_source.py +0 -112
  491. unit_tests/sources/test_config.py +0 -92
  492. unit_tests/sources/test_connector_state_manager.py +0 -482
  493. unit_tests/sources/test_http_logger.py +0 -252
  494. unit_tests/sources/test_integration_source.py +0 -86
  495. unit_tests/sources/test_source.py +0 -684
  496. unit_tests/sources/test_source_read.py +0 -460
  497. unit_tests/test/__init__.py +0 -0
  498. unit_tests/test/mock_http/__init__.py +0 -0
  499. unit_tests/test/mock_http/test_matcher.py +0 -53
  500. unit_tests/test/mock_http/test_mocker.py +0 -214
  501. unit_tests/test/mock_http/test_request.py +0 -117
  502. unit_tests/test/mock_http/test_response_builder.py +0 -177
  503. unit_tests/test/test_entrypoint_wrapper.py +0 -240
  504. unit_tests/utils/__init__.py +0 -0
  505. unit_tests/utils/test_datetime_format_inferrer.py +0 -60
  506. unit_tests/utils/test_mapping_helpers.py +0 -54
  507. unit_tests/utils/test_message_utils.py +0 -91
  508. unit_tests/utils/test_rate_limiting.py +0 -26
  509. unit_tests/utils/test_schema_inferrer.py +0 -202
  510. unit_tests/utils/test_secret_utils.py +0 -135
  511. unit_tests/utils/test_stream_status_utils.py +0 -61
  512. unit_tests/utils/test_traced_exception.py +0 -107
  513. /airbyte_cdk/sources/{deprecated → declarative/async_job}/__init__.py +0 -0
  514. {source_declarative_manifest → airbyte_cdk/sources/declarative/migrations}/__init__.py +0 -0
  515. {unit_tests/destinations → airbyte_cdk/sql}/__init__.py +0 -0
  516. {unit_tests/singer → airbyte_cdk/sql/_util}/__init__.py +0 -0
  517. {airbyte_cdk-0.72.1.dist-info → airbyte_cdk-6.13.1.dev4107.dist-info}/LICENSE.txt +0 -0
@@ -1,16 +1,23 @@
1
1
  #
2
2
  # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
3
  #
4
-
4
+ import copy
5
+ import logging
5
6
  from dataclasses import InitVar, dataclass
6
7
  from typing import TYPE_CHECKING, Any, Iterable, List, Mapping, Optional, Union
7
8
 
8
- import dpath.util
9
- from airbyte_cdk.models import AirbyteMessage, SyncMode, Type
9
+ import dpath
10
+
11
+ from airbyte_cdk.models import AirbyteMessage
12
+ from airbyte_cdk.models import Type as MessageType
10
13
  from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
11
- from airbyte_cdk.sources.declarative.requesters.request_option import RequestOption, RequestOptionType
12
- from airbyte_cdk.sources.declarative.stream_slicers.stream_slicer import StreamSlicer
13
- from airbyte_cdk.sources.declarative.types import Config, Record, StreamSlice, StreamState
14
+ from airbyte_cdk.sources.declarative.partition_routers.partition_router import PartitionRouter
15
+ from airbyte_cdk.sources.declarative.requesters.request_option import (
16
+ RequestOption,
17
+ RequestOptionType,
18
+ )
19
+ from airbyte_cdk.sources.types import Config, Record, StreamSlice, StreamState
20
+ from airbyte_cdk.utils import AirbyteTracedException
14
21
 
15
22
  if TYPE_CHECKING:
16
23
  from airbyte_cdk.sources.declarative.declarative_stream import DeclarativeStream
@@ -24,7 +31,9 @@ class ParentStreamConfig:
24
31
  stream: The stream to read records from
25
32
  parent_key: The key of the parent stream's records that will be the stream slice key
26
33
  partition_field: The partition key
34
+ extra_fields: Additional field paths to include in the stream slice
27
35
  request_option: How to inject the slice value on an outgoing HTTP request
36
+ incremental_dependency (bool): Indicates if the parent stream should be read incrementally.
28
37
  """
29
38
 
30
39
  stream: "DeclarativeStream" # Parent streams must be DeclarativeStream because we can't know which part of the stream slice is a partition for regular Stream
@@ -32,15 +41,27 @@ class ParentStreamConfig:
32
41
  partition_field: Union[InterpolatedString, str]
33
42
  config: Config
34
43
  parameters: InitVar[Mapping[str, Any]]
44
+ extra_fields: Optional[Union[List[List[str]], List[List[InterpolatedString]]]] = (
45
+ None # List of field paths (arrays of strings)
46
+ )
35
47
  request_option: Optional[RequestOption] = None
48
+ incremental_dependency: bool = False
36
49
 
37
50
  def __post_init__(self, parameters: Mapping[str, Any]) -> None:
38
51
  self.parent_key = InterpolatedString.create(self.parent_key, parameters=parameters)
39
- self.partition_field = InterpolatedString.create(self.partition_field, parameters=parameters)
52
+ self.partition_field = InterpolatedString.create(
53
+ self.partition_field, parameters=parameters
54
+ )
55
+ if self.extra_fields:
56
+ # Create InterpolatedString for each field path in extra_keys
57
+ self.extra_fields = [
58
+ [InterpolatedString.create(path, parameters=parameters) for path in key_path]
59
+ for key_path in self.extra_fields
60
+ ]
40
61
 
41
62
 
42
63
  @dataclass
43
- class SubstreamPartitionRouter(StreamSlicer):
64
+ class SubstreamPartitionRouter(PartitionRouter):
44
65
  """
45
66
  Partition router that iterates over the parent's stream records and emits slices
46
67
  Will populate the state with `partition_field` and `parent_slice` so they can be accessed by other components
@@ -94,15 +115,26 @@ class SubstreamPartitionRouter(StreamSlicer):
94
115
  # Pass the stream_slice from the argument, not the cursor because the cursor is updated after processing the response
95
116
  return self._get_request_option(RequestOptionType.body_json, stream_slice)
96
117
 
97
- def _get_request_option(self, option_type: RequestOptionType, stream_slice: Optional[StreamSlice]) -> Mapping[str, Any]:
118
+ def _get_request_option(
119
+ self, option_type: RequestOptionType, stream_slice: Optional[StreamSlice]
120
+ ) -> Mapping[str, Any]:
98
121
  params = {}
99
122
  if stream_slice:
100
123
  for parent_config in self.parent_stream_configs:
101
- if parent_config.request_option and parent_config.request_option.inject_into == option_type:
124
+ if (
125
+ parent_config.request_option
126
+ and parent_config.request_option.inject_into == option_type
127
+ ):
102
128
  key = parent_config.partition_field.eval(self.config) # type: ignore # partition_field is always casted to an interpolated string
103
129
  value = stream_slice.get(key)
104
130
  if value:
105
- params.update({parent_config.request_option.field_name.eval(config=self.config): value}) # type: ignore # field_name is always casted to an interpolated string
131
+ params.update(
132
+ {
133
+ parent_config.request_option.field_name.eval( # type: ignore [union-attr]
134
+ config=self.config
135
+ ): value
136
+ }
137
+ )
106
138
  return params
107
139
 
108
140
  def stream_slices(self) -> Iterable[StreamSlice]:
@@ -127,32 +159,183 @@ class SubstreamPartitionRouter(StreamSlicer):
127
159
  parent_stream = parent_stream_config.stream
128
160
  parent_field = parent_stream_config.parent_key.eval(self.config) # type: ignore # parent_key is always casted to an interpolated string
129
161
  partition_field = parent_stream_config.partition_field.eval(self.config) # type: ignore # partition_field is always casted to an interpolated string
130
- for parent_stream_slice in parent_stream.stream_slices(
131
- sync_mode=SyncMode.full_refresh, cursor_field=None, stream_state=None
132
- ):
133
- empty_parent_slice = True
134
- parent_partition = parent_stream_slice.partition if parent_stream_slice else {}
135
-
136
- for parent_record in parent_stream.read_records(
137
- sync_mode=SyncMode.full_refresh, cursor_field=None, stream_slice=parent_stream_slice, stream_state=None
138
- ):
139
- # Skip non-records (eg AirbyteLogMessage)
140
- if isinstance(parent_record, AirbyteMessage):
141
- if parent_record.type == Type.RECORD:
142
- parent_record = parent_record.record.data
143
- else:
144
- continue
145
- elif isinstance(parent_record, Record):
146
- parent_record = parent_record.data
147
- try:
148
- partition_value = dpath.util.get(parent_record, parent_field)
149
- except KeyError:
150
- pass
162
+ extra_fields = None
163
+ if parent_stream_config.extra_fields:
164
+ extra_fields = [
165
+ [field_path_part.eval(self.config) for field_path_part in field_path] # type: ignore [union-attr]
166
+ for field_path in parent_stream_config.extra_fields
167
+ ]
168
+
169
+ # read_stateless() assumes the parent is not concurrent. This is currently okay since the concurrent CDK does
170
+ # not support either substreams or RFR, but something that needs to be considered once we do
171
+ for parent_record in parent_stream.read_only_records():
172
+ parent_partition = None
173
+ # Skip non-records (eg AirbyteLogMessage)
174
+ if isinstance(parent_record, AirbyteMessage):
175
+ self.logger.warning(
176
+ f"Parent stream {parent_stream.name} returns records of type AirbyteMessage. This SubstreamPartitionRouter is not able to checkpoint incremental parent state."
177
+ )
178
+ if parent_record.type == MessageType.RECORD:
179
+ parent_record = parent_record.record.data # type: ignore[union-attr, assignment] # record is always a Record
151
180
  else:
152
- empty_parent_slice = False
153
- yield StreamSlice(
154
- partition={partition_field: partition_value, "parent_slice": parent_partition}, cursor_slice={}
155
- )
156
- # If the parent slice contains no records,
157
- if empty_parent_slice:
158
- yield from []
181
+ continue
182
+ elif isinstance(parent_record, Record):
183
+ parent_partition = (
184
+ parent_record.associated_slice.partition
185
+ if parent_record.associated_slice
186
+ else {}
187
+ )
188
+ parent_record = parent_record.data
189
+ elif not isinstance(parent_record, Mapping):
190
+ # The parent_record should only take the form of a Record, AirbyteMessage, or Mapping. Anything else is invalid
191
+ raise AirbyteTracedException(
192
+ message=f"Parent stream returned records as invalid type {type(parent_record)}"
193
+ )
194
+ try:
195
+ partition_value = dpath.get(
196
+ parent_record, # type: ignore [arg-type]
197
+ parent_field,
198
+ )
199
+ except KeyError:
200
+ continue
201
+
202
+ # Add extra fields
203
+ extracted_extra_fields = self._extract_extra_fields(parent_record, extra_fields)
204
+
205
+ yield StreamSlice(
206
+ partition={
207
+ partition_field: partition_value,
208
+ "parent_slice": parent_partition or {},
209
+ },
210
+ cursor_slice={},
211
+ extra_fields=extracted_extra_fields,
212
+ )
213
+
214
+ def _extract_extra_fields(
215
+ self,
216
+ parent_record: Mapping[str, Any] | AirbyteMessage,
217
+ extra_fields: Optional[List[List[str]]] = None,
218
+ ) -> Mapping[str, Any]:
219
+ """
220
+ Extracts additional fields specified by their paths from the parent record.
221
+
222
+ Args:
223
+ parent_record (Mapping[str, Any]): The record from the parent stream to extract fields from.
224
+ extra_fields (Optional[List[List[str]]]): A list of field paths (as lists of strings) to extract from the parent record.
225
+
226
+ Returns:
227
+ Mapping[str, Any]: A dictionary containing the extracted fields.
228
+ The keys are the joined field paths, and the values are the corresponding extracted values.
229
+ """
230
+ extracted_extra_fields = {}
231
+ if extra_fields:
232
+ for extra_field_path in extra_fields:
233
+ try:
234
+ extra_field_value = dpath.get(
235
+ parent_record, # type: ignore [arg-type]
236
+ extra_field_path,
237
+ )
238
+ self.logger.debug(
239
+ f"Extracted extra_field_path: {extra_field_path} with value: {extra_field_value}"
240
+ )
241
+ except KeyError:
242
+ self.logger.debug(f"Failed to extract extra_field_path: {extra_field_path}")
243
+ extra_field_value = None
244
+ extracted_extra_fields[".".join(extra_field_path)] = extra_field_value
245
+ return extracted_extra_fields
246
+
247
+ def set_initial_state(self, stream_state: StreamState) -> None:
248
+ """
249
+ Set the state of the parent streams.
250
+
251
+ If the `parent_state` key is missing from `stream_state`, migrate the child stream state to the parent stream's state format.
252
+ This migration applies only to parent streams with incremental dependencies.
253
+
254
+ Args:
255
+ stream_state (StreamState): The state of the streams to be set.
256
+
257
+ Example of state format:
258
+ {
259
+ "parent_state": {
260
+ "parent_stream_name1": {
261
+ "last_updated": "2023-05-27T00:00:00Z"
262
+ },
263
+ "parent_stream_name2": {
264
+ "last_updated": "2023-05-27T00:00:00Z"
265
+ }
266
+ }
267
+ }
268
+
269
+ Example of migrating to parent state format:
270
+ - Initial state:
271
+ {
272
+ "updated_at": "2023-05-27T00:00:00Z"
273
+ }
274
+ - After migration:
275
+ {
276
+ "updated_at": "2023-05-27T00:00:00Z",
277
+ "parent_state": {
278
+ "parent_stream_name": {
279
+ "parent_stream_cursor": "2023-05-27T00:00:00Z"
280
+ }
281
+ }
282
+ }
283
+ """
284
+ if not stream_state:
285
+ return
286
+
287
+ parent_state = stream_state.get("parent_state", {})
288
+
289
+ # If `parent_state` doesn't exist and at least one parent stream has an incremental dependency,
290
+ # copy the child state to parent streams with incremental dependencies.
291
+ incremental_dependency = any(
292
+ [parent_config.incremental_dependency for parent_config in self.parent_stream_configs]
293
+ )
294
+ if not parent_state and not incremental_dependency:
295
+ return
296
+
297
+ if not parent_state and incremental_dependency:
298
+ # Attempt to retrieve child state
299
+ substream_state = list(stream_state.values())
300
+ substream_state = substream_state[0] if substream_state else {} # type: ignore [assignment] # Incorrect type for assignment
301
+ parent_state = {}
302
+
303
+ # Copy child state to parent streams with incremental dependencies
304
+ if substream_state:
305
+ for parent_config in self.parent_stream_configs:
306
+ if parent_config.incremental_dependency:
307
+ parent_state[parent_config.stream.name] = {
308
+ parent_config.stream.cursor_field: substream_state
309
+ }
310
+
311
+ # Set state for each parent stream with an incremental dependency
312
+ for parent_config in self.parent_stream_configs:
313
+ if parent_config.incremental_dependency:
314
+ parent_config.stream.state = parent_state.get(parent_config.stream.name, {})
315
+
316
+ def get_stream_state(self) -> Optional[Mapping[str, StreamState]]:
317
+ """
318
+ Get the state of the parent streams.
319
+
320
+ Returns:
321
+ StreamState: The current state of the parent streams.
322
+
323
+ Example of state format:
324
+ {
325
+ "parent_stream_name1": {
326
+ "last_updated": "2023-05-27T00:00:00Z"
327
+ },
328
+ "parent_stream_name2": {
329
+ "last_updated": "2023-05-27T00:00:00Z"
330
+ }
331
+ }
332
+ """
333
+ parent_state = {}
334
+ for parent_config in self.parent_stream_configs:
335
+ if parent_config.incremental_dependency:
336
+ parent_state[parent_config.stream.name] = copy.deepcopy(parent_config.stream.state)
337
+ return parent_state
338
+
339
+ @property
340
+ def logger(self) -> logging.Logger:
341
+ return logging.getLogger("airbyte.SubstreamPartitionRouter")
@@ -2,10 +2,24 @@
2
2
  # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
3
  #
4
4
 
5
- from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategy import BackoffStrategy
6
- from airbyte_cdk.sources.declarative.requesters.error_handlers.composite_error_handler import CompositeErrorHandler
7
- from airbyte_cdk.sources.declarative.requesters.error_handlers.default_error_handler import DefaultErrorHandler
5
+ from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategy import (
6
+ BackoffStrategy,
7
+ )
8
+ from airbyte_cdk.sources.declarative.requesters.error_handlers.composite_error_handler import (
9
+ CompositeErrorHandler,
10
+ )
11
+ from airbyte_cdk.sources.declarative.requesters.error_handlers.default_error_handler import (
12
+ DefaultErrorHandler,
13
+ )
8
14
  from airbyte_cdk.sources.declarative.requesters.error_handlers.error_handler import ErrorHandler
9
- from airbyte_cdk.sources.declarative.requesters.error_handlers.http_response_filter import HttpResponseFilter
15
+ from airbyte_cdk.sources.declarative.requesters.error_handlers.http_response_filter import (
16
+ HttpResponseFilter,
17
+ )
10
18
 
11
- __all__ = ["BackoffStrategy", "CompositeErrorHandler", "DefaultErrorHandler", "ErrorHandler", "HttpResponseFilter"]
19
+ __all__ = [
20
+ "BackoffStrategy",
21
+ "CompositeErrorHandler",
22
+ "DefaultErrorHandler",
23
+ "ErrorHandler",
24
+ "HttpResponseFilter",
25
+ ]
@@ -2,7 +2,9 @@
2
2
  # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
3
  #
4
4
 
5
- from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies.constant_backoff_strategy import ConstantBackoffStrategy
5
+ from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies.constant_backoff_strategy import (
6
+ ConstantBackoffStrategy,
7
+ )
6
8
  from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies.exponential_backoff_strategy import (
7
9
  ExponentialBackoffStrategy,
8
10
  )
@@ -6,9 +6,10 @@ from dataclasses import InitVar, dataclass
6
6
  from typing import Any, Mapping, Optional, Union
7
7
 
8
8
  import requests
9
+
9
10
  from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
10
- from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategy import BackoffStrategy
11
- from airbyte_cdk.sources.declarative.types import Config
11
+ from airbyte_cdk.sources.streams.http.error_handlers import BackoffStrategy
12
+ from airbyte_cdk.sources.types import Config
12
13
 
13
14
 
14
15
  @dataclass
@@ -24,10 +25,21 @@ class ConstantBackoffStrategy(BackoffStrategy):
24
25
  parameters: InitVar[Mapping[str, Any]]
25
26
  config: Config
26
27
 
27
- def __post_init__(self, parameters: Mapping[str, Any]):
28
+ def __post_init__(self, parameters: Mapping[str, Any]) -> None:
28
29
  if not isinstance(self.backoff_time_in_seconds, InterpolatedString):
29
30
  self.backoff_time_in_seconds = str(self.backoff_time_in_seconds)
30
- self.backoff_time_in_seconds = InterpolatedString.create(self.backoff_time_in_seconds, parameters=parameters)
31
-
32
- def backoff(self, response: requests.Response, attempt_count: int) -> Optional[float]:
33
- return self.backoff_time_in_seconds.eval(self.config)
31
+ if isinstance(self.backoff_time_in_seconds, float):
32
+ self.backoff_time_in_seconds = InterpolatedString.create(
33
+ str(self.backoff_time_in_seconds), parameters=parameters
34
+ )
35
+ else:
36
+ self.backoff_time_in_seconds = InterpolatedString.create(
37
+ self.backoff_time_in_seconds, parameters=parameters
38
+ )
39
+
40
+ def backoff_time(
41
+ self,
42
+ response_or_exception: Optional[Union[requests.Response, requests.RequestException]],
43
+ attempt_count: int,
44
+ ) -> Optional[float]:
45
+ return self.backoff_time_in_seconds.eval(self.config) # type: ignore # backoff_time_in_seconds is always cast to an interpolated string
@@ -6,9 +6,10 @@ from dataclasses import InitVar, dataclass
6
6
  from typing import Any, Mapping, Optional, Union
7
7
 
8
8
  import requests
9
+
9
10
  from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
10
- from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategy import BackoffStrategy
11
- from airbyte_cdk.sources.declarative.types import Config
11
+ from airbyte_cdk.sources.streams.http.error_handlers import BackoffStrategy
12
+ from airbyte_cdk.sources.types import Config
12
13
 
13
14
 
14
15
  @dataclass
@@ -24,10 +25,21 @@ class ExponentialBackoffStrategy(BackoffStrategy):
24
25
  config: Config
25
26
  factor: Union[float, InterpolatedString, str] = 5
26
27
 
27
- def __post_init__(self, parameters: Mapping[str, Any]):
28
+ def __post_init__(self, parameters: Mapping[str, Any]) -> None:
28
29
  if not isinstance(self.factor, InterpolatedString):
29
30
  self.factor = str(self.factor)
30
- self.factor = InterpolatedString.create(self.factor, parameters=parameters)
31
-
32
- def backoff(self, response: requests.Response, attempt_count: int) -> Optional[float]:
33
- return self.factor.eval(self.config) * 2**attempt_count
31
+ if isinstance(self.factor, float):
32
+ self._factor = InterpolatedString.create(str(self.factor), parameters=parameters)
33
+ else:
34
+ self._factor = InterpolatedString.create(self.factor, parameters=parameters)
35
+
36
+ @property
37
+ def _retry_factor(self) -> float:
38
+ return self._factor.eval(self.config) # type: ignore # factor is always cast to an interpolated string
39
+
40
+ def backoff_time(
41
+ self,
42
+ response_or_exception: Optional[Union[requests.Response, requests.RequestException]],
43
+ attempt_count: int,
44
+ ) -> Optional[float]:
45
+ return self._retry_factor * 2**attempt_count # type: ignore # factor is always cast to an interpolated string
@@ -9,7 +9,9 @@ from typing import Optional
9
9
  import requests
10
10
 
11
11
 
12
- def get_numeric_value_from_header(response: requests.Response, header: str, regex: Optional[Pattern]) -> Optional[float]:
12
+ def get_numeric_value_from_header(
13
+ response: requests.Response, header: str, regex: Optional[Pattern[str]]
14
+ ) -> Optional[float]:
13
15
  """
14
16
  Extract a header value from the response as a float
15
17
  :param response: response the extract header value from
@@ -27,7 +29,7 @@ def get_numeric_value_from_header(response: requests.Response, header: str, rege
27
29
  header_value = match.group()
28
30
  return _as_float(header_value)
29
31
  elif isinstance(header_value, numbers.Number):
30
- return float(header_value)
32
+ return float(header_value) # type: ignore[arg-type]
31
33
  else:
32
34
  return None
33
35
 
@@ -7,10 +7,17 @@ from dataclasses import InitVar, dataclass
7
7
  from typing import Any, Mapping, Optional, Union
8
8
 
9
9
  import requests
10
+
11
+ from airbyte_cdk.models import FailureType
10
12
  from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
11
- from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies.header_helper import get_numeric_value_from_header
12
- from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategy import BackoffStrategy
13
- from airbyte_cdk.sources.declarative.types import Config
13
+ from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies.header_helper import (
14
+ get_numeric_value_from_header,
15
+ )
16
+ from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategy import (
17
+ BackoffStrategy,
18
+ )
19
+ from airbyte_cdk.sources.types import Config
20
+ from airbyte_cdk.utils import AirbyteTracedException
14
21
 
15
22
 
16
23
  @dataclass
@@ -21,18 +28,43 @@ class WaitTimeFromHeaderBackoffStrategy(BackoffStrategy):
21
28
  Attributes:
22
29
  header (str): header to read wait time from
23
30
  regex (Optional[str]): optional regex to apply on the header to extract its value
31
+ max_waiting_time_in_seconds: (Optional[float]): given the value extracted from the header is greater than this value, stop the stream
24
32
  """
25
33
 
26
34
  header: Union[InterpolatedString, str]
27
35
  parameters: InitVar[Mapping[str, Any]]
28
36
  config: Config
29
- regex: Optional[str] = None
37
+ regex: Optional[Union[InterpolatedString, str]] = None
38
+ max_waiting_time_in_seconds: Optional[float] = None
30
39
 
31
- def __post_init__(self, parameters: Mapping[str, Any]):
32
- self.regex = re.compile(self.regex) if self.regex else None
40
+ def __post_init__(self, parameters: Mapping[str, Any]) -> None:
41
+ self.regex = (
42
+ InterpolatedString.create(self.regex, parameters=parameters) if self.regex else None
43
+ )
33
44
  self.header = InterpolatedString.create(self.header, parameters=parameters)
34
45
 
35
- def backoff(self, response: requests.Response, attempt_count: int) -> Optional[float]:
36
- header = self.header.eval(config=self.config)
37
- header_value = get_numeric_value_from_header(response, header, self.regex)
46
+ def backoff_time(
47
+ self,
48
+ response_or_exception: Optional[Union[requests.Response, requests.RequestException]],
49
+ attempt_count: int,
50
+ ) -> Optional[float]:
51
+ header = self.header.eval(config=self.config) # type: ignore # header is always cast to an interpolated stream
52
+ if self.regex:
53
+ evaled_regex = self.regex.eval(self.config) # type: ignore # header is always cast to an interpolated string
54
+ regex = re.compile(evaled_regex)
55
+ else:
56
+ regex = None
57
+ header_value = None
58
+ if isinstance(response_or_exception, requests.Response):
59
+ header_value = get_numeric_value_from_header(response_or_exception, header, regex)
60
+ if (
61
+ self.max_waiting_time_in_seconds
62
+ and header_value
63
+ and header_value >= self.max_waiting_time_in_seconds
64
+ ):
65
+ raise AirbyteTracedException(
66
+ internal_message=f"Rate limit wait time {header_value} is greater than max waiting time of {self.max_waiting_time_in_seconds} seconds. Stopping the stream...",
67
+ message="The rate limit is greater than max waiting time has been reached.",
68
+ failure_type=FailureType.transient_error,
69
+ )
38
70
  return header_value
@@ -9,10 +9,15 @@ from dataclasses import InitVar, dataclass
9
9
  from typing import Any, Mapping, Optional, Union
10
10
 
11
11
  import requests
12
+
12
13
  from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
13
- from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies.header_helper import get_numeric_value_from_header
14
- from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategy import BackoffStrategy
15
- from airbyte_cdk.sources.declarative.types import Config
14
+ from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies.header_helper import (
15
+ get_numeric_value_from_header,
16
+ )
17
+ from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategy import (
18
+ BackoffStrategy,
19
+ )
20
+ from airbyte_cdk.sources.types import Config
16
21
 
17
22
 
18
23
  @dataclass
@@ -33,30 +38,40 @@ class WaitUntilTimeFromHeaderBackoffStrategy(BackoffStrategy):
33
38
  min_wait: Optional[Union[float, InterpolatedString, str]] = None
34
39
  regex: Optional[Union[InterpolatedString, str]] = None
35
40
 
36
- def __post_init__(self, parameters: Mapping[str, Any]):
41
+ def __post_init__(self, parameters: Mapping[str, Any]) -> None:
37
42
  self.header = InterpolatedString.create(self.header, parameters=parameters)
38
- self.regex = InterpolatedString.create(self.regex, parameters=parameters) if self.regex else None
43
+ self.regex = (
44
+ InterpolatedString.create(self.regex, parameters=parameters) if self.regex else None
45
+ )
39
46
  if not isinstance(self.min_wait, InterpolatedString):
40
47
  self.min_wait = InterpolatedString.create(str(self.min_wait), parameters=parameters)
41
48
 
42
- def backoff(self, response: requests.Response, attempt_count: int) -> Optional[float]:
49
+ def backoff_time(
50
+ self,
51
+ response_or_exception: Optional[Union[requests.Response, requests.RequestException]],
52
+ attempt_count: int,
53
+ ) -> Optional[float]:
43
54
  now = time.time()
44
- header = self.header.eval(self.config)
55
+ header = self.header.eval(self.config) # type: ignore # header is always cast to an interpolated string
45
56
  if self.regex:
46
- evaled_regex = self.regex.eval(self.config)
57
+ evaled_regex = self.regex.eval(self.config) # type: ignore # header is always cast to an interpolated string
47
58
  regex = re.compile(evaled_regex)
48
59
  else:
49
60
  regex = None
50
- wait_until = get_numeric_value_from_header(response, header, regex)
51
- min_wait = self.min_wait.eval(self.config)
61
+ wait_until = None
62
+ if isinstance(response_or_exception, requests.Response):
63
+ wait_until = get_numeric_value_from_header(response_or_exception, header, regex)
64
+ min_wait = self.min_wait.eval(self.config) # type: ignore # header is always cast to an interpolated string
52
65
  if wait_until is None or not wait_until:
53
- return min_wait
54
- if (isinstance(wait_until, str) and wait_until.isnumeric()) or isinstance(wait_until, numbers.Number):
66
+ return float(min_wait) if min_wait else None
67
+ if (isinstance(wait_until, str) and wait_until.isnumeric()) or isinstance(
68
+ wait_until, numbers.Number
69
+ ):
55
70
  wait_time = float(wait_until) - now
56
71
  else:
57
- return self.min_wait
72
+ return float(min_wait)
58
73
  if min_wait:
59
- return max(wait_time, min_wait)
74
+ return float(max(wait_time, min_wait))
60
75
  elif wait_time < 0:
61
76
  return None
62
77
  return wait_time
@@ -2,24 +2,16 @@
2
2
  # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
3
  #
4
4
 
5
- from abc import abstractmethod
5
+ from abc import ABC
6
6
  from dataclasses import dataclass
7
- from typing import Optional
8
7
 
9
- import requests
8
+ from airbyte_cdk.sources.streams.http.error_handlers import BackoffStrategy
10
9
 
11
10
 
12
11
  @dataclass
13
- class BackoffStrategy:
12
+ class DecalarativeBackoffStrategy(BackoffStrategy, ABC):
14
13
  """
14
+ This interface exists to retain backwards compatability with connectors that reference the declarative BackoffStrategy. As part of the effort to promote common interfaces to the Python CDK, this now extends the Python CDK backoff strategy interface.
15
+
15
16
  Backoff strategy defining how long to wait before retrying a request that resulted in an error.
16
17
  """
17
-
18
- @abstractmethod
19
- def backoff(self, response: requests.Response, attempt_count: int) -> Optional[float]:
20
- """
21
- Return time to wait before retrying the request.
22
- :param response: response received for the request to retry
23
- :param attempt_count: number of attempts to submit the request
24
- :return: time to wait in seconds
25
- """