airbyte-cdk 6.34.0.dev2__py3-none-any.whl → 6.34.1.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 (55) hide show
  1. airbyte_cdk/connector_builder/connector_builder_handler.py +12 -16
  2. airbyte_cdk/connector_builder/message_grouper.py +448 -0
  3. airbyte_cdk/sources/declarative/async_job/job_orchestrator.py +7 -7
  4. airbyte_cdk/sources/declarative/auth/jwt.py +11 -17
  5. airbyte_cdk/sources/declarative/auth/oauth.py +1 -6
  6. airbyte_cdk/sources/declarative/auth/token.py +8 -3
  7. airbyte_cdk/sources/declarative/concurrent_declarative_source.py +19 -30
  8. airbyte_cdk/sources/declarative/declarative_component_schema.yaml +85 -203
  9. airbyte_cdk/sources/declarative/declarative_stream.py +1 -3
  10. airbyte_cdk/sources/declarative/decoders/__init__.py +4 -0
  11. airbyte_cdk/sources/declarative/decoders/composite_raw_decoder.py +2 -7
  12. airbyte_cdk/sources/declarative/decoders/json_decoder.py +58 -12
  13. airbyte_cdk/sources/declarative/extractors/record_selector.py +3 -12
  14. airbyte_cdk/sources/declarative/incremental/concurrent_partition_cursor.py +38 -122
  15. airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py +6 -12
  16. airbyte_cdk/sources/declarative/manifest_declarative_source.py +0 -9
  17. airbyte_cdk/sources/declarative/models/declarative_component_schema.py +41 -150
  18. airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +84 -234
  19. airbyte_cdk/sources/declarative/partition_routers/async_job_partition_router.py +5 -5
  20. airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py +2 -4
  21. airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py +18 -26
  22. airbyte_cdk/sources/declarative/requesters/http_requester.py +1 -8
  23. airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +5 -16
  24. airbyte_cdk/sources/declarative/requesters/request_option.py +4 -83
  25. airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py +6 -7
  26. airbyte_cdk/sources/declarative/retrievers/async_retriever.py +12 -6
  27. airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +1 -4
  28. airbyte_cdk/sources/file_based/config/abstract_file_based_spec.py +1 -2
  29. airbyte_cdk/sources/file_based/file_based_source.py +37 -70
  30. airbyte_cdk/sources/file_based/file_based_stream_reader.py +12 -107
  31. airbyte_cdk/sources/file_based/stream/__init__.py +1 -10
  32. airbyte_cdk/sources/streams/call_rate.py +47 -185
  33. airbyte_cdk/sources/streams/http/http.py +2 -1
  34. airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +56 -217
  35. airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py +73 -144
  36. airbyte_cdk/test/mock_http/mocker.py +1 -9
  37. airbyte_cdk/test/mock_http/response.py +3 -6
  38. airbyte_cdk/utils/datetime_helpers.py +66 -48
  39. airbyte_cdk/utils/mapping_helpers.py +26 -126
  40. {airbyte_cdk-6.34.0.dev2.dist-info → airbyte_cdk-6.34.1.dev0.dist-info}/METADATA +1 -1
  41. {airbyte_cdk-6.34.0.dev2.dist-info → airbyte_cdk-6.34.1.dev0.dist-info}/RECORD +45 -54
  42. airbyte_cdk/connector_builder/test_reader/__init__.py +0 -7
  43. airbyte_cdk/connector_builder/test_reader/helpers.py +0 -591
  44. airbyte_cdk/connector_builder/test_reader/message_grouper.py +0 -160
  45. airbyte_cdk/connector_builder/test_reader/reader.py +0 -441
  46. airbyte_cdk/connector_builder/test_reader/types.py +0 -75
  47. airbyte_cdk/sources/file_based/config/validate_config_transfer_modes.py +0 -81
  48. airbyte_cdk/sources/file_based/stream/identities_stream.py +0 -47
  49. airbyte_cdk/sources/file_based/stream/permissions_file_based_stream.py +0 -85
  50. airbyte_cdk/sources/specs/transfer_modes.py +0 -26
  51. airbyte_cdk/sources/streams/permissions/identities_stream.py +0 -75
  52. {airbyte_cdk-6.34.0.dev2.dist-info → airbyte_cdk-6.34.1.dev0.dist-info}/LICENSE.txt +0 -0
  53. {airbyte_cdk-6.34.0.dev2.dist-info → airbyte_cdk-6.34.1.dev0.dist-info}/LICENSE_SHORT +0 -0
  54. {airbyte_cdk-6.34.0.dev2.dist-info → airbyte_cdk-6.34.1.dev0.dist-info}/WHEEL +0 -0
  55. {airbyte_cdk-6.34.0.dev2.dist-info → airbyte_cdk-6.34.1.dev0.dist-info}/entry_points.txt +0 -0
@@ -25,13 +25,6 @@ logger = logging.getLogger("airbyte")
25
25
  _NOOP_MESSAGE_REPOSITORY = NoopMessageRepository()
26
26
 
27
27
 
28
- class ResponseKeysMaxRecurtionReached(AirbyteTracedException):
29
- """
30
- Raised when the max level of recursion is reached, when trying to
31
- find-and-get the target key, during the `_make_handled_request`
32
- """
33
-
34
-
35
28
  class AbstractOauth2Authenticator(AuthBase):
36
29
  """
37
30
  Abstract class for an OAuth authenticators that implements the OAuth token refresh flow. The authenticator
@@ -60,31 +53,15 @@ class AbstractOauth2Authenticator(AuthBase):
60
53
  request.headers.update(self.get_auth_header())
61
54
  return request
62
55
 
63
- @property
64
- def _is_access_token_flow(self) -> bool:
65
- return self.get_token_refresh_endpoint() is None and self.access_token is not None
66
-
67
- @property
68
- def token_expiry_is_time_of_expiration(self) -> bool:
69
- """
70
- Indicates that the Token Expiry returns the date until which the token will be valid, not the amount of time it will be valid.
71
- """
72
-
73
- return False
74
-
75
- @property
76
- def token_expiry_date_format(self) -> Optional[str]:
77
- """
78
- Format of the datetime; exists it if expires_in is returned as the expiration datetime instead of seconds until it expires
79
- """
80
-
81
- return None
82
-
83
56
  def get_auth_header(self) -> Mapping[str, Any]:
84
57
  """HTTP header to set on the requests"""
85
58
  token = self.access_token if self._is_access_token_flow else self.get_access_token()
86
59
  return {"Authorization": f"Bearer {token}"}
87
60
 
61
+ @property
62
+ def _is_access_token_flow(self) -> bool:
63
+ return self.get_token_refresh_endpoint() is None and self.access_token is not None
64
+
88
65
  def get_access_token(self) -> str:
89
66
  """Returns the access token"""
90
67
  if self.token_has_expired():
@@ -130,39 +107,9 @@ class AbstractOauth2Authenticator(AuthBase):
130
107
  headers = self.get_refresh_request_headers()
131
108
  return headers if headers else None
132
109
 
133
- def refresh_access_token(self) -> Tuple[str, Union[str, int]]:
134
- """
135
- Returns the refresh token and its expiration datetime
136
-
137
- :return: a tuple of (access_token, token_lifespan)
138
- """
139
- response_json = self._make_handled_request()
140
- self._ensure_access_token_in_response(response_json)
141
-
142
- return (
143
- self._extract_access_token(response_json),
144
- self._extract_token_expiry_date(response_json),
145
- )
146
-
147
- # ----------------
148
- # PRIVATE METHODS
149
- # ----------------
150
-
151
110
  def _wrap_refresh_token_exception(
152
111
  self, exception: requests.exceptions.RequestException
153
112
  ) -> bool:
154
- """
155
- Wraps and handles exceptions that occur during the refresh token process.
156
-
157
- This method checks if the provided exception is related to a refresh token error
158
- by examining the response status code and specific error content.
159
-
160
- Args:
161
- exception (requests.exceptions.RequestException): The exception raised during the request.
162
-
163
- Returns:
164
- bool: True if the exception is related to a refresh token error, False otherwise.
165
- """
166
113
  try:
167
114
  if exception.response is not None:
168
115
  exception_content = exception.response.json()
@@ -184,24 +131,7 @@ class AbstractOauth2Authenticator(AuthBase):
184
131
  ),
185
132
  max_time=300,
186
133
  )
187
- def _make_handled_request(self) -> Any:
188
- """
189
- Makes a handled HTTP request to refresh an OAuth token.
190
-
191
- This method sends a POST request to the token refresh endpoint with the necessary
192
- headers and body to obtain a new access token. It handles various exceptions that
193
- may occur during the request and logs the response for troubleshooting purposes.
194
-
195
- Returns:
196
- Mapping[str, Any]: The JSON response from the token refresh endpoint.
197
-
198
- Raises:
199
- DefaultBackoffException: If the response status code is 429 (Too Many Requests)
200
- or any 5xx server error.
201
- AirbyteTracedException: If the refresh token is invalid or expired, prompting
202
- re-authentication.
203
- Exception: For any other exceptions that occur during the request.
204
- """
134
+ def _get_refresh_access_token_response(self) -> Any:
205
135
  try:
206
136
  response = requests.request(
207
137
  method="POST",
@@ -209,10 +139,22 @@ class AbstractOauth2Authenticator(AuthBase):
209
139
  data=self.build_refresh_request_body(),
210
140
  headers=self.build_refresh_request_headers(),
211
141
  )
212
- # log the response even if the request failed for troubleshooting purposes
213
- self._log_response(response)
214
- response.raise_for_status()
215
- return response.json()
142
+ if response.ok:
143
+ response_json = response.json()
144
+ # Add the access token to the list of secrets so it is replaced before logging the response
145
+ # An argument could be made to remove the prevous access key from the list of secrets, but unmasking values seems like a security incident waiting to happen...
146
+ access_key = response_json.get(self.get_access_token_name())
147
+ if not access_key:
148
+ raise Exception(
149
+ "Token refresh API response was missing access token {self.get_access_token_name()}"
150
+ )
151
+ add_to_secrets(access_key)
152
+ self._log_response(response)
153
+ return response_json
154
+ else:
155
+ # log the response even if the request failed for troubleshooting purposes
156
+ self._log_response(response)
157
+ response.raise_for_status()
216
158
  except requests.exceptions.RequestException as e:
217
159
  if e.response is not None:
218
160
  if e.response.status_code == 429 or e.response.status_code >= 500:
@@ -226,34 +168,17 @@ class AbstractOauth2Authenticator(AuthBase):
226
168
  except Exception as e:
227
169
  raise Exception(f"Error while refreshing access token: {e}") from e
228
170
 
229
- def _ensure_access_token_in_response(self, response_data: Mapping[str, Any]) -> None:
171
+ def refresh_access_token(self) -> Tuple[str, Union[str, int]]:
230
172
  """
231
- Ensures that the access token is present in the response data.
232
-
233
- This method attempts to extract the access token from the provided response data.
234
- If the access token is not found, it raises an exception indicating that the token
235
- refresh API response was missing the access token. If the access token is found,
236
- it adds the token to the list of secrets to ensure it is replaced before logging
237
- the response.
238
-
239
- Args:
240
- response_data (Mapping[str, Any]): The response data from which to extract the access token.
173
+ Returns the refresh token and its expiration datetime
241
174
 
242
- Raises:
243
- Exception: If the access token is not found in the response data.
244
- ResponseKeysMaxRecurtionReached: If the maximum recursion depth is reached while extracting the access token.
175
+ :return: a tuple of (access_token, token_lifespan)
245
176
  """
246
- try:
247
- access_key = self._extract_access_token(response_data)
248
- if not access_key:
249
- raise Exception(
250
- "Token refresh API response was missing access token {self.get_access_token_name()}"
251
- )
252
- # Add the access token to the list of secrets so it is replaced before logging the response
253
- # An argument could be made to remove the prevous access key from the list of secrets, but unmasking values seems like a security incident waiting to happen...
254
- add_to_secrets(access_key)
255
- except ResponseKeysMaxRecurtionReached as e:
256
- raise e
177
+ response_json = self._get_refresh_access_token_response()
178
+
179
+ return response_json[self.get_access_token_name()], response_json[
180
+ self.get_expires_in_name()
181
+ ]
257
182
 
258
183
  def _parse_token_expiration_date(self, value: Union[str, int]) -> AirbyteDateTime:
259
184
  """
@@ -261,9 +186,6 @@ class AbstractOauth2Authenticator(AuthBase):
261
186
 
262
187
  :return: expiration datetime
263
188
  """
264
- if not value and not self.token_has_expired():
265
- # No expiry token was provided but the previous one is not expired so it's fine
266
- return self.get_token_expiry_date()
267
189
 
268
190
  if self.token_expiry_is_time_of_expiration:
269
191
  if not self.token_expiry_date_format:
@@ -284,124 +206,21 @@ class AbstractOauth2Authenticator(AuthBase):
284
206
  f"Invalid expires_in value: {value}. Expected number of seconds when no format specified."
285
207
  )
286
208
 
287
- def _extract_access_token(self, response_data: Mapping[str, Any]) -> Any:
288
- """
289
- Extracts the access token from the given response data.
290
-
291
- Args:
292
- response_data (Mapping[str, Any]): The response data from which to extract the access token.
293
-
294
- Returns:
295
- str: The extracted access token.
296
- """
297
- return self._find_and_get_value_from_response(response_data, self.get_access_token_name())
298
-
299
- def _extract_refresh_token(self, response_data: Mapping[str, Any]) -> Any:
300
- """
301
- Extracts the refresh token from the given response data.
302
-
303
- Args:
304
- response_data (Mapping[str, Any]): The response data from which to extract the refresh token.
305
-
306
- Returns:
307
- str: The extracted refresh token.
308
- """
309
- return self._find_and_get_value_from_response(response_data, self.get_refresh_token_name())
310
-
311
- def _extract_token_expiry_date(self, response_data: Mapping[str, Any]) -> Any:
312
- """
313
- Extracts the token_expiry_date, like `expires_in` or `expires_at`, etc from the given response data.
314
-
315
- Args:
316
- response_data (Mapping[str, Any]): The response data from which to extract the token_expiry_date.
317
-
318
- Returns:
319
- str: The extracted token_expiry_date.
320
- """
321
- return self._find_and_get_value_from_response(response_data, self.get_expires_in_name())
322
-
323
- def _find_and_get_value_from_response(
324
- self,
325
- response_data: Mapping[str, Any],
326
- key_name: str,
327
- max_depth: int = 5,
328
- current_depth: int = 0,
329
- ) -> Any:
209
+ @property
210
+ def token_expiry_is_time_of_expiration(self) -> bool:
330
211
  """
331
- Recursively searches for a specified key in a nested dictionary or list and returns its value if found.
332
-
333
- Args:
334
- response_data (Mapping[str, Any]): The response data to search through, which can be a dictionary or a list.
335
- key_name (str): The key to search for in the response data.
336
- max_depth (int, optional): The maximum depth to search for the key to avoid infinite recursion. Defaults to 5.
337
- current_depth (int, optional): The current depth of the recursion. Defaults to 0.
338
-
339
- Returns:
340
- Any: The value associated with the specified key if found, otherwise None.
341
-
342
- Raises:
343
- AirbyteTracedException: If the maximum recursion depth is reached without finding the key.
212
+ Indicates that the Token Expiry returns the date until which the token will be valid, not the amount of time it will be valid.
344
213
  """
345
- if current_depth > max_depth:
346
- # this is needed to avoid an inf loop, possible with a very deep nesting observed.
347
- message = f"The maximum level of recursion is reached. Couldn't find the speficied `{key_name}` in the response."
348
- raise ResponseKeysMaxRecurtionReached(
349
- internal_message=message, message=message, failure_type=FailureType.config_error
350
- )
351
214
 
352
- if isinstance(response_data, dict):
353
- # get from the root level
354
- if key_name in response_data:
355
- return response_data[key_name]
356
-
357
- # get from the nested object
358
- for _, value in response_data.items():
359
- result = self._find_and_get_value_from_response(
360
- value, key_name, max_depth, current_depth + 1
361
- )
362
- if result is not None:
363
- return result
364
-
365
- # get from the nested array object
366
- elif isinstance(response_data, list):
367
- for item in response_data:
368
- result = self._find_and_get_value_from_response(
369
- item, key_name, max_depth, current_depth + 1
370
- )
371
- if result is not None:
372
- return result
373
-
374
- return None
215
+ return False
375
216
 
376
217
  @property
377
- def _message_repository(self) -> Optional[MessageRepository]:
378
- """
379
- The implementation can define a message_repository if it wants debugging logs for HTTP requests
380
- """
381
- return _NOOP_MESSAGE_REPOSITORY
382
-
383
- def _log_response(self, response: requests.Response) -> None:
218
+ def token_expiry_date_format(self) -> Optional[str]:
384
219
  """
385
- Logs the HTTP response using the message repository if it is available.
386
-
387
- Args:
388
- response (requests.Response): The HTTP response to log.
220
+ Format of the datetime; exists it if expires_in is returned as the expiration datetime instead of seconds until it expires
389
221
  """
390
- if self._message_repository:
391
- self._message_repository.log_message(
392
- Level.DEBUG,
393
- lambda: format_http_message(
394
- response,
395
- "Refresh token",
396
- "Obtains access token",
397
- self._NO_STREAM_NAME,
398
- is_auxiliary=True,
399
- ),
400
- )
401
222
 
402
- # ----------------
403
- # ABSTR METHODS
404
- # ----------------
223
+ return None
405
224
 
406
225
  @abstractmethod
407
226
  def get_token_refresh_endpoint(self) -> Optional[str]:
@@ -476,3 +295,23 @@ class AbstractOauth2Authenticator(AuthBase):
476
295
  @abstractmethod
477
296
  def access_token(self, value: str) -> str:
478
297
  """Setter for the access token"""
298
+
299
+ @property
300
+ def _message_repository(self) -> Optional[MessageRepository]:
301
+ """
302
+ The implementation can define a message_repository if it wants debugging logs for HTTP requests
303
+ """
304
+ return _NOOP_MESSAGE_REPOSITORY
305
+
306
+ def _log_response(self, response: requests.Response) -> None:
307
+ if self._message_repository:
308
+ self._message_repository.log_message(
309
+ Level.DEBUG,
310
+ lambda: format_http_message(
311
+ response,
312
+ "Refresh token",
313
+ "Obtains access token",
314
+ self._NO_STREAM_NAME,
315
+ is_auxiliary=True,
316
+ ),
317
+ )
@@ -51,7 +51,7 @@ class Oauth2Authenticator(AbstractOauth2Authenticator):
51
51
  refresh_token_error_status_codes: Tuple[int, ...] = (),
52
52
  refresh_token_error_key: str = "",
53
53
  refresh_token_error_values: Tuple[str, ...] = (),
54
- ) -> None:
54
+ ):
55
55
  self._token_refresh_endpoint = token_refresh_endpoint
56
56
  self._client_secret_name = client_secret_name
57
57
  self._client_secret = client_secret
@@ -175,7 +175,7 @@ class SingleUseRefreshTokenOauth2Authenticator(Oauth2Authenticator):
175
175
  refresh_token_error_status_codes: Tuple[int, ...] = (),
176
176
  refresh_token_error_key: str = "",
177
177
  refresh_token_error_values: Tuple[str, ...] = (),
178
- ) -> None:
178
+ ):
179
179
  """
180
180
  Args:
181
181
  connector_config (Mapping[str, Any]): The full connector configuration
@@ -196,12 +196,18 @@ class SingleUseRefreshTokenOauth2Authenticator(Oauth2Authenticator):
196
196
  token_expiry_is_time_of_expiration bool: set True it if expires_in is returned as time of expiration instead of the number seconds until expiration
197
197
  message_repository (MessageRepository): the message repository used to emit logs on HTTP requests and control message on config update
198
198
  """
199
- self._connector_config = connector_config
200
- self._client_id: str = self._get_config_value_by_path(
201
- ("credentials", "client_id"), client_id
199
+ self._client_id = (
200
+ client_id # type: ignore[assignment] # Incorrect type for assignment
201
+ if client_id is not None
202
+ else dpath.get(connector_config, ("credentials", "client_id")) # type: ignore[arg-type]
202
203
  )
203
- self._client_secret: str = self._get_config_value_by_path(
204
- ("credentials", "client_secret"), client_secret
204
+ self._client_secret = (
205
+ client_secret # type: ignore[assignment] # Incorrect type for assignment
206
+ if client_secret is not None
207
+ else dpath.get(
208
+ connector_config, # type: ignore[arg-type]
209
+ ("credentials", "client_secret"),
210
+ )
205
211
  )
206
212
  self._client_id_name = client_id_name
207
213
  self._client_secret_name = client_secret_name
@@ -216,9 +222,9 @@ class SingleUseRefreshTokenOauth2Authenticator(Oauth2Authenticator):
216
222
  super().__init__(
217
223
  token_refresh_endpoint=token_refresh_endpoint,
218
224
  client_id_name=self._client_id_name,
219
- client_id=self._client_id,
225
+ client_id=self.get_client_id(),
220
226
  client_secret_name=self._client_secret_name,
221
- client_secret=self._client_secret,
227
+ client_secret=self.get_client_secret(),
222
228
  refresh_token=self.get_refresh_token(),
223
229
  refresh_token_name=self._refresh_token_name,
224
230
  scopes=scopes,
@@ -236,62 +242,51 @@ class SingleUseRefreshTokenOauth2Authenticator(Oauth2Authenticator):
236
242
  refresh_token_error_values=refresh_token_error_values,
237
243
  )
238
244
 
245
+ def get_refresh_token_name(self) -> str:
246
+ return self._refresh_token_name
247
+
248
+ def get_client_id(self) -> str:
249
+ return self._client_id
250
+
251
+ def get_client_secret(self) -> str:
252
+ return self._client_secret
253
+
239
254
  @property
240
255
  def access_token(self) -> str:
241
- """
242
- Retrieve the access token from the configuration.
243
-
244
- Returns:
245
- str: The access token.
246
- """
247
- return self._get_config_value_by_path(self._access_token_config_path) # type: ignore[return-value]
256
+ return dpath.get( # type: ignore[return-value]
257
+ self._connector_config, # type: ignore[arg-type]
258
+ self._access_token_config_path,
259
+ default="",
260
+ )
248
261
 
249
262
  @access_token.setter
250
263
  def access_token(self, new_access_token: str) -> None:
251
- """
252
- Sets a new access token.
253
-
254
- Args:
255
- new_access_token (str): The new access token to be set.
256
- """
257
- self._set_config_value_by_path(self._access_token_config_path, new_access_token)
264
+ dpath.new(
265
+ self._connector_config, # type: ignore[arg-type]
266
+ self._access_token_config_path,
267
+ new_access_token,
268
+ )
258
269
 
259
270
  def get_refresh_token(self) -> str:
260
- """
261
- Retrieve the refresh token from the configuration.
262
-
263
- This method fetches the refresh token using the configuration path specified
264
- by `_refresh_token_config_path`.
265
-
266
- Returns:
267
- str: The refresh token as a string.
268
- """
269
- return self._get_config_value_by_path(self._refresh_token_config_path) # type: ignore[return-value]
271
+ return dpath.get( # type: ignore[return-value]
272
+ self._connector_config, # type: ignore[arg-type]
273
+ self._refresh_token_config_path,
274
+ default="",
275
+ )
270
276
 
271
277
  def set_refresh_token(self, new_refresh_token: str) -> None:
272
- """
273
- Updates the refresh token in the configuration.
274
-
275
- Args:
276
- new_refresh_token (str): The new refresh token to be set.
277
- """
278
- self._set_config_value_by_path(self._refresh_token_config_path, new_refresh_token)
278
+ dpath.new(
279
+ self._connector_config, # type: ignore[arg-type]
280
+ self._refresh_token_config_path,
281
+ new_refresh_token,
282
+ )
279
283
 
280
284
  def get_token_expiry_date(self) -> AirbyteDateTime:
281
- """
282
- Retrieves the token expiry date from the configuration.
283
-
284
- This method fetches the token expiry date from the configuration using the specified path.
285
- If the expiry date is an empty string, it returns the current date and time minus one day.
286
- Otherwise, it parses the expiry date string into an AirbyteDateTime object.
287
-
288
- Returns:
289
- AirbyteDateTime: The parsed or calculated token expiry date.
290
-
291
- Raises:
292
- TypeError: If the result is not an instance of AirbyteDateTime.
293
- """
294
- expiry_date = self._get_config_value_by_path(self._token_expiry_date_config_path)
285
+ expiry_date = dpath.get(
286
+ self._connector_config, # type: ignore[arg-type]
287
+ self._token_expiry_date_config_path,
288
+ default="",
289
+ )
295
290
  result = (
296
291
  ab_datetime_now() - timedelta(days=1)
297
292
  if expiry_date == ""
@@ -301,15 +296,14 @@ class SingleUseRefreshTokenOauth2Authenticator(Oauth2Authenticator):
301
296
  return result
302
297
  raise TypeError("Invalid datetime conversion")
303
298
 
304
- def set_token_expiry_date(self, new_token_expiry_date: AirbyteDateTime) -> None: # type: ignore[override]
305
- """
306
- Sets the token expiry date in the configuration.
307
-
308
- Args:
309
- new_token_expiry_date (AirbyteDateTime): The new expiry date for the token.
310
- """
311
- self._set_config_value_by_path(
312
- self._token_expiry_date_config_path, str(new_token_expiry_date)
299
+ def set_token_expiry_date( # type: ignore[override]
300
+ self,
301
+ new_token_expiry_date: AirbyteDateTime,
302
+ ) -> None:
303
+ dpath.new(
304
+ self._connector_config, # type: ignore[arg-type]
305
+ self._token_expiry_date_config_path,
306
+ str(new_token_expiry_date),
313
307
  )
314
308
 
315
309
  def token_has_expired(self) -> bool:
@@ -321,16 +315,6 @@ class SingleUseRefreshTokenOauth2Authenticator(Oauth2Authenticator):
321
315
  access_token_expires_in: str,
322
316
  token_expiry_date_format: str | None = None,
323
317
  ) -> AirbyteDateTime:
324
- """
325
- Calculate the new token expiry date based on the provided expiration duration or format.
326
-
327
- Args:
328
- access_token_expires_in (str): The duration (in seconds) until the access token expires, or the expiry date in a specific format.
329
- token_expiry_date_format (str | None, optional): The format of the expiry date if provided. Defaults to None.
330
-
331
- Returns:
332
- AirbyteDateTime: The calculated expiry date of the access token.
333
- """
334
318
  if token_expiry_date_format:
335
319
  return ab_datetime_parse(access_token_expires_in)
336
320
  else:
@@ -352,82 +336,27 @@ class SingleUseRefreshTokenOauth2Authenticator(Oauth2Authenticator):
352
336
  self.access_token = new_access_token
353
337
  self.set_refresh_token(new_refresh_token)
354
338
  self.set_token_expiry_date(new_token_expiry_date)
355
- self._emit_control_message()
339
+ # FIXME emit_configuration_as_airbyte_control_message as been deprecated in favor of package airbyte_cdk.sources.message
340
+ # Usually, a class shouldn't care about the implementation details but to keep backward compatibility where we print the
341
+ # message directly in the console, this is needed
342
+ if not isinstance(self._message_repository, NoopMessageRepository):
343
+ self._message_repository.emit_message(
344
+ create_connector_config_control_message(self._connector_config) # type: ignore[arg-type]
345
+ )
346
+ else:
347
+ emit_configuration_as_airbyte_control_message(self._connector_config) # type: ignore[arg-type]
356
348
  return self.access_token
357
349
 
358
- def refresh_access_token(self) -> Tuple[str, str, str]: # type: ignore[override]
359
- """
360
- Refreshes the access token by making a handled request and extracting the necessary token information.
361
-
362
- Returns:
363
- Tuple[str, str, str]: A tuple containing the new access token, token expiry date, and refresh token.
364
- """
365
- response_json = self._make_handled_request()
350
+ def refresh_access_token( # type: ignore[override] # Signature doesn't match base class
351
+ self,
352
+ ) -> Tuple[str, str, str]:
353
+ response_json = self._get_refresh_access_token_response()
366
354
  return (
367
- self._extract_access_token(response_json),
368
- self._extract_token_expiry_date(response_json),
369
- self._extract_refresh_token(response_json),
370
- )
371
-
372
- def _set_config_value_by_path(self, config_path: Union[str, Sequence[str]], value: Any) -> None:
373
- """
374
- Set a value in the connector configuration at the specified path.
375
-
376
- Args:
377
- config_path (Union[str, Sequence[str]]): The path within the configuration where the value should be set.
378
- This can be a string representing a single key or a sequence of strings representing a nested path.
379
- value (Any): The value to set at the specified path in the configuration.
380
-
381
- Returns:
382
- None
383
- """
384
- dpath.new(self._connector_config, config_path, value) # type: ignore[arg-type]
385
-
386
- def _get_config_value_by_path(
387
- self, config_path: Union[str, Sequence[str]], default: Optional[str] = None
388
- ) -> str | Any:
389
- """
390
- Retrieve a value from the connector configuration using a specified path.
391
-
392
- Args:
393
- config_path (Union[str, Sequence[str]]): The path to the desired configuration value. This can be a string or a sequence of strings.
394
- default (Optional[str], optional): The default value to return if the specified path does not exist in the configuration. Defaults to None.
395
-
396
- Returns:
397
- Any: The value from the configuration at the specified path, or the default value if the path does not exist.
398
- """
399
- return dpath.get(
400
- self._connector_config, # type: ignore[arg-type]
401
- config_path,
402
- default=default if default is not None else "",
355
+ response_json[self.get_access_token_name()],
356
+ response_json[self.get_expires_in_name()],
357
+ response_json[self.get_refresh_token_name()],
403
358
  )
404
359
 
405
- def _emit_control_message(self) -> None:
406
- """
407
- Emits a control message based on the connector configuration.
408
-
409
- This method checks if the message repository is not a NoopMessageRepository.
410
- If it is not, it emits a message using the message repository. Otherwise,
411
- it falls back to emitting the configuration as an Airbyte control message
412
- directly to the console for backward compatibility.
413
-
414
- Note:
415
- The function `emit_configuration_as_airbyte_control_message` has been deprecated
416
- in favor of the package `airbyte_cdk.sources.message`.
417
-
418
- Raises:
419
- TypeError: If the argument types are incorrect.
420
- """
421
- # FIXME emit_configuration_as_airbyte_control_message as been deprecated in favor of package airbyte_cdk.sources.message
422
- # Usually, a class shouldn't care about the implementation details but to keep backward compatibility where we print the
423
- # message directly in the console, this is needed
424
- if not isinstance(self._message_repository, NoopMessageRepository):
425
- self._message_repository.emit_message(
426
- create_connector_config_control_message(self._connector_config) # type: ignore[arg-type]
427
- )
428
- else:
429
- emit_configuration_as_airbyte_control_message(self._connector_config) # type: ignore[arg-type]
430
-
431
360
  @property
432
361
  def _message_repository(self) -> MessageRepository:
433
362
  """
@@ -17,7 +17,6 @@ class SupportedHttpMethods(str, Enum):
17
17
  GET = "get"
18
18
  PATCH = "patch"
19
19
  POST = "post"
20
- PUT = "put"
21
20
  DELETE = "delete"
22
21
 
23
22
 
@@ -78,7 +77,7 @@ class HttpMocker(contextlib.ContextDecorator):
78
77
  additional_matcher=self._matches_wrapper(matcher),
79
78
  response_list=[
80
79
  {
81
- self._get_body_field(response): response.body,
80
+ "text": response.body,
82
81
  "status_code": response.status_code,
83
82
  "headers": response.headers,
84
83
  }
@@ -86,10 +85,6 @@ class HttpMocker(contextlib.ContextDecorator):
86
85
  ],
87
86
  )
88
87
 
89
- @staticmethod
90
- def _get_body_field(response: HttpResponse) -> str:
91
- return "text" if isinstance(response.body, str) else "content"
92
-
93
88
  def get(self, request: HttpRequest, responses: Union[HttpResponse, List[HttpResponse]]) -> None:
94
89
  self._mock_request_method(SupportedHttpMethods.GET, request, responses)
95
90
 
@@ -103,9 +98,6 @@ class HttpMocker(contextlib.ContextDecorator):
103
98
  ) -> None:
104
99
  self._mock_request_method(SupportedHttpMethods.POST, request, responses)
105
100
 
106
- def put(self, request: HttpRequest, responses: Union[HttpResponse, List[HttpResponse]]) -> None:
107
- self._mock_request_method(SupportedHttpMethods.PUT, request, responses)
108
-
109
101
  def delete(
110
102
  self, request: HttpRequest, responses: Union[HttpResponse, List[HttpResponse]]
111
103
  ) -> None: