airbyte-cdk 0.39.2__tar.gz → 0.39.4__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (275) hide show
  1. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/PKG-INFO +1 -1
  2. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/connector_builder/message_grouper.py +17 -19
  3. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/connector_builder/models.py +1 -0
  4. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/auth/oauth.py +11 -0
  5. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/declarative_component_schema.yaml +40 -0
  6. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/models/declarative_component_schema.py +32 -0
  7. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +22 -2
  8. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py +30 -46
  9. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk.egg-info/PKG-INFO +1 -1
  10. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/setup.py +1 -1
  11. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/connector_builder/test_connector_builder_handler.py +4 -1
  12. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/connector_builder/test_message_grouper.py +100 -3
  13. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/parsers/test_model_to_component_factory.py +40 -0
  14. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/http/requests_native_auth/test_requests_native_auth.py +20 -22
  15. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/LICENSE.txt +0 -0
  16. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/README.md +0 -0
  17. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/__init__.py +0 -0
  18. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/config_observation.py +0 -0
  19. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/connector.py +0 -0
  20. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/connector_builder/__init__.py +0 -0
  21. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/connector_builder/connector_builder_handler.py +0 -0
  22. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/connector_builder/main.py +0 -0
  23. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/destinations/__init__.py +0 -0
  24. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/destinations/destination.py +0 -0
  25. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/entrypoint.py +0 -0
  26. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/exception_handler.py +0 -0
  27. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/logger.py +0 -0
  28. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/models/__init__.py +0 -0
  29. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/models/airbyte_protocol.py +0 -0
  30. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/models/well_known_types.py +0 -0
  31. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/py.typed +0 -0
  32. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/__init__.py +0 -0
  33. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/abstract_source.py +0 -0
  34. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/config.py +0 -0
  35. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/connector_state_manager.py +0 -0
  36. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/__init__.py +0 -0
  37. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/auth/__init__.py +0 -0
  38. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/auth/declarative_authenticator.py +0 -0
  39. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/auth/token.py +0 -0
  40. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/checks/__init__.py +0 -0
  41. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/checks/check_stream.py +0 -0
  42. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/checks/connection_checker.py +0 -0
  43. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/create_partial.py +0 -0
  44. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/datetime/__init__.py +0 -0
  45. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/datetime/datetime_parser.py +0 -0
  46. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/datetime/min_max_datetime.py +0 -0
  47. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/declarative_source.py +0 -0
  48. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/declarative_stream.py +0 -0
  49. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/decoders/__init__.py +0 -0
  50. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/decoders/decoder.py +0 -0
  51. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/decoders/json_decoder.py +0 -0
  52. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/exceptions.py +0 -0
  53. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/extractors/__init__.py +0 -0
  54. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/extractors/dpath_extractor.py +0 -0
  55. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/extractors/http_selector.py +0 -0
  56. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/extractors/record_extractor.py +0 -0
  57. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/extractors/record_filter.py +0 -0
  58. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/extractors/record_selector.py +0 -0
  59. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/incremental/__init__.py +0 -0
  60. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py +0 -0
  61. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/interpolation/__init__.py +0 -0
  62. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/interpolation/filters.py +0 -0
  63. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/interpolation/interpolated_boolean.py +0 -0
  64. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/interpolation/interpolated_mapping.py +0 -0
  65. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/interpolation/interpolated_nested_mapping.py +0 -0
  66. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/interpolation/interpolated_string.py +0 -0
  67. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/interpolation/interpolation.py +0 -0
  68. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/interpolation/jinja.py +0 -0
  69. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/interpolation/macros.py +0 -0
  70. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/manifest_declarative_source.py +0 -0
  71. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/models/__init__.py +0 -0
  72. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/parsers/__init__.py +0 -0
  73. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/parsers/class_types_registry.py +0 -0
  74. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/parsers/custom_exceptions.py +0 -0
  75. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/parsers/default_implementation_registry.py +0 -0
  76. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py +0 -0
  77. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/parsers/manifest_reference_resolver.py +0 -0
  78. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/partition_routers/__init__.py +0 -0
  79. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py +0 -0
  80. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/partition_routers/single_partition_router.py +0 -0
  81. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py +0 -0
  82. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/__init__.py +0 -0
  83. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/__init__.py +0 -0
  84. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/__init__.py +0 -0
  85. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/constant_backoff_strategy.py +0 -0
  86. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/exponential_backoff_strategy.py +0 -0
  87. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/header_helper.py +0 -0
  88. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_time_from_header_backoff_strategy.py +0 -0
  89. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/wait_until_time_from_header_backoff_strategy.py +0 -0
  90. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategy.py +0 -0
  91. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/composite_error_handler.py +0 -0
  92. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py +0 -0
  93. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/error_handler.py +0 -0
  94. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py +0 -0
  95. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/response_action.py +0 -0
  96. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/error_handlers/response_status.py +0 -0
  97. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/http_requester.py +0 -0
  98. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/paginators/__init__.py +0 -0
  99. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +0 -0
  100. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py +0 -0
  101. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/paginators/paginator.py +0 -0
  102. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/paginators/strategies/__init__.py +0 -0
  103. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/paginators/strategies/cursor_pagination_strategy.py +0 -0
  104. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/paginators/strategies/offset_increment.py +0 -0
  105. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/paginators/strategies/page_increment.py +0 -0
  106. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/paginators/strategies/pagination_strategy.py +0 -0
  107. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/request_option.py +0 -0
  108. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/request_options/__init__.py +0 -0
  109. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/request_options/interpolated_nested_request_input_provider.py +0 -0
  110. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_input_provider.py +0 -0
  111. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py +0 -0
  112. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/request_options/request_options_provider.py +0 -0
  113. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/request_path.py +0 -0
  114. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/requesters/requester.py +0 -0
  115. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/retrievers/__init__.py +0 -0
  116. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/retrievers/retriever.py +0 -0
  117. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +0 -0
  118. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/schema/__init__.py +0 -0
  119. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/schema/default_schema_loader.py +0 -0
  120. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/schema/inline_schema_loader.py +0 -0
  121. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/schema/json_file_schema_loader.py +0 -0
  122. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/schema/schema_loader.py +0 -0
  123. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/spec/__init__.py +0 -0
  124. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/spec/spec.py +0 -0
  125. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/stream_slicers/__init__.py +0 -0
  126. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/stream_slicers/cartesian_product_stream_slicer.py +0 -0
  127. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/stream_slicers/stream_slicer.py +0 -0
  128. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/transformations/__init__.py +0 -0
  129. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/transformations/add_fields.py +0 -0
  130. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/transformations/remove_fields.py +0 -0
  131. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/transformations/transformation.py +0 -0
  132. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/types.py +0 -0
  133. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/declarative/yaml_declarative_source.py +0 -0
  134. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/deprecated/__init__.py +0 -0
  135. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/deprecated/base_source.py +0 -0
  136. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/deprecated/client.py +0 -0
  137. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/singer/__init__.py +0 -0
  138. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/singer/singer_helpers.py +0 -0
  139. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/singer/source.py +0 -0
  140. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/source.py +0 -0
  141. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/__init__.py +0 -0
  142. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/availability_strategy.py +0 -0
  143. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/core.py +0 -0
  144. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/__init__.py +0 -0
  145. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/auth/__init__.py +0 -0
  146. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/auth/core.py +0 -0
  147. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/auth/oauth.py +0 -0
  148. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/auth/token.py +0 -0
  149. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/availability_strategy.py +0 -0
  150. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/exceptions.py +0 -0
  151. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/http.py +0 -0
  152. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/rate_limiting.py +0 -0
  153. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/requests_native_auth/__init__.py +0 -0
  154. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +0 -0
  155. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/requests_native_auth/abstract_token.py +0 -0
  156. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/http/requests_native_auth/token.py +0 -0
  157. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/utils/__init__.py +0 -0
  158. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/streams/utils/stream_helper.py +0 -0
  159. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/utils/__init__.py +0 -0
  160. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/utils/casing.py +0 -0
  161. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/utils/catalog_helpers.py +0 -0
  162. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/utils/record_helper.py +0 -0
  163. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/utils/schema_helpers.py +0 -0
  164. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/utils/schema_models.py +0 -0
  165. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/sources/utils/transform.py +0 -0
  166. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/utils/__init__.py +0 -0
  167. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/utils/airbyte_secrets_utils.py +0 -0
  168. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/utils/event_timing.py +0 -0
  169. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/utils/schema_inferrer.py +0 -0
  170. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/utils/stream_status_utils.py +0 -0
  171. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk/utils/traced_exception.py +0 -0
  172. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk.egg-info/SOURCES.txt +0 -0
  173. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk.egg-info/dependency_links.txt +0 -0
  174. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk.egg-info/requires.txt +0 -0
  175. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/airbyte_cdk.egg-info/top_level.txt +0 -0
  176. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/pyproject.toml +0 -0
  177. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/setup.cfg +0 -0
  178. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/source_declarative_manifest/__init__.py +0 -0
  179. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/source_declarative_manifest/main.py +0 -0
  180. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/connector_builder/__init__.py +0 -0
  181. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/connector_builder/utils.py +0 -0
  182. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/destinations/__init__.py +0 -0
  183. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/destinations/test_destination.py +0 -0
  184. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/singer/__init__.py +0 -0
  185. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/singer/test_singer_helpers.py +0 -0
  186. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/singer/test_singer_source.py +0 -0
  187. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/__init__.py +0 -0
  188. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/__init__.py +0 -0
  189. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/auth/__init__.py +0 -0
  190. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/auth/test_oauth.py +0 -0
  191. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/auth/test_session_token_auth.py +0 -0
  192. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/auth/test_token_auth.py +0 -0
  193. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/checks/__init__.py +0 -0
  194. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/checks/test_check_stream.py +0 -0
  195. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/decoders/__init__.py +0 -0
  196. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/decoders/test_json_decoder.py +0 -0
  197. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/external_component.py +0 -0
  198. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/extractors/__init__.py +0 -0
  199. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/extractors/test_dpath_extractor.py +0 -0
  200. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/extractors/test_record_filter.py +0 -0
  201. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/extractors/test_record_selector.py +0 -0
  202. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/incremental/__init__.py +0 -0
  203. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/incremental/test_datetime_based_cursor.py +0 -0
  204. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/interpolation/__init__.py +0 -0
  205. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/interpolation/test_filters.py +0 -0
  206. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/interpolation/test_interpolated_boolean.py +0 -0
  207. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/interpolation/test_interpolated_mapping.py +0 -0
  208. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/interpolation/test_interpolated_nested_mapping.py +0 -0
  209. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/interpolation/test_interpolated_string.py +0 -0
  210. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/interpolation/test_jinja.py +0 -0
  211. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/interpolation/test_macros.py +0 -0
  212. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/parsers/__init__.py +0 -0
  213. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/parsers/test_manifest_component_transformer.py +0 -0
  214. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/parsers/test_manifest_reference_resolver.py +0 -0
  215. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/parsers/testing_components.py +0 -0
  216. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/partition_routers/__init__.py +0 -0
  217. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/partition_routers/test_list_partition_router.py +0 -0
  218. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/partition_routers/test_single_partition_router.py +0 -0
  219. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/partition_routers/test_substream_partition_router.py +0 -0
  220. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/__init__.py +0 -0
  221. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/__init__.py +0 -0
  222. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/__init__.py +0 -0
  223. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_constant_backoff.py +0 -0
  224. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_exponential_backoff.py +0 -0
  225. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_header_helper.py +0 -0
  226. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_wait_time_from_header.py +0 -0
  227. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/backoff_strategies/test_wait_until_time_from_header.py +0 -0
  228. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/test_composite_error_handler.py +0 -0
  229. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/test_default_error_handler.py +0 -0
  230. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/test_http_response_filter.py +0 -0
  231. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/error_handlers/test_response_status.py +0 -0
  232. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/paginators/__init__.py +0 -0
  233. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/paginators/test_cursor_pagination_strategy.py +0 -0
  234. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/paginators/test_default_paginator.py +0 -0
  235. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/paginators/test_no_paginator.py +0 -0
  236. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/paginators/test_offset_increment.py +0 -0
  237. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/paginators/test_page_increment.py +0 -0
  238. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/paginators/test_request_option.py +0 -0
  239. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/request_options/__init__.py +0 -0
  240. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/request_options/test_interpolated_request_options_provider.py +0 -0
  241. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/test_http_requester.py +0 -0
  242. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/requesters/test_interpolated_request_input_provider.py +0 -0
  243. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/retrievers/__init__.py +0 -0
  244. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/retrievers/test_simple_retriever.py +0 -0
  245. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/schema/__init__.py +0 -0
  246. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/schema/source_test/SourceTest.py +0 -0
  247. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/schema/source_test/__init__.py +0 -0
  248. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/schema/test_default_schema_loader.py +0 -0
  249. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/schema/test_inline_schema_loader.py +0 -0
  250. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/schema/test_json_file_schema_loader.py +0 -0
  251. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/states/__init__.py +0 -0
  252. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/stream_slicers/__init__.py +0 -0
  253. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/stream_slicers/test_cartesian_product_stream_slicer.py +0 -0
  254. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/test_create_partial.py +0 -0
  255. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/test_declarative_stream.py +0 -0
  256. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/test_manifest_declarative_source.py +0 -0
  257. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/declarative/test_yaml_declarative_source.py +0 -0
  258. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/__init__.py +0 -0
  259. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/http/__init__.py +0 -0
  260. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/http/auth/__init__.py +0 -0
  261. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/http/auth/test_auth.py +0 -0
  262. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/http/requests_native_auth/__init__.py +0 -0
  263. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/http/test_availability_strategy.py +0 -0
  264. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/http/test_http.py +0 -0
  265. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/test_availability_strategy.py +0 -0
  266. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/streams/test_streams_core.py +0 -0
  267. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/test_abstract_source.py +0 -0
  268. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/test_config.py +0 -0
  269. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/test_connector_state_manager.py +0 -0
  270. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/sources/test_source.py +0 -0
  271. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/utils/__init__.py +0 -0
  272. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/utils/test_schema_inferrer.py +0 -0
  273. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/utils/test_secret_utils.py +0 -0
  274. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/utils/test_stream_status_utils.py +0 -0
  275. {airbyte-cdk-0.39.2 → airbyte-cdk-0.39.4}/unit_tests/utils/test_traced_exception.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: airbyte-cdk
3
- Version: 0.39.2
3
+ Version: 0.39.4
4
4
  Summary: A framework for writing Airbyte Connectors.
5
5
  Home-page: https://github.com/airbytehq/airbyte
6
6
  Author: Airbyte
@@ -16,11 +16,13 @@ from airbyte_cdk.sources.declarative.declarative_source import DeclarativeSource
16
16
  from airbyte_cdk.utils import AirbyteTracedException
17
17
  from airbyte_cdk.utils.schema_inferrer import SchemaInferrer
18
18
  from airbyte_protocol.models.airbyte_protocol import (
19
+ AirbyteControlMessage,
19
20
  AirbyteLogMessage,
20
21
  AirbyteMessage,
21
22
  AirbyteTraceMessage,
22
23
  ConfiguredAirbyteCatalog,
23
24
  Level,
25
+ OrchestratorType,
24
26
  TraceType,
25
27
  )
26
28
  from airbyte_protocol.models.airbyte_protocol import Type as MessageType
@@ -52,6 +54,7 @@ class MessageGrouper:
52
54
 
53
55
  slices = []
54
56
  log_messages = []
57
+ latest_config_update: AirbyteControlMessage = None
55
58
  for message_group in self._get_message_groups(
56
59
  self._read_stream(source, config, configured_catalog),
57
60
  schema_inferrer,
@@ -63,7 +66,9 @@ class MessageGrouper:
63
66
  if message_group.type == TraceType.ERROR:
64
67
  error_message = f"{message_group.error.message} - {message_group.error.stack_trace}"
65
68
  log_messages.append(LogMessage(**{"message": error_message, "level": "ERROR"}))
66
-
69
+ elif isinstance(message_group, AirbyteControlMessage):
70
+ if not latest_config_update or latest_config_update.emitted_at <= message_group.emitted_at:
71
+ latest_config_update = message_group
67
72
  else:
68
73
  slices.append(message_group)
69
74
 
@@ -74,11 +79,12 @@ class MessageGrouper:
74
79
  inferred_schema=schema_inferrer.get_stream_schema(
75
80
  configured_catalog.streams[0].stream.name
76
81
  ), # The connector builder currently only supports reading from a single stream at a time
82
+ latest_config_update=latest_config_update.connectorConfig.config if latest_config_update else self._clean_config(config),
77
83
  )
78
84
 
79
85
  def _get_message_groups(
80
86
  self, messages: Iterator[AirbyteMessage], schema_inferrer: SchemaInferrer, limit: int
81
- ) -> Iterable[Union[StreamReadPages, AirbyteLogMessage, AirbyteTraceMessage]]:
87
+ ) -> Iterable[Union[StreamReadPages, AirbyteControlMessage, AirbyteLogMessage, AirbyteTraceMessage]]:
82
88
  """
83
89
  Message groups are partitioned according to when request log messages are received. Subsequent response log messages
84
90
  and record messages belong to the prior request log message and when we encounter another request, append the latest
@@ -135,6 +141,8 @@ class MessageGrouper:
135
141
  current_page_records.append(message.record.data)
136
142
  records_count += 1
137
143
  schema_inferrer.accumulate(message.record)
144
+ elif message.type == MessageType.CONTROL and message.control.type == OrchestratorType.CONNECTOR_CONFIG:
145
+ yield message.control
138
146
  else:
139
147
  self._close_page(current_page_request, current_page_response, current_slice_pages, current_page_records, validate_page_complete=not had_error)
140
148
  yield StreamReadSlices(pages=current_slice_pages, slice_descriptor=current_slice_descriptor)
@@ -217,20 +225,10 @@ class MessageGrouper:
217
225
  def _parse_slice_description(self, log_message):
218
226
  return json.loads(log_message.replace(AbstractSource.SLICE_LOG_PREFIX, "", 1))
219
227
 
220
- @classmethod
221
- def _create_configure_catalog(cls, stream_name: str) -> ConfiguredAirbyteCatalog:
222
- return ConfiguredAirbyteCatalog.parse_obj(
223
- {
224
- "streams": [
225
- {
226
- "stream": {
227
- "name": stream_name,
228
- "json_schema": {},
229
- "supported_sync_modes": ["full_refresh", "incremental"],
230
- },
231
- "sync_mode": "full_refresh",
232
- "destination_sync_mode": "overwrite",
233
- }
234
- ]
235
- }
236
- )
228
+ @staticmethod
229
+ def _clean_config(config: Mapping[str, Any]):
230
+ cleaned_config = deepcopy(config)
231
+ for key in config.keys():
232
+ if key.startswith("__"):
233
+ del cleaned_config[key]
234
+ return cleaned_config
@@ -48,6 +48,7 @@ class StreamRead(object):
48
48
  slices: List[StreamReadSlices]
49
49
  test_read_limit_reached: bool
50
50
  inferred_schema: Optional[Dict[str, Any]]
51
+ latest_config_update: Optional[Dict[str, Any]]
51
52
 
52
53
 
53
54
  @dataclass
@@ -10,6 +10,7 @@ from airbyte_cdk.sources.declarative.auth.declarative_authenticator import Decla
10
10
  from airbyte_cdk.sources.declarative.interpolation.interpolated_mapping import InterpolatedMapping
11
11
  from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
12
12
  from airbyte_cdk.sources.streams.http.requests_native_auth.abstract_oauth import AbstractOauth2Authenticator
13
+ from airbyte_cdk.sources.streams.http.requests_native_auth.oauth import SingleUseRefreshTokenOauth2Authenticator
13
14
 
14
15
 
15
16
  @dataclass
@@ -133,3 +134,13 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
133
134
  @access_token.setter
134
135
  def access_token(self, value: str):
135
136
  self._access_token = value
137
+
138
+
139
+ @dataclass
140
+ class DeclarativeSingleUseRefreshTokenOauth2Authenticator(SingleUseRefreshTokenOauth2Authenticator, DeclarativeAuthenticator):
141
+ """
142
+ Declarative version of SingleUseRefreshTokenOauth2Authenticator which can be used in declarative connectors.
143
+ """
144
+
145
+ def __init__(self, *args, **kwargs):
146
+ super().__init__(*args, **kwargs)
@@ -733,6 +733,46 @@ definitions:
733
733
  type: string
734
734
  examples:
735
735
  - "%Y-%m-%d %H:%M:%S.%f+00:00"
736
+ refresh_token_updater:
737
+ title: Token Updater
738
+ description: When the token updater is defined, new refresh tokens, access tokens and the access token expiry date are written back from the authentication response to the config object. This is important if the refresh token can only used once.
739
+ properties:
740
+ refresh_token_name:
741
+ title: Refresh Token Property Name
742
+ description: The name of the property which contains the updated refresh token in the response from the token refresh endpoint.
743
+ type: string
744
+ default: "refresh_token"
745
+ examples:
746
+ - "refresh_token"
747
+ access_token_config_path:
748
+ title: Config Path To Access Token
749
+ description: Config path to the access token. Make sure the field actually exists in the config.
750
+ type: array
751
+ items:
752
+ type: string
753
+ default: ["credentials", "access_token"]
754
+ examples:
755
+ - ["credentials", "access_token"]
756
+ - ["access_token"]
757
+ refresh_token_config_path:
758
+ title: Config Path To Refresh Token
759
+ description: Config path to the access token. Make sure the field actually exists in the config.
760
+ type: array
761
+ items:
762
+ type: string
763
+ default: ["credentials", "refresh_token"]
764
+ examples:
765
+ - ["credentials", "refresh_token"]
766
+ - ["refresh_token"]
767
+ token_expiry_date_config_path:
768
+ title: Config Path To Expiry Date
769
+ description: Config path to the expiry date. Make sure actually exists in the config.
770
+ type: array
771
+ items:
772
+ type: string
773
+ default: ["credentials", "token_expiry_date"]
774
+ examples:
775
+ - ["credentials", "token_expiry_date"]
736
776
  $parameters:
737
777
  type: object
738
778
  additionalProperties: true
@@ -260,6 +260,33 @@ class CustomTransformation(BaseModel):
260
260
  parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters")
261
261
 
262
262
 
263
+ class RefreshTokenUpdater(BaseModel):
264
+ refresh_token_name: Optional[str] = Field(
265
+ "refresh_token",
266
+ description="The name of the property which contains the updated refresh token in the response from the token refresh endpoint.",
267
+ examples=["refresh_token"],
268
+ title="Refresh Token Property Name",
269
+ )
270
+ access_token_config_path: Optional[List[str]] = Field(
271
+ ["credentials", "access_token"],
272
+ description="Config path to the access token. Make sure the field actually exists in the config.",
273
+ examples=[["credentials", "access_token"], ["access_token"]],
274
+ title="Config Path To Access Token",
275
+ )
276
+ refresh_token_config_path: Optional[List[str]] = Field(
277
+ ["credentials", "refresh_token"],
278
+ description="Config path to the access token. Make sure the field actually exists in the config.",
279
+ examples=[["credentials", "refresh_token"], ["refresh_token"]],
280
+ title="Config Path To Refresh Token",
281
+ )
282
+ token_expiry_date_config_path: Optional[List[str]] = Field(
283
+ ["credentials", "token_expiry_date"],
284
+ description="Config path to the expiry date. Make sure actually exists in the config.",
285
+ examples=[["credentials", "token_expiry_date"]],
286
+ title="Config Path To Expiry Date",
287
+ )
288
+
289
+
263
290
  class OAuthAuthenticator(BaseModel):
264
291
  type: Literal["OAuthAuthenticator"]
265
292
  client_id: str = Field(
@@ -340,6 +367,11 @@ class OAuthAuthenticator(BaseModel):
340
367
  examples=["%Y-%m-%d %H:%M:%S.%f+00:00"],
341
368
  title="Token Expiry Date Format",
342
369
  )
370
+ refresh_token_updater: Optional[RefreshTokenUpdater] = Field(
371
+ None,
372
+ description="When the token updater is defined, new refresh tokens, access tokens and the access token expiry date are written back from the authentication response to the config object. This is important if the refresh token can only used once.",
373
+ title="Token Updater",
374
+ )
343
375
  parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters")
344
376
 
345
377
 
@@ -9,8 +9,10 @@ import inspect
9
9
  import re
10
10
  from typing import Any, Callable, List, Literal, Mapping, Optional, Type, Union, get_args, get_origin, get_type_hints
11
11
 
12
+ import dpath
12
13
  from airbyte_cdk.sources.declarative.auth import DeclarativeOauth2Authenticator
13
14
  from airbyte_cdk.sources.declarative.auth.declarative_authenticator import NoAuth
15
+ from airbyte_cdk.sources.declarative.auth.oauth import DeclarativeSingleUseRefreshTokenOauth2Authenticator
14
16
  from airbyte_cdk.sources.declarative.auth.token import (
15
17
  ApiKeyAuthenticator,
16
18
  BasicHttpAuthenticator,
@@ -24,6 +26,7 @@ from airbyte_cdk.sources.declarative.decoders import JsonDecoder
24
26
  from airbyte_cdk.sources.declarative.extractors import DpathExtractor, RecordFilter, RecordSelector
25
27
  from airbyte_cdk.sources.declarative.incremental import DatetimeBasedCursor
26
28
  from airbyte_cdk.sources.declarative.interpolation import InterpolatedString
29
+ from airbyte_cdk.sources.declarative.interpolation.interpolated_mapping import InterpolatedMapping
27
30
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import AddedFieldDefinition as AddedFieldDefinitionModel
28
31
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import AddFields as AddFieldsModel
29
32
  from airbyte_cdk.sources.declarative.models.declarative_component_schema import ApiKeyAuthenticator as ApiKeyAuthenticatorModel
@@ -659,6 +662,23 @@ class ModelToComponentFactory:
659
662
 
660
663
  @staticmethod
661
664
  def create_oauth_authenticator(model: OAuthAuthenticatorModel, config: Config, **kwargs) -> DeclarativeOauth2Authenticator:
665
+ if model.refresh_token_updater:
666
+ return DeclarativeSingleUseRefreshTokenOauth2Authenticator(
667
+ config,
668
+ InterpolatedString.create(model.token_refresh_endpoint, parameters=model.parameters).eval(config),
669
+ access_token_name=InterpolatedString.create(model.access_token_name, parameters=model.parameters).eval(config),
670
+ refresh_token_name=model.refresh_token_updater.refresh_token_name,
671
+ expires_in_name=InterpolatedString.create(model.expires_in_name, parameters=model.parameters).eval(config),
672
+ client_id=InterpolatedString.create(model.client_id, parameters=model.parameters).eval(config),
673
+ client_secret=InterpolatedString.create(model.client_secret, parameters=model.parameters).eval(config),
674
+ access_token_config_path=model.refresh_token_updater.access_token_config_path,
675
+ refresh_token_config_path=model.refresh_token_updater.refresh_token_config_path,
676
+ token_expiry_date_config_path=model.refresh_token_updater.token_expiry_date_config_path,
677
+ grant_type=InterpolatedString.create(model.grant_type, parameters=model.parameters).eval(config),
678
+ refresh_request_body=InterpolatedMapping(model.refresh_request_body or {}, parameters=model.parameters).eval(config),
679
+ scopes=model.scopes,
680
+ token_expiry_date_format=model.token_expiry_date_format,
681
+ )
662
682
  return DeclarativeOauth2Authenticator(
663
683
  access_token_name=model.access_token_name,
664
684
  client_id=model.client_id,
@@ -685,8 +705,8 @@ class ModelToComponentFactory:
685
705
  access_token_name=model.access_token_name,
686
706
  refresh_token_name=model.refresh_token_name,
687
707
  expires_in_name=model.expires_in_name,
688
- client_id_config_path=model.client_id_config_path,
689
- client_secret_config_path=model.client_secret_config_path,
708
+ client_id=dpath.util.get(config, model.client_id_config_path),
709
+ client_secret=dpath.util.get(config, model.client_secret_config_path),
690
710
  access_token_config_path=model.access_token_config_path,
691
711
  refresh_token_config_path=model.refresh_token_config_path,
692
712
  token_expiry_date_config_path=model.token_expiry_date_config_path,
@@ -2,7 +2,7 @@
2
2
  # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
3
  #
4
4
 
5
- from typing import Any, List, Mapping, Sequence, Tuple, Union
5
+ from typing import Any, List, Mapping, Optional, Sequence, Tuple, Union
6
6
 
7
7
  import dpath
8
8
  import pendulum
@@ -109,11 +109,12 @@ class SingleUseRefreshTokenOauth2Authenticator(Oauth2Authenticator):
109
109
  refresh_token_name: str = "refresh_token",
110
110
  refresh_request_body: Mapping[str, Any] = None,
111
111
  grant_type: str = "refresh_token",
112
- client_id_config_path: Sequence[str] = ("credentials", "client_id"),
113
- client_secret_config_path: Sequence[str] = ("credentials", "client_secret"),
112
+ client_id: Optional[str] = None,
113
+ client_secret: Optional[str] = None,
114
114
  access_token_config_path: Sequence[str] = ("credentials", "access_token"),
115
115
  refresh_token_config_path: Sequence[str] = ("credentials", "refresh_token"),
116
116
  token_expiry_date_config_path: Sequence[str] = ("credentials", "token_expiry_date"),
117
+ token_expiry_date_format: Optional[str] = None,
117
118
  ):
118
119
  """
119
120
 
@@ -126,20 +127,23 @@ class SingleUseRefreshTokenOauth2Authenticator(Oauth2Authenticator):
126
127
  refresh_token_name (str, optional): Name of the name of the refresh token field, used to parse the refresh token response. Defaults to "refresh_token".
127
128
  refresh_request_body (Mapping[str, Any], optional): Custom key value pair that will be added to the refresh token request body. Defaults to None.
128
129
  grant_type (str, optional): OAuth grant type. Defaults to "refresh_token".
129
- client_id_config_path (Sequence[str]): Dpath to the client_id field in the connector configuration. Defaults to ("credentials", "client_id").
130
- client_secret_config_path (Sequence[str]): Dpath to the client_secret field in the connector configuration. Defaults to ("credentials", "client_secret").
130
+ client_id (Optional[str]): The client id to authenticate. If not specified, defaults to credentials.client_id in the config object.
131
+ client_secret (Optional[str]): The client secret to authenticate. If not specified, defaults to credentials.client_secret in the config object.
131
132
  access_token_config_path (Sequence[str]): Dpath to the access_token field in the connector configuration. Defaults to ("credentials", "access_token").
132
133
  refresh_token_config_path (Sequence[str]): Dpath to the refresh_token field in the connector configuration. Defaults to ("credentials", "refresh_token").
133
134
  token_expiry_date_config_path (Sequence[str]): Dpath to the token_expiry_date field in the connector configuration. Defaults to ("credentials", "token_expiry_date").
135
+ token_expiry_date_format (Optional[str]): Date format of the token expiry date field (set by expires_in_name). If not specified the token expiry date is interpreted as number of seconds until expiration.
134
136
  """
135
- self._client_id_config_path = client_id_config_path
136
- self._client_secret_config_path = client_secret_config_path
137
+ self._client_id = client_id if client_id is not None else dpath.util.get(connector_config, ("credentials", "client_id"))
138
+ self._client_secret = (
139
+ client_secret if client_secret is not None else dpath.util.get(connector_config, ("credentials", "client_secret"))
140
+ )
137
141
  self._access_token_config_path = access_token_config_path
138
142
  self._refresh_token_config_path = refresh_token_config_path
139
143
  self._token_expiry_date_config_path = token_expiry_date_config_path
144
+ self._token_expiry_date_format = token_expiry_date_format
140
145
  self._refresh_token_name = refresh_token_name
141
146
  self._connector_config = connector_config
142
- self._validate_connector_config()
143
147
  super().__init__(
144
148
  token_refresh_endpoint,
145
149
  self.get_client_id(),
@@ -151,69 +155,49 @@ class SingleUseRefreshTokenOauth2Authenticator(Oauth2Authenticator):
151
155
  expires_in_name=expires_in_name,
152
156
  refresh_request_body=refresh_request_body,
153
157
  grant_type=grant_type,
158
+ token_expiry_date_format=token_expiry_date_format,
154
159
  )
155
160
 
156
- def _validate_connector_config(self):
157
- """Validates the defined getters for configuration values are returning values.
158
-
159
- Raises:
160
- ValueError: Raised if the defined getters are not returning a value.
161
- """
162
- try:
163
- assert self.access_token
164
- except KeyError:
165
- raise ValueError(
166
- f"This authenticator expects a value under the {self._access_token_config_path} field path. Please check your configuration structure or change the access_token_config_path value at initialization of this authenticator."
167
- )
168
- for field_path, getter, parameter_name in [
169
- (self._client_id_config_path, self.get_client_id, "client_id_config_path"),
170
- (self._client_secret_config_path, self.get_client_secret, "client_secret_config_path"),
171
- (self._refresh_token_config_path, self.get_refresh_token, "refresh_token_config_path"),
172
- (self._token_expiry_date_config_path, self.get_token_expiry_date, "token_expiry_date_config_path"),
173
- ]:
174
- try:
175
- assert getter()
176
- except KeyError:
177
- raise ValueError(
178
- f"This authenticator expects a value under the {field_path} field path. Please check your configuration structure or change the {parameter_name} value at initialization of this authenticator."
179
- )
180
-
181
161
  def get_refresh_token_name(self) -> str:
182
162
  return self._refresh_token_name
183
163
 
184
164
  def get_client_id(self) -> str:
185
- return dpath.util.get(self._connector_config, self._client_id_config_path)
165
+ return self._client_id
186
166
 
187
167
  def get_client_secret(self) -> str:
188
- return dpath.util.get(self._connector_config, self._client_secret_config_path)
168
+ return self._client_secret
189
169
 
190
170
  @property
191
171
  def access_token(self) -> str:
192
- return dpath.util.get(self._connector_config, self._access_token_config_path)
172
+ return dpath.util.get(self._connector_config, self._access_token_config_path, default="")
193
173
 
194
174
  @access_token.setter
195
175
  def access_token(self, new_access_token: str):
196
- dpath.util.set(self._connector_config, self._access_token_config_path, new_access_token)
176
+ dpath.util.new(self._connector_config, self._access_token_config_path, new_access_token)
197
177
 
198
178
  def get_refresh_token(self) -> str:
199
- return dpath.util.get(self._connector_config, self._refresh_token_config_path)
179
+ return dpath.util.get(self._connector_config, self._refresh_token_config_path, default="")
200
180
 
201
181
  def set_refresh_token(self, new_refresh_token: str):
202
- dpath.util.set(self._connector_config, self._refresh_token_config_path, new_refresh_token)
182
+ dpath.util.new(self._connector_config, self._refresh_token_config_path, new_refresh_token)
203
183
 
204
184
  def get_token_expiry_date(self) -> pendulum.DateTime:
205
- return pendulum.parse(dpath.util.get(self._connector_config, self._token_expiry_date_config_path))
185
+ expiry_date = dpath.util.get(self._connector_config, self._token_expiry_date_config_path, default="")
186
+ return pendulum.now().subtract(days=1) if expiry_date == "" else pendulum.parse(expiry_date)
206
187
 
207
188
  def set_token_expiry_date(self, new_token_expiry_date):
208
- dpath.util.set(self._connector_config, self._token_expiry_date_config_path, str(new_token_expiry_date))
189
+ dpath.util.new(self._connector_config, self._token_expiry_date_config_path, str(new_token_expiry_date))
209
190
 
210
191
  def token_has_expired(self) -> bool:
211
192
  """Returns True if the token is expired"""
212
193
  return pendulum.now("UTC") > self.get_token_expiry_date()
213
194
 
214
195
  @staticmethod
215
- def get_new_token_expiry_date(access_token_expires_in: int):
216
- return pendulum.now("UTC").add(seconds=access_token_expires_in)
196
+ def get_new_token_expiry_date(access_token_expires_in: str, token_expiry_date_format: str = None) -> pendulum.DateTime:
197
+ if token_expiry_date_format:
198
+ return pendulum.from_format(access_token_expires_in, token_expiry_date_format)
199
+ else:
200
+ return pendulum.now("UTC").add(seconds=int(access_token_expires_in))
217
201
 
218
202
  def get_access_token(self) -> str:
219
203
  """Retrieve new access and refresh token if the access token has expired.
@@ -223,17 +207,17 @@ class SingleUseRefreshTokenOauth2Authenticator(Oauth2Authenticator):
223
207
  """
224
208
  if self.token_has_expired():
225
209
  new_access_token, access_token_expires_in, new_refresh_token = self.refresh_access_token()
226
- new_token_expiry_date = self.get_new_token_expiry_date(access_token_expires_in)
210
+ new_token_expiry_date = self.get_new_token_expiry_date(access_token_expires_in, self._token_expiry_date_format)
227
211
  self.access_token = new_access_token
228
212
  self.set_refresh_token(new_refresh_token)
229
213
  self.set_token_expiry_date(new_token_expiry_date)
230
214
  emit_configuration_as_airbyte_control_message(self._connector_config)
231
215
  return self.access_token
232
216
 
233
- def refresh_access_token(self) -> Tuple[str, int, str]:
217
+ def refresh_access_token(self) -> Tuple[str, str, str]:
234
218
  response_json = self._get_refresh_access_token_response()
235
219
  return (
236
220
  response_json[self.get_access_token_name()],
237
- int(response_json[self.get_expires_in_name()]),
221
+ response_json[self.get_expires_in_name()],
238
222
  response_json[self.get_refresh_token_name()],
239
223
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: airbyte-cdk
3
- Version: 0.39.2
3
+ Version: 0.39.4
4
4
  Summary: A framework for writing Airbyte Connectors.
5
5
  Home-page: https://github.com/airbytehq/airbyte
6
6
  Author: Airbyte
@@ -17,7 +17,7 @@ setup(
17
17
  name="airbyte-cdk",
18
18
  # The version of the airbyte-cdk package is used at runtime to validate manifests. That validation must be
19
19
  # updated if our semver format changes such as using release candidate versions.
20
- version="0.39.2",
20
+ version="0.39.4",
21
21
  description="A framework for writing Airbyte Connectors.",
22
22
  long_description=README,
23
23
  long_description_content_type="text/markdown",
@@ -354,6 +354,7 @@ def test_read():
354
354
  ],
355
355
  test_read_limit_reached=False,
356
356
  inferred_schema=None,
357
+ latest_config_update={}
357
358
  )
358
359
 
359
360
  expected_airbyte_message = AirbyteMessage(
@@ -367,6 +368,7 @@ def test_read():
367
368
  ],
368
369
  "test_read_limit_reached": False,
369
370
  "inferred_schema": None,
371
+ "latest_config_update": {}
370
372
  },
371
373
  emitted_at=1,
372
374
  ),
@@ -407,7 +409,8 @@ def test_read_returns_error_response(mock_from_exception):
407
409
  pages=[StreamReadPages(records=[], request=None, response=None)],
408
410
  slice_descriptor=None, state=None)],
409
411
  test_read_limit_reached=False,
410
- inferred_schema=None)
412
+ inferred_schema=None,
413
+ latest_config_update={})
411
414
 
412
415
  expected_message = AirbyteMessage(
413
416
  type=MessageType.RECORD,
@@ -9,7 +9,15 @@ from unittest.mock import MagicMock, patch
9
9
  import pytest
10
10
  from airbyte_cdk.connector_builder.message_grouper import MessageGrouper
11
11
  from airbyte_cdk.connector_builder.models import HttpRequest, HttpResponse, LogMessage, StreamRead, StreamReadPages
12
- from airbyte_cdk.models import AirbyteLogMessage, AirbyteMessage, AirbyteRecordMessage, Level
12
+ from airbyte_cdk.models import (
13
+ AirbyteControlConnectorConfigMessage,
14
+ AirbyteControlMessage,
15
+ AirbyteLogMessage,
16
+ AirbyteMessage,
17
+ AirbyteRecordMessage,
18
+ Level,
19
+ OrchestratorType,
20
+ )
13
21
  from airbyte_cdk.models import Type as MessageType
14
22
  from unit_tests.connector_builder.utils import create_configured_catalog
15
23
 
@@ -463,9 +471,9 @@ def test_get_grouped_messages_with_many_slices(mock_entrypoint_read):
463
471
  )
464
472
  )
465
473
 
466
- connecto_builder_handler = MessageGrouper(MAX_PAGES_PER_SLICE, MAX_SLICES)
474
+ connector_builder_handler = MessageGrouper(MAX_PAGES_PER_SLICE, MAX_SLICES)
467
475
 
468
- stream_read: StreamRead = connecto_builder_handler.get_message_groups(
476
+ stream_read: StreamRead = connector_builder_handler.get_message_groups(
469
477
  source=mock_source, config=CONFIG, configured_catalog=create_configured_catalog("hashiras")
470
478
  )
471
479
 
@@ -530,6 +538,76 @@ def test_read_stream_returns_error_if_stream_does_not_exist():
530
538
  assert "ERROR" in actual_response.logs[0].level
531
539
 
532
540
 
541
+ @patch('airbyte_cdk.connector_builder.message_grouper.AirbyteEntrypoint.read')
542
+ def test_given_control_message_then_stream_read_has_config_update(mock_entrypoint_read):
543
+ updated_config = {"x": 1}
544
+ mock_source = make_mock_source(mock_entrypoint_read, iter(
545
+ any_request_and_response_with_a_record() + [connector_configuration_control_message(1, updated_config)]
546
+ ))
547
+ connector_builder_handler = MessageGrouper(MAX_PAGES_PER_SLICE, MAX_SLICES)
548
+ stream_read: StreamRead = connector_builder_handler.get_message_groups(
549
+ source=mock_source, config=CONFIG, configured_catalog=create_configured_catalog("hashiras")
550
+ )
551
+
552
+ assert stream_read.latest_config_update == updated_config
553
+
554
+
555
+ @patch('airbyte_cdk.connector_builder.message_grouper.AirbyteEntrypoint.read')
556
+ def test_given_no_control_message_then_use_in_memory_config_change_as_update(mock_entrypoint_read):
557
+ mock_source = make_mock_source(mock_entrypoint_read, iter(any_request_and_response_with_a_record()))
558
+ connector_builder_handler = MessageGrouper(MAX_PAGES_PER_SLICE, MAX_SLICES)
559
+ full_config = {**CONFIG, **{"__injected_declarative_manifest": MANIFEST}}
560
+ stream_read: StreamRead = connector_builder_handler.get_message_groups(
561
+ source=mock_source, config=full_config, configured_catalog=create_configured_catalog("hashiras")
562
+ )
563
+
564
+ assert stream_read.latest_config_update == CONFIG
565
+
566
+
567
+ @patch('airbyte_cdk.connector_builder.message_grouper.AirbyteEntrypoint.read')
568
+ def test_given_multiple_control_messages_then_stream_read_has_latest_based_on_emitted_at(mock_entrypoint_read):
569
+ earliest = 0
570
+ earliest_config = {"earliest": 0}
571
+ latest = 1
572
+ latest_config = {"latest": 1}
573
+ mock_source = make_mock_source(mock_entrypoint_read, iter(
574
+ any_request_and_response_with_a_record() +
575
+ [
576
+ # here, we test that even if messages are emitted in a different order, we still rely on `emitted_at`
577
+ connector_configuration_control_message(latest, latest_config),
578
+ connector_configuration_control_message(earliest, earliest_config),
579
+ ]
580
+ )
581
+ )
582
+ connector_builder_handler = MessageGrouper(MAX_PAGES_PER_SLICE, MAX_SLICES)
583
+ stream_read: StreamRead = connector_builder_handler.get_message_groups(
584
+ source=mock_source, config=CONFIG, configured_catalog=create_configured_catalog("hashiras")
585
+ )
586
+
587
+ assert stream_read.latest_config_update == latest_config
588
+
589
+
590
+ @patch('airbyte_cdk.connector_builder.message_grouper.AirbyteEntrypoint.read')
591
+ def test_given_multiple_control_messages_with_same_timestamp_then_stream_read_has_latest_based_on_message_order(mock_entrypoint_read):
592
+ emitted_at = 0
593
+ earliest_config = {"earliest": 0}
594
+ latest_config = {"latest": 1}
595
+ mock_source = make_mock_source(mock_entrypoint_read, iter(
596
+ any_request_and_response_with_a_record() +
597
+ [
598
+ connector_configuration_control_message(emitted_at, earliest_config),
599
+ connector_configuration_control_message(emitted_at, latest_config),
600
+ ]
601
+ )
602
+ )
603
+ connector_builder_handler = MessageGrouper(MAX_PAGES_PER_SLICE, MAX_SLICES)
604
+ stream_read: StreamRead = connector_builder_handler.get_message_groups(
605
+ source=mock_source, config=CONFIG, configured_catalog=create_configured_catalog("hashiras")
606
+ )
607
+
608
+ assert stream_read.latest_config_update == latest_config
609
+
610
+
533
611
  def make_mock_source(mock_entrypoint_read, return_value: Iterator) -> MagicMock:
534
612
  mock_source = MagicMock()
535
613
  mock_entrypoint_read.return_value = return_value
@@ -550,3 +628,22 @@ def record_message(stream: str, data: dict) -> AirbyteMessage:
550
628
 
551
629
  def slice_message(slice_descriptor: str = '{"key": "value"}') -> AirbyteMessage:
552
630
  return AirbyteMessage(type=MessageType.LOG, log=AirbyteLogMessage(level=Level.INFO, message="slice:" + slice_descriptor))
631
+
632
+
633
+ def connector_configuration_control_message(emitted_at: float, config: dict) -> AirbyteMessage:
634
+ return AirbyteMessage(
635
+ type=MessageType.CONTROL,
636
+ control=AirbyteControlMessage(
637
+ type=OrchestratorType.CONNECTOR_CONFIG,
638
+ emitted_at=emitted_at,
639
+ connectorConfig=AirbyteControlConnectorConfigMessage(config=config),
640
+ )
641
+ )
642
+
643
+
644
+ def any_request_and_response_with_a_record():
645
+ return [
646
+ request_log_message({"request": 1}),
647
+ response_log_message({"response": 2}),
648
+ record_message("hashiras", {"name": "Shinobu Kocho"}),
649
+ ]