airbyte-cdk 0.0.0.dev0__py3-none-any.whl

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