airbyte-cdk 0.36.5__py3-none-any.whl → 0.37.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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":