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,703 @@
1
+ #
2
+ # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
+ #
4
+ import copy
5
+ import inspect
6
+ import itertools
7
+ import logging
8
+ from abc import ABC, abstractmethod
9
+ from dataclasses import dataclass
10
+ from functools import cached_property, lru_cache
11
+ from typing import Any, Dict, Iterable, Iterator, List, Mapping, MutableMapping, Optional, Union
12
+
13
+ from typing_extensions import deprecated
14
+
15
+ import airbyte_cdk.sources.utils.casing as casing
16
+ from airbyte_cdk.models import (
17
+ AirbyteMessage,
18
+ AirbyteStream,
19
+ ConfiguredAirbyteStream,
20
+ DestinationSyncMode,
21
+ SyncMode,
22
+ )
23
+ from airbyte_cdk.models import Type as MessageType
24
+ from airbyte_cdk.sources.streams.checkpoint import (
25
+ CheckpointMode,
26
+ CheckpointReader,
27
+ Cursor,
28
+ CursorBasedCheckpointReader,
29
+ FullRefreshCheckpointReader,
30
+ IncrementalCheckpointReader,
31
+ LegacyCursorBasedCheckpointReader,
32
+ ResumableFullRefreshCheckpointReader,
33
+ )
34
+ from airbyte_cdk.sources.types import StreamSlice
35
+
36
+ # list of all possible HTTP methods which can be used for sending of request bodies
37
+ from airbyte_cdk.sources.utils.schema_helpers import InternalConfig, ResourceSchemaLoader
38
+ from airbyte_cdk.sources.utils.slice_logger import DebugSliceLogger, SliceLogger
39
+ from airbyte_cdk.sources.utils.transform import TransformConfig, TypeTransformer
40
+
41
+ # A stream's read method can return one of the following types:
42
+ # Mapping[str, Any]: The content of an AirbyteRecordMessage
43
+ # AirbyteMessage: An AirbyteMessage. Could be of any type
44
+ StreamData = Union[Mapping[str, Any], AirbyteMessage]
45
+
46
+ JsonSchema = Mapping[str, Any]
47
+
48
+ NO_CURSOR_STATE_KEY = "__ab_no_cursor_state_message"
49
+
50
+
51
+ def package_name_from_class(cls: object) -> str:
52
+ """Find the package name given a class name"""
53
+ module = inspect.getmodule(cls)
54
+ if module is not None:
55
+ return module.__name__.split(".")[0]
56
+ else:
57
+ raise ValueError(f"Could not find package name for class {cls}")
58
+
59
+
60
+ class CheckpointMixin(ABC):
61
+ """Mixin for a stream that implements reading and writing the internal state used to checkpoint sync progress to the platform
62
+
63
+ class CheckpointedStream(Stream, CheckpointMixin):
64
+ @property
65
+ def state(self):
66
+ return self._state
67
+
68
+ @state.setter
69
+ def state(self, value):
70
+ self._state[self.cursor_field] = value[self.cursor_field]
71
+ """
72
+
73
+ @property
74
+ @abstractmethod
75
+ def state(self) -> MutableMapping[str, Any]:
76
+ """State getter, should return state in form that can serialized to a string and send to the output
77
+ as a STATE AirbyteMessage.
78
+
79
+ A good example of a state is a cursor_value:
80
+ {
81
+ self.cursor_field: "cursor_value"
82
+ }
83
+
84
+ State should try to be as small as possible but at the same time descriptive enough to restore
85
+ syncing process from the point where it stopped.
86
+ """
87
+
88
+ @state.setter
89
+ @abstractmethod
90
+ def state(self, value: MutableMapping[str, Any]) -> None:
91
+ """State setter, accept state serialized by state getter."""
92
+
93
+
94
+ @deprecated(
95
+ "Deprecated as of CDK version 0.87.0. "
96
+ "Deprecated in favor of the `CheckpointMixin` which offers similar functionality."
97
+ )
98
+ class IncrementalMixin(CheckpointMixin, ABC):
99
+ """Mixin to make stream incremental.
100
+
101
+ class IncrementalStream(Stream, IncrementalMixin):
102
+ @property
103
+ def state(self):
104
+ return self._state
105
+
106
+ @state.setter
107
+ def state(self, value):
108
+ self._state[self.cursor_field] = value[self.cursor_field]
109
+ """
110
+
111
+
112
+ @dataclass
113
+ class StreamClassification:
114
+ is_legacy_format: bool
115
+ has_multiple_slices: bool
116
+
117
+
118
+ class Stream(ABC):
119
+ """
120
+ Base abstract class for an Airbyte Stream. Makes no assumption of the Stream's underlying transport protocol.
121
+ """
122
+
123
+ _configured_json_schema: Optional[Dict[str, Any]] = None
124
+ _exit_on_rate_limit: bool = False
125
+
126
+ # Use self.logger in subclasses to log any messages
127
+ @property
128
+ def logger(self) -> logging.Logger:
129
+ return logging.getLogger(f"airbyte.streams.{self.name}")
130
+
131
+ # TypeTransformer object to perform output data transformation
132
+ transformer: TypeTransformer = TypeTransformer(TransformConfig.NoTransform)
133
+
134
+ cursor: Optional[Cursor] = None
135
+
136
+ has_multiple_slices = False
137
+
138
+ @cached_property
139
+ def name(self) -> str:
140
+ """
141
+ :return: Stream name. By default this is the implementing class name, but it can be overridden as needed.
142
+ """
143
+ return casing.camel_to_snake(self.__class__.__name__)
144
+
145
+ def get_error_display_message(self, exception: BaseException) -> Optional[str]:
146
+ """
147
+ Retrieves the user-friendly display message that corresponds to an exception.
148
+ This will be called when encountering an exception while reading records from the stream, and used to build the AirbyteTraceMessage.
149
+
150
+ The default implementation of this method does not return user-friendly messages for any exception type, but it should be overriden as needed.
151
+
152
+ :param exception: The exception that was raised
153
+ :return: A user-friendly message that indicates the cause of the error
154
+ """
155
+ return None
156
+
157
+ def read( # type: ignore # ignoring typing for ConnectorStateManager because of circular dependencies
158
+ self,
159
+ configured_stream: ConfiguredAirbyteStream,
160
+ logger: logging.Logger,
161
+ slice_logger: SliceLogger,
162
+ stream_state: MutableMapping[str, Any],
163
+ state_manager,
164
+ internal_config: InternalConfig,
165
+ ) -> Iterable[StreamData]:
166
+ sync_mode = configured_stream.sync_mode
167
+ cursor_field = configured_stream.cursor_field
168
+ self.configured_json_schema = configured_stream.stream.json_schema
169
+
170
+ # WARNING: When performing a read() that uses incoming stream state, we MUST use the self.state that is defined as
171
+ # opposed to the incoming stream_state value. Because some connectors like ones using the file-based CDK modify
172
+ # state before setting the value on the Stream attribute, the most up-to-date state is derived from Stream.state
173
+ # instead of the stream_state parameter. This does not apply to legacy connectors using get_updated_state().
174
+ try:
175
+ stream_state = self.state # type: ignore # we know the field might not exist...
176
+ except AttributeError:
177
+ pass
178
+
179
+ should_checkpoint = bool(state_manager)
180
+ checkpoint_reader = self._get_checkpoint_reader(
181
+ logger=logger, cursor_field=cursor_field, sync_mode=sync_mode, stream_state=stream_state
182
+ )
183
+
184
+ next_slice = checkpoint_reader.next()
185
+ record_counter = 0
186
+ stream_state_tracker = copy.deepcopy(stream_state)
187
+ while next_slice is not None:
188
+ if slice_logger.should_log_slice_message(logger):
189
+ yield slice_logger.create_slice_log_message(next_slice)
190
+ records = self.read_records(
191
+ sync_mode=sync_mode, # todo: change this interface to no longer rely on sync_mode for behavior
192
+ stream_slice=next_slice,
193
+ stream_state=stream_state,
194
+ cursor_field=cursor_field or None,
195
+ )
196
+ for record_data_or_message in records:
197
+ yield record_data_or_message
198
+ if isinstance(record_data_or_message, Mapping) or (
199
+ hasattr(record_data_or_message, "type")
200
+ and record_data_or_message.type == MessageType.RECORD
201
+ ):
202
+ record_data = (
203
+ record_data_or_message
204
+ if isinstance(record_data_or_message, Mapping)
205
+ else record_data_or_message.record
206
+ )
207
+
208
+ # Thanks I hate it. RFR fundamentally doesn't fit with the concept of the legacy Stream.get_updated_state()
209
+ # method because RFR streams rely on pagination as a cursor. Stream.get_updated_state() was designed to make
210
+ # the CDK manage state using specifically the last seen record. don't @ brian.lai
211
+ #
212
+ # Also, because the legacy incremental state case decouples observing incoming records from emitting state, it
213
+ # requires that we separate CheckpointReader.observe() and CheckpointReader.get_checkpoint() which could
214
+ # otherwise be combined.
215
+ if self.cursor_field:
216
+ # Some connectors have streams that implement get_updated_state(), but do not define a cursor_field. This
217
+ # should be fixed on the stream implementation, but we should also protect against this in the CDK as well
218
+ stream_state_tracker = self.get_updated_state(
219
+ stream_state_tracker,
220
+ record_data, # type: ignore [arg-type]
221
+ )
222
+ self._observe_state(checkpoint_reader, stream_state_tracker)
223
+ record_counter += 1
224
+
225
+ checkpoint_interval = self.state_checkpoint_interval
226
+ if (
227
+ should_checkpoint
228
+ and checkpoint_interval
229
+ and record_counter % checkpoint_interval == 0
230
+ ):
231
+ checkpoint = checkpoint_reader.get_checkpoint()
232
+ if checkpoint:
233
+ airbyte_state_message = self._checkpoint_state(
234
+ checkpoint, state_manager=state_manager
235
+ )
236
+ yield airbyte_state_message
237
+
238
+ if internal_config.is_limit_reached(record_counter):
239
+ break
240
+ self._observe_state(checkpoint_reader)
241
+ checkpoint_state = checkpoint_reader.get_checkpoint()
242
+ if should_checkpoint and checkpoint_state is not None:
243
+ airbyte_state_message = self._checkpoint_state(
244
+ checkpoint_state, state_manager=state_manager
245
+ )
246
+ yield airbyte_state_message
247
+
248
+ next_slice = checkpoint_reader.next()
249
+
250
+ checkpoint = checkpoint_reader.get_checkpoint()
251
+ if should_checkpoint and checkpoint is not None:
252
+ airbyte_state_message = self._checkpoint_state(checkpoint, state_manager=state_manager)
253
+ yield airbyte_state_message
254
+
255
+ def read_only_records(self, state: Optional[Mapping[str, Any]] = None) -> Iterable[StreamData]:
256
+ """
257
+ Helper method that performs a read on a stream with an optional state and emits records. If the parent stream supports
258
+ incremental, this operation does not update the stream's internal state (if it uses the modern state setter/getter)
259
+ or emit state messages.
260
+ """
261
+
262
+ configured_stream = ConfiguredAirbyteStream(
263
+ stream=AirbyteStream(
264
+ name=self.name,
265
+ json_schema={},
266
+ supported_sync_modes=[SyncMode.full_refresh, SyncMode.incremental],
267
+ ),
268
+ sync_mode=SyncMode.incremental if state else SyncMode.full_refresh,
269
+ destination_sync_mode=DestinationSyncMode.append,
270
+ )
271
+
272
+ yield from self.read(
273
+ configured_stream=configured_stream,
274
+ logger=self.logger,
275
+ slice_logger=DebugSliceLogger(),
276
+ stream_state=dict(state)
277
+ if state
278
+ else {}, # read() expects MutableMapping instead of Mapping which is used more often
279
+ state_manager=None,
280
+ internal_config=InternalConfig(), # type: ignore [call-arg]
281
+ )
282
+
283
+ @abstractmethod
284
+ def read_records(
285
+ self,
286
+ sync_mode: SyncMode,
287
+ cursor_field: Optional[List[str]] = None,
288
+ stream_slice: Optional[Mapping[str, Any]] = None,
289
+ stream_state: Optional[Mapping[str, Any]] = None,
290
+ ) -> Iterable[StreamData]:
291
+ """
292
+ This method should be overridden by subclasses to read records based on the inputs
293
+ """
294
+
295
+ @lru_cache(maxsize=None)
296
+ def get_json_schema(self) -> Mapping[str, Any]:
297
+ """
298
+ :return: A dict of the JSON schema representing this stream.
299
+
300
+ The default implementation of this method looks for a JSONSchema file with the same name as this stream's "name" property.
301
+ Override as needed.
302
+ """
303
+ # TODO show an example of using pydantic to define the JSON schema, or reading an OpenAPI spec
304
+ return ResourceSchemaLoader(package_name_from_class(self.__class__)).get_schema(self.name)
305
+
306
+ def as_airbyte_stream(self) -> AirbyteStream:
307
+ stream = AirbyteStream(
308
+ name=self.name,
309
+ json_schema=dict(self.get_json_schema()),
310
+ supported_sync_modes=[SyncMode.full_refresh],
311
+ is_resumable=self.is_resumable,
312
+ )
313
+
314
+ if self.namespace:
315
+ stream.namespace = self.namespace
316
+
317
+ # If we can offer incremental we always should. RFR is always less reliable than incremental which uses a real cursor value
318
+ if self.supports_incremental:
319
+ stream.source_defined_cursor = self.source_defined_cursor
320
+ stream.supported_sync_modes.append(SyncMode.incremental)
321
+ stream.default_cursor_field = self._wrapped_cursor_field()
322
+
323
+ keys = Stream._wrapped_primary_key(self.primary_key)
324
+ if keys and len(keys) > 0:
325
+ stream.source_defined_primary_key = keys
326
+
327
+ return stream
328
+
329
+ @property
330
+ def supports_incremental(self) -> bool:
331
+ """
332
+ :return: True if this stream supports incrementally reading data
333
+ """
334
+ return len(self._wrapped_cursor_field()) > 0
335
+
336
+ @property
337
+ def is_resumable(self) -> bool:
338
+ """
339
+ :return: True if this stream allows the checkpointing of sync progress and can resume from it on subsequent attempts.
340
+ This differs from supports_incremental because certain kinds of streams like those supporting resumable full refresh
341
+ can checkpoint progress in between attempts for improved fault tolerance. However, they will start from the beginning
342
+ on the next sync job.
343
+ """
344
+ if self.supports_incremental:
345
+ return True
346
+ if self.has_multiple_slices:
347
+ # We temporarily gate substream to not support RFR because puts a pretty high burden on connector developers
348
+ # to structure stream state in a very specific way. We also can't check for issubclass(HttpSubStream) because
349
+ # not all substreams implement the interface and it would be a circular dependency so we use parent as a surrogate
350
+ return False
351
+ elif hasattr(type(self), "state") and getattr(type(self), "state").fset is not None:
352
+ # Modern case where a stream manages state using getter/setter
353
+ return True
354
+ else:
355
+ # Legacy case where the CDK manages state via the get_updated_state() method. This is determined by checking if
356
+ # the stream's get_updated_state() differs from the Stream class and therefore has been overridden
357
+ return type(self).get_updated_state != Stream.get_updated_state
358
+
359
+ def _wrapped_cursor_field(self) -> List[str]:
360
+ return [self.cursor_field] if isinstance(self.cursor_field, str) else self.cursor_field
361
+
362
+ @property
363
+ def cursor_field(self) -> Union[str, List[str]]:
364
+ """
365
+ Override to return the default cursor field used by this stream e.g: an API entity might always use created_at as the cursor field.
366
+ :return: The name of the field used as a cursor. If the cursor is nested, return an array consisting of the path to the cursor.
367
+ """
368
+ return []
369
+
370
+ @property
371
+ def namespace(self) -> Optional[str]:
372
+ """
373
+ Override to return the namespace of this stream, e.g. the Postgres schema which this stream will emit records for.
374
+ :return: A string containing the name of the namespace.
375
+ """
376
+ return None
377
+
378
+ @property
379
+ def source_defined_cursor(self) -> bool:
380
+ """
381
+ Return False if the cursor can be configured by the user.
382
+ """
383
+ return True
384
+
385
+ @property
386
+ def exit_on_rate_limit(self) -> bool:
387
+ """Exit on rate limit getter, should return bool value. False if the stream will retry endlessly when rate limited."""
388
+ return self._exit_on_rate_limit
389
+
390
+ @exit_on_rate_limit.setter
391
+ def exit_on_rate_limit(self, value: bool) -> None:
392
+ """Exit on rate limit setter, accept bool value."""
393
+ self._exit_on_rate_limit = value
394
+
395
+ @property
396
+ @abstractmethod
397
+ def primary_key(self) -> Optional[Union[str, List[str], List[List[str]]]]:
398
+ """
399
+ :return: string if single primary key, list of strings if composite primary key, list of list of strings if composite primary key consisting of nested fields.
400
+ If the stream has no primary keys, return None.
401
+ """
402
+
403
+ def stream_slices(
404
+ self,
405
+ *,
406
+ sync_mode: SyncMode,
407
+ cursor_field: Optional[List[str]] = None,
408
+ stream_state: Optional[Mapping[str, Any]] = None,
409
+ ) -> Iterable[Optional[Mapping[str, Any]]]:
410
+ """
411
+ Override to define the slices for this stream. See the stream slicing section of the docs for more information.
412
+
413
+ :param sync_mode:
414
+ :param cursor_field:
415
+ :param stream_state:
416
+ :return:
417
+ """
418
+ yield StreamSlice(partition={}, cursor_slice={})
419
+
420
+ @property
421
+ def state_checkpoint_interval(self) -> Optional[int]:
422
+ """
423
+ Decides how often to checkpoint state (i.e: emit a STATE message). E.g: if this returns a value of 100, then state is persisted after reading
424
+ 100 records, then 200, 300, etc.. A good default value is 1000 although your mileage may vary depending on the underlying data source.
425
+
426
+ Checkpointing a stream avoids re-reading records in the case a sync is failed or cancelled.
427
+
428
+ return None if state should not be checkpointed e.g: because records returned from the underlying data source are not returned in
429
+ ascending order with respect to the cursor field. This can happen if the source does not support reading records in ascending order of
430
+ created_at date (or whatever the cursor is). In those cases, state must only be saved once the full stream has been read.
431
+ """
432
+ return None
433
+
434
+ # Commented-out to avoid any runtime penalty, since this is used in a hot per-record codepath.
435
+ # To be evaluated for re-introduction here: https://github.com/airbytehq/airbyte-python-cdk/issues/116
436
+ # @deprecated(
437
+ # "Deprecated method `get_updated_state` as of CDK version 0.1.49. "
438
+ # "Please use explicit state property instead, see `IncrementalMixin` docs."
439
+ # )
440
+ def get_updated_state(
441
+ self, current_stream_state: MutableMapping[str, Any], latest_record: Mapping[str, Any]
442
+ ) -> MutableMapping[str, Any]:
443
+ """DEPRECATED. Please use explicit state property instead, see `IncrementalMixin` docs.
444
+
445
+ Override to extract state from the latest record. Needed to implement incremental sync.
446
+
447
+ Inspects the latest record extracted from the data source and the current state object and return an updated state object.
448
+
449
+ For example: if the state object is based on created_at timestamp, and the current state is {'created_at': 10}, and the latest_record is
450
+ {'name': 'octavia', 'created_at': 20 } then this method would return {'created_at': 20} to indicate state should be updated to this object.
451
+
452
+ :param current_stream_state: The stream's current state object
453
+ :param latest_record: The latest record extracted from the stream
454
+ :return: An updated state object
455
+ """
456
+ return {}
457
+
458
+ def get_cursor(self) -> Optional[Cursor]:
459
+ """
460
+ A Cursor is an interface that a stream can implement to manage how its internal state is read and updated while
461
+ reading records. Historically, Python connectors had no concept of a cursor to manage state. Python streams need
462
+ to define a cursor implementation and override this method to manage state through a Cursor.
463
+ """
464
+ return self.cursor
465
+
466
+ def _get_checkpoint_reader(
467
+ self,
468
+ logger: logging.Logger,
469
+ cursor_field: Optional[List[str]],
470
+ sync_mode: SyncMode,
471
+ stream_state: MutableMapping[str, Any],
472
+ ) -> CheckpointReader:
473
+ mappings_or_slices = self.stream_slices(
474
+ cursor_field=cursor_field,
475
+ sync_mode=sync_mode, # todo: change this interface to no longer rely on sync_mode for behavior
476
+ stream_state=stream_state,
477
+ )
478
+
479
+ # Because of poor foresight, we wrote the default Stream.stream_slices() method to return [None] which is confusing and
480
+ # has now normalized this behavior for connector developers. Now some connectors return [None]. This is objectively
481
+ # misleading and a more ideal interface is [{}] to indicate we still want to iterate over one slice, but with no
482
+ # specific slice values. None is bad, and now I feel bad that I have to write this hack.
483
+ if mappings_or_slices == [None]:
484
+ mappings_or_slices = [{}]
485
+
486
+ slices_iterable_copy, iterable_for_detecting_format = itertools.tee(mappings_or_slices, 2)
487
+ stream_classification = self._classify_stream(
488
+ mappings_or_slices=iterable_for_detecting_format
489
+ )
490
+
491
+ # Streams that override has_multiple_slices are explicitly indicating that they will iterate over
492
+ # multiple partitions. Inspecting slices to automatically apply the correct cursor is only needed as
493
+ # a backup. So if this value was already assigned to True by the stream, we don't need to reassign it
494
+ self.has_multiple_slices = (
495
+ self.has_multiple_slices or stream_classification.has_multiple_slices
496
+ )
497
+
498
+ cursor = self.get_cursor()
499
+ if cursor:
500
+ cursor.set_initial_state(stream_state=stream_state)
501
+
502
+ checkpoint_mode = self._checkpoint_mode
503
+
504
+ if cursor and stream_classification.is_legacy_format:
505
+ return LegacyCursorBasedCheckpointReader(
506
+ stream_slices=slices_iterable_copy, cursor=cursor, read_state_from_cursor=True
507
+ )
508
+ elif cursor:
509
+ return CursorBasedCheckpointReader(
510
+ stream_slices=slices_iterable_copy,
511
+ cursor=cursor,
512
+ read_state_from_cursor=checkpoint_mode == CheckpointMode.RESUMABLE_FULL_REFRESH,
513
+ )
514
+ elif checkpoint_mode == CheckpointMode.RESUMABLE_FULL_REFRESH:
515
+ # Resumable full refresh readers rely on the stream state dynamically being updated during pagination and does
516
+ # not iterate over a static set of slices.
517
+ return ResumableFullRefreshCheckpointReader(stream_state=stream_state)
518
+ elif checkpoint_mode == CheckpointMode.INCREMENTAL:
519
+ return IncrementalCheckpointReader(
520
+ stream_slices=slices_iterable_copy, stream_state=stream_state
521
+ )
522
+ else:
523
+ return FullRefreshCheckpointReader(stream_slices=slices_iterable_copy)
524
+
525
+ @property
526
+ def _checkpoint_mode(self) -> CheckpointMode:
527
+ if self.is_resumable and len(self._wrapped_cursor_field()) > 0:
528
+ return CheckpointMode.INCREMENTAL
529
+ elif self.is_resumable:
530
+ return CheckpointMode.RESUMABLE_FULL_REFRESH
531
+ else:
532
+ return CheckpointMode.FULL_REFRESH
533
+
534
+ @staticmethod
535
+ def _classify_stream(
536
+ mappings_or_slices: Iterator[Optional[Union[Mapping[str, Any], StreamSlice]]],
537
+ ) -> StreamClassification:
538
+ """
539
+ This is a bit of a crazy solution, but also the only way we can detect certain attributes about the stream since Python
540
+ streams do not follow consistent implementation patterns. We care about the following two attributes:
541
+ - is_substream: Helps to incrementally release changes since substreams w/ parents are much more complicated. Also
542
+ helps de-risk the release of changes that might impact all connectors
543
+ - uses_legacy_slice_format: Since the checkpoint reader must manage a complex state object, we opted to have it always
544
+ use the structured StreamSlice object. However, this requires backwards compatibility with Python sources that only
545
+ support the legacy mapping object
546
+
547
+ Both attributes can eventually be deprecated once stream's define this method deleted once substreams have been implemented and
548
+ legacy connectors all adhere to the StreamSlice object.
549
+ """
550
+ if not mappings_or_slices:
551
+ raise ValueError("A stream should always have at least one slice")
552
+ try:
553
+ next_slice = next(mappings_or_slices)
554
+ if isinstance(next_slice, StreamSlice) and next_slice == StreamSlice(
555
+ partition={}, cursor_slice={}
556
+ ):
557
+ is_legacy_format = False
558
+ slice_has_value = False
559
+ elif next_slice == {}:
560
+ is_legacy_format = True
561
+ slice_has_value = False
562
+ elif isinstance(next_slice, StreamSlice):
563
+ is_legacy_format = False
564
+ slice_has_value = True
565
+ else:
566
+ is_legacy_format = True
567
+ slice_has_value = True
568
+ except StopIteration:
569
+ # If the stream has no slices, the format ultimately does not matter since no data will get synced. This is technically
570
+ # a valid case because it is up to the stream to define its slicing behavior
571
+ return StreamClassification(is_legacy_format=False, has_multiple_slices=False)
572
+
573
+ if slice_has_value:
574
+ # If the first slice contained a partition value from the result of stream_slices(), this is a substream that might
575
+ # have multiple parent records to iterate over
576
+ return StreamClassification(
577
+ is_legacy_format=is_legacy_format, has_multiple_slices=slice_has_value
578
+ )
579
+
580
+ try:
581
+ # If stream_slices() returns multiple slices, this is also a substream that can potentially generate empty slices
582
+ next(mappings_or_slices)
583
+ return StreamClassification(is_legacy_format=is_legacy_format, has_multiple_slices=True)
584
+ except StopIteration:
585
+ # If the result of stream_slices() only returns a single empty stream slice, then we know this is a regular stream
586
+ return StreamClassification(
587
+ is_legacy_format=is_legacy_format, has_multiple_slices=False
588
+ )
589
+
590
+ def log_stream_sync_configuration(self) -> None:
591
+ """
592
+ Logs the configuration of this stream.
593
+ """
594
+ self.logger.debug(
595
+ f"Syncing stream instance: {self.name}",
596
+ extra={
597
+ "primary_key": self.primary_key,
598
+ "cursor_field": self.cursor_field,
599
+ },
600
+ )
601
+
602
+ @staticmethod
603
+ def _wrapped_primary_key(
604
+ keys: Optional[Union[str, List[str], List[List[str]]]],
605
+ ) -> Optional[List[List[str]]]:
606
+ """
607
+ :return: wrap the primary_key property in a list of list of strings required by the Airbyte Stream object.
608
+ """
609
+ if not keys:
610
+ return None
611
+
612
+ if isinstance(keys, str):
613
+ return [[keys]]
614
+ elif isinstance(keys, list):
615
+ wrapped_keys = []
616
+ for component in keys:
617
+ if isinstance(component, str):
618
+ wrapped_keys.append([component])
619
+ elif isinstance(component, list):
620
+ wrapped_keys.append(component)
621
+ else:
622
+ raise ValueError(f"Element must be either list or str. Got: {type(component)}")
623
+ return wrapped_keys
624
+ else:
625
+ raise ValueError(f"Element must be either list or str. Got: {type(keys)}")
626
+
627
+ def _observe_state(
628
+ self, checkpoint_reader: CheckpointReader, stream_state: Optional[Mapping[str, Any]] = None
629
+ ) -> None:
630
+ """
631
+ Convenience method that attempts to read the Stream's state using the recommended way of connector's managing their
632
+ own state via state setter/getter. But if we get back an AttributeError, then the legacy Stream.get_updated_state()
633
+ method is used as a fallback method.
634
+ """
635
+
636
+ # This is an inversion of the original logic that used to try state getter/setters first. As part of the work to
637
+ # automatically apply resumable full refresh to all streams, all HttpStream classes implement default state
638
+ # getter/setter methods, we should default to only using the incoming stream_state parameter value is {} which
639
+ # indicates the stream does not override the default get_updated_state() implementation. When the default method
640
+ # is not overridden, then the stream defers to self.state getter
641
+ if stream_state:
642
+ checkpoint_reader.observe(stream_state)
643
+ elif type(self).get_updated_state == Stream.get_updated_state:
644
+ # We only default to the state getter/setter if the stream does not use the legacy get_updated_state() method
645
+ try:
646
+ new_state = self.state # type: ignore # This will always exist on HttpStreams, but may not for Stream
647
+ if new_state:
648
+ checkpoint_reader.observe(new_state)
649
+ except AttributeError:
650
+ pass
651
+
652
+ def _checkpoint_state( # type: ignore # ignoring typing for ConnectorStateManager because of circular dependencies
653
+ self,
654
+ stream_state: Mapping[str, Any],
655
+ state_manager,
656
+ ) -> AirbyteMessage:
657
+ # todo: This can be consolidated into one ConnectorStateManager.update_and_create_state_message() method, but I want
658
+ # to reduce changes right now and this would span concurrent as well
659
+ state_manager.update_state_for_stream(self.name, self.namespace, stream_state)
660
+ return state_manager.create_state_message(self.name, self.namespace) # type: ignore [no-any-return]
661
+
662
+ @property
663
+ def configured_json_schema(self) -> Optional[Dict[str, Any]]:
664
+ """
665
+ This property is set from the read method.
666
+
667
+ :return Optional[Dict]: JSON schema from configured catalog if provided, otherwise None.
668
+ """
669
+ return self._configured_json_schema
670
+
671
+ @configured_json_schema.setter
672
+ def configured_json_schema(self, json_schema: Dict[str, Any]) -> None:
673
+ self._configured_json_schema = self._filter_schema_invalid_properties(json_schema)
674
+
675
+ def _filter_schema_invalid_properties(
676
+ self, configured_catalog_json_schema: Dict[str, Any]
677
+ ) -> Dict[str, Any]:
678
+ """
679
+ Filters the properties in json_schema that are not present in the stream schema.
680
+ Configured Schemas can have very old fields, so we need to housekeeping ourselves.
681
+ """
682
+ configured_schema: Any = configured_catalog_json_schema.get("properties", {})
683
+ stream_schema_properties: Any = self.get_json_schema().get("properties", {})
684
+
685
+ configured_keys = configured_schema.keys()
686
+ stream_keys = stream_schema_properties.keys()
687
+ invalid_properties = configured_keys - stream_keys
688
+ if not invalid_properties:
689
+ return configured_catalog_json_schema
690
+
691
+ self.logger.warning(
692
+ f"Stream {self.name}: the following fields are deprecated and cannot be synced. {invalid_properties}. Refresh the connection's source schema to resolve this warning."
693
+ )
694
+
695
+ valid_configured_schema_properties_keys = stream_keys & configured_keys
696
+ valid_configured_schema_properties = {}
697
+
698
+ for configured_schema_property in valid_configured_schema_properties_keys:
699
+ valid_configured_schema_properties[configured_schema_property] = (
700
+ stream_schema_properties[configured_schema_property]
701
+ )
702
+
703
+ return {**configured_catalog_json_schema, "properties": valid_configured_schema_properties}