airbyte-cdk 0.36.5__py3-none-any.whl → 0.37.0__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.
@@ -31,15 +31,15 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
31
31
  token_expiry_date (Optional[Union[InterpolatedString, str]]): The access token expiration date
32
32
  token_expiry_date_format str: format of the datetime; provide it if expires_in is returned in datetime instead of seconds
33
33
  refresh_request_body (Optional[Mapping[str, Any]]): The request body to send in the refresh request
34
- grant_type: The grant_type to request for access_token
34
+ grant_type: The grant_type to request for access_token. If set to refresh_token, the refresh_token parameter has to be provided
35
35
  """
36
36
 
37
37
  token_refresh_endpoint: Union[InterpolatedString, str]
38
38
  client_id: Union[InterpolatedString, str]
39
39
  client_secret: Union[InterpolatedString, str]
40
- refresh_token: Union[InterpolatedString, str]
41
40
  config: Mapping[str, Any]
42
41
  parameters: InitVar[Mapping[str, Any]]
42
+ refresh_token: Optional[Union[InterpolatedString, str]] = None
43
43
  scopes: Optional[List[str]] = None
44
44
  token_expiry_date: Optional[Union[InterpolatedString, str]] = None
45
45
  _token_expiry_date: pendulum.DateTime = field(init=False, repr=False, default=None)
@@ -53,7 +53,8 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
53
53
  self.token_refresh_endpoint = InterpolatedString.create(self.token_refresh_endpoint, parameters=parameters)
54
54
  self.client_id = InterpolatedString.create(self.client_id, parameters=parameters)
55
55
  self.client_secret = InterpolatedString.create(self.client_secret, parameters=parameters)
56
- self.refresh_token = InterpolatedString.create(self.refresh_token, parameters=parameters)
56
+ if self.refresh_token is not None:
57
+ self.refresh_token = InterpolatedString.create(self.refresh_token, parameters=parameters)
57
58
  self.access_token_name = InterpolatedString.create(self.access_token_name, parameters=parameters)
58
59
  self.expires_in_name = InterpolatedString.create(self.expires_in_name, parameters=parameters)
59
60
  self.grant_type = InterpolatedString.create(self.grant_type, parameters=parameters)
@@ -65,6 +66,9 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
65
66
  )
66
67
  self._access_token = None
67
68
 
69
+ if self.get_grant_type() == "refresh_token" and self.refresh_token is None:
70
+ raise ValueError("OAuthAuthenticator needs a refresh_token parameter if grant_type is set to `refresh_token`")
71
+
68
72
  def get_token_refresh_endpoint(self) -> str:
69
73
  return self.token_refresh_endpoint.eval(self.config)
70
74
 
@@ -74,8 +78,8 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
74
78
  def get_client_secret(self) -> str:
75
79
  return self.client_secret.eval(self.config)
76
80
 
77
- def get_refresh_token(self) -> str:
78
- return self.refresh_token.eval(self.config)
81
+ def get_refresh_token(self) -> Optional[str]:
82
+ return None if self.refresh_token is None else self.refresh_token.eval(self.config)
79
83
 
80
84
  def get_scopes(self) -> [str]:
81
85
  return self.scopes
@@ -117,7 +121,10 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
117
121
  if self.token_expiry_date_format:
118
122
  self._token_expiry_date = pendulum.from_format(value, self.token_expiry_date_format)
119
123
  else:
120
- self._token_expiry_date = pendulum.now().add(seconds=value)
124
+ try:
125
+ self._token_expiry_date = pendulum.now().add(seconds=int(float(value)))
126
+ except ValueError:
127
+ raise ValueError(f"Invalid token expiry value {value}; a number is required.")
121
128
 
122
129
  @property
123
130
  def access_token(self) -> str:
@@ -647,7 +647,6 @@ definitions:
647
647
  - type
648
648
  - client_id
649
649
  - client_secret
650
- - refresh_token
651
650
  - token_refresh_endpoint
652
651
  properties:
653
652
  type:
@@ -277,8 +277,8 @@ class OAuthAuthenticator(BaseModel):
277
277
  ],
278
278
  title="Client Secret",
279
279
  )
280
- refresh_token: str = Field(
281
- ...,
280
+ refresh_token: Optional[str] = Field(
281
+ None,
282
282
  description="Credential artifact used to get a new access token.",
283
283
  examples=[
284
284
  "{{ config['refresh_token'] }}",
@@ -4,7 +4,7 @@
4
4
 
5
5
  import logging
6
6
  from abc import abstractmethod
7
- from typing import Any, List, Mapping, MutableMapping, Tuple, Union
7
+ from typing import Any, List, Mapping, MutableMapping, Optional, Tuple, Union
8
8
 
9
9
  import backoff
10
10
  import pendulum
@@ -111,7 +111,7 @@ class AbstractOauth2Authenticator(AuthBase):
111
111
  """The client secret to authenticate"""
112
112
 
113
113
  @abstractmethod
114
- def get_refresh_token(self) -> str:
114
+ def get_refresh_token(self) -> Optional[str]:
115
115
  """The token used to refresh the access token when it expires"""
116
116
 
117
117
  @abstractmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: airbyte-cdk
3
- Version: 0.36.5
3
+ Version: 0.37.0
4
4
  Summary: A framework for writing Airbyte Connectors.
5
5
  Home-page: https://github.com/airbytehq/airbyte
6
6
  Author: Airbyte
@@ -22,7 +22,7 @@ airbyte_cdk/sources/connector_state_manager.py,sha256=_R-2QnMGimKL0t5aV4f6P1dgd-
22
22
  airbyte_cdk/sources/source.py,sha256=N3vHZzdUsBETFsql-YpO-LcgjolT_jcnAuHBhGD6Hqk,4278
23
23
  airbyte_cdk/sources/declarative/__init__.py,sha256=ZnqYNxHsKCgO38IwB34RQyRMXTs4GTvlRi3ImKnIioo,61
24
24
  airbyte_cdk/sources/declarative/create_partial.py,sha256=sUJOwD8hBzW4pxw2XhYlSTMgl-WMc5WpP5Oq_jo3fHw,3371
25
- airbyte_cdk/sources/declarative/declarative_component_schema.yaml,sha256=rOwWCeYWSQi5jz_1hvG48xbv_4kP3bHmVlrLcPfGc7A,75109
25
+ airbyte_cdk/sources/declarative/declarative_component_schema.yaml,sha256=0BMvpifiVZ-IZytuxGlPRJAFMtiVBEFRjYBot9Wl40I,75087
26
26
  airbyte_cdk/sources/declarative/declarative_source.py,sha256=U2As9PDKmcWDgbsWUo-RetJ9fxQOBlwntWZ0NOgs5Ac,1453
27
27
  airbyte_cdk/sources/declarative/declarative_stream.py,sha256=0iZSpypxt8bhO3Lmf3BpGRTO7Fp0Q2GI8m8xyJJUjeM,6580
28
28
  airbyte_cdk/sources/declarative/exceptions.py,sha256=kTPUA4I2NV4J6HDz-mKPGMrfuc592akJnOyYx38l_QM,176
@@ -31,7 +31,7 @@ airbyte_cdk/sources/declarative/types.py,sha256=b_RJpL9TyAgxJIRYZx5BxpC39p-WccHK
31
31
  airbyte_cdk/sources/declarative/yaml_declarative_source.py,sha256=I9Bs9RDsFT8JNiJWRDjKYhqwvv4pqzgYZtF5hVuTDqI,1684
32
32
  airbyte_cdk/sources/declarative/auth/__init__.py,sha256=DyQdO5mdKGsttWdEUqxb6WVgD7zTcvpJz-Oet_VNeBg,201
33
33
  airbyte_cdk/sources/declarative/auth/declarative_authenticator.py,sha256=4nEvMQWGmQ_-KROwcI8dsDbU4NjjxiGz3nsxxfWqBF8,663
34
- airbyte_cdk/sources/declarative/auth/oauth.py,sha256=Rvnzyq2xmMKICTbcWejLyBJgGO_viKVr0HwIxol_3Xo,6849
34
+ airbyte_cdk/sources/declarative/auth/oauth.py,sha256=pD0KnWA8kECcm1wMHsIrLJzUT9sil0iZu1ZUTn-los0,7403
35
35
  airbyte_cdk/sources/declarative/auth/token.py,sha256=8WVZoV02ujk5KuWC6eOsnUSI6zp4Jm7TvWdgxKFiffk,10520
36
36
  airbyte_cdk/sources/declarative/checks/__init__.py,sha256=WWXMfvKkndqwAUZdgSr7xVHVXDFTKCUQ9EubqT7H4QE,274
37
37
  airbyte_cdk/sources/declarative/checks/check_stream.py,sha256=9fJt1ma31tBiKfuqjTO2SIK_b14NgjqkPJa4om0eJM0,2065
@@ -59,7 +59,7 @@ airbyte_cdk/sources/declarative/interpolation/interpolation.py,sha256=dyIM-bzh54
59
59
  airbyte_cdk/sources/declarative/interpolation/jinja.py,sha256=Dc0F87nElWsz_Ikj938eQ9uqZvyqgFhZ8Dqf_-hvndc,4800
60
60
  airbyte_cdk/sources/declarative/interpolation/macros.py,sha256=V6WGKJ9cXX1rjuM4bK3Cs9xEryMlkY2U3FMsSBhrgC8,3098
61
61
  airbyte_cdk/sources/declarative/models/__init__.py,sha256=EiYnzwCHZV7EYqMJqcy6xKSeHvTKZBsQndjbEwmiTW4,93
62
- airbyte_cdk/sources/declarative/models/declarative_component_schema.py,sha256=rr7LMOAJhrpdkHHSD2-Mh7-sOjl0q2xCccHiEEkGxnQ,50441
62
+ airbyte_cdk/sources/declarative/models/declarative_component_schema.py,sha256=PghniMTIvEHi-NPIRK7rpPJtYRtYgigR1T0hyKP6fl8,50452
63
63
  airbyte_cdk/sources/declarative/parsers/__init__.py,sha256=ZnqYNxHsKCgO38IwB34RQyRMXTs4GTvlRi3ImKnIioo,61
64
64
  airbyte_cdk/sources/declarative/parsers/class_types_registry.py,sha256=bK4a74opm6WHyV7HqOVws6GE5Z7cLNc5MaTha69abIQ,6086
65
65
  airbyte_cdk/sources/declarative/parsers/custom_exceptions.py,sha256=y7_G5mM07zxT5YG975kdC2PAja-Uc83pYp8WrV3GNdo,522
@@ -139,7 +139,7 @@ airbyte_cdk/sources/streams/http/auth/core.py,sha256=_s9wewvvIcOgYjhHGDj_YHApnF5
139
139
  airbyte_cdk/sources/streams/http/auth/oauth.py,sha256=zchPWN1utNg02F93f5b4UFI5OXYo8-QhocbsXhLdG4U,4135
140
140
  airbyte_cdk/sources/streams/http/auth/token.py,sha256=oU1ul0LsGsPGN_vOJOKw1xX2y_XWULRxjqXu7Rivcr8,1940
141
141
  airbyte_cdk/sources/streams/http/requests_native_auth/__init__.py,sha256=RN0D3nOX1xLgwEwKWu6pkGy3XqBFzKSNZ8Lf6umU2eY,413
142
- airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py,sha256=goK6MRBekhosrdOSbWK54eCiciqYlL0jX7euwndM8QU,5319
142
+ airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py,sha256=dw9mmIOf05NDqKzzvRA3tXKjx1LvVGm1tPt8TQhf5Y8,5339
143
143
  airbyte_cdk/sources/streams/http/requests_native_auth/abstract_token.py,sha256=T0hVF2cBXGgIfrCslvTC1uNm9rNbYjENNl2Cb3mXuSY,961
144
144
  airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py,sha256=QWTjL6blaEAK457TSJlTDcczITdAu0RqMEhxX-rpAWo,11704
145
145
  airbyte_cdk/sources/streams/http/requests_native_auth/token.py,sha256=hDti8DlF_R5YYX95hg9BPogYtG-KUYtOifrFDv_L3Hk,2456
@@ -181,7 +181,7 @@ unit_tests/sources/declarative/test_declarative_stream.py,sha256=3leJnZIYHiFq8XI
181
181
  unit_tests/sources/declarative/test_manifest_declarative_source.py,sha256=GckUc3nepzZkD1UM24woHlYCVZb5DP4IAQC3IeMyZF0,58924
182
182
  unit_tests/sources/declarative/test_yaml_declarative_source.py,sha256=6HhsUFgB7ueN0yOUHWb4gpPYLng5jasxN_plvz3x37g,5097
183
183
  unit_tests/sources/declarative/auth/__init__.py,sha256=4Hw-PX1-VgESLF16cDdvuYCzGJtHntThLF4qIiULWeo,61
184
- unit_tests/sources/declarative/auth/test_oauth.py,sha256=mqXE_mQBcM78-ZaDX5GCWFOkbXPCvYeCj81aKyPZ3D8,5204
184
+ unit_tests/sources/declarative/auth/test_oauth.py,sha256=j-xEUbRPs5jnRAvKCNLKDpEbAZLmXHEy9tSEkYUrYx0,8442
185
185
  unit_tests/sources/declarative/auth/test_session_token_auth.py,sha256=mxWCm_0AyVI6J1Q5CjogXY-EkXFfWkMZjNtBeb0bOow,6135
186
186
  unit_tests/sources/declarative/auth/test_token_auth.py,sha256=voeoFiZOp2BUSJ0FvlXdRsan_vwIbteA2tpb1oRb40g,3711
187
187
  unit_tests/sources/declarative/checks/__init__.py,sha256=ZnqYNxHsKCgO38IwB34RQyRMXTs4GTvlRi3ImKnIioo,61
@@ -259,8 +259,8 @@ unit_tests/utils/test_schema_inferrer.py,sha256=ckl17GlNOZInqgxni7Z2A0bg_p6JDy0G
259
259
  unit_tests/utils/test_secret_utils.py,sha256=XKe0f1RHYii8iwE6ATmBr5JGDI1pzzrnZUGdUSMJQP4,4886
260
260
  unit_tests/utils/test_stream_status_utils.py,sha256=NpV155JMXA6CG-2Zvofa14lItobyh3Onttc59X4m5DI,3382
261
261
  unit_tests/utils/test_traced_exception.py,sha256=bDFP5zMBizFenz6V2WvEZTRCKGB5ijh3DBezjbfoYIs,4198
262
- airbyte_cdk-0.36.5.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
263
- airbyte_cdk-0.36.5.dist-info/METADATA,sha256=e8O2J7ZTOAytqohSQVqndtkg9tkHCf81iwopeM_ntcY,8902
264
- airbyte_cdk-0.36.5.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
265
- airbyte_cdk-0.36.5.dist-info/top_level.txt,sha256=edvsDKTnE6sD2wfCUaeTfKf5gQIL6CPVMwVL2sWZzqo,51
266
- airbyte_cdk-0.36.5.dist-info/RECORD,,
262
+ airbyte_cdk-0.37.0.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
263
+ airbyte_cdk-0.37.0.dist-info/METADATA,sha256=6HRZ3Hyt-WKn2QNme7H6B-s3mCYhi1ur_apLnXMnODU,8902
264
+ airbyte_cdk-0.37.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
265
+ airbyte_cdk-0.37.0.dist-info/top_level.txt,sha256=edvsDKTnE6sD2wfCUaeTfKf5gQIL6CPVMwVL2sWZzqo,51
266
+ airbyte_cdk-0.37.0.dist-info/RECORD,,
@@ -65,6 +65,42 @@ class TestOauth2Authenticator:
65
65
  }
66
66
  assert body == expected
67
67
 
68
+ def test_refresh_without_refresh_token(self):
69
+ """
70
+ Should work fine for grant_type client_credentials.
71
+ """
72
+ oauth = DeclarativeOauth2Authenticator(
73
+ token_refresh_endpoint="{{ config['refresh_endpoint'] }}",
74
+ client_id="{{ config['client_id'] }}",
75
+ client_secret="{{ config['client_secret'] }}",
76
+ config=config,
77
+ parameters={},
78
+ grant_type="client_credentials",
79
+ )
80
+ body = oauth.build_refresh_request_body()
81
+ expected = {
82
+ "grant_type": "client_credentials",
83
+ "client_id": "some_client_id",
84
+ "client_secret": "some_client_secret",
85
+ "refresh_token": None,
86
+ "scopes": None,
87
+ }
88
+ assert body == expected
89
+
90
+ def test_error_on_refresh_token_grant_without_refresh_token(self):
91
+ """
92
+ Should throw an error if grant_type refresh_token is configured without refresh_token.
93
+ """
94
+ with pytest.raises(ValueError):
95
+ DeclarativeOauth2Authenticator(
96
+ token_refresh_endpoint="{{ config['refresh_endpoint'] }}",
97
+ client_id="{{ config['client_id'] }}",
98
+ client_secret="{{ config['client_secret'] }}",
99
+ config=config,
100
+ parameters={},
101
+ grant_type="refresh_token",
102
+ )
103
+
68
104
  def test_refresh_access_token(self, mocker):
69
105
  oauth = DeclarativeOauth2Authenticator(
70
106
  token_refresh_endpoint="{{ config['refresh_endpoint'] }}",
@@ -92,12 +128,11 @@ class TestOauth2Authenticator:
92
128
  @pytest.mark.parametrize(
93
129
  "expires_in_response, token_expiry_date_format",
94
130
  [
95
- (86400, None),
96
131
  ("2020-01-02T00:00:00Z", "YYYY-MM-DDTHH:mm:ss[Z]"),
97
132
  ("2020-01-02T00:00:00.000000+00:00", "YYYY-MM-DDTHH:mm:ss.SSSSSSZ"),
98
133
  ("2020-01-02", "YYYY-MM-DD"),
99
134
  ],
100
- ids=["time_in_seconds", "rfc3339", "iso8601", "simple_date"],
135
+ ids=["rfc3339", "iso8601", "simple_date"],
101
136
  )
102
137
  @freezegun.freeze_time("2020-01-01")
103
138
  def test_refresh_access_token_expire_format(self, mocker, expires_in_response, token_expiry_date_format):
@@ -127,6 +162,46 @@ class TestOauth2Authenticator:
127
162
  assert "access_token" == token
128
163
  assert oauth.get_token_expiry_date() == pendulum.parse(next_day)
129
164
 
165
+ @pytest.mark.parametrize(
166
+ "expires_in_response, next_day, raises",
167
+ [
168
+ (86400, "2020-01-02T00:00:00Z", False),
169
+ (86400.1, "2020-01-02T00:00:00Z", False),
170
+ ("86400", "2020-01-02T00:00:00Z", False),
171
+ ("86400.1", "2020-01-02T00:00:00Z", False),
172
+ ("2020-01-02T00:00:00Z", "2020-01-02T00:00:00Z", True),
173
+ ],
174
+ ids=["time_in_seconds", "time_in_seconds_float", "time_in_seconds_str", "time_in_seconds_str_float", "invalid"],
175
+ )
176
+ @freezegun.freeze_time("2020-01-01")
177
+ def test_set_token_expiry_date_no_format(self, mocker, expires_in_response, next_day, raises):
178
+ config.update({"token_expiry_date": pendulum.parse(next_day).subtract(days=2).to_rfc3339_string()})
179
+ oauth = DeclarativeOauth2Authenticator(
180
+ token_refresh_endpoint="{{ config['refresh_endpoint'] }}",
181
+ client_id="{{ config['client_id'] }}",
182
+ client_secret="{{ config['client_secret'] }}",
183
+ refresh_token="{{ config['refresh_token'] }}",
184
+ config=config,
185
+ scopes=["scope1", "scope2"],
186
+ refresh_request_body={
187
+ "custom_field": "{{ config['custom_field'] }}",
188
+ "another_field": "{{ config['another_field'] }}",
189
+ "scopes": ["no_override"],
190
+ },
191
+ parameters={},
192
+ )
193
+
194
+ resp.status_code = 200
195
+ mocker.patch.object(resp, "json", return_value={"access_token": "access_token", "expires_in": expires_in_response})
196
+ mocker.patch.object(requests, "request", side_effect=mock_request, autospec=True)
197
+ if raises:
198
+ with pytest.raises(ValueError):
199
+ oauth.get_access_token()
200
+ else:
201
+ token = oauth.get_access_token()
202
+ assert "access_token" == token
203
+ assert oauth.get_token_expiry_date() == pendulum.parse(next_day)
204
+
130
205
 
131
206
  def mock_request(method, url, data):
132
207
  if url == "refresh_end":