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