airbyte-cdk 6.18.0.dev2__py3-none-any.whl → 6.18.1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- airbyte_cdk/sources/declarative/auth/oauth.py +26 -0
- airbyte_cdk/sources/declarative/declarative_component_schema.yaml +52 -36
- airbyte_cdk/sources/declarative/decoders/composite_raw_decoder.py +0 -44
- airbyte_cdk/sources/declarative/models/declarative_component_schema.py +44 -20
- airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +25 -11
- airbyte_cdk/sources/declarative/requesters/README.md +57 -0
- airbyte_cdk/sources/declarative/requesters/http_job_repository.py +33 -4
- airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +20 -4
- airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py +34 -4
- airbyte_cdk/sources/types.py +3 -0
- {airbyte_cdk-6.18.0.dev2.dist-info → airbyte_cdk-6.18.1.dist-info}/METADATA +1 -1
- {airbyte_cdk-6.18.0.dev2.dist-info → airbyte_cdk-6.18.1.dist-info}/RECORD +15 -14
- {airbyte_cdk-6.18.0.dev2.dist-info → airbyte_cdk-6.18.1.dist-info}/LICENSE.txt +0 -0
- {airbyte_cdk-6.18.0.dev2.dist-info → airbyte_cdk-6.18.1.dist-info}/WHEEL +0 -0
- {airbyte_cdk-6.18.0.dev2.dist-info → airbyte_cdk-6.18.1.dist-info}/entry_points.txt +0 -0
@@ -56,8 +56,12 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
|
|
56
56
|
token_expiry_is_time_of_expiration: bool = False
|
57
57
|
access_token_name: Union[InterpolatedString, str] = "access_token"
|
58
58
|
access_token_value: Optional[Union[InterpolatedString, str]] = None
|
59
|
+
client_id_name: Union[InterpolatedString, str] = "client_id"
|
60
|
+
client_secret_name: Union[InterpolatedString, str] = "client_secret"
|
59
61
|
expires_in_name: Union[InterpolatedString, str] = "expires_in"
|
62
|
+
refresh_token_name: Union[InterpolatedString, str] = "refresh_token"
|
60
63
|
refresh_request_body: Optional[Mapping[str, Any]] = None
|
64
|
+
grant_type_name: Union[InterpolatedString, str] = "grant_type"
|
61
65
|
grant_type: Union[InterpolatedString, str] = "refresh_token"
|
62
66
|
message_repository: MessageRepository = NoopMessageRepository()
|
63
67
|
|
@@ -69,8 +73,15 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
|
|
69
73
|
)
|
70
74
|
else:
|
71
75
|
self._token_refresh_endpoint = None
|
76
|
+
self._client_id_name = InterpolatedString.create(self.client_id_name, parameters=parameters)
|
72
77
|
self._client_id = InterpolatedString.create(self.client_id, parameters=parameters)
|
78
|
+
self._client_secret_name = InterpolatedString.create(
|
79
|
+
self.client_secret_name, parameters=parameters
|
80
|
+
)
|
73
81
|
self._client_secret = InterpolatedString.create(self.client_secret, parameters=parameters)
|
82
|
+
self._refresh_token_name = InterpolatedString.create(
|
83
|
+
self.refresh_token_name, parameters=parameters
|
84
|
+
)
|
74
85
|
if self.refresh_token is not None:
|
75
86
|
self._refresh_token: Optional[InterpolatedString] = InterpolatedString.create(
|
76
87
|
self.refresh_token, parameters=parameters
|
@@ -83,6 +94,9 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
|
|
83
94
|
self.expires_in_name = InterpolatedString.create(
|
84
95
|
self.expires_in_name, parameters=parameters
|
85
96
|
)
|
97
|
+
self.grant_type_name = InterpolatedString.create(
|
98
|
+
self.grant_type_name, parameters=parameters
|
99
|
+
)
|
86
100
|
self.grant_type = InterpolatedString.create(self.grant_type, parameters=parameters)
|
87
101
|
self._refresh_request_body = InterpolatedMapping(
|
88
102
|
self.refresh_request_body or {}, parameters=parameters
|
@@ -122,18 +136,27 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
|
|
122
136
|
return refresh_token_endpoint
|
123
137
|
return None
|
124
138
|
|
139
|
+
def get_client_id_name(self) -> str:
|
140
|
+
return self._client_id_name.eval(self.config) # type: ignore # eval returns a string in this context
|
141
|
+
|
125
142
|
def get_client_id(self) -> str:
|
126
143
|
client_id: str = self._client_id.eval(self.config)
|
127
144
|
if not client_id:
|
128
145
|
raise ValueError("OAuthAuthenticator was unable to evaluate client_id parameter")
|
129
146
|
return client_id
|
130
147
|
|
148
|
+
def get_client_secret_name(self) -> str:
|
149
|
+
return self._client_secret_name.eval(self.config) # type: ignore # eval returns a string in this context
|
150
|
+
|
131
151
|
def get_client_secret(self) -> str:
|
132
152
|
client_secret: str = self._client_secret.eval(self.config)
|
133
153
|
if not client_secret:
|
134
154
|
raise ValueError("OAuthAuthenticator was unable to evaluate client_secret parameter")
|
135
155
|
return client_secret
|
136
156
|
|
157
|
+
def get_refresh_token_name(self) -> str:
|
158
|
+
return self._refresh_token_name.eval(self.config) # type: ignore # eval returns a string in this context
|
159
|
+
|
137
160
|
def get_refresh_token(self) -> Optional[str]:
|
138
161
|
return None if self._refresh_token is None else str(self._refresh_token.eval(self.config))
|
139
162
|
|
@@ -146,6 +169,9 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
|
|
146
169
|
def get_expires_in_name(self) -> str:
|
147
170
|
return self.expires_in_name.eval(self.config) # type: ignore # eval returns a string in this context
|
148
171
|
|
172
|
+
def get_grant_type_name(self) -> str:
|
173
|
+
return self.grant_type_name.eval(self.config) # type: ignore # eval returns a string in this context
|
174
|
+
|
149
175
|
def get_grant_type(self) -> str:
|
150
176
|
return self.grant_type.eval(self.config) # type: ignore # eval returns a string in this context
|
151
177
|
|
@@ -678,7 +678,7 @@ definitions:
|
|
678
678
|
properties:
|
679
679
|
type:
|
680
680
|
type: string
|
681
|
-
enum: [CustomSchemaNormalization]
|
681
|
+
enum: [ CustomSchemaNormalization ]
|
682
682
|
class_name:
|
683
683
|
title: Class Name
|
684
684
|
description: Fully-qualified name of the class that will be implementing the custom normalization. The format is `source_<name>.<package>.<class_name>`.
|
@@ -1047,6 +1047,13 @@ definitions:
|
|
1047
1047
|
type:
|
1048
1048
|
type: string
|
1049
1049
|
enum: [OAuthAuthenticator]
|
1050
|
+
client_id_name:
|
1051
|
+
title: Client ID Property Name
|
1052
|
+
description: The name of the property to use to refresh the `access_token`.
|
1053
|
+
type: string
|
1054
|
+
default: "client_id"
|
1055
|
+
examples:
|
1056
|
+
- custom_app_id
|
1050
1057
|
client_id:
|
1051
1058
|
title: Client ID
|
1052
1059
|
description: The OAuth client ID. Fill it in the user inputs.
|
@@ -1054,6 +1061,13 @@ definitions:
|
|
1054
1061
|
examples:
|
1055
1062
|
- "{{ config['client_id }}"
|
1056
1063
|
- "{{ config['credentials']['client_id }}"
|
1064
|
+
client_secret_name:
|
1065
|
+
title: Client Secret Property Name
|
1066
|
+
description: The name of the property to use to refresh the `access_token`.
|
1067
|
+
type: string
|
1068
|
+
default: "client_secret"
|
1069
|
+
examples:
|
1070
|
+
- custom_app_secret
|
1057
1071
|
client_secret:
|
1058
1072
|
title: Client Secret
|
1059
1073
|
description: The OAuth client secret. Fill it in the user inputs.
|
@@ -1061,6 +1075,13 @@ definitions:
|
|
1061
1075
|
examples:
|
1062
1076
|
- "{{ config['client_secret }}"
|
1063
1077
|
- "{{ config['credentials']['client_secret }}"
|
1078
|
+
refresh_token_name:
|
1079
|
+
title: Refresh Token Property Name
|
1080
|
+
description: The name of the property to use to refresh the `access_token`.
|
1081
|
+
type: string
|
1082
|
+
default: "refresh_token"
|
1083
|
+
examples:
|
1084
|
+
- custom_app_refresh_value
|
1064
1085
|
refresh_token:
|
1065
1086
|
title: Refresh Token
|
1066
1087
|
description: Credential artifact used to get a new access token.
|
@@ -1094,6 +1115,13 @@ definitions:
|
|
1094
1115
|
default: "expires_in"
|
1095
1116
|
examples:
|
1096
1117
|
- expires_in
|
1118
|
+
grant_type_name:
|
1119
|
+
title: Grant Type Property Name
|
1120
|
+
description: The name of the property to use to refresh the `access_token`.
|
1121
|
+
type: string
|
1122
|
+
default: "grant_type"
|
1123
|
+
examples:
|
1124
|
+
- custom_grant_type
|
1097
1125
|
grant_type:
|
1098
1126
|
title: Grant Type
|
1099
1127
|
description: Specifies the OAuth2 grant type. If set to refresh_token, the refresh_token needs to be provided as well. For client_credentials, only client id and secret are required. Other grant types are not officially supported.
|
@@ -2204,15 +2232,15 @@ definitions:
|
|
2204
2232
|
Pertains to the fields defined by the connector relating to the OAuth flow.
|
2205
2233
|
|
2206
2234
|
Interpolation capabilities:
|
2207
|
-
- The variables placeholders are declared as `{my_var}`.
|
2208
|
-
- The nested resolution variables like `{{my_nested_var}}` is allowed as well.
|
2235
|
+
- The variables placeholders are declared as `{{my_var}}`.
|
2236
|
+
- The nested resolution variables like `{{ {{my_nested_var}} }}` is allowed as well.
|
2209
2237
|
|
2210
2238
|
- The allowed interpolation context is:
|
2211
|
-
+ base64Encoder - encode to `base64`, {
|
2212
|
-
+ base64Decorer - decode from `base64` encoded string, {
|
2213
|
-
+ urlEncoder - encode the input string to URL-like format, {
|
2214
|
-
+ urlDecorer - decode the input url-encoded string into text format, {urlDecoder:https%3A%2F%2Fairbyte.io}
|
2215
|
-
+ codeChallengeS256 - get the `codeChallenge` encoded value to provide additional data-provider specific authorisation values, {
|
2239
|
+
+ base64Encoder - encode to `base64`, {{ {{my_var_a}}:{{my_var_b}} | base64Encoder }}
|
2240
|
+
+ base64Decorer - decode from `base64` encoded string, {{ {{my_string_variable_or_string_value}} | base64Decoder }}
|
2241
|
+
+ urlEncoder - encode the input string to URL-like format, {{ https://test.host.com/endpoint | urlEncoder}}
|
2242
|
+
+ urlDecorer - decode the input url-encoded string into text format, {{ urlDecoder:https%3A%2F%2Fairbyte.io | urlDecoder}}
|
2243
|
+
+ codeChallengeS256 - get the `codeChallenge` encoded value to provide additional data-provider specific authorisation values, {{ {{state_value}} | codeChallengeS256 }}
|
2216
2244
|
|
2217
2245
|
Examples:
|
2218
2246
|
- The TikTok Marketing DeclarativeOAuth spec:
|
@@ -2221,12 +2249,12 @@ definitions:
|
|
2221
2249
|
"type": "object",
|
2222
2250
|
"additionalProperties": false,
|
2223
2251
|
"properties": {
|
2224
|
-
"consent_url": "https://ads.tiktok.com/marketing_api/auth?{client_id_key}={{
|
2252
|
+
"consent_url": "https://ads.tiktok.com/marketing_api/auth?{{client_id_key}}={{client_id_value}}&{{redirect_uri_key}}={{ {{redirect_uri_value}} | urlEncoder}}&{{state_key}}={{state_value}}",
|
2225
2253
|
"access_token_url": "https://business-api.tiktok.com/open_api/v1.3/oauth2/access_token/",
|
2226
2254
|
"access_token_params": {
|
2227
|
-
"{auth_code_key}": "{{
|
2228
|
-
"{client_id_key}": "{{
|
2229
|
-
"{client_secret_key}": "{{
|
2255
|
+
"{{ auth_code_key }}": "{{ auth_code_value }}",
|
2256
|
+
"{{ client_id_key }}": "{{ client_id_value }}",
|
2257
|
+
"{{ client_secret_key }}": "{{ client_secret_value }}"
|
2230
2258
|
},
|
2231
2259
|
"access_token_headers": {
|
2232
2260
|
"Content-Type": "application/json",
|
@@ -2244,7 +2272,6 @@ definitions:
|
|
2244
2272
|
required:
|
2245
2273
|
- consent_url
|
2246
2274
|
- access_token_url
|
2247
|
-
- extract_output
|
2248
2275
|
properties:
|
2249
2276
|
consent_url:
|
2250
2277
|
title: Consent URL
|
@@ -2253,8 +2280,8 @@ definitions:
|
|
2253
2280
|
The DeclarativeOAuth Specific string URL string template to initiate the authentication.
|
2254
2281
|
The placeholders are replaced during the processing to provide neccessary values.
|
2255
2282
|
examples:
|
2256
|
-
- https://domain.host.com/marketing_api/auth?{client_id_key}={{
|
2257
|
-
- https://endpoint.host.com/oauth2/authorize?{client_id_key}={{
|
2283
|
+
- https://domain.host.com/marketing_api/auth?{{client_id_key}}={{client_id_value}}&{{redirect_uri_key}}={{{{redirect_uri_value}} | urlEncoder}}&{{state_key}}={{state_value}}
|
2284
|
+
- https://endpoint.host.com/oauth2/authorize?{{client_id_key}}={{client_id_value}}&{{redirect_uri_key}}={{{{redirect_uri_value}} | urlEncoder}}&{{scope_key}}={{{{scope_value}} | urlEncoder}}&{{state_key}}={{state_value}}&subdomain={{subdomain}}
|
2258
2285
|
scope:
|
2259
2286
|
title: Scopes
|
2260
2287
|
type: string
|
@@ -2269,7 +2296,7 @@ definitions:
|
|
2269
2296
|
The DeclarativeOAuth Specific URL templated string to obtain the `access_token`, `refresh_token` etc.
|
2270
2297
|
The placeholders are replaced during the processing to provide neccessary values.
|
2271
2298
|
examples:
|
2272
|
-
- https://auth.host.com/oauth2/token?{client_id_key}={{
|
2299
|
+
- https://auth.host.com/oauth2/token?{{client_id_key}}={{client_id_value}}&{{client_secret_key}}={{client_secret_value}}&{{auth_code_key}}={{auth_code_value}}&{{redirect_uri_key}}={{{{redirect_uri_value}} | urlEncoder}}
|
2273
2300
|
access_token_headers:
|
2274
2301
|
title: Access Token Headers
|
2275
2302
|
type: object
|
@@ -2278,7 +2305,7 @@ definitions:
|
|
2278
2305
|
The DeclarativeOAuth Specific optional headers to inject while exchanging the `auth_code` to `access_token` during `completeOAuthFlow` step.
|
2279
2306
|
examples:
|
2280
2307
|
- {
|
2281
|
-
"Authorization": "Basic {
|
2308
|
+
"Authorization": "Basic {{ {{ client_id_value }}:{{ client_secret_value }} | base64Encoder }}",
|
2282
2309
|
}
|
2283
2310
|
access_token_params:
|
2284
2311
|
title: Access Token Query Params (Json Encoded)
|
@@ -2289,9 +2316,9 @@ definitions:
|
|
2289
2316
|
When this property is provided, the query params will be encoded as `Json` and included in the outgoing API request.
|
2290
2317
|
examples:
|
2291
2318
|
- {
|
2292
|
-
"{auth_code_key}": "{{
|
2293
|
-
"{client_id_key}": "{{
|
2294
|
-
"{client_secret_key}": "{{
|
2319
|
+
"{{ auth_code_key }}": "{{ auth_code_value }}",
|
2320
|
+
"{{ client_id_key }}": "{{ client_id_value }}",
|
2321
|
+
"{{ client_secret_key }}": "{{ client_secret_value }}",
|
2295
2322
|
}
|
2296
2323
|
extract_output:
|
2297
2324
|
title: Extract Output
|
@@ -2859,7 +2886,6 @@ definitions:
|
|
2859
2886
|
parser:
|
2860
2887
|
anyOf:
|
2861
2888
|
- "$ref": "#/definitions/GzipParser"
|
2862
|
-
- "$ref": "#/definitions/JsonParser"
|
2863
2889
|
- "$ref": "#/definitions/JsonLineParser"
|
2864
2890
|
- "$ref": "#/definitions/CsvParser"
|
2865
2891
|
# PARSERS
|
@@ -2876,21 +2902,6 @@ definitions:
|
|
2876
2902
|
anyOf:
|
2877
2903
|
- "$ref": "#/definitions/JsonLineParser"
|
2878
2904
|
- "$ref": "#/definitions/CsvParser"
|
2879
|
-
- "$ref": "#/definitions/JsonParser"
|
2880
|
-
JsonParser:
|
2881
|
-
title: JsonParser
|
2882
|
-
description: Parser used for parsing str, bytes, or bytearray data and returning data in a dictionary format.
|
2883
|
-
type: object
|
2884
|
-
additionalProperties: true
|
2885
|
-
required:
|
2886
|
-
- type
|
2887
|
-
properties:
|
2888
|
-
type:
|
2889
|
-
type: string
|
2890
|
-
enum: [JsonParser]
|
2891
|
-
encoding:
|
2892
|
-
type: string
|
2893
|
-
default: utf-8
|
2894
2905
|
JsonLineParser:
|
2895
2906
|
type: object
|
2896
2907
|
required:
|
@@ -2993,6 +3004,11 @@ definitions:
|
|
2993
3004
|
anyOf:
|
2994
3005
|
- "$ref": "#/definitions/CustomRequester"
|
2995
3006
|
- "$ref": "#/definitions/HttpRequester"
|
3007
|
+
url_requester:
|
3008
|
+
description: Requester component that describes how to prepare HTTP requests to send to the source API to extract the url from polling response by the completed async job.
|
3009
|
+
anyOf:
|
3010
|
+
- "$ref": "#/definitions/CustomRequester"
|
3011
|
+
- "$ref": "#/definitions/HttpRequester"
|
2996
3012
|
download_requester:
|
2997
3013
|
description: Requester component that describes how to prepare HTTP requests to send to the source API to download the data provided by the completed async job.
|
2998
3014
|
anyOf:
|
@@ -7,12 +7,9 @@ from dataclasses import dataclass
|
|
7
7
|
from io import BufferedIOBase, TextIOWrapper
|
8
8
|
from typing import Any, Generator, MutableMapping, Optional
|
9
9
|
|
10
|
-
import orjson
|
11
10
|
import requests
|
12
11
|
|
13
|
-
from airbyte_cdk.models import FailureType
|
14
12
|
from airbyte_cdk.sources.declarative.decoders.decoder import Decoder
|
15
|
-
from airbyte_cdk.utils import AirbyteTracedException
|
16
13
|
|
17
14
|
logger = logging.getLogger("airbyte")
|
18
15
|
|
@@ -45,47 +42,6 @@ class GzipParser(Parser):
|
|
45
42
|
yield from self.inner_parser.parse(gzipobj)
|
46
43
|
|
47
44
|
|
48
|
-
@dataclass
|
49
|
-
class JsonParser(Parser):
|
50
|
-
encoding: str = "utf-8"
|
51
|
-
|
52
|
-
def parse(self, data: BufferedIOBase) -> Generator[MutableMapping[str, Any], None, None]:
|
53
|
-
"""
|
54
|
-
Attempts to deserialize data using orjson library. As an extra layer of safety we fallback on the json library to deserialize the data.
|
55
|
-
"""
|
56
|
-
raw_data = data.read()
|
57
|
-
|
58
|
-
body_json = self._parse_orjson(raw_data) or self._parse_json(raw_data)
|
59
|
-
|
60
|
-
if body_json is None:
|
61
|
-
raise AirbyteTracedException(
|
62
|
-
message="Response JSON data failed to be parsed. See logs for more information.",
|
63
|
-
internal_message=f"Response JSON data failed to be parsed.",
|
64
|
-
failure_type=FailureType.system_error,
|
65
|
-
)
|
66
|
-
|
67
|
-
if isinstance(body_json, list):
|
68
|
-
yield from body_json
|
69
|
-
else:
|
70
|
-
yield from [body_json]
|
71
|
-
|
72
|
-
def _parse_orjson(self, raw_data: bytes) -> Optional[Any]:
|
73
|
-
try:
|
74
|
-
return orjson.loads(raw_data.decode(self.encoding))
|
75
|
-
except Exception as exc:
|
76
|
-
logger.warning(
|
77
|
-
f"Failed to parse JSON data using orjson library. Falling back to json library. {exc=}"
|
78
|
-
)
|
79
|
-
return None
|
80
|
-
|
81
|
-
def _parse_json(self, raw_data: bytes) -> Optional[Any]:
|
82
|
-
try:
|
83
|
-
return json.loads(raw_data.decode(self.encoding))
|
84
|
-
except Exception as exc:
|
85
|
-
logger.error(f"Failed to parse JSON data using json library. {exc=}")
|
86
|
-
return None
|
87
|
-
|
88
|
-
|
89
45
|
@dataclass
|
90
46
|
class JsonLineParser(Parser):
|
91
47
|
encoding: Optional[str] = "utf-8"
|
@@ -481,12 +481,24 @@ class RefreshTokenUpdater(BaseModel):
|
|
481
481
|
|
482
482
|
class OAuthAuthenticator(BaseModel):
|
483
483
|
type: Literal["OAuthAuthenticator"]
|
484
|
+
client_id_name: Optional[str] = Field(
|
485
|
+
"client_id",
|
486
|
+
description="The name of the property to use to refresh the `access_token`.",
|
487
|
+
examples=["custom_app_id"],
|
488
|
+
title="Client ID Property Name",
|
489
|
+
)
|
484
490
|
client_id: str = Field(
|
485
491
|
...,
|
486
492
|
description="The OAuth client ID. Fill it in the user inputs.",
|
487
493
|
examples=["{{ config['client_id }}", "{{ config['credentials']['client_id }}"],
|
488
494
|
title="Client ID",
|
489
495
|
)
|
496
|
+
client_secret_name: Optional[str] = Field(
|
497
|
+
"client_secret",
|
498
|
+
description="The name of the property to use to refresh the `access_token`.",
|
499
|
+
examples=["custom_app_secret"],
|
500
|
+
title="Client Secret Property Name",
|
501
|
+
)
|
490
502
|
client_secret: str = Field(
|
491
503
|
...,
|
492
504
|
description="The OAuth client secret. Fill it in the user inputs.",
|
@@ -496,6 +508,12 @@ class OAuthAuthenticator(BaseModel):
|
|
496
508
|
],
|
497
509
|
title="Client Secret",
|
498
510
|
)
|
511
|
+
refresh_token_name: Optional[str] = Field(
|
512
|
+
"refresh_token",
|
513
|
+
description="The name of the property to use to refresh the `access_token`.",
|
514
|
+
examples=["custom_app_refresh_value"],
|
515
|
+
title="Refresh Token Property Name",
|
516
|
+
)
|
499
517
|
refresh_token: Optional[str] = Field(
|
500
518
|
None,
|
501
519
|
description="Credential artifact used to get a new access token.",
|
@@ -529,6 +547,12 @@ class OAuthAuthenticator(BaseModel):
|
|
529
547
|
examples=["expires_in"],
|
530
548
|
title="Token Expiry Property Name",
|
531
549
|
)
|
550
|
+
grant_type_name: Optional[str] = Field(
|
551
|
+
"grant_type",
|
552
|
+
description="The name of the property to use to refresh the `access_token`.",
|
553
|
+
examples=["custom_grant_type"],
|
554
|
+
title="Grant Type Property Name",
|
555
|
+
)
|
532
556
|
grant_type: Optional[str] = Field(
|
533
557
|
"refresh_token",
|
534
558
|
description="Specifies the OAuth2 grant type. If set to refresh_token, the refresh_token needs to be provided as well. For client_credentials, only client id and secret are required. Other grant types are not officially supported.",
|
@@ -859,8 +883,8 @@ class OauthConnectorInputSpecification(BaseModel):
|
|
859
883
|
...,
|
860
884
|
description="The DeclarativeOAuth Specific string URL string template to initiate the authentication.\nThe placeholders are replaced during the processing to provide neccessary values.",
|
861
885
|
examples=[
|
862
|
-
"https://domain.host.com/marketing_api/auth?{client_id_key}={{
|
863
|
-
"https://endpoint.host.com/oauth2/authorize?{client_id_key}={{
|
886
|
+
"https://domain.host.com/marketing_api/auth?{{client_id_key}}={{client_id_value}}&{{redirect_uri_key}}={{{{redirect_uri_value}} | urlEncoder}}&{{state_key}}={{state_value}}",
|
887
|
+
"https://endpoint.host.com/oauth2/authorize?{{client_id_key}}={{client_id_value}}&{{redirect_uri_key}}={{{{redirect_uri_value}} | urlEncoder}}&{{scope_key}}={{{{scope_value}} | urlEncoder}}&{{state_key}}={{state_value}}&subdomain={{subdomain}}",
|
864
888
|
],
|
865
889
|
title="Consent URL",
|
866
890
|
)
|
@@ -874,14 +898,18 @@ class OauthConnectorInputSpecification(BaseModel):
|
|
874
898
|
...,
|
875
899
|
description="The DeclarativeOAuth Specific URL templated string to obtain the `access_token`, `refresh_token` etc.\nThe placeholders are replaced during the processing to provide neccessary values.",
|
876
900
|
examples=[
|
877
|
-
"https://auth.host.com/oauth2/token?{client_id_key}={{
|
901
|
+
"https://auth.host.com/oauth2/token?{{client_id_key}}={{client_id_value}}&{{client_secret_key}}={{client_secret_value}}&{{auth_code_key}}={{auth_code_value}}&{{redirect_uri_key}}={{{{redirect_uri_value}} | urlEncoder}}"
|
878
902
|
],
|
879
903
|
title="Access Token URL",
|
880
904
|
)
|
881
905
|
access_token_headers: Optional[Dict[str, Any]] = Field(
|
882
906
|
None,
|
883
907
|
description="The DeclarativeOAuth Specific optional headers to inject while exchanging the `auth_code` to `access_token` during `completeOAuthFlow` step.",
|
884
|
-
examples=[
|
908
|
+
examples=[
|
909
|
+
{
|
910
|
+
"Authorization": "Basic {{ {{ client_id_value }}:{{ client_secret_value }} | base64Encoder }}"
|
911
|
+
}
|
912
|
+
],
|
885
913
|
title="Access Token Headers",
|
886
914
|
)
|
887
915
|
access_token_params: Optional[Dict[str, Any]] = Field(
|
@@ -889,15 +917,15 @@ class OauthConnectorInputSpecification(BaseModel):
|
|
889
917
|
description="The DeclarativeOAuth Specific optional query parameters to inject while exchanging the `auth_code` to `access_token` during `completeOAuthFlow` step.\nWhen this property is provided, the query params will be encoded as `Json` and included in the outgoing API request.",
|
890
918
|
examples=[
|
891
919
|
{
|
892
|
-
"{auth_code_key}": "{{
|
893
|
-
"{client_id_key}": "{{
|
894
|
-
"{client_secret_key}": "{{
|
920
|
+
"{{ auth_code_key }}": "{{ auth_code_value }}",
|
921
|
+
"{{ client_id_key }}": "{{ client_id_value }}",
|
922
|
+
"{{ client_secret_key }}": "{{ client_secret_value }}",
|
895
923
|
}
|
896
924
|
],
|
897
925
|
title="Access Token Query Params (Json Encoded)",
|
898
926
|
)
|
899
|
-
extract_output: List[str] = Field(
|
900
|
-
|
927
|
+
extract_output: Optional[List[str]] = Field(
|
928
|
+
None,
|
901
929
|
description="The DeclarativeOAuth Specific list of strings to indicate which keys should be extracted and returned back to the input config.",
|
902
930
|
examples=[["access_token", "refresh_token", "other_field"]],
|
903
931
|
title="Extract Output",
|
@@ -966,7 +994,7 @@ class OAuthConfigSpecification(BaseModel):
|
|
966
994
|
)
|
967
995
|
oauth_connector_input_specification: Optional[OauthConnectorInputSpecification] = Field(
|
968
996
|
None,
|
969
|
-
description='The DeclarativeOAuth specific blob.\nPertains to the fields defined by the connector relating to the OAuth flow.\n\nInterpolation capabilities:\n- The variables placeholders are declared as `{my_var}`.\n- The nested resolution variables like `{{my_nested_var}}` is allowed as well.\n\n- The allowed interpolation context is:\n + base64Encoder - encode to `base64`, {
|
997
|
+
description='The DeclarativeOAuth specific blob.\nPertains to the fields defined by the connector relating to the OAuth flow.\n\nInterpolation capabilities:\n- The variables placeholders are declared as `{{my_var}}`.\n- The nested resolution variables like `{{ {{my_nested_var}} }}` is allowed as well.\n\n- The allowed interpolation context is:\n + base64Encoder - encode to `base64`, {{ {{my_var_a}}:{{my_var_b}} | base64Encoder }}\n + base64Decorer - decode from `base64` encoded string, {{ {{my_string_variable_or_string_value}} | base64Decoder }}\n + urlEncoder - encode the input string to URL-like format, {{ https://test.host.com/endpoint | urlEncoder}}\n + urlDecorer - decode the input url-encoded string into text format, {{ urlDecoder:https%3A%2F%2Fairbyte.io | urlDecoder}}\n + codeChallengeS256 - get the `codeChallenge` encoded value to provide additional data-provider specific authorisation values, {{ {{state_value}} | codeChallengeS256 }}\n\nExamples:\n - The TikTok Marketing DeclarativeOAuth spec:\n {\n "oauth_connector_input_specification": {\n "type": "object",\n "additionalProperties": false,\n "properties": {\n "consent_url": "https://ads.tiktok.com/marketing_api/auth?{{client_id_key}}={{client_id_value}}&{{redirect_uri_key}}={{ {{redirect_uri_value}} | urlEncoder}}&{{state_key}}={{state_value}}",\n "access_token_url": "https://business-api.tiktok.com/open_api/v1.3/oauth2/access_token/",\n "access_token_params": {\n "{{ auth_code_key }}": "{{ auth_code_value }}",\n "{{ client_id_key }}": "{{ client_id_value }}",\n "{{ client_secret_key }}": "{{ client_secret_value }}"\n },\n "access_token_headers": {\n "Content-Type": "application/json",\n "Accept": "application/json"\n },\n "extract_output": ["data.access_token"],\n "client_id_key": "app_id",\n "client_secret_key": "secret",\n "auth_code_key": "auth_code"\n }\n }\n }',
|
970
998
|
title="DeclarativeOAuth Connector Specification",
|
971
999
|
)
|
972
1000
|
complete_oauth_output_specification: Optional[Dict[str, Any]] = Field(
|
@@ -1173,14 +1201,6 @@ class LegacySessionTokenAuthenticator(BaseModel):
|
|
1173
1201
|
parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters")
|
1174
1202
|
|
1175
1203
|
|
1176
|
-
class JsonParser(BaseModel):
|
1177
|
-
class Config:
|
1178
|
-
extra = Extra.allow
|
1179
|
-
|
1180
|
-
type: Literal["JsonParser"]
|
1181
|
-
encoding: Optional[str] = "utf-8"
|
1182
|
-
|
1183
|
-
|
1184
1204
|
class JsonLineParser(BaseModel):
|
1185
1205
|
type: Literal["JsonLineParser"]
|
1186
1206
|
encoding: Optional[str] = "utf-8"
|
@@ -1579,7 +1599,7 @@ class RecordSelector(BaseModel):
|
|
1579
1599
|
|
1580
1600
|
class GzipParser(BaseModel):
|
1581
1601
|
type: Literal["GzipParser"]
|
1582
|
-
inner_parser: Union[JsonLineParser, CsvParser
|
1602
|
+
inner_parser: Union[JsonLineParser, CsvParser]
|
1583
1603
|
|
1584
1604
|
|
1585
1605
|
class Spec(BaseModel):
|
@@ -1614,7 +1634,7 @@ class CompositeErrorHandler(BaseModel):
|
|
1614
1634
|
|
1615
1635
|
class CompositeRawDecoder(BaseModel):
|
1616
1636
|
type: Literal["CompositeRawDecoder"]
|
1617
|
-
parser: Union[GzipParser,
|
1637
|
+
parser: Union[GzipParser, JsonLineParser, CsvParser]
|
1618
1638
|
|
1619
1639
|
|
1620
1640
|
class DeclarativeSource1(BaseModel):
|
@@ -2058,6 +2078,10 @@ class AsyncRetriever(BaseModel):
|
|
2058
2078
|
...,
|
2059
2079
|
description="Requester component that describes how to prepare HTTP requests to send to the source API to fetch the status of the running async job.",
|
2060
2080
|
)
|
2081
|
+
url_requester: Optional[Union[CustomRequester, HttpRequester]] = Field(
|
2082
|
+
None,
|
2083
|
+
description="Requester component that describes how to prepare HTTP requests to send to the source API to extract the url from polling response by the completed async job.",
|
2084
|
+
)
|
2061
2085
|
download_requester: Union[CustomRequester, HttpRequester] = Field(
|
2062
2086
|
...,
|
2063
2087
|
description="Requester component that describes how to prepare HTTP requests to send to the source API to download the data provided by the completed async job.",
|
@@ -72,7 +72,6 @@ from airbyte_cdk.sources.declarative.decoders.composite_raw_decoder import (
|
|
72
72
|
CsvParser,
|
73
73
|
GzipParser,
|
74
74
|
JsonLineParser,
|
75
|
-
JsonParser,
|
76
75
|
)
|
77
76
|
from airbyte_cdk.sources.declarative.extractors import (
|
78
77
|
DpathExtractor,
|
@@ -248,9 +247,6 @@ from airbyte_cdk.sources.declarative.models.declarative_component_schema import
|
|
248
247
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
249
248
|
JsonLineParser as JsonLineParserModel,
|
250
249
|
)
|
251
|
-
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
252
|
-
JsonParser as JsonParserModel,
|
253
|
-
)
|
254
250
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
255
251
|
JwtAuthenticator as JwtAuthenticatorModel,
|
256
252
|
)
|
@@ -526,7 +522,6 @@ class ModelToComponentFactory:
|
|
526
522
|
JsonDecoderModel: self.create_json_decoder,
|
527
523
|
JsonlDecoderModel: self.create_jsonl_decoder,
|
528
524
|
JsonLineParserModel: self.create_json_line_parser,
|
529
|
-
JsonParserModel: self.create_json_parser,
|
530
525
|
GzipJsonDecoderModel: self.create_gzipjson_decoder,
|
531
526
|
GzipParserModel: self.create_gzip_parser,
|
532
527
|
KeysToLowerModel: self.create_keys_to_lower_transformation,
|
@@ -1753,11 +1748,6 @@ class ModelToComponentFactory:
|
|
1753
1748
|
def create_json_decoder(model: JsonDecoderModel, config: Config, **kwargs: Any) -> JsonDecoder:
|
1754
1749
|
return JsonDecoder(parameters={})
|
1755
1750
|
|
1756
|
-
@staticmethod
|
1757
|
-
def create_json_parser(model: JsonParserModel, config: Config, **kwargs: Any) -> JsonParser:
|
1758
|
-
encoding = model.encoding or "utf-8"
|
1759
|
-
return JsonParser(encoding=encoding)
|
1760
|
-
|
1761
1751
|
@staticmethod
|
1762
1752
|
def create_jsonl_decoder(
|
1763
1753
|
model: JsonlDecoderModel, config: Config, **kwargs: Any
|
@@ -1895,15 +1885,24 @@ class ModelToComponentFactory:
|
|
1895
1885
|
expires_in_name=InterpolatedString.create(
|
1896
1886
|
model.expires_in_name or "expires_in", parameters=model.parameters or {}
|
1897
1887
|
).eval(config),
|
1888
|
+
client_id_name=InterpolatedString.create(
|
1889
|
+
model.client_id_name or "client_id", parameters=model.parameters or {}
|
1890
|
+
).eval(config),
|
1898
1891
|
client_id=InterpolatedString.create(
|
1899
1892
|
model.client_id, parameters=model.parameters or {}
|
1900
1893
|
).eval(config),
|
1894
|
+
client_secret_name=InterpolatedString.create(
|
1895
|
+
model.client_secret_name or "client_secret", parameters=model.parameters or {}
|
1896
|
+
).eval(config),
|
1901
1897
|
client_secret=InterpolatedString.create(
|
1902
1898
|
model.client_secret, parameters=model.parameters or {}
|
1903
1899
|
).eval(config),
|
1904
1900
|
access_token_config_path=model.refresh_token_updater.access_token_config_path,
|
1905
1901
|
refresh_token_config_path=model.refresh_token_updater.refresh_token_config_path,
|
1906
1902
|
token_expiry_date_config_path=model.refresh_token_updater.token_expiry_date_config_path,
|
1903
|
+
grant_type_name=InterpolatedString.create(
|
1904
|
+
model.grant_type_name or "grant_type", parameters=model.parameters or {}
|
1905
|
+
).eval(config),
|
1907
1906
|
grant_type=InterpolatedString.create(
|
1908
1907
|
model.grant_type or "refresh_token", parameters=model.parameters or {}
|
1909
1908
|
).eval(config),
|
@@ -1921,11 +1920,15 @@ class ModelToComponentFactory:
|
|
1921
1920
|
return DeclarativeOauth2Authenticator( # type: ignore
|
1922
1921
|
access_token_name=model.access_token_name or "access_token",
|
1923
1922
|
access_token_value=model.access_token_value,
|
1923
|
+
client_id_name=model.client_id_name or "client_id",
|
1924
1924
|
client_id=model.client_id,
|
1925
|
+
client_secret_name=model.client_secret_name or "client_secret",
|
1925
1926
|
client_secret=model.client_secret,
|
1926
1927
|
expires_in_name=model.expires_in_name or "expires_in",
|
1928
|
+
grant_type_name=model.grant_type_name or "grant_type",
|
1927
1929
|
grant_type=model.grant_type or "refresh_token",
|
1928
1930
|
refresh_request_body=model.refresh_request_body,
|
1931
|
+
refresh_token_name=model.refresh_token_name or "refresh_token",
|
1929
1932
|
refresh_token=model.refresh_token,
|
1930
1933
|
scopes=model.scopes,
|
1931
1934
|
token_expiry_date=model.token_expiry_date,
|
@@ -2297,7 +2300,7 @@ class ModelToComponentFactory:
|
|
2297
2300
|
extractor=download_extractor,
|
2298
2301
|
name=name,
|
2299
2302
|
record_filter=None,
|
2300
|
-
transformations=
|
2303
|
+
transformations=transformations,
|
2301
2304
|
schema_normalization=TypeTransformer(TransformConfig.NoTransform),
|
2302
2305
|
config=config,
|
2303
2306
|
parameters={},
|
@@ -2334,6 +2337,16 @@ class ModelToComponentFactory:
|
|
2334
2337
|
if model.delete_requester
|
2335
2338
|
else None
|
2336
2339
|
)
|
2340
|
+
url_requester = (
|
2341
|
+
self._create_component_from_model(
|
2342
|
+
model=model.url_requester,
|
2343
|
+
decoder=decoder,
|
2344
|
+
config=config,
|
2345
|
+
name=f"job extract_url - {name}",
|
2346
|
+
)
|
2347
|
+
if model.url_requester
|
2348
|
+
else None
|
2349
|
+
)
|
2337
2350
|
status_extractor = self._create_component_from_model(
|
2338
2351
|
model=model.status_extractor, decoder=decoder, config=config, name=name
|
2339
2352
|
)
|
@@ -2344,6 +2357,7 @@ class ModelToComponentFactory:
|
|
2344
2357
|
creation_requester=creation_requester,
|
2345
2358
|
polling_requester=polling_requester,
|
2346
2359
|
download_retriever=download_retriever,
|
2360
|
+
url_requester=url_requester,
|
2347
2361
|
abort_requester=abort_requester,
|
2348
2362
|
delete_requester=delete_requester,
|
2349
2363
|
status_extractor=status_extractor,
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# AsyncHttpJobRepository sequence diagram
|
2
|
+
|
3
|
+
- Components marked as optional are not required and can be ignored.
|
4
|
+
- if `url_requester` is not provided, `urls_extractor` will get urls from the `polling_job_response`
|
5
|
+
- interpolation_context, e.g. `create_job_response` or `polling_job_response` can be obtained from stream_slice
|
6
|
+
|
7
|
+
|
8
|
+
```mermaid
|
9
|
+
---
|
10
|
+
title: AsyncHttpJobRepository Sequence Diagram
|
11
|
+
---
|
12
|
+
sequenceDiagram
|
13
|
+
participant AsyncHttpJobRepository as AsyncOrchestrator
|
14
|
+
participant CreationRequester as creation_requester
|
15
|
+
participant PollingRequester as polling_requester
|
16
|
+
participant UrlRequester as url_requester (Optional)
|
17
|
+
participant DownloadRetriever as download_retriever
|
18
|
+
participant AbortRequester as abort_requester (Optional)
|
19
|
+
participant DeleteRequester as delete_requester (Optional)
|
20
|
+
participant Reporting Server as Async Reporting Server
|
21
|
+
|
22
|
+
AsyncHttpJobRepository ->> CreationRequester: Initiate job creation
|
23
|
+
CreationRequester ->> Reporting Server: Create job request
|
24
|
+
Reporting Server -->> CreationRequester: Job ID response
|
25
|
+
CreationRequester -->> AsyncHttpJobRepository: Job ID
|
26
|
+
|
27
|
+
loop Poll for job status
|
28
|
+
AsyncHttpJobRepository ->> PollingRequester: Check job status
|
29
|
+
PollingRequester ->> Reporting Server: Status request (interpolation_context: `create_job_response`)
|
30
|
+
Reporting Server -->> PollingRequester: Status response
|
31
|
+
PollingRequester -->> AsyncHttpJobRepository: Job status
|
32
|
+
end
|
33
|
+
|
34
|
+
alt Status: Ready
|
35
|
+
AsyncHttpJobRepository ->> UrlRequester: Request download URLs (if applicable)
|
36
|
+
UrlRequester ->> Reporting Server: URL request (interpolation_context: `polling_job_response`)
|
37
|
+
Reporting Server -->> UrlRequester: Download URLs
|
38
|
+
UrlRequester -->> AsyncHttpJobRepository: Download URLs
|
39
|
+
|
40
|
+
AsyncHttpJobRepository ->> DownloadRetriever: Download reports
|
41
|
+
DownloadRetriever ->> Reporting Server: Retrieve report data (interpolation_context: `url`)
|
42
|
+
Reporting Server -->> DownloadRetriever: Report data
|
43
|
+
DownloadRetriever -->> AsyncHttpJobRepository: Report data
|
44
|
+
else Status: Failed
|
45
|
+
AsyncHttpJobRepository ->> AbortRequester: Send abort request
|
46
|
+
AbortRequester ->> Reporting Server: Abort job
|
47
|
+
Reporting Server -->> AbortRequester: Abort confirmation
|
48
|
+
AbortRequester -->> AsyncHttpJobRepository: Confirmation
|
49
|
+
end
|
50
|
+
|
51
|
+
AsyncHttpJobRepository ->> DeleteRequester: Send delete job request
|
52
|
+
DeleteRequester ->> Reporting Server: Delete job
|
53
|
+
Reporting Server -->> DeleteRequester: Deletion confirmation
|
54
|
+
DeleteRequester -->> AsyncHttpJobRepository: Confirmation
|
55
|
+
|
56
|
+
|
57
|
+
```
|
@@ -31,6 +31,10 @@ LOGGER = logging.getLogger("airbyte")
|
|
31
31
|
|
32
32
|
@dataclass
|
33
33
|
class AsyncHttpJobRepository(AsyncJobRepository):
|
34
|
+
"""
|
35
|
+
See Readme file for more details about flow.
|
36
|
+
"""
|
37
|
+
|
34
38
|
creation_requester: Requester
|
35
39
|
polling_requester: Requester
|
36
40
|
download_retriever: SimpleRetriever
|
@@ -44,6 +48,9 @@ class AsyncHttpJobRepository(AsyncJobRepository):
|
|
44
48
|
record_extractor: RecordExtractor = field(
|
45
49
|
init=False, repr=False, default_factory=lambda: ResponseToFileExtractor({})
|
46
50
|
)
|
51
|
+
url_requester: Optional[Requester] = (
|
52
|
+
None # use it in case polling_requester provides some <id> and extra request is needed to obtain list of urls to download from
|
53
|
+
)
|
47
54
|
|
48
55
|
def __post_init__(self) -> None:
|
49
56
|
self._create_job_response_by_id: Dict[str, Response] = {}
|
@@ -186,10 +193,13 @@ class AsyncHttpJobRepository(AsyncJobRepository):
|
|
186
193
|
|
187
194
|
"""
|
188
195
|
|
189
|
-
for url in self.
|
190
|
-
|
191
|
-
|
192
|
-
|
196
|
+
for url in self._get_download_url(job):
|
197
|
+
job_slice = job.job_parameters()
|
198
|
+
stream_slice = StreamSlice(
|
199
|
+
partition=job_slice.partition,
|
200
|
+
cursor_slice=job_slice.cursor_slice,
|
201
|
+
extra_fields={**job_slice.extra_fields, "url": url},
|
202
|
+
)
|
193
203
|
for message in self.download_retriever.read_records({}, stream_slice):
|
194
204
|
if isinstance(message, Record):
|
195
205
|
yield message.data
|
@@ -226,3 +236,22 @@ class AsyncHttpJobRepository(AsyncJobRepository):
|
|
226
236
|
cursor_slice={},
|
227
237
|
)
|
228
238
|
return stream_slice
|
239
|
+
|
240
|
+
def _get_download_url(self, job: AsyncJob) -> Iterable[str]:
|
241
|
+
if not self.url_requester:
|
242
|
+
url_response = self._polling_job_response_by_id[job.api_job_id()]
|
243
|
+
else:
|
244
|
+
stream_slice: StreamSlice = StreamSlice(
|
245
|
+
partition={
|
246
|
+
"polling_job_response": self._polling_job_response_by_id[job.api_job_id()]
|
247
|
+
},
|
248
|
+
cursor_slice={},
|
249
|
+
)
|
250
|
+
url_response = self.url_requester.send_request(stream_slice=stream_slice) # type: ignore # we expect url_requester to always be presented, otherwise raise an exception as we cannot proceed with the report
|
251
|
+
if not url_response:
|
252
|
+
raise AirbyteTracedException(
|
253
|
+
internal_message="Always expect a response or an exception from url_requester",
|
254
|
+
failure_type=FailureType.system_error,
|
255
|
+
)
|
256
|
+
|
257
|
+
yield from self.urls_extractor.extract_records(url_response) # type: ignore # we expect urls_extractor to always return list of strings
|
@@ -81,10 +81,10 @@ class AbstractOauth2Authenticator(AuthBase):
|
|
81
81
|
Override to define additional parameters
|
82
82
|
"""
|
83
83
|
payload: MutableMapping[str, Any] = {
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
84
|
+
self.get_grant_type_name(): self.get_grant_type(),
|
85
|
+
self.get_client_id_name(): self.get_client_id(),
|
86
|
+
self.get_client_secret_name(): self.get_client_secret(),
|
87
|
+
self.get_refresh_token_name(): self.get_refresh_token(),
|
88
88
|
}
|
89
89
|
|
90
90
|
if self.get_scopes():
|
@@ -206,14 +206,26 @@ class AbstractOauth2Authenticator(AuthBase):
|
|
206
206
|
def get_token_refresh_endpoint(self) -> Optional[str]:
|
207
207
|
"""Returns the endpoint to refresh the access token"""
|
208
208
|
|
209
|
+
@abstractmethod
|
210
|
+
def get_client_id_name(self) -> str:
|
211
|
+
"""The client id name to authenticate"""
|
212
|
+
|
209
213
|
@abstractmethod
|
210
214
|
def get_client_id(self) -> str:
|
211
215
|
"""The client id to authenticate"""
|
212
216
|
|
217
|
+
@abstractmethod
|
218
|
+
def get_client_secret_name(self) -> str:
|
219
|
+
"""The client secret name to authenticate"""
|
220
|
+
|
213
221
|
@abstractmethod
|
214
222
|
def get_client_secret(self) -> str:
|
215
223
|
"""The client secret to authenticate"""
|
216
224
|
|
225
|
+
@abstractmethod
|
226
|
+
def get_refresh_token_name(self) -> str:
|
227
|
+
"""The refresh token name to authenticate"""
|
228
|
+
|
217
229
|
@abstractmethod
|
218
230
|
def get_refresh_token(self) -> Optional[str]:
|
219
231
|
"""The token used to refresh the access token when it expires"""
|
@@ -246,6 +258,10 @@ class AbstractOauth2Authenticator(AuthBase):
|
|
246
258
|
def get_grant_type(self) -> str:
|
247
259
|
"""Returns grant_type specified for requesting access_token"""
|
248
260
|
|
261
|
+
@abstractmethod
|
262
|
+
def get_grant_type_name(self) -> str:
|
263
|
+
"""Returns grant_type specified name for requesting access_token"""
|
264
|
+
|
249
265
|
@property
|
250
266
|
@abstractmethod
|
251
267
|
def access_token(self) -> str:
|
@@ -30,12 +30,16 @@ class Oauth2Authenticator(AbstractOauth2Authenticator):
|
|
30
30
|
client_id: str,
|
31
31
|
client_secret: str,
|
32
32
|
refresh_token: str,
|
33
|
+
client_id_name: str = "client_id",
|
34
|
+
client_secret_name: str = "client_secret",
|
35
|
+
refresh_token_name: str = "refresh_token",
|
33
36
|
scopes: List[str] | None = None,
|
34
37
|
token_expiry_date: pendulum.DateTime | None = None,
|
35
38
|
token_expiry_date_format: str | None = None,
|
36
39
|
access_token_name: str = "access_token",
|
37
40
|
expires_in_name: str = "expires_in",
|
38
41
|
refresh_request_body: Mapping[str, Any] | None = None,
|
42
|
+
grant_type_name: str = "grant_type",
|
39
43
|
grant_type: str = "refresh_token",
|
40
44
|
token_expiry_is_time_of_expiration: bool = False,
|
41
45
|
refresh_token_error_status_codes: Tuple[int, ...] = (),
|
@@ -43,13 +47,17 @@ class Oauth2Authenticator(AbstractOauth2Authenticator):
|
|
43
47
|
refresh_token_error_values: Tuple[str, ...] = (),
|
44
48
|
):
|
45
49
|
self._token_refresh_endpoint = token_refresh_endpoint
|
50
|
+
self._client_secret_name = client_secret_name
|
46
51
|
self._client_secret = client_secret
|
52
|
+
self._client_id_name = client_id_name
|
47
53
|
self._client_id = client_id
|
54
|
+
self._refresh_token_name = refresh_token_name
|
48
55
|
self._refresh_token = refresh_token
|
49
56
|
self._scopes = scopes
|
50
57
|
self._access_token_name = access_token_name
|
51
58
|
self._expires_in_name = expires_in_name
|
52
59
|
self._refresh_request_body = refresh_request_body
|
60
|
+
self._grant_type_name = grant_type_name
|
53
61
|
self._grant_type = grant_type
|
54
62
|
|
55
63
|
self._token_expiry_date = token_expiry_date or pendulum.now().subtract(days=1) # type: ignore [no-untyped-call]
|
@@ -63,12 +71,21 @@ class Oauth2Authenticator(AbstractOauth2Authenticator):
|
|
63
71
|
def get_token_refresh_endpoint(self) -> str:
|
64
72
|
return self._token_refresh_endpoint
|
65
73
|
|
74
|
+
def get_client_id_name(self) -> str:
|
75
|
+
return self._client_id_name
|
76
|
+
|
66
77
|
def get_client_id(self) -> str:
|
67
78
|
return self._client_id
|
68
79
|
|
80
|
+
def get_client_secret_name(self) -> str:
|
81
|
+
return self._client_secret_name
|
82
|
+
|
69
83
|
def get_client_secret(self) -> str:
|
70
84
|
return self._client_secret
|
71
85
|
|
86
|
+
def get_refresh_token_name(self) -> str:
|
87
|
+
return self._refresh_token_name
|
88
|
+
|
72
89
|
def get_refresh_token(self) -> str:
|
73
90
|
return self._refresh_token
|
74
91
|
|
@@ -84,6 +101,9 @@ class Oauth2Authenticator(AbstractOauth2Authenticator):
|
|
84
101
|
def get_refresh_request_body(self) -> Mapping[str, Any]:
|
85
102
|
return self._refresh_request_body # type: ignore [return-value]
|
86
103
|
|
104
|
+
def get_grant_type_name(self) -> str:
|
105
|
+
return self._grant_type_name
|
106
|
+
|
87
107
|
def get_grant_type(self) -> str:
|
88
108
|
return self._grant_type
|
89
109
|
|
@@ -129,8 +149,11 @@ class SingleUseRefreshTokenOauth2Authenticator(Oauth2Authenticator):
|
|
129
149
|
expires_in_name: str = "expires_in",
|
130
150
|
refresh_token_name: str = "refresh_token",
|
131
151
|
refresh_request_body: Mapping[str, Any] | None = None,
|
152
|
+
grant_type_name: str = "grant_type",
|
132
153
|
grant_type: str = "refresh_token",
|
154
|
+
client_id_name: str = "client_id",
|
133
155
|
client_id: Optional[str] = None,
|
156
|
+
client_secret_name: str = "client_secret",
|
134
157
|
client_secret: Optional[str] = None,
|
135
158
|
access_token_config_path: Sequence[str] = ("credentials", "access_token"),
|
136
159
|
refresh_token_config_path: Sequence[str] = ("credentials", "refresh_token"),
|
@@ -174,23 +197,30 @@ class SingleUseRefreshTokenOauth2Authenticator(Oauth2Authenticator):
|
|
174
197
|
("credentials", "client_secret"),
|
175
198
|
)
|
176
199
|
)
|
200
|
+
self._client_id_name = client_id_name
|
201
|
+
self._client_secret_name = client_secret_name
|
177
202
|
self._access_token_config_path = access_token_config_path
|
178
203
|
self._refresh_token_config_path = refresh_token_config_path
|
179
204
|
self._token_expiry_date_config_path = token_expiry_date_config_path
|
180
205
|
self._token_expiry_date_format = token_expiry_date_format
|
181
206
|
self._refresh_token_name = refresh_token_name
|
207
|
+
self._grant_type_name = grant_type_name
|
182
208
|
self._connector_config = connector_config
|
183
209
|
self.__message_repository = message_repository
|
184
210
|
super().__init__(
|
185
|
-
token_refresh_endpoint,
|
186
|
-
self.
|
187
|
-
self.
|
188
|
-
self.
|
211
|
+
token_refresh_endpoint=token_refresh_endpoint,
|
212
|
+
client_id_name=self._client_id_name,
|
213
|
+
client_id=self.get_client_id(),
|
214
|
+
client_secret_name=self._client_secret_name,
|
215
|
+
client_secret=self.get_client_secret(),
|
216
|
+
refresh_token=self.get_refresh_token(),
|
217
|
+
refresh_token_name=self._refresh_token_name,
|
189
218
|
scopes=scopes,
|
190
219
|
token_expiry_date=self.get_token_expiry_date(),
|
191
220
|
access_token_name=access_token_name,
|
192
221
|
expires_in_name=expires_in_name,
|
193
222
|
refresh_request_body=refresh_request_body,
|
223
|
+
grant_type_name=self._grant_type_name,
|
194
224
|
grant_type=grant_type,
|
195
225
|
token_expiry_date_format=token_expiry_date_format,
|
196
226
|
token_expiry_is_time_of_expiration=token_expiry_is_time_of_expiration,
|
airbyte_cdk/sources/types.py
CHANGED
@@ -53,7 +53,7 @@ airbyte_cdk/sources/declarative/async_job/timer.py,sha256=Fb8P72CQ7jIzJyzMSSNuBf
|
|
53
53
|
airbyte_cdk/sources/declarative/auth/__init__.py,sha256=e2CRrcBWGhz3sQu3Oh34d1riEIwXipGS8hrSB1pu0Oo,284
|
54
54
|
airbyte_cdk/sources/declarative/auth/declarative_authenticator.py,sha256=nf-OmRUHYG4ORBwyb5CANzuHEssE-oNmL-Lccn41Td8,1099
|
55
55
|
airbyte_cdk/sources/declarative/auth/jwt.py,sha256=7r5q1zOekjw8kEmEk1oUyovzVt3cbD6BuFnRILeLZi8,8250
|
56
|
-
airbyte_cdk/sources/declarative/auth/oauth.py,sha256=
|
56
|
+
airbyte_cdk/sources/declarative/auth/oauth.py,sha256=EDx-tY0ZhBXe6yVHkqjUAqMWJAl4rQXjQbr4rElt_Ds,10555
|
57
57
|
airbyte_cdk/sources/declarative/auth/selective_authenticator.py,sha256=qGwC6YsCldr1bIeKG6Qo-A9a5cTdHw-vcOn3OtQrS4c,1540
|
58
58
|
airbyte_cdk/sources/declarative/auth/token.py,sha256=r4u3WXyVa7WmiSZ9-eZXlrUI-pS0D4YWJnwjLzwV-Fk,11210
|
59
59
|
airbyte_cdk/sources/declarative/auth/token_provider.py,sha256=9oq3dcBPAPwXSfkISjhA05dMhIzxaDQTmwOydBrnsMk,3028
|
@@ -66,11 +66,11 @@ airbyte_cdk/sources/declarative/concurrent_declarative_source.py,sha256=tSTCSmyM
|
|
66
66
|
airbyte_cdk/sources/declarative/datetime/__init__.py,sha256=l9LG7Qm6e5r_qgqfVKnx3mXYtg1I9MmMjomVIPfU4XA,177
|
67
67
|
airbyte_cdk/sources/declarative/datetime/datetime_parser.py,sha256=SX9JjdesN1edN2WVUVMzU_ptqp2QB1OnsnjZ4mwcX7w,2579
|
68
68
|
airbyte_cdk/sources/declarative/datetime/min_max_datetime.py,sha256=0BHBtDNQZfvwM45-tY5pNlTcKAFSGGNxemoi0Jic-0E,5785
|
69
|
-
airbyte_cdk/sources/declarative/declarative_component_schema.yaml,sha256=
|
69
|
+
airbyte_cdk/sources/declarative/declarative_component_schema.yaml,sha256=00X3palFmBp9WqQugXgtzFVn7s17KYWKTrn83ObmBzc,134673
|
70
70
|
airbyte_cdk/sources/declarative/declarative_source.py,sha256=nF7wBqFd3AQmEKAm4CnIo29CJoQL562cJGSCeL8U8bA,1531
|
71
71
|
airbyte_cdk/sources/declarative/declarative_stream.py,sha256=JRyNeOIpsFu4ztVZsN6sncqUEIqIE-bUkD2TPgbMgk0,10375
|
72
72
|
airbyte_cdk/sources/declarative/decoders/__init__.py,sha256=edGj4fGxznBk4xzRQyCA1rGfbpqe7z-RE0K3kQQWbgA,858
|
73
|
-
airbyte_cdk/sources/declarative/decoders/composite_raw_decoder.py,sha256
|
73
|
+
airbyte_cdk/sources/declarative/decoders/composite_raw_decoder.py,sha256=-aO3ujXX9YTP2ZDvI2BP-x0VOKdAq2TlHo4zG8DCTlY,2748
|
74
74
|
airbyte_cdk/sources/declarative/decoders/decoder.py,sha256=sl-Gt8lXi7yD2Q-sD8je5QS2PbgrgsYjxRLWsay7DMc,826
|
75
75
|
airbyte_cdk/sources/declarative/decoders/json_decoder.py,sha256=qdbjeR6RffKaah_iWvMsOcDolYuxJY5DaI3b9AMTZXg,3327
|
76
76
|
airbyte_cdk/sources/declarative/decoders/noop_decoder.py,sha256=iZh0yKY_JzgBnJWiubEusf5c0o6Khd-8EWFWT-8EgFo,542
|
@@ -106,12 +106,12 @@ airbyte_cdk/sources/declarative/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW
|
|
106
106
|
airbyte_cdk/sources/declarative/migrations/legacy_to_per_partition_state_migration.py,sha256=iemy3fKLczcU0-Aor7tx5jcT6DRedKMqyK7kCOp01hg,3924
|
107
107
|
airbyte_cdk/sources/declarative/migrations/state_migration.py,sha256=KWPjealMLKSMtajXgkdGgKg7EmTLR-CqqD7UIh0-eDU,794
|
108
108
|
airbyte_cdk/sources/declarative/models/__init__.py,sha256=nUFxNCiKeYRVXuZEKA7GD-lTHxsiKcQ8FitZjKhPIvE,100
|
109
|
-
airbyte_cdk/sources/declarative/models/declarative_component_schema.py,sha256=
|
109
|
+
airbyte_cdk/sources/declarative/models/declarative_component_schema.py,sha256=1wrAW9XeEq2xdUAAkmHcelka-LOwyYb-izRcACkNPKM,94915
|
110
110
|
airbyte_cdk/sources/declarative/parsers/__init__.py,sha256=ZnqYNxHsKCgO38IwB34RQyRMXTs4GTvlRi3ImKnIioo,61
|
111
111
|
airbyte_cdk/sources/declarative/parsers/custom_exceptions.py,sha256=Rir9_z3Kcd5Es0-LChrzk-0qubAsiK_RSEnLmK2OXm8,553
|
112
112
|
airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py,sha256=CXwTfD3wSQq3okcqwigpprbHhSURUokh4GK2OmOyKC8,9132
|
113
113
|
airbyte_cdk/sources/declarative/parsers/manifest_reference_resolver.py,sha256=IWUOdF03o-aQn0Occo1BJCxU0Pz-QILk5L67nzw2thw,6803
|
114
|
-
airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py,sha256=
|
114
|
+
airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py,sha256=NElLb7eLDVmxDgtTX9fQ-ZPrpfH3d7RpMDaQiLtvuuQ,110550
|
115
115
|
airbyte_cdk/sources/declarative/partition_routers/__init__.py,sha256=HJ-Syp3p7RpyR_OK0X_a2kSyISfu3W-PKrRI16iY0a8,957
|
116
116
|
airbyte_cdk/sources/declarative/partition_routers/async_job_partition_router.py,sha256=n82J15S8bjeMZ5uROu--P3hnbQoxkY5v7RPHYx7g7ro,2929
|
117
117
|
airbyte_cdk/sources/declarative/partition_routers/cartesian_product_stream_slicer.py,sha256=c5cuVFM6NFkuQqG8Z5IwkBuwDrvXZN1CunUOM_L0ezg,6892
|
@@ -119,6 +119,7 @@ airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py,sha25
|
|
119
119
|
airbyte_cdk/sources/declarative/partition_routers/partition_router.py,sha256=YyEIzdmLd1FjbVP3QbQ2VFCLW_P-OGbVh6VpZShp54k,2218
|
120
120
|
airbyte_cdk/sources/declarative/partition_routers/single_partition_router.py,sha256=SKzKjSyfccq4dxGIh-J6ejrgkCHzaiTIazmbmeQiRD4,1942
|
121
121
|
airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py,sha256=5bgXoJfBg_6i53krQMptAGb50XB5XoVfqQxKQhlLtBA,15383
|
122
|
+
airbyte_cdk/sources/declarative/requesters/README.md,sha256=WabtHlwHg_J34aL1Kwm8vboYqBaSgsFjq10qR-P2sx8,2658
|
122
123
|
airbyte_cdk/sources/declarative/requesters/__init__.py,sha256=d7a3OoHbqaJDyyPli3nqqJ2yAW_SLX6XDaBAKOwvpxw,364
|
123
124
|
airbyte_cdk/sources/declarative/requesters/error_handlers/__init__.py,sha256=SkEDcJxlT1683rNx93K9whoS0OyUukkuOfToGtgpF58,776
|
124
125
|
airbyte_cdk/sources/declarative/requesters/error_handlers/backoff_strategies/__init__.py,sha256=1WZdpFmWL6W_Dko0qjflTaKIWeqt8jHT-D6HcujIp3s,884
|
@@ -133,7 +134,7 @@ airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.
|
|
133
134
|
airbyte_cdk/sources/declarative/requesters/error_handlers/default_http_response_filter.py,sha256=q0YkeYUUWO6iErUy0vjqiOkhg8_9d5YcCmtlpXAJJ9E,1314
|
134
135
|
airbyte_cdk/sources/declarative/requesters/error_handlers/error_handler.py,sha256=Tan66odx8VHzfdyyXMQkXz2pJYksllGqvxmpoajgcK4,669
|
135
136
|
airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py,sha256=vhWsEKNTYEzZ4gerhHqnDNKu4wGIP485NAzpSQ5DRZg,7941
|
136
|
-
airbyte_cdk/sources/declarative/requesters/http_job_repository.py,sha256=
|
137
|
+
airbyte_cdk/sources/declarative/requesters/http_job_repository.py,sha256=3GtOefPH08evlSUxaILkiKLTHbIspFY4qd5B3ZqNE60,10063
|
137
138
|
airbyte_cdk/sources/declarative/requesters/http_requester.py,sha256=RqYPkgJFAWfcZBTc-JBcGHPm4JL1ZQOhs9GKU4MP2eE,14723
|
138
139
|
airbyte_cdk/sources/declarative/requesters/paginators/__init__.py,sha256=uArbKs9JKNCt7t9tZoeWwjDpyI1HoPp29FNW0JzvaEM,644
|
139
140
|
airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py,sha256=FnSl3qPvv5wD6ieAI2Ic5c4dqBk-3fRe4tCaWzq3YwM,11840
|
@@ -287,12 +288,12 @@ airbyte_cdk/sources/streams/http/http.py,sha256=JAMpiTdS9HFNOlwayWNvQdxoqs2rpW9w
|
|
287
288
|
airbyte_cdk/sources/streams/http/http_client.py,sha256=tDE0ROtxjGMVphvsw8INvGMtZ97hIF-v47pZ3jIyiwc,23011
|
288
289
|
airbyte_cdk/sources/streams/http/rate_limiting.py,sha256=IwdjrHKUnU97XO4qONgYRv4YYW51xQ8SJm4WLafXDB8,6351
|
289
290
|
airbyte_cdk/sources/streams/http/requests_native_auth/__init__.py,sha256=RN0D3nOX1xLgwEwKWu6pkGy3XqBFzKSNZ8Lf6umU2eY,413
|
290
|
-
airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py,sha256=
|
291
|
+
airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py,sha256=xX-qKbMVN-OTwNda-B6cVQqnQrNDGBnvavAcSvXK2wU,11179
|
291
292
|
airbyte_cdk/sources/streams/http/requests_native_auth/abstract_token.py,sha256=Y3n7J-sk5yGjv_OxtY6Z6k0PEsFZmtIRi-x0KCbaHdA,1010
|
292
|
-
airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py,sha256=
|
293
|
+
airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py,sha256=wjsp4Xf8u3AnY7ZmTTGfLnfj6ztDBogR5biURkqcwCA,16156
|
293
294
|
airbyte_cdk/sources/streams/http/requests_native_auth/token.py,sha256=h5PTzcdH-RQLeCg7xZ45w_484OPUDSwNWl_iMJQmZoI,2526
|
294
295
|
airbyte_cdk/sources/streams/utils/__init__.py,sha256=4Hw-PX1-VgESLF16cDdvuYCzGJtHntThLF4qIiULWeo,61
|
295
|
-
airbyte_cdk/sources/types.py,sha256=
|
296
|
+
airbyte_cdk/sources/types.py,sha256=nLPkTpyfGV4E6e99qcBWX4r8C3fE4I8Fvgx2EjvT9ic,5005
|
296
297
|
airbyte_cdk/sources/utils/__init__.py,sha256=TTN6VUxVy6Is8BhYQZR5pxJGQh8yH4duXh4O1TiMiEY,118
|
297
298
|
airbyte_cdk/sources/utils/casing.py,sha256=QC-gV1O4e8DR4-bhdXieUPKm_JamzslVyfABLYYRSXA,256
|
298
299
|
airbyte_cdk/sources/utils/record_helper.py,sha256=jeB0mucudzna7Zvj-pCBbwFrbLJ36SlAWZTh5O4Fb9Y,2168
|
@@ -342,8 +343,8 @@ airbyte_cdk/utils/slice_hasher.py,sha256=-pHexlNYoWYPnXNH-M7HEbjmeJe9Zk7SJijdQ7d
|
|
342
343
|
airbyte_cdk/utils/spec_schema_transformations.py,sha256=-5HTuNsnDBAhj-oLeQXwpTGA0HdcjFOf2zTEMUTTg_Y,816
|
343
344
|
airbyte_cdk/utils/stream_status_utils.py,sha256=ZmBoiy5HVbUEHAMrUONxZvxnvfV9CesmQJLDTAIWnWw,1171
|
344
345
|
airbyte_cdk/utils/traced_exception.py,sha256=C8uIBuCL_E4WnBAOPSxBicD06JAldoN9fGsQDp463OY,6292
|
345
|
-
airbyte_cdk-6.18.
|
346
|
-
airbyte_cdk-6.18.
|
347
|
-
airbyte_cdk-6.18.
|
348
|
-
airbyte_cdk-6.18.
|
349
|
-
airbyte_cdk-6.18.
|
346
|
+
airbyte_cdk-6.18.1.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
|
347
|
+
airbyte_cdk-6.18.1.dist-info/METADATA,sha256=OMpca59Gc1MJOlwEgvDJX0uwp7skSel83qkbtcan6hE,6000
|
348
|
+
airbyte_cdk-6.18.1.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
349
|
+
airbyte_cdk-6.18.1.dist-info/entry_points.txt,sha256=fj-e3PAQvsxsQzyyq8UkG1k8spunWnD4BAH2AwlR6NM,95
|
350
|
+
airbyte_cdk-6.18.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|