airbyte-cdk 6.31.1__py3-none-any.whl → 6.31.2.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.
- airbyte_cdk/cli/source_declarative_manifest/_run.py +3 -9
- airbyte_cdk/connector_builder/connector_builder_handler.py +2 -3
- airbyte_cdk/sources/declarative/async_job/job_orchestrator.py +4 -4
- airbyte_cdk/sources/declarative/auth/jwt.py +11 -17
- airbyte_cdk/sources/declarative/auth/oauth.py +23 -89
- airbyte_cdk/sources/declarative/auth/token.py +3 -8
- airbyte_cdk/sources/declarative/auth/token_provider.py +5 -4
- airbyte_cdk/sources/declarative/checks/check_dynamic_stream.py +9 -19
- airbyte_cdk/sources/declarative/concurrent_declarative_source.py +43 -134
- airbyte_cdk/sources/declarative/declarative_component_schema.yaml +16 -55
- airbyte_cdk/sources/declarative/declarative_stream.py +1 -3
- airbyte_cdk/sources/declarative/extractors/record_filter.py +5 -3
- airbyte_cdk/sources/declarative/incremental/__init__.py +0 -6
- airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py +7 -6
- airbyte_cdk/sources/declarative/incremental/global_substream_cursor.py +0 -3
- airbyte_cdk/sources/declarative/incremental/per_partition_cursor.py +3 -35
- airbyte_cdk/sources/declarative/manifest_declarative_source.py +7 -15
- airbyte_cdk/sources/declarative/models/declarative_component_schema.py +15 -45
- airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +64 -343
- airbyte_cdk/sources/declarative/partition_routers/async_job_partition_router.py +5 -5
- airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py +4 -2
- airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py +15 -55
- airbyte_cdk/sources/declarative/requesters/error_handlers/composite_error_handler.py +0 -22
- airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py +4 -4
- airbyte_cdk/sources/declarative/requesters/http_requester.py +5 -1
- airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +6 -5
- airbyte_cdk/sources/declarative/requesters/request_option.py +83 -4
- airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py +7 -6
- airbyte_cdk/sources/declarative/retrievers/async_retriever.py +12 -6
- airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +5 -2
- airbyte_cdk/sources/declarative/schema/__init__.py +0 -2
- airbyte_cdk/sources/declarative/schema/dynamic_schema_loader.py +5 -44
- airbyte_cdk/sources/http_logger.py +1 -1
- airbyte_cdk/sources/streams/concurrent/cursor.py +57 -51
- airbyte_cdk/sources/streams/concurrent/state_converters/datetime_stream_state_converter.py +13 -22
- airbyte_cdk/sources/streams/core.py +6 -6
- airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +62 -231
- airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py +88 -171
- airbyte_cdk/sources/types.py +2 -4
- airbyte_cdk/sources/utils/transform.py +2 -23
- airbyte_cdk/test/utils/manifest_only_fixtures.py +2 -1
- airbyte_cdk/utils/mapping_helpers.py +86 -27
- airbyte_cdk/utils/slice_hasher.py +1 -8
- {airbyte_cdk-6.31.1.dist-info → airbyte_cdk-6.31.2.dev0.dist-info}/METADATA +6 -6
- {airbyte_cdk-6.31.1.dist-info → airbyte_cdk-6.31.2.dev0.dist-info}/RECORD +48 -54
- {airbyte_cdk-6.31.1.dist-info → airbyte_cdk-6.31.2.dev0.dist-info}/WHEEL +1 -1
- airbyte_cdk/sources/declarative/incremental/concurrent_partition_cursor.py +0 -400
- airbyte_cdk/sources/declarative/parsers/custom_code_compiler.py +0 -143
- airbyte_cdk/sources/streams/concurrent/clamping.py +0 -99
- airbyte_cdk/sources/streams/concurrent/cursor_types.py +0 -32
- airbyte_cdk/utils/datetime_helpers.py +0 -499
- airbyte_cdk-6.31.1.dist-info/LICENSE_SHORT +0 -1
- {airbyte_cdk-6.31.1.dist-info → airbyte_cdk-6.31.2.dev0.dist-info}/LICENSE.txt +0 -0
- {airbyte_cdk-6.31.1.dist-info → airbyte_cdk-6.31.2.dev0.dist-info}/entry_points.txt +0 -0
@@ -21,6 +21,7 @@ import pkgutil
|
|
21
21
|
import sys
|
22
22
|
import traceback
|
23
23
|
from collections.abc import Mapping
|
24
|
+
from datetime import datetime
|
24
25
|
from pathlib import Path
|
25
26
|
from typing import Any, cast
|
26
27
|
|
@@ -43,7 +44,6 @@ from airbyte_cdk.sources.declarative.concurrent_declarative_source import (
|
|
43
44
|
)
|
44
45
|
from airbyte_cdk.sources.declarative.yaml_declarative_source import YamlDeclarativeSource
|
45
46
|
from airbyte_cdk.sources.source import TState
|
46
|
-
from airbyte_cdk.utils.datetime_helpers import ab_datetime_now
|
47
47
|
|
48
48
|
|
49
49
|
class SourceLocalYaml(YamlDeclarativeSource):
|
@@ -101,7 +101,7 @@ def _get_local_yaml_source(args: list[str]) -> SourceLocalYaml:
|
|
101
101
|
type=Type.TRACE,
|
102
102
|
trace=AirbyteTraceMessage(
|
103
103
|
type=TraceType.ERROR,
|
104
|
-
emitted_at=
|
104
|
+
emitted_at=int(datetime.now().timestamp() * 1000),
|
105
105
|
error=AirbyteErrorTraceMessage(
|
106
106
|
message=f"Error starting the sync. This could be due to an invalid configuration or catalog. Please contact Support for assistance. Error: {error}",
|
107
107
|
stack_trace=traceback.format_exc(),
|
@@ -171,12 +171,6 @@ def create_declarative_source(
|
|
171
171
|
"Invalid config: `__injected_declarative_manifest` should be provided at the root "
|
172
172
|
f"of the config but config only has keys: {list(config.keys() if config else [])}"
|
173
173
|
)
|
174
|
-
if not isinstance(config["__injected_declarative_manifest"], dict):
|
175
|
-
raise ValueError(
|
176
|
-
"Invalid config: `__injected_declarative_manifest` should be a dictionary, "
|
177
|
-
f"but got type: {type(config['__injected_declarative_manifest'])}"
|
178
|
-
)
|
179
|
-
|
180
174
|
return ConcurrentDeclarativeSource(
|
181
175
|
config=config,
|
182
176
|
catalog=catalog,
|
@@ -191,7 +185,7 @@ def create_declarative_source(
|
|
191
185
|
type=Type.TRACE,
|
192
186
|
trace=AirbyteTraceMessage(
|
193
187
|
type=TraceType.ERROR,
|
194
|
-
emitted_at=
|
188
|
+
emitted_at=int(datetime.now().timestamp() * 1000),
|
195
189
|
error=AirbyteErrorTraceMessage(
|
196
190
|
message=f"Error starting the sync. This could be due to an invalid configuration or catalog. Please contact Support for assistance. Error: {error}",
|
197
191
|
stack_trace=traceback.format_exc(),
|
@@ -3,6 +3,7 @@
|
|
3
3
|
#
|
4
4
|
|
5
5
|
import dataclasses
|
6
|
+
from datetime import datetime
|
6
7
|
from typing import Any, List, Mapping
|
7
8
|
|
8
9
|
from airbyte_cdk.connector_builder.message_grouper import MessageGrouper
|
@@ -20,7 +21,6 @@ from airbyte_cdk.sources.declarative.parsers.model_to_component_factory import (
|
|
20
21
|
ModelToComponentFactory,
|
21
22
|
)
|
22
23
|
from airbyte_cdk.utils.airbyte_secrets_utils import filter_secrets
|
23
|
-
from airbyte_cdk.utils.datetime_helpers import ab_datetime_now
|
24
24
|
from airbyte_cdk.utils.traced_exception import AirbyteTracedException
|
25
25
|
|
26
26
|
DEFAULT_MAXIMUM_NUMBER_OF_PAGES_PER_SLICE = 5
|
@@ -52,7 +52,6 @@ def get_limits(config: Mapping[str, Any]) -> TestReadLimits:
|
|
52
52
|
def create_source(config: Mapping[str, Any], limits: TestReadLimits) -> ManifestDeclarativeSource:
|
53
53
|
manifest = config["__injected_declarative_manifest"]
|
54
54
|
return ManifestDeclarativeSource(
|
55
|
-
config=config,
|
56
55
|
emit_connector_builder_messages=True,
|
57
56
|
source_config=manifest,
|
58
57
|
component_factory=ModelToComponentFactory(
|
@@ -114,4 +113,4 @@ def resolve_manifest(source: ManifestDeclarativeSource) -> AirbyteMessage:
|
|
114
113
|
|
115
114
|
|
116
115
|
def _emitted_at() -> int:
|
117
|
-
return
|
116
|
+
return int(datetime.now().timestamp()) * 1000
|
@@ -482,16 +482,16 @@ class AsyncJobOrchestrator:
|
|
482
482
|
and exception.failure_type == FailureType.config_error
|
483
483
|
)
|
484
484
|
|
485
|
-
def fetch_records(self,
|
485
|
+
def fetch_records(self, partition: AsyncPartition) -> Iterable[Mapping[str, Any]]:
|
486
486
|
"""
|
487
|
-
Fetches records from the given jobs.
|
487
|
+
Fetches records from the given partition's jobs.
|
488
488
|
|
489
489
|
Args:
|
490
|
-
|
490
|
+
partition (AsyncPartition): The partition containing the jobs.
|
491
491
|
|
492
492
|
Yields:
|
493
493
|
Iterable[Mapping[str, Any]]: The fetched records from the jobs.
|
494
494
|
"""
|
495
|
-
for job in
|
495
|
+
for job in partition.jobs:
|
496
496
|
yield from self._job_repository.fetch_records(job)
|
497
497
|
self._job_repository.delete(job)
|
@@ -3,7 +3,6 @@
|
|
3
3
|
#
|
4
4
|
|
5
5
|
import base64
|
6
|
-
import json
|
7
6
|
from dataclasses import InitVar, dataclass
|
8
7
|
from datetime import datetime
|
9
8
|
from typing import Any, Mapping, Optional, Union
|
@@ -105,21 +104,21 @@ class JwtAuthenticator(DeclarativeAuthenticator):
|
|
105
104
|
)
|
106
105
|
|
107
106
|
def _get_jwt_headers(self) -> dict[str, Any]:
|
108
|
-
"""
|
107
|
+
""" "
|
109
108
|
Builds and returns the headers used when signing the JWT.
|
110
109
|
"""
|
111
|
-
headers = self._additional_jwt_headers.eval(self.config
|
110
|
+
headers = self._additional_jwt_headers.eval(self.config)
|
112
111
|
if any(prop in headers for prop in ["kid", "alg", "typ", "cty"]):
|
113
112
|
raise ValueError(
|
114
113
|
"'kid', 'alg', 'typ', 'cty' are reserved headers and should not be set as part of 'additional_jwt_headers'"
|
115
114
|
)
|
116
115
|
|
117
116
|
if self._kid:
|
118
|
-
headers["kid"] = self._kid.eval(self.config
|
117
|
+
headers["kid"] = self._kid.eval(self.config)
|
119
118
|
if self._typ:
|
120
|
-
headers["typ"] = self._typ.eval(self.config
|
119
|
+
headers["typ"] = self._typ.eval(self.config)
|
121
120
|
if self._cty:
|
122
|
-
headers["cty"] = self._cty.eval(self.config
|
121
|
+
headers["cty"] = self._cty.eval(self.config)
|
123
122
|
headers["alg"] = self._algorithm
|
124
123
|
return headers
|
125
124
|
|
@@ -131,19 +130,18 @@ class JwtAuthenticator(DeclarativeAuthenticator):
|
|
131
130
|
exp = now + self._token_duration if isinstance(self._token_duration, int) else now
|
132
131
|
nbf = now
|
133
132
|
|
134
|
-
payload = self._additional_jwt_payload.eval(self.config
|
133
|
+
payload = self._additional_jwt_payload.eval(self.config)
|
135
134
|
if any(prop in payload for prop in ["iss", "sub", "aud", "iat", "exp", "nbf"]):
|
136
135
|
raise ValueError(
|
137
136
|
"'iss', 'sub', 'aud', 'iat', 'exp', 'nbf' are reserved properties and should not be set as part of 'additional_jwt_payload'"
|
138
137
|
)
|
139
138
|
|
140
139
|
if self._iss:
|
141
|
-
payload["iss"] = self._iss.eval(self.config
|
140
|
+
payload["iss"] = self._iss.eval(self.config)
|
142
141
|
if self._sub:
|
143
|
-
payload["sub"] = self._sub.eval(self.config
|
142
|
+
payload["sub"] = self._sub.eval(self.config)
|
144
143
|
if self._aud:
|
145
|
-
payload["aud"] = self._aud.eval(self.config
|
146
|
-
|
144
|
+
payload["aud"] = self._aud.eval(self.config)
|
147
145
|
payload["iat"] = now
|
148
146
|
payload["exp"] = exp
|
149
147
|
payload["nbf"] = nbf
|
@@ -153,7 +151,7 @@ class JwtAuthenticator(DeclarativeAuthenticator):
|
|
153
151
|
"""
|
154
152
|
Returns the secret key used to sign the JWT.
|
155
153
|
"""
|
156
|
-
secret_key: str = self._secret_key.eval(self.config
|
154
|
+
secret_key: str = self._secret_key.eval(self.config)
|
157
155
|
return (
|
158
156
|
base64.b64encode(secret_key.encode()).decode()
|
159
157
|
if self._base64_encode_secret_key
|
@@ -178,11 +176,7 @@ class JwtAuthenticator(DeclarativeAuthenticator):
|
|
178
176
|
"""
|
179
177
|
Returns the header prefix to be used when attaching the token to the request.
|
180
178
|
"""
|
181
|
-
return (
|
182
|
-
self._header_prefix.eval(self.config, json_loads=json.loads)
|
183
|
-
if self._header_prefix
|
184
|
-
else None
|
185
|
-
)
|
179
|
+
return self._header_prefix.eval(self.config) if self._header_prefix else None
|
186
180
|
|
187
181
|
@property
|
188
182
|
def auth_header(self) -> str:
|
@@ -3,11 +3,11 @@
|
|
3
3
|
#
|
4
4
|
|
5
5
|
from dataclasses import InitVar, dataclass, field
|
6
|
-
from
|
7
|
-
|
6
|
+
from typing import Any, List, Mapping, Optional, Union
|
7
|
+
|
8
|
+
import pendulum
|
8
9
|
|
9
10
|
from airbyte_cdk.sources.declarative.auth.declarative_authenticator import DeclarativeAuthenticator
|
10
|
-
from airbyte_cdk.sources.declarative.interpolation.interpolated_boolean import InterpolatedBoolean
|
11
11
|
from airbyte_cdk.sources.declarative.interpolation.interpolated_mapping import InterpolatedMapping
|
12
12
|
from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
|
13
13
|
from airbyte_cdk.sources.message import MessageRepository, NoopMessageRepository
|
@@ -17,7 +17,6 @@ from airbyte_cdk.sources.streams.http.requests_native_auth.abstract_oauth import
|
|
17
17
|
from airbyte_cdk.sources.streams.http.requests_native_auth.oauth import (
|
18
18
|
SingleUseRefreshTokenOauth2Authenticator,
|
19
19
|
)
|
20
|
-
from airbyte_cdk.utils.datetime_helpers import AirbyteDateTime, ab_datetime_now, ab_datetime_parse
|
21
20
|
|
22
21
|
|
23
22
|
@dataclass
|
@@ -45,15 +44,15 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
|
|
45
44
|
message_repository (MessageRepository): the message repository used to emit logs on HTTP requests
|
46
45
|
"""
|
47
46
|
|
47
|
+
client_id: Union[InterpolatedString, str]
|
48
|
+
client_secret: Union[InterpolatedString, str]
|
48
49
|
config: Mapping[str, Any]
|
49
50
|
parameters: InitVar[Mapping[str, Any]]
|
50
|
-
client_id: Optional[Union[InterpolatedString, str]] = None
|
51
|
-
client_secret: Optional[Union[InterpolatedString, str]] = None
|
52
51
|
token_refresh_endpoint: Optional[Union[InterpolatedString, str]] = None
|
53
52
|
refresh_token: Optional[Union[InterpolatedString, str]] = None
|
54
53
|
scopes: Optional[List[str]] = None
|
55
54
|
token_expiry_date: Optional[Union[InterpolatedString, str]] = None
|
56
|
-
_token_expiry_date: Optional[
|
55
|
+
_token_expiry_date: Optional[pendulum.DateTime] = field(init=False, repr=False, default=None)
|
57
56
|
token_expiry_date_format: Optional[str] = None
|
58
57
|
token_expiry_is_time_of_expiration: bool = False
|
59
58
|
access_token_name: Union[InterpolatedString, str] = "access_token"
|
@@ -67,8 +66,6 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
|
|
67
66
|
grant_type_name: Union[InterpolatedString, str] = "grant_type"
|
68
67
|
grant_type: Union[InterpolatedString, str] = "refresh_token"
|
69
68
|
message_repository: MessageRepository = NoopMessageRepository()
|
70
|
-
profile_assertion: Optional[DeclarativeAuthenticator] = None
|
71
|
-
use_profile_assertion: Optional[Union[InterpolatedBoolean, str, bool]] = False
|
72
69
|
|
73
70
|
def __post_init__(self, parameters: Mapping[str, Any]) -> None:
|
74
71
|
super().__init__()
|
@@ -79,19 +76,11 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
|
|
79
76
|
else:
|
80
77
|
self._token_refresh_endpoint = None
|
81
78
|
self._client_id_name = InterpolatedString.create(self.client_id_name, parameters=parameters)
|
82
|
-
self._client_id = (
|
83
|
-
InterpolatedString.create(self.client_id, parameters=parameters)
|
84
|
-
if self.client_id
|
85
|
-
else self.client_id
|
86
|
-
)
|
79
|
+
self._client_id = InterpolatedString.create(self.client_id, parameters=parameters)
|
87
80
|
self._client_secret_name = InterpolatedString.create(
|
88
81
|
self.client_secret_name, parameters=parameters
|
89
82
|
)
|
90
|
-
self._client_secret = (
|
91
|
-
InterpolatedString.create(self.client_secret, parameters=parameters)
|
92
|
-
if self.client_secret
|
93
|
-
else self.client_secret
|
94
|
-
)
|
83
|
+
self._client_secret = InterpolatedString.create(self.client_secret, parameters=parameters)
|
95
84
|
self._refresh_token_name = InterpolatedString.create(
|
96
85
|
self.refresh_token_name, parameters=parameters
|
97
86
|
)
|
@@ -110,43 +99,22 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
|
|
110
99
|
self.grant_type_name = InterpolatedString.create(
|
111
100
|
self.grant_type_name, parameters=parameters
|
112
101
|
)
|
113
|
-
self.grant_type = InterpolatedString.create(
|
114
|
-
"urn:ietf:params:oauth:grant-type:jwt-bearer"
|
115
|
-
if self.use_profile_assertion
|
116
|
-
else self.grant_type,
|
117
|
-
parameters=parameters,
|
118
|
-
)
|
102
|
+
self.grant_type = InterpolatedString.create(self.grant_type, parameters=parameters)
|
119
103
|
self._refresh_request_body = InterpolatedMapping(
|
120
104
|
self.refresh_request_body or {}, parameters=parameters
|
121
105
|
)
|
122
106
|
self._refresh_request_headers = InterpolatedMapping(
|
123
107
|
self.refresh_request_headers or {}, parameters=parameters
|
124
108
|
)
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
):
|
130
|
-
self._token_expiry_date = ab_datetime_parse(self.token_expiry_date)
|
131
|
-
else:
|
132
|
-
self._token_expiry_date = (
|
133
|
-
ab_datetime_parse(
|
134
|
-
InterpolatedString.create(
|
135
|
-
self.token_expiry_date, parameters=parameters
|
136
|
-
).eval(self.config)
|
137
|
-
)
|
138
|
-
if self.token_expiry_date
|
139
|
-
else ab_datetime_now() - timedelta(days=1)
|
109
|
+
self._token_expiry_date: pendulum.DateTime = (
|
110
|
+
pendulum.parse(
|
111
|
+
InterpolatedString.create(self.token_expiry_date, parameters=parameters).eval(
|
112
|
+
self.config
|
140
113
|
)
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
InterpolatedBoolean(self.use_profile_assertion, parameters=parameters)
|
145
|
-
if isinstance(self.use_profile_assertion, str)
|
146
|
-
else self.use_profile_assertion
|
114
|
+
) # type: ignore # pendulum.parse returns a datetime in this context
|
115
|
+
if self.token_expiry_date
|
116
|
+
else pendulum.now().subtract(days=1) # type: ignore # substract does not have type hints
|
147
117
|
)
|
148
|
-
self.assertion_name = "assertion"
|
149
|
-
|
150
118
|
if self.access_token_value is not None:
|
151
119
|
self._access_token_value = InterpolatedString.create(
|
152
120
|
self.access_token_value, parameters=parameters
|
@@ -158,20 +126,9 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
|
|
158
126
|
self._access_token_value if self.access_token_value else None
|
159
127
|
)
|
160
128
|
|
161
|
-
if not self.use_profile_assertion and any(
|
162
|
-
client_creds is None for client_creds in [self.client_id, self.client_secret]
|
163
|
-
):
|
164
|
-
raise ValueError(
|
165
|
-
"OAuthAuthenticator configuration error: Both 'client_id' and 'client_secret' are required for the "
|
166
|
-
"basic OAuth flow."
|
167
|
-
)
|
168
|
-
if self.profile_assertion is None and self.use_profile_assertion:
|
169
|
-
raise ValueError(
|
170
|
-
"OAuthAuthenticator configuration error: 'profile_assertion' is required when using the profile assertion flow."
|
171
|
-
)
|
172
129
|
if self.get_grant_type() == "refresh_token" and self._refresh_token is None:
|
173
130
|
raise ValueError(
|
174
|
-
"OAuthAuthenticator
|
131
|
+
"OAuthAuthenticator needs a refresh_token parameter if grant_type is set to `refresh_token`"
|
175
132
|
)
|
176
133
|
|
177
134
|
def get_token_refresh_endpoint(self) -> Optional[str]:
|
@@ -188,21 +145,19 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
|
|
188
145
|
return self._client_id_name.eval(self.config) # type: ignore # eval returns a string in this context
|
189
146
|
|
190
147
|
def get_client_id(self) -> str:
|
191
|
-
client_id = self._client_id.eval(self.config)
|
148
|
+
client_id: str = self._client_id.eval(self.config)
|
192
149
|
if not client_id:
|
193
150
|
raise ValueError("OAuthAuthenticator was unable to evaluate client_id parameter")
|
194
|
-
return client_id
|
151
|
+
return client_id
|
195
152
|
|
196
153
|
def get_client_secret_name(self) -> str:
|
197
154
|
return self._client_secret_name.eval(self.config) # type: ignore # eval returns a string in this context
|
198
155
|
|
199
156
|
def get_client_secret(self) -> str:
|
200
|
-
client_secret = (
|
201
|
-
self._client_secret.eval(self.config) if self._client_secret else self._client_secret
|
202
|
-
)
|
157
|
+
client_secret: str = self._client_secret.eval(self.config)
|
203
158
|
if not client_secret:
|
204
159
|
raise ValueError("OAuthAuthenticator was unable to evaluate client_secret parameter")
|
205
|
-
return client_secret
|
160
|
+
return client_secret
|
206
161
|
|
207
162
|
def get_refresh_token_name(self) -> str:
|
208
163
|
return self._refresh_token_name.eval(self.config) # type: ignore # eval returns a string in this context
|
@@ -231,33 +186,12 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
|
|
231
186
|
def get_refresh_request_headers(self) -> Mapping[str, Any]:
|
232
187
|
return self._refresh_request_headers.eval(self.config)
|
233
188
|
|
234
|
-
def get_token_expiry_date(self) ->
|
235
|
-
return self._token_expiry_date # type: ignore # _token_expiry_date is
|
189
|
+
def get_token_expiry_date(self) -> pendulum.DateTime:
|
190
|
+
return self._token_expiry_date # type: ignore # _token_expiry_date is a pendulum.DateTime. It is never None despite what mypy thinks
|
236
191
|
|
237
192
|
def set_token_expiry_date(self, value: Union[str, int]) -> None:
|
238
193
|
self._token_expiry_date = self._parse_token_expiration_date(value)
|
239
194
|
|
240
|
-
def get_assertion_name(self) -> str:
|
241
|
-
return self.assertion_name
|
242
|
-
|
243
|
-
def get_assertion(self) -> str:
|
244
|
-
if self.profile_assertion is None:
|
245
|
-
raise ValueError("profile_assertion is not set")
|
246
|
-
return self.profile_assertion.token
|
247
|
-
|
248
|
-
def build_refresh_request_body(self) -> Mapping[str, Any]:
|
249
|
-
"""
|
250
|
-
Returns the request body to set on the refresh request
|
251
|
-
|
252
|
-
Override to define additional parameters
|
253
|
-
"""
|
254
|
-
if self.use_profile_assertion:
|
255
|
-
return {
|
256
|
-
self.get_grant_type_name(): self.get_grant_type(),
|
257
|
-
self.get_assertion_name(): self.get_assertion(),
|
258
|
-
}
|
259
|
-
return super().build_refresh_request_body()
|
260
|
-
|
261
195
|
@property
|
262
196
|
def access_token(self) -> str:
|
263
197
|
if self._access_token is None:
|
@@ -5,7 +5,7 @@
|
|
5
5
|
import base64
|
6
6
|
import logging
|
7
7
|
from dataclasses import InitVar, dataclass
|
8
|
-
from typing import Any, Mapping, Union
|
8
|
+
from typing import Any, Mapping, MutableMapping, Union
|
9
9
|
|
10
10
|
import requests
|
11
11
|
from cachetools import TTLCache, cached
|
@@ -45,11 +45,6 @@ class ApiKeyAuthenticator(DeclarativeAuthenticator):
|
|
45
45
|
config: Config
|
46
46
|
parameters: InitVar[Mapping[str, Any]]
|
47
47
|
|
48
|
-
def __post_init__(self, parameters: Mapping[str, Any]) -> None:
|
49
|
-
self._field_name = InterpolatedString.create(
|
50
|
-
self.request_option.field_name, parameters=parameters
|
51
|
-
)
|
52
|
-
|
53
48
|
@property
|
54
49
|
def auth_header(self) -> str:
|
55
50
|
options = self._get_request_options(RequestOptionType.header)
|
@@ -60,9 +55,9 @@ class ApiKeyAuthenticator(DeclarativeAuthenticator):
|
|
60
55
|
return self.token_provider.get_token()
|
61
56
|
|
62
57
|
def _get_request_options(self, option_type: RequestOptionType) -> Mapping[str, Any]:
|
63
|
-
options = {}
|
58
|
+
options: MutableMapping[str, Any] = {}
|
64
59
|
if self.request_option.inject_into == option_type:
|
65
|
-
|
60
|
+
self.request_option.inject_into_request(options, self.token, self.config)
|
66
61
|
return options
|
67
62
|
|
68
63
|
def get_request_params(self) -> Mapping[str, Any]:
|
@@ -9,7 +9,9 @@ from dataclasses import InitVar, dataclass, field
|
|
9
9
|
from typing import Any, List, Mapping, Optional, Union
|
10
10
|
|
11
11
|
import dpath
|
12
|
+
import pendulum
|
12
13
|
from isodate import Duration
|
14
|
+
from pendulum import DateTime
|
13
15
|
|
14
16
|
from airbyte_cdk.sources.declarative.decoders.decoder import Decoder
|
15
17
|
from airbyte_cdk.sources.declarative.decoders.json_decoder import JsonDecoder
|
@@ -19,7 +21,6 @@ from airbyte_cdk.sources.declarative.requesters.requester import Requester
|
|
19
21
|
from airbyte_cdk.sources.http_logger import format_http_message
|
20
22
|
from airbyte_cdk.sources.message import MessageRepository, NoopMessageRepository
|
21
23
|
from airbyte_cdk.sources.types import Config
|
22
|
-
from airbyte_cdk.utils.datetime_helpers import AirbyteDateTime, ab_datetime_now
|
23
24
|
|
24
25
|
|
25
26
|
class TokenProvider:
|
@@ -37,7 +38,7 @@ class SessionTokenProvider(TokenProvider):
|
|
37
38
|
message_repository: MessageRepository = NoopMessageRepository()
|
38
39
|
decoder: Decoder = field(default_factory=lambda: JsonDecoder(parameters={}))
|
39
40
|
|
40
|
-
_next_expiration_time: Optional[
|
41
|
+
_next_expiration_time: Optional[DateTime] = None
|
41
42
|
_token: Optional[str] = None
|
42
43
|
|
43
44
|
def get_token(self) -> str:
|
@@ -47,7 +48,7 @@ class SessionTokenProvider(TokenProvider):
|
|
47
48
|
return self._token
|
48
49
|
|
49
50
|
def _refresh_if_necessary(self) -> None:
|
50
|
-
if self._next_expiration_time is None or self._next_expiration_time <
|
51
|
+
if self._next_expiration_time is None or self._next_expiration_time < pendulum.now():
|
51
52
|
self._refresh()
|
52
53
|
|
53
54
|
def _refresh(self) -> None:
|
@@ -64,7 +65,7 @@ class SessionTokenProvider(TokenProvider):
|
|
64
65
|
raise ReadException("Failed to get session token, response got ignored by requester")
|
65
66
|
session_token = dpath.get(next(self.decoder.decode(response)), self.session_token_path)
|
66
67
|
if self.expiration_duration is not None:
|
67
|
-
self._next_expiration_time =
|
68
|
+
self._next_expiration_time = pendulum.now() + self.expiration_duration
|
68
69
|
self._token = session_token # type: ignore # Returned decoded response will be Mapping and therefore session_token will be str or None
|
69
70
|
|
70
71
|
|
@@ -21,12 +21,8 @@ class CheckDynamicStream(ConnectionChecker):
|
|
21
21
|
stream_count (int): numbers of streams to check
|
22
22
|
"""
|
23
23
|
|
24
|
-
# TODO: Add field stream_names to check_connection for static streams
|
25
|
-
# https://github.com/airbytehq/airbyte-python-cdk/pull/293#discussion_r1934933483
|
26
|
-
|
27
24
|
stream_count: int
|
28
25
|
parameters: InitVar[Mapping[str, Any]]
|
29
|
-
use_check_availability: bool = True
|
30
26
|
|
31
27
|
def __post_init__(self, parameters: Mapping[str, Any]) -> None:
|
32
28
|
self._parameters = parameters
|
@@ -35,27 +31,21 @@ class CheckDynamicStream(ConnectionChecker):
|
|
35
31
|
self, source: AbstractSource, logger: logging.Logger, config: Mapping[str, Any]
|
36
32
|
) -> Tuple[bool, Any]:
|
37
33
|
streams = source.streams(config=config)
|
38
|
-
|
39
34
|
if len(streams) == 0:
|
40
35
|
return False, f"No streams to connect to from source {source}"
|
41
|
-
if not self.use_check_availability:
|
42
|
-
return True, None
|
43
|
-
|
44
|
-
availability_strategy = HttpAvailabilityStrategy()
|
45
36
|
|
46
|
-
|
47
|
-
|
37
|
+
for stream_index in range(min(self.stream_count, len(streams))):
|
38
|
+
stream = streams[stream_index]
|
39
|
+
availability_strategy = HttpAvailabilityStrategy()
|
40
|
+
try:
|
48
41
|
stream_is_available, reason = availability_strategy.check_availability(
|
49
42
|
stream, logger
|
50
43
|
)
|
51
44
|
if not stream_is_available:
|
52
|
-
logger.warning(f"Stream {stream.name} is not available: {reason}")
|
53
45
|
return False, reason
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
return False, error_message
|
60
|
-
|
46
|
+
except Exception as error:
|
47
|
+
logger.error(
|
48
|
+
f"Encountered an error trying to connect to stream {stream.name}. Error: \n {traceback.format_exc()}"
|
49
|
+
)
|
50
|
+
return False, f"Unable to connect to stream {stream.name} - {error}"
|
61
51
|
return True, None
|