airbyte-cdk 6.48.2.post1.dev14733922675__py3-none-any.whl → 6.48.4__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/airbyte_cdk/_secrets.py +328 -54
- airbyte_cdk/cli/airbyte_cdk/_util.py +26 -0
- airbyte_cdk/manifest_migrations/migrations/http_requester_request_body_json_data_to_request_body.py +35 -10
- airbyte_cdk/manifest_migrations/migrations/registry.yaml +1 -1
- airbyte_cdk/sources/declarative/auth/oauth.py +2 -5
- airbyte_cdk/sources/declarative/declarative_component_schema.yaml +78 -23
- airbyte_cdk/sources/declarative/models/declarative_component_schema.py +50 -11
- airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py +19 -5
- {airbyte_cdk-6.48.2.post1.dev14733922675.dist-info → airbyte_cdk-6.48.4.dist-info}/METADATA +2 -1
- {airbyte_cdk-6.48.2.post1.dev14733922675.dist-info → airbyte_cdk-6.48.4.dist-info}/RECORD +14 -14
- {airbyte_cdk-6.48.2.post1.dev14733922675.dist-info → airbyte_cdk-6.48.4.dist-info}/LICENSE.txt +0 -0
- {airbyte_cdk-6.48.2.post1.dev14733922675.dist-info → airbyte_cdk-6.48.4.dist-info}/LICENSE_SHORT +0 -0
- {airbyte_cdk-6.48.2.post1.dev14733922675.dist-info → airbyte_cdk-6.48.4.dist-info}/WHEEL +0 -0
- {airbyte_cdk-6.48.2.post1.dev14733922675.dist-info → airbyte_cdk-6.48.4.dist-info}/entry_points.txt +0 -0
@@ -1,36 +1,71 @@
|
|
1
1
|
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
2
|
-
"""Secret management commands
|
2
|
+
"""**Secret management commands.**
|
3
3
|
|
4
4
|
This module provides commands for managing secrets for Airbyte connectors.
|
5
5
|
|
6
|
-
Usage
|
7
|
-
airbyte-cdk secrets fetch --connector-name source-github
|
8
|
-
airbyte-cdk secrets fetch --connector-directory /path/to/connector
|
9
|
-
airbyte-cdk secrets fetch # Run from within a connector directory
|
6
|
+
**Usage:**
|
10
7
|
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
```bash
|
9
|
+
# Fetch secrets
|
10
|
+
airbyte-cdk secrets fetch --connector-name source-github
|
11
|
+
airbyte-cdk secrets fetch --connector-directory /path/to/connector
|
12
|
+
airbyte-cdk secrets fetch # Run from within a connector directory
|
14
13
|
|
15
|
-
|
14
|
+
# List secrets (without fetching)
|
15
|
+
airbyte-cdk secrets list --connector-name source-github
|
16
|
+
airbyte-cdk secrets list --connector-directory /path/to/connector
|
17
|
+
```
|
18
|
+
|
19
|
+
**Usage without pre-installing (stateless):**
|
20
|
+
|
21
|
+
```bash
|
22
|
+
pipx run airbyte-cdk secrets fetch ...
|
23
|
+
uvx airbyte-cdk secrets fetch ...
|
24
|
+
```
|
25
|
+
|
26
|
+
The command retrieves secrets from Google Secret Manager based on connector
|
16
27
|
labels and writes them to the connector's `secrets` directory.
|
17
28
|
"""
|
18
29
|
|
30
|
+
from __future__ import annotations
|
31
|
+
|
19
32
|
import json
|
33
|
+
import logging
|
20
34
|
import os
|
35
|
+
from functools import lru_cache
|
21
36
|
from pathlib import Path
|
37
|
+
from typing import Any, cast
|
22
38
|
|
39
|
+
import requests
|
23
40
|
import rich_click as click
|
41
|
+
import yaml
|
42
|
+
from click import style
|
43
|
+
from rich.console import Console
|
44
|
+
from rich.table import Table
|
24
45
|
|
25
|
-
from airbyte_cdk.cli.airbyte_cdk._util import
|
46
|
+
from airbyte_cdk.cli.airbyte_cdk._util import (
|
47
|
+
resolve_connector_name,
|
48
|
+
resolve_connector_name_and_directory,
|
49
|
+
)
|
26
50
|
|
27
51
|
AIRBYTE_INTERNAL_GCP_PROJECT = "dataline-integration-testing"
|
28
52
|
CONNECTOR_LABEL = "connector"
|
53
|
+
GLOBAL_MASK_KEYS_URL = "https://connectors.airbyte.com/files/registries/v0/specs_secrets_mask.yaml"
|
54
|
+
|
55
|
+
logger = logging.getLogger("airbyte-cdk.cli.secrets")
|
56
|
+
|
57
|
+
try:
|
58
|
+
from google.cloud import secretmanager_v1 as secretmanager
|
59
|
+
from google.cloud.secretmanager_v1 import Secret
|
60
|
+
except ImportError:
|
61
|
+
# If the package is not installed, we will raise an error in the CLI command.
|
62
|
+
secretmanager = None # type: ignore
|
63
|
+
Secret = None # type: ignore
|
29
64
|
|
30
65
|
|
31
66
|
@click.group(
|
32
67
|
name="secrets",
|
33
|
-
help=__doc__.replace("\n", "\n\n"), # Render docstring as help text (markdown)
|
68
|
+
help=__doc__.replace("\n", "\n\n"), # Render docstring as help text (markdown) # type: ignore
|
34
69
|
)
|
35
70
|
def secrets_cli_group() -> None:
|
36
71
|
"""Secret management commands."""
|
@@ -54,10 +89,18 @@ def secrets_cli_group() -> None:
|
|
54
89
|
default=AIRBYTE_INTERNAL_GCP_PROJECT,
|
55
90
|
help=f"GCP project ID. Defaults to '{AIRBYTE_INTERNAL_GCP_PROJECT}'.",
|
56
91
|
)
|
92
|
+
@click.option(
|
93
|
+
"--print-ci-secrets-masks",
|
94
|
+
help="Print GitHub CI mask for secrets.",
|
95
|
+
type=bool,
|
96
|
+
is_flag=True,
|
97
|
+
default=False,
|
98
|
+
)
|
57
99
|
def fetch(
|
58
100
|
connector_name: str | None = None,
|
59
101
|
connector_directory: Path | None = None,
|
60
102
|
gcp_project_id: str = AIRBYTE_INTERNAL_GCP_PROJECT,
|
103
|
+
print_ci_secrets_masks: bool = False,
|
61
104
|
) -> None:
|
62
105
|
"""Fetch secrets for a connector from Google Secret Manager.
|
63
106
|
|
@@ -67,19 +110,185 @@ def fetch(
|
|
67
110
|
If no connector name or directory is provided, we will look within the current working
|
68
111
|
directory. If the current working directory is not a connector directory (e.g. starting
|
69
112
|
with 'source-') and no connector name or path is provided, the process will fail.
|
113
|
+
|
114
|
+
The `--print-ci-secrets-masks` option will print the GitHub CI mask for the secrets.
|
115
|
+
This is useful for masking secrets in CI logs.
|
116
|
+
|
117
|
+
WARNING: This action causes the secrets to be printed in clear text to `STDOUT`. For security
|
118
|
+
reasons, this function will only execute if the `CI` environment variable is set. Otherwise,
|
119
|
+
masks will not be printed.
|
70
120
|
"""
|
71
|
-
|
72
|
-
|
73
|
-
|
121
|
+
click.echo("Fetching secrets...", err=True)
|
122
|
+
|
123
|
+
client = _get_gsm_secrets_client()
|
124
|
+
connector_name, connector_directory = resolve_connector_name_and_directory(
|
125
|
+
connector_name=connector_name,
|
126
|
+
connector_directory=connector_directory,
|
127
|
+
)
|
128
|
+
secrets_dir = _get_secrets_dir(
|
129
|
+
connector_directory=connector_directory,
|
130
|
+
connector_name=connector_name,
|
131
|
+
ensure_exists=True,
|
132
|
+
)
|
133
|
+
secrets = _fetch_secret_handles(
|
134
|
+
connector_name=connector_name,
|
135
|
+
gcp_project_id=gcp_project_id,
|
136
|
+
)
|
137
|
+
# Fetch and write secrets
|
138
|
+
secret_count = 0
|
139
|
+
for secret in secrets:
|
140
|
+
secret_file_path = _get_secret_filepath(
|
141
|
+
secrets_dir=secrets_dir,
|
142
|
+
secret=secret,
|
143
|
+
)
|
144
|
+
_write_secret_file(
|
145
|
+
secret=secret,
|
146
|
+
client=client,
|
147
|
+
file_path=secret_file_path,
|
148
|
+
)
|
149
|
+
click.echo(f"Secret written to: {secret_file_path.absolute()!s}", err=True)
|
150
|
+
secret_count += 1
|
151
|
+
|
152
|
+
if secret_count == 0:
|
153
|
+
click.echo(
|
154
|
+
f"No secrets found for connector: '{connector_name}'",
|
155
|
+
err=True,
|
156
|
+
)
|
157
|
+
|
158
|
+
if not print_ci_secrets_masks:
|
159
|
+
return
|
160
|
+
|
161
|
+
if not os.environ.get("CI", None):
|
162
|
+
click.echo(
|
163
|
+
"The `--print-ci-secrets-masks` option is only available in CI environments. "
|
164
|
+
"The `CI` env var is either not set or not set to a truthy value. "
|
165
|
+
"Skipping printing secret masks.",
|
166
|
+
err=True,
|
167
|
+
)
|
168
|
+
return
|
169
|
+
|
170
|
+
# Else print the CI mask
|
171
|
+
_print_ci_secrets_masks(
|
172
|
+
secrets_dir=secrets_dir,
|
173
|
+
)
|
174
|
+
|
175
|
+
|
176
|
+
@secrets_cli_group.command("list")
|
177
|
+
@click.option(
|
178
|
+
"--connector-name",
|
179
|
+
type=str,
|
180
|
+
help="Name of the connector to fetch secrets for. Ignored if --connector-directory is provided.",
|
181
|
+
)
|
182
|
+
@click.option(
|
183
|
+
"--connector-directory",
|
184
|
+
type=click.Path(exists=True, file_okay=False, path_type=Path),
|
185
|
+
help="Path to the connector directory.",
|
186
|
+
)
|
187
|
+
@click.option(
|
188
|
+
"--gcp-project-id",
|
189
|
+
type=str,
|
190
|
+
default=AIRBYTE_INTERNAL_GCP_PROJECT,
|
191
|
+
help=f"GCP project ID. Defaults to '{AIRBYTE_INTERNAL_GCP_PROJECT}'.",
|
192
|
+
)
|
193
|
+
def list_(
|
194
|
+
connector_name: str | None = None,
|
195
|
+
connector_directory: Path | None = None,
|
196
|
+
gcp_project_id: str = AIRBYTE_INTERNAL_GCP_PROJECT,
|
197
|
+
) -> None:
|
198
|
+
"""List secrets for a connector from Google Secret Manager.
|
199
|
+
|
200
|
+
This command fetches secrets for a connector from Google Secret Manager and prints
|
201
|
+
them as a table.
|
202
|
+
|
203
|
+
If no connector name or directory is provided, we will look within the current working
|
204
|
+
directory. If the current working directory is not a connector directory (e.g. starting
|
205
|
+
with 'source-') and no connector name or path is provided, the process will fail.
|
206
|
+
"""
|
207
|
+
click.echo("Scanning secrets...", err=True)
|
208
|
+
|
209
|
+
connector_name = connector_name or resolve_connector_name(
|
210
|
+
connector_directory=connector_directory or Path().resolve().absolute(),
|
211
|
+
)
|
212
|
+
secrets: list[Secret] = _fetch_secret_handles( # type: ignore
|
213
|
+
connector_name=connector_name,
|
214
|
+
gcp_project_id=gcp_project_id,
|
215
|
+
)
|
216
|
+
|
217
|
+
if not secrets:
|
218
|
+
click.echo(
|
219
|
+
f"No secrets found for connector: '{connector_name}'",
|
220
|
+
err=True,
|
221
|
+
)
|
222
|
+
return
|
223
|
+
# print a rich table with the secrets
|
224
|
+
click.echo(
|
225
|
+
style(
|
226
|
+
f"Secrets for connector '{connector_name}' in project '{gcp_project_id}':",
|
227
|
+
fg="green",
|
228
|
+
)
|
229
|
+
)
|
230
|
+
|
231
|
+
console = Console()
|
232
|
+
table = Table(title=f"'{connector_name}' Secrets")
|
233
|
+
table.add_column("Name", justify="left", style="cyan", overflow="fold")
|
234
|
+
table.add_column("Labels", justify="left", style="magenta", overflow="fold")
|
235
|
+
table.add_column("Created", justify="left", style="blue", overflow="fold")
|
236
|
+
for secret in secrets:
|
237
|
+
full_secret_name = secret.name
|
238
|
+
secret_name = full_secret_name.split("/secrets/")[-1] # Removes project prefix
|
239
|
+
# E.g. https://console.cloud.google.com/security/secret-manager/secret/SECRET_SOURCE-SHOPIFY__CREDS/versions?hl=en&project=<gcp_project_id>
|
240
|
+
secret_url = f"https://console.cloud.google.com/security/secret-manager/secret/{secret_name}/versions?hl=en&project={gcp_project_id}"
|
241
|
+
table.add_row(
|
242
|
+
f"[link={secret_url}]{secret_name}[/link]",
|
243
|
+
"\n".join([f"{k}={v}" for k, v in secret.labels.items()]),
|
244
|
+
str(secret.create_time),
|
245
|
+
)
|
246
|
+
|
247
|
+
console.print(table)
|
248
|
+
|
249
|
+
|
250
|
+
def _fetch_secret_handles(
|
251
|
+
connector_name: str,
|
252
|
+
gcp_project_id: str = AIRBYTE_INTERNAL_GCP_PROJECT,
|
253
|
+
) -> list["Secret"]: # type: ignore
|
254
|
+
"""Fetch secrets from Google Secret Manager."""
|
255
|
+
if not secretmanager:
|
74
256
|
raise ImportError(
|
75
257
|
"google-cloud-secret-manager package is required for Secret Manager integration. "
|
76
258
|
"Install it with 'pip install airbyte-cdk[dev]' "
|
77
259
|
"or 'pip install google-cloud-secret-manager'."
|
78
260
|
)
|
79
261
|
|
80
|
-
|
262
|
+
client = _get_gsm_secrets_client()
|
263
|
+
|
264
|
+
# List all secrets with the connector label
|
265
|
+
parent = f"projects/{gcp_project_id}"
|
266
|
+
filter_string = f"labels.{CONNECTOR_LABEL}={connector_name}"
|
267
|
+
secrets = client.list_secrets(
|
268
|
+
request=secretmanager.ListSecretsRequest(
|
269
|
+
parent=parent,
|
270
|
+
filter=filter_string,
|
271
|
+
)
|
272
|
+
)
|
273
|
+
return [s for s in secrets]
|
274
|
+
|
81
275
|
|
82
|
-
|
276
|
+
def _write_secret_file(
|
277
|
+
secret: "Secret", # type: ignore
|
278
|
+
client: "secretmanager.SecretManagerServiceClient", # type: ignore
|
279
|
+
file_path: Path,
|
280
|
+
) -> None:
|
281
|
+
version_name = f"{secret.name}/versions/latest"
|
282
|
+
response = client.access_secret_version(name=version_name)
|
283
|
+
file_path.write_text(response.payload.data.decode("UTF-8"))
|
284
|
+
file_path.chmod(0o600) # default to owner read/write only
|
285
|
+
|
286
|
+
|
287
|
+
def _get_secrets_dir(
|
288
|
+
connector_directory: Path,
|
289
|
+
connector_name: str,
|
290
|
+
ensure_exists: bool = True,
|
291
|
+
) -> Path:
|
83
292
|
try:
|
84
293
|
connector_name, connector_directory = resolve_connector_name_and_directory(
|
85
294
|
connector_name=connector_name,
|
@@ -95,56 +304,121 @@ def fetch(
|
|
95
304
|
except ValueError as e:
|
96
305
|
raise ValueError(str(e))
|
97
306
|
|
98
|
-
# Create secrets directory if it doesn't exist
|
99
307
|
secrets_dir = connector_directory / "secrets"
|
100
|
-
|
308
|
+
if ensure_exists:
|
309
|
+
secrets_dir.mkdir(parents=True, exist_ok=True)
|
310
|
+
|
311
|
+
gitignore_path = secrets_dir / ".gitignore"
|
312
|
+
if not gitignore_path.exists():
|
313
|
+
gitignore_path.write_text("*")
|
101
314
|
|
102
|
-
|
103
|
-
|
315
|
+
return secrets_dir
|
316
|
+
|
317
|
+
|
318
|
+
def _get_secret_filepath(
|
319
|
+
secrets_dir: Path,
|
320
|
+
secret: Secret, # type: ignore
|
321
|
+
) -> Path:
|
322
|
+
"""Get the file path for a secret based on its labels."""
|
323
|
+
if secret.labels and "filename" in secret.labels:
|
324
|
+
return secrets_dir / f"{secret.labels['filename']}.json"
|
325
|
+
|
326
|
+
return secrets_dir / "config.json" # Default filename
|
327
|
+
|
328
|
+
|
329
|
+
def _get_gsm_secrets_client() -> "secretmanager.SecretManagerServiceClient": # type: ignore
|
330
|
+
"""Get the Google Secret Manager client."""
|
331
|
+
if not secretmanager:
|
332
|
+
raise ImportError(
|
333
|
+
"google-cloud-secret-manager package is required for Secret Manager integration. "
|
334
|
+
"Install it with 'pip install airbyte-cdk[dev]' "
|
335
|
+
"or 'pip install google-cloud-secret-manager'."
|
336
|
+
)
|
104
337
|
|
105
|
-
# Get GSM client
|
106
338
|
credentials_json = os.environ.get("GCP_GSM_CREDENTIALS")
|
107
339
|
if not credentials_json:
|
108
340
|
raise ValueError(
|
109
|
-
"No Google Cloud credentials found.
|
341
|
+
"No Google Cloud credentials found. "
|
342
|
+
"Please set the `GCP_GSM_CREDENTIALS` environment variable."
|
110
343
|
)
|
111
344
|
|
112
|
-
|
113
|
-
|
345
|
+
return cast(
|
346
|
+
"secretmanager.SecretManagerServiceClient",
|
347
|
+
secretmanager.SecretManagerServiceClient.from_service_account_info(
|
348
|
+
json.loads(credentials_json)
|
349
|
+
),
|
114
350
|
)
|
115
351
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
352
|
+
|
353
|
+
def _print_ci_secrets_masks(
|
354
|
+
secrets_dir: Path,
|
355
|
+
) -> None:
|
356
|
+
"""Print GitHub CI mask for secrets.
|
357
|
+
|
358
|
+
https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#example-masking-an-environment-variable
|
359
|
+
|
360
|
+
The env var `CI` is set to a truthy value in GitHub Actions, so we can use it to
|
361
|
+
determine if we are in a CI environment. If not, we don't want to print the masks,
|
362
|
+
as it will cause the secrets to be printed in clear text to STDOUT.
|
363
|
+
"""
|
364
|
+
if not os.environ.get("CI", None):
|
365
|
+
click.echo(
|
366
|
+
"The `--print-ci-secrets-masks` option is only available in CI environments. "
|
367
|
+
"The `CI` env var is either not set or not set to a truthy value. "
|
368
|
+
"Skipping printing secret masks.",
|
369
|
+
err=True,
|
123
370
|
)
|
124
|
-
|
371
|
+
return
|
125
372
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
secret_name = secret.name
|
130
|
-
version_name = f"{secret_name}/versions/latest"
|
131
|
-
response = client.access_secret_version(name=version_name)
|
132
|
-
payload = response.payload.data.decode("UTF-8")
|
133
|
-
|
134
|
-
filename_base = "config" # Default filename
|
135
|
-
if secret.labels and "filename" in secret.labels:
|
136
|
-
filename_base = secret.labels["filename"]
|
137
|
-
|
138
|
-
secret_file_path = secrets_dir / f"{filename_base}.json"
|
139
|
-
secret_file_path.write_text(payload)
|
140
|
-
secret_file_path.chmod(0o600) # default to owner read/write only
|
141
|
-
click.echo(f"Secret written to: {secret_file_path.absolute()!s}")
|
142
|
-
secret_count += 1
|
373
|
+
for secret_file_path in secrets_dir.glob("*.json"):
|
374
|
+
config_dict = json.loads(secret_file_path.read_text())
|
375
|
+
_print_ci_secrets_masks_for_config(config=config_dict)
|
143
376
|
|
144
|
-
|
145
|
-
|
377
|
+
|
378
|
+
def _print_ci_secrets_masks_for_config(
|
379
|
+
config: dict[str, str] | list[Any] | Any,
|
380
|
+
) -> None:
|
381
|
+
"""Print GitHub CI mask for secrets config, navigating child nodes recursively."""
|
382
|
+
if isinstance(config, list):
|
383
|
+
for item in config:
|
384
|
+
_print_ci_secrets_masks_for_config(item)
|
385
|
+
|
386
|
+
if isinstance(config, dict):
|
387
|
+
for key, value in config.items():
|
388
|
+
if _is_secret_property(key):
|
389
|
+
logger.debug(f"Masking secret for config key: {key}")
|
390
|
+
print(f"::add-mask::{value!s}")
|
391
|
+
if isinstance(value, dict):
|
392
|
+
# For nested dicts, we also need to mask the json-stringified version
|
393
|
+
print(f"::add-mask::{json.dumps(value)!s}")
|
394
|
+
|
395
|
+
if isinstance(value, (dict, list)):
|
396
|
+
_print_ci_secrets_masks_for_config(config=value)
|
146
397
|
|
147
398
|
|
148
|
-
|
149
|
-
"
|
150
|
-
|
399
|
+
def _is_secret_property(property_name: str) -> bool:
|
400
|
+
"""Check if the property name is in the list of properties to mask.
|
401
|
+
|
402
|
+
To avoid false negatives, we perform a case-insensitive check, and we include any property name
|
403
|
+
that contains a rule entry, even if it is not an exact match.
|
404
|
+
|
405
|
+
For example, if the rule entry is "password", we will also match "PASSWORD" and "my_password".
|
406
|
+
"""
|
407
|
+
names_to_mask: list[str] = _get_spec_mask()
|
408
|
+
if any([mask.lower() in property_name.lower() for mask in names_to_mask]):
|
409
|
+
return True
|
410
|
+
|
411
|
+
return False
|
412
|
+
|
413
|
+
|
414
|
+
@lru_cache
|
415
|
+
def _get_spec_mask() -> list[str]:
|
416
|
+
"""Get the list of properties to mask from the spec mask file."""
|
417
|
+
response = requests.get(GLOBAL_MASK_KEYS_URL, allow_redirects=True)
|
418
|
+
if not response.ok:
|
419
|
+
logger.error(f"Failed to fetch spec mask: {response.content.decode('utf-8')}")
|
420
|
+
try:
|
421
|
+
return cast(list[str], yaml.safe_load(response.content)["properties"])
|
422
|
+
except Exception as e:
|
423
|
+
logger.error(f"Failed to parse spec mask: {e}")
|
424
|
+
raise
|
@@ -41,3 +41,29 @@ def resolve_connector_name_and_directory(
|
|
41
41
|
raise ValueError("Either connector_name or connector_directory must be provided.")
|
42
42
|
|
43
43
|
return connector_name, connector_directory
|
44
|
+
|
45
|
+
|
46
|
+
def resolve_connector_name(
|
47
|
+
connector_directory: Path,
|
48
|
+
) -> str:
|
49
|
+
"""Resolve the connector name.
|
50
|
+
|
51
|
+
This function will resolve the connector name based on the provided connector directory.
|
52
|
+
If the current working directory is not a connector directory
|
53
|
+
(e.g. starting with 'source-'), the process will fail.
|
54
|
+
|
55
|
+
Raises:
|
56
|
+
FileNotFoundError: If the connector directory does not exist or cannot be found.
|
57
|
+
"""
|
58
|
+
if not connector_directory:
|
59
|
+
raise FileNotFoundError(
|
60
|
+
"Connector directory does not exist or cannot be found. Please provide a valid "
|
61
|
+
"connector directory."
|
62
|
+
)
|
63
|
+
connector_name = connector_directory.absolute().name
|
64
|
+
if not connector_name.startswith("source-") and not connector_name.startswith("destination-"):
|
65
|
+
raise ValueError(
|
66
|
+
f"Connector directory '{connector_name}' does not look like a valid connector directory. "
|
67
|
+
f"Full path: {connector_directory.absolute()}"
|
68
|
+
)
|
69
|
+
return connector_name
|
airbyte_cdk/manifest_migrations/migrations/http_requester_request_body_json_data_to_request_body.py
CHANGED
@@ -33,19 +33,44 @@ class HttpRequesterRequestBodyJsonDataToRequestBody(ManifestMigration):
|
|
33
33
|
def migrate(self, manifest: ManifestType) -> None:
|
34
34
|
for key in self.original_keys:
|
35
35
|
if key == self.body_json_key and key in manifest:
|
36
|
-
|
37
|
-
"type": "RequestBodyJson",
|
38
|
-
"value": manifest[key],
|
39
|
-
}
|
40
|
-
manifest.pop(key, None)
|
36
|
+
self._migrate_body_json(manifest, key)
|
41
37
|
elif key == self.body_data_key and key in manifest:
|
42
|
-
|
43
|
-
"type": "RequestBodyData",
|
44
|
-
"value": manifest[key],
|
45
|
-
}
|
46
|
-
manifest.pop(key, None)
|
38
|
+
self._migrate_body_data(manifest, key)
|
47
39
|
|
48
40
|
def validate(self, manifest: ManifestType) -> bool:
|
49
41
|
return self.replacement_key in manifest and all(
|
50
42
|
key not in manifest for key in self.original_keys
|
51
43
|
)
|
44
|
+
|
45
|
+
def _migrate_body_json(self, manifest: ManifestType, key: str) -> None:
|
46
|
+
"""
|
47
|
+
Migrate the value of the request_body_json.
|
48
|
+
"""
|
49
|
+
query_key = "query"
|
50
|
+
text_type = "RequestBodyPlainText"
|
51
|
+
graph_ql_type = "RequestBodyGraphQL"
|
52
|
+
json_object_type = "RequestBodyJsonObject"
|
53
|
+
|
54
|
+
if isinstance(manifest[key], str):
|
55
|
+
self._migrate_value(manifest, key, text_type)
|
56
|
+
elif isinstance(manifest[key], dict):
|
57
|
+
if manifest[key].get(query_key) is not None:
|
58
|
+
self._migrate_value(manifest, key, graph_ql_type)
|
59
|
+
else:
|
60
|
+
self._migrate_value(manifest, key, json_object_type)
|
61
|
+
|
62
|
+
def _migrate_body_data(self, manifest: ManifestType, key: str) -> None:
|
63
|
+
"""
|
64
|
+
Migrate the value of the request_body_data.
|
65
|
+
"""
|
66
|
+
self._migrate_value(manifest, key, "RequestBodyUrlEncodedForm")
|
67
|
+
|
68
|
+
def _migrate_value(self, manifest: ManifestType, key: str, type: str) -> None:
|
69
|
+
"""
|
70
|
+
Migrate the value of the key to a specific type and update the manifest.
|
71
|
+
"""
|
72
|
+
manifest[self.replacement_key] = {
|
73
|
+
"type": type,
|
74
|
+
"value": manifest[key],
|
75
|
+
}
|
76
|
+
manifest.pop(key, None)
|
@@ -2,10 +2,9 @@
|
|
2
2
|
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
3
3
|
#
|
4
4
|
|
5
|
-
import logging
|
6
5
|
from dataclasses import InitVar, dataclass, field
|
7
6
|
from datetime import datetime, timedelta
|
8
|
-
from typing import Any, List, Mapping, Optional, Union
|
7
|
+
from typing import Any, List, Mapping, MutableMapping, Optional, Union
|
9
8
|
|
10
9
|
from airbyte_cdk.sources.declarative.auth.declarative_authenticator import DeclarativeAuthenticator
|
11
10
|
from airbyte_cdk.sources.declarative.interpolation.interpolated_boolean import InterpolatedBoolean
|
@@ -20,7 +19,6 @@ from airbyte_cdk.sources.streams.http.requests_native_auth.oauth import (
|
|
20
19
|
)
|
21
20
|
from airbyte_cdk.utils.datetime_helpers import AirbyteDateTime, ab_datetime_now, ab_datetime_parse
|
22
21
|
|
23
|
-
logger = logging.getLogger("airbyte")
|
24
22
|
|
25
23
|
@dataclass
|
26
24
|
class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAuthenticator):
|
@@ -203,8 +201,7 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
|
|
203
201
|
self._client_secret.eval(self.config) if self._client_secret else self._client_secret
|
204
202
|
)
|
205
203
|
if not client_secret:
|
206
|
-
|
207
|
-
logger.warning("OAuthAuthenticator was unable to evaluate client_secret parameter hence it'll be empty")
|
204
|
+
raise ValueError("OAuthAuthenticator was unable to evaluate client_secret parameter")
|
208
205
|
return client_secret # type: ignore # value will be returned as a string, or an error will be raised
|
209
206
|
|
210
207
|
def get_refresh_token_name(self) -> str:
|
@@ -2048,31 +2048,39 @@ definitions:
|
|
2048
2048
|
title: Request Body Payload to be send as a part of the API request.
|
2049
2049
|
description: Specifies how to populate the body of the request with a payload. Can contain nested objects.
|
2050
2050
|
anyOf:
|
2051
|
-
- "$ref": "#/definitions/
|
2051
|
+
- "$ref": "#/definitions/RequestBodyPlainText"
|
2052
|
+
- "$ref": "#/definitions/RequestBodyUrlEncodedForm"
|
2053
|
+
- "$ref": "#/definitions/RequestBodyJsonObject"
|
2054
|
+
- "$ref": "#/definitions/RequestBodyGraphQL"
|
2052
2055
|
interpolation_context:
|
2053
2056
|
- next_page_token
|
2054
2057
|
- stream_interval
|
2055
2058
|
- stream_partition
|
2056
2059
|
- stream_slice
|
2057
2060
|
examples:
|
2058
|
-
- type:
|
2061
|
+
- type: RequestBodyJsonObject
|
2059
2062
|
value:
|
2060
2063
|
sort_order: "ASC"
|
2061
2064
|
sort_field: "CREATED_AT"
|
2062
|
-
- type:
|
2065
|
+
- type: RequestBodyJsonObject
|
2063
2066
|
value:
|
2064
2067
|
key: "{{ config['value'] }}"
|
2065
|
-
- type:
|
2068
|
+
- type: RequestBodyJsonObject
|
2066
2069
|
value:
|
2067
2070
|
sort:
|
2068
2071
|
field: "updated_at"
|
2069
2072
|
order: "ascending"
|
2070
|
-
- type:
|
2073
|
+
- type: RequestBodyPlainText
|
2071
2074
|
value: "plain_text_body"
|
2072
|
-
- type:
|
2075
|
+
- type: RequestBodyUrlEncodedForm
|
2073
2076
|
value:
|
2074
2077
|
param1: "value1"
|
2075
2078
|
param2: "{{ config['param2_value'] }}"
|
2079
|
+
- type: RequestBodyGraphQL
|
2080
|
+
value:
|
2081
|
+
query:
|
2082
|
+
param1: "value1"
|
2083
|
+
param2: "{{ config['param2_value'] }}"
|
2076
2084
|
request_headers:
|
2077
2085
|
title: Request Headers
|
2078
2086
|
description: Return any non-auth headers. Authentication headers will overwrite any overlapping headers returned from this method.
|
@@ -4073,27 +4081,74 @@ definitions:
|
|
4073
4081
|
- type
|
4074
4082
|
- stream_template
|
4075
4083
|
- components_resolver
|
4076
|
-
|
4084
|
+
RequestBodyPlainText:
|
4085
|
+
title: Plain-text Body
|
4086
|
+
description: Request body value is sent as plain text
|
4077
4087
|
type: object
|
4078
|
-
|
4088
|
+
required:
|
4089
|
+
- type
|
4090
|
+
- value
|
4079
4091
|
properties:
|
4080
4092
|
type:
|
4081
|
-
|
4082
|
-
|
4083
|
-
enum: [RequestBodyData]
|
4084
|
-
- type: string
|
4085
|
-
enum: [RequestBodyJson]
|
4093
|
+
type: string
|
4094
|
+
enum: [RequestBodyPlainText]
|
4086
4095
|
value:
|
4087
|
-
|
4088
|
-
|
4089
|
-
|
4090
|
-
|
4091
|
-
|
4092
|
-
|
4093
|
-
|
4094
|
-
|
4095
|
-
|
4096
|
-
|
4096
|
+
type: string
|
4097
|
+
RequestBodyUrlEncodedForm:
|
4098
|
+
title: URL-encoded Body
|
4099
|
+
description: Request body value is converted into a url-encoded form
|
4100
|
+
type: object
|
4101
|
+
required:
|
4102
|
+
- type
|
4103
|
+
- value
|
4104
|
+
properties:
|
4105
|
+
type:
|
4106
|
+
type: string
|
4107
|
+
enum: [RequestBodyUrlEncodedForm]
|
4108
|
+
value:
|
4109
|
+
type: object
|
4110
|
+
additionalProperties:
|
4111
|
+
type: string
|
4112
|
+
RequestBodyJsonObject:
|
4113
|
+
title: Json Object Body
|
4114
|
+
description: Request body value converted into a JSON object
|
4115
|
+
type: object
|
4116
|
+
required:
|
4117
|
+
- type
|
4118
|
+
- value
|
4119
|
+
properties:
|
4120
|
+
type:
|
4121
|
+
type: string
|
4122
|
+
enum: [RequestBodyJsonObject]
|
4123
|
+
value:
|
4124
|
+
type: object
|
4125
|
+
additionalProperties: true
|
4126
|
+
RequestBodyGraphQL:
|
4127
|
+
title: GraphQL Body
|
4128
|
+
description: Request body value converted into a GraphQL query object
|
4129
|
+
type: object
|
4130
|
+
required:
|
4131
|
+
- type
|
4132
|
+
- value
|
4133
|
+
properties:
|
4134
|
+
type:
|
4135
|
+
type: string
|
4136
|
+
enum: [RequestBodyGraphQL]
|
4137
|
+
value:
|
4138
|
+
"$ref": "#/definitions/RequestBodyGraphQlQuery"
|
4139
|
+
RequestBodyGraphQlQuery:
|
4140
|
+
title: GraphQL Query Body
|
4141
|
+
description: Request body GraphQL query object
|
4142
|
+
type: object
|
4143
|
+
required:
|
4144
|
+
- query
|
4145
|
+
properties:
|
4146
|
+
query:
|
4147
|
+
type: object
|
4148
|
+
additionalProperties: true
|
4149
|
+
description: The GraphQL query to be executed
|
4150
|
+
default: {}
|
4151
|
+
additionalProperties: true
|
4097
4152
|
interpolation:
|
4098
4153
|
variables:
|
4099
4154
|
- title: config
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
2
|
-
|
3
1
|
# generated by datamodel-codegen:
|
4
2
|
# filename: declarative_component_schema.yaml
|
5
3
|
|
@@ -1501,9 +1499,26 @@ class ConfigComponentsResolver(BaseModel):
|
|
1501
1499
|
parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters")
|
1502
1500
|
|
1503
1501
|
|
1504
|
-
class
|
1505
|
-
type:
|
1506
|
-
value:
|
1502
|
+
class RequestBodyPlainText(BaseModel):
|
1503
|
+
type: Literal["RequestBodyPlainText"]
|
1504
|
+
value: str
|
1505
|
+
|
1506
|
+
|
1507
|
+
class RequestBodyUrlEncodedForm(BaseModel):
|
1508
|
+
type: Literal["RequestBodyUrlEncodedForm"]
|
1509
|
+
value: Dict[str, str]
|
1510
|
+
|
1511
|
+
|
1512
|
+
class RequestBodyJsonObject(BaseModel):
|
1513
|
+
type: Literal["RequestBodyJsonObject"]
|
1514
|
+
value: Dict[str, Any]
|
1515
|
+
|
1516
|
+
|
1517
|
+
class RequestBodyGraphQlQuery(BaseModel):
|
1518
|
+
class Config:
|
1519
|
+
extra = Extra.allow
|
1520
|
+
|
1521
|
+
query: Dict[str, Any] = Field(..., description="The GraphQL query to be executed")
|
1507
1522
|
|
1508
1523
|
|
1509
1524
|
class AddedFieldDefinition(BaseModel):
|
@@ -1908,6 +1923,11 @@ class Spec(BaseModel):
|
|
1908
1923
|
)
|
1909
1924
|
|
1910
1925
|
|
1926
|
+
class RequestBodyGraphQL(BaseModel):
|
1927
|
+
type: Literal["RequestBodyGraphQL"]
|
1928
|
+
value: RequestBodyGraphQlQuery
|
1929
|
+
|
1930
|
+
|
1911
1931
|
class CompositeErrorHandler(BaseModel):
|
1912
1932
|
type: Literal["CompositeErrorHandler"]
|
1913
1933
|
error_handlers: List[Union[CompositeErrorHandler, DefaultErrorHandler]] = Field(
|
@@ -2305,24 +2325,43 @@ class HttpRequester(BaseModelWithDeprecations):
|
|
2305
2325
|
],
|
2306
2326
|
title="Request Body JSON Payload",
|
2307
2327
|
)
|
2308
|
-
request_body: Optional[
|
2328
|
+
request_body: Optional[
|
2329
|
+
Union[
|
2330
|
+
RequestBodyPlainText,
|
2331
|
+
RequestBodyUrlEncodedForm,
|
2332
|
+
RequestBodyJsonObject,
|
2333
|
+
RequestBodyGraphQL,
|
2334
|
+
]
|
2335
|
+
] = Field(
|
2309
2336
|
None,
|
2310
2337
|
description="Specifies how to populate the body of the request with a payload. Can contain nested objects.",
|
2311
2338
|
examples=[
|
2312
2339
|
{
|
2313
|
-
"type": "
|
2340
|
+
"type": "RequestBodyJsonObject",
|
2314
2341
|
"value": {"sort_order": "ASC", "sort_field": "CREATED_AT"},
|
2315
2342
|
},
|
2316
|
-
{"type": "RequestBodyJson", "value": {"key": "{{ config['value'] }}"}},
|
2317
2343
|
{
|
2318
|
-
"type": "
|
2344
|
+
"type": "RequestBodyJsonObject",
|
2345
|
+
"value": {"key": "{{ config['value'] }}"},
|
2346
|
+
},
|
2347
|
+
{
|
2348
|
+
"type": "RequestBodyJsonObject",
|
2319
2349
|
"value": {"sort": {"field": "updated_at", "order": "ascending"}},
|
2320
2350
|
},
|
2321
|
-
{"type": "
|
2351
|
+
{"type": "RequestBodyPlainText", "value": "plain_text_body"},
|
2322
2352
|
{
|
2323
|
-
"type": "
|
2353
|
+
"type": "RequestBodyUrlEncodedForm",
|
2324
2354
|
"value": {"param1": "value1", "param2": "{{ config['param2_value'] }}"},
|
2325
2355
|
},
|
2356
|
+
{
|
2357
|
+
"type": "RequestBodyGraphQL",
|
2358
|
+
"value": {
|
2359
|
+
"query": {
|
2360
|
+
"param1": "value1",
|
2361
|
+
"param2": "{{ config['param2_value'] }}",
|
2362
|
+
}
|
2363
|
+
},
|
2364
|
+
},
|
2326
2365
|
],
|
2327
2366
|
title="Request Body Payload to be send as a part of the API request.",
|
2328
2367
|
)
|
airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py
CHANGED
@@ -7,7 +7,10 @@ from typing import Any, List, Mapping, MutableMapping, Optional, Union
|
|
7
7
|
|
8
8
|
from airbyte_cdk.sources.declarative.interpolation.interpolated_nested_mapping import NestedMapping
|
9
9
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
10
|
-
|
10
|
+
RequestBodyGraphQL,
|
11
|
+
RequestBodyJsonObject,
|
12
|
+
RequestBodyPlainText,
|
13
|
+
RequestBodyUrlEncodedForm,
|
11
14
|
)
|
12
15
|
from airbyte_cdk.sources.declarative.requesters.request_options.interpolated_nested_request_input_provider import (
|
13
16
|
InterpolatedNestedRequestInputProvider,
|
@@ -41,7 +44,14 @@ class InterpolatedRequestOptionsProvider(RequestOptionsProvider):
|
|
41
44
|
config: Config = field(default_factory=dict)
|
42
45
|
request_parameters: Optional[RequestInput] = None
|
43
46
|
request_headers: Optional[RequestInput] = None
|
44
|
-
request_body: Optional[
|
47
|
+
request_body: Optional[
|
48
|
+
Union[
|
49
|
+
RequestBodyGraphQL,
|
50
|
+
RequestBodyJsonObject,
|
51
|
+
RequestBodyPlainText,
|
52
|
+
RequestBodyUrlEncodedForm,
|
53
|
+
]
|
54
|
+
] = None
|
45
55
|
request_body_data: Optional[RequestInput] = None
|
46
56
|
request_body_json: Optional[NestedMapping] = None
|
47
57
|
query_properties_key: Optional[str] = None
|
@@ -83,14 +93,18 @@ class InterpolatedRequestOptionsProvider(RequestOptionsProvider):
|
|
83
93
|
based on the type specified in `self.request_body`. If neither is provided, both are initialized as empty
|
84
94
|
dictionaries. Raises a ValueError if both `request_body_data` and `request_body_json` are set simultaneously.
|
85
95
|
Raises:
|
86
|
-
ValueError:
|
96
|
+
ValueError: if an unsupported request body type is provided.
|
87
97
|
"""
|
88
98
|
# Resolve the request body to either data or json
|
89
99
|
if self.request_body is not None and self.request_body.type is not None:
|
90
|
-
if self.request_body.type == "
|
100
|
+
if self.request_body.type == "RequestBodyUrlEncodedForm":
|
91
101
|
self.request_body_data = self.request_body.value
|
92
|
-
elif self.request_body.type == "
|
102
|
+
elif self.request_body.type == "RequestBodyGraphQL":
|
103
|
+
self.request_body_json = {"query": self.request_body.value.query}
|
104
|
+
elif self.request_body.type in ("RequestBodyJsonObject", "RequestBodyPlainText"):
|
93
105
|
self.request_body_json = self.request_body.value
|
106
|
+
else:
|
107
|
+
raise ValueError(f"Unsupported request body type: {self.request_body.type}")
|
94
108
|
|
95
109
|
def get_request_params(
|
96
110
|
self,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: airbyte-cdk
|
3
|
-
Version: 6.48.
|
3
|
+
Version: 6.48.4
|
4
4
|
Summary: A framework for writing Airbyte Connectors.
|
5
5
|
Home-page: https://airbyte.com
|
6
6
|
License: MIT
|
@@ -66,6 +66,7 @@ Requires-Dist: pytz (==2024.2)
|
|
66
66
|
Requires-Dist: rapidfuzz (>=3.10.1,<4.0.0)
|
67
67
|
Requires-Dist: requests
|
68
68
|
Requires-Dist: requests_cache
|
69
|
+
Requires-Dist: rich
|
69
70
|
Requires-Dist: rich-click (>=1.8.8,<2.0.0)
|
70
71
|
Requires-Dist: serpyco-rs (>=1.10.2,<2.0.0)
|
71
72
|
Requires-Dist: sqlalchemy (>=2.0,<3.0,!=2.0.36) ; extra == "sql"
|
@@ -4,8 +4,8 @@ airbyte_cdk/cli/airbyte_cdk/__init__.py,sha256=8IoEcbdYr7CMAh97Xut5__uHH9vV4LKUt
|
|
4
4
|
airbyte_cdk/cli/airbyte_cdk/_connector.py,sha256=bz6PGAuer17mSTkbCw10rTllOEjG_gw3U2lsvXAcPQE,5337
|
5
5
|
airbyte_cdk/cli/airbyte_cdk/_image.py,sha256=F0XtvR2CyFi1EPIUBiEnDia9NfCuM7T_itdNj9yyb2E,2907
|
6
6
|
airbyte_cdk/cli/airbyte_cdk/_manifest.py,sha256=aFdeeWgek7oXR3YfZPxk7kBZ64Blmsr0dAXN6BVGiIA,482
|
7
|
-
airbyte_cdk/cli/airbyte_cdk/_secrets.py,sha256=
|
8
|
-
airbyte_cdk/cli/airbyte_cdk/_util.py,sha256=
|
7
|
+
airbyte_cdk/cli/airbyte_cdk/_secrets.py,sha256=iRA8435sYDcWe6IBv4VAo-3yTIAFqySbDvmUsgpEInA,14712
|
8
|
+
airbyte_cdk/cli/airbyte_cdk/_util.py,sha256=bnzBIAIvuukbOwYwHXay33yAkyyOVJw24xNjNXsPG74,2732
|
9
9
|
airbyte_cdk/cli/airbyte_cdk/_version.py,sha256=ohZNIktLFk91sdzqFW5idaNrZAPX2dIRnz---_fcKOE,352
|
10
10
|
airbyte_cdk/cli/source_declarative_manifest/__init__.py,sha256=-0ST722Nj65bgRokzpzPkD1NBBW5CytEHFUe38cB86Q,91
|
11
11
|
airbyte_cdk/cli/source_declarative_manifest/_run.py,sha256=9qtbjt-I_stGWzWX6yVUKO_eE-Ga7g-uTuibML9qLBs,8330
|
@@ -43,9 +43,9 @@ airbyte_cdk/manifest_migrations/manifest_migration.py,sha256=4ohLfbj2PeuPSgCMVbC
|
|
43
43
|
airbyte_cdk/manifest_migrations/migration_handler.py,sha256=CF8in-Eb45TGzFBxEJrXSzqVr8Lgv0vqvZlbuz1rbQk,6096
|
44
44
|
airbyte_cdk/manifest_migrations/migrations/__init__.py,sha256=SJ7imfOgCRYOVaFkW2bVEnSUxbYPlkryWwYT2semsF0,62
|
45
45
|
airbyte_cdk/manifest_migrations/migrations/http_requester_path_to_url.py,sha256=IIn2SjRh1v2yaSBFUCDyBHpX6mBhlckhvbsSg55mREI,2153
|
46
|
-
airbyte_cdk/manifest_migrations/migrations/http_requester_request_body_json_data_to_request_body.py,sha256=
|
46
|
+
airbyte_cdk/manifest_migrations/migrations/http_requester_request_body_json_data_to_request_body.py,sha256=4nX0oUcFytjpCFnz-oEf4JpeROP7_NBOEX9gCKFoBgg,2726
|
47
47
|
airbyte_cdk/manifest_migrations/migrations/http_requester_url_base_to_url.py,sha256=EX1MVYVpoWypA28qoH48wA0SYZjGdlR8bcSixTDzfgo,1346
|
48
|
-
airbyte_cdk/manifest_migrations/migrations/registry.yaml,sha256=
|
48
|
+
airbyte_cdk/manifest_migrations/migrations/registry.yaml,sha256=K5KBQ2C1T_dWExEJFuEAe1VO_QqOijOCh90rnUOCEyc,960
|
49
49
|
airbyte_cdk/manifest_migrations/migrations_registry.py,sha256=zly2fwaOxDukqC7eowzrDlvhA2v71FjW74kDzvRXhSY,2619
|
50
50
|
airbyte_cdk/models/__init__.py,sha256=Et9wJWs5VOWynGbb-3aJRhsdAHAiLkNNLxdwqJAuqkw,2114
|
51
51
|
airbyte_cdk/models/airbyte_protocol.py,sha256=oZdKsZ7yPjUt9hvxdWNpxCtgjSV2RWhf4R9Np03sqyY,3613
|
@@ -75,7 +75,7 @@ airbyte_cdk/sources/declarative/async_job/timer.py,sha256=Fb8P72CQ7jIzJyzMSSNuBf
|
|
75
75
|
airbyte_cdk/sources/declarative/auth/__init__.py,sha256=e2CRrcBWGhz3sQu3Oh34d1riEIwXipGS8hrSB1pu0Oo,284
|
76
76
|
airbyte_cdk/sources/declarative/auth/declarative_authenticator.py,sha256=nf-OmRUHYG4ORBwyb5CANzuHEssE-oNmL-Lccn41Td8,1099
|
77
77
|
airbyte_cdk/sources/declarative/auth/jwt.py,sha256=SICqNsN2Cn_EgKadIgWuZpQxuMHyzrMZD_2-Uwy10rY,8539
|
78
|
-
airbyte_cdk/sources/declarative/auth/oauth.py,sha256=
|
78
|
+
airbyte_cdk/sources/declarative/auth/oauth.py,sha256=Uxh3EpIV0noe23e1j8efoegjk1d5B7xyVAFSaFtbwoQ,14127
|
79
79
|
airbyte_cdk/sources/declarative/auth/selective_authenticator.py,sha256=qGwC6YsCldr1bIeKG6Qo-A9a5cTdHw-vcOn3OtQrS4c,1540
|
80
80
|
airbyte_cdk/sources/declarative/auth/token.py,sha256=2EnE78EhBOY9hbeZnQJ9AuFaM-G7dccU-oKo_LThRQk,11070
|
81
81
|
airbyte_cdk/sources/declarative/auth/token_provider.py,sha256=Jzuxlmt1_-_aFC_n0OmP8L1nDOacLzbEVVx3kjdX_W8,3104
|
@@ -89,7 +89,7 @@ airbyte_cdk/sources/declarative/concurrent_declarative_source.py,sha256=GoZJ8Oxb
|
|
89
89
|
airbyte_cdk/sources/declarative/datetime/__init__.py,sha256=4Hw-PX1-VgESLF16cDdvuYCzGJtHntThLF4qIiULWeo,61
|
90
90
|
airbyte_cdk/sources/declarative/datetime/datetime_parser.py,sha256=_zGNGq31RNy_0QBLt_EcTvgPyhj7urPdx6oA3M5-r3o,3150
|
91
91
|
airbyte_cdk/sources/declarative/datetime/min_max_datetime.py,sha256=0BHBtDNQZfvwM45-tY5pNlTcKAFSGGNxemoi0Jic-0E,5785
|
92
|
-
airbyte_cdk/sources/declarative/declarative_component_schema.yaml,sha256=
|
92
|
+
airbyte_cdk/sources/declarative/declarative_component_schema.yaml,sha256=AK65U441O1kjmTnURofXyxDVyLkwCI-mVNflOdVpQM8,166443
|
93
93
|
airbyte_cdk/sources/declarative/declarative_source.py,sha256=qmyMnnet92eGc3C22yBtpvD5UZjqdhsAafP_zxI5wp8,1814
|
94
94
|
airbyte_cdk/sources/declarative/declarative_stream.py,sha256=dCRlddBUSaJmBNBz1pSO1r2rTw8AP5d2_vlmIeGs2gg,10767
|
95
95
|
airbyte_cdk/sources/declarative/decoders/__init__.py,sha256=JHb_0d3SE6kNY10mxA5YBEKPeSbsWYjByq1gUQxepoE,953
|
@@ -133,7 +133,7 @@ airbyte_cdk/sources/declarative/migrations/legacy_to_per_partition_state_migrati
|
|
133
133
|
airbyte_cdk/sources/declarative/migrations/state_migration.py,sha256=KWPjealMLKSMtajXgkdGgKg7EmTLR-CqqD7UIh0-eDU,794
|
134
134
|
airbyte_cdk/sources/declarative/models/__init__.py,sha256=nUFxNCiKeYRVXuZEKA7GD-lTHxsiKcQ8FitZjKhPIvE,100
|
135
135
|
airbyte_cdk/sources/declarative/models/base_model_with_deprecations.py,sha256=Rq5kzR5bflqBf6td2ZAgw6lP3iN_mNi4tjntn_R01_o,5851
|
136
|
-
airbyte_cdk/sources/declarative/models/declarative_component_schema.py,sha256=
|
136
|
+
airbyte_cdk/sources/declarative/models/declarative_component_schema.py,sha256=CXk0TwSGrpEwtbrfa8PSt6JZxpov0NqjTpEEu9Pv0OM,118040
|
137
137
|
airbyte_cdk/sources/declarative/parsers/__init__.py,sha256=ZnqYNxHsKCgO38IwB34RQyRMXTs4GTvlRi3ImKnIioo,61
|
138
138
|
airbyte_cdk/sources/declarative/parsers/custom_code_compiler.py,sha256=nlVvHC511NUyDEEIRBkoeDTAvLqKNp-hRy8D19z8tdk,5941
|
139
139
|
airbyte_cdk/sources/declarative/parsers/custom_exceptions.py,sha256=wnRUP0Xeru9Rbu5OexXSDN9QWDo8YU4tT9M2LDVOgGA,802
|
@@ -189,7 +189,7 @@ airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_reques
|
|
189
189
|
airbyte_cdk/sources/declarative/requesters/request_options/default_request_options_provider.py,sha256=SRROdPJZ5kuqHLOlkh115pWP9nDGfDxRYPgH9oD3hPo,1798
|
190
190
|
airbyte_cdk/sources/declarative/requesters/request_options/interpolated_nested_request_input_provider.py,sha256=86YozYuBDfu0t9NbevIvQoGU0vqTP4rt3dRSTsHz3PA,2269
|
191
191
|
airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_input_provider.py,sha256=rR00kE64U2yL0McU1gPr4_W5_sLUqwDgL3Nvj691nRU,2884
|
192
|
-
airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py,sha256=
|
192
|
+
airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py,sha256=ks4rCSdEKTX2bm8obDf-PzWggel1rQEwtu35-rQvqVk,8274
|
193
193
|
airbyte_cdk/sources/declarative/requesters/request_options/request_options_provider.py,sha256=8YRiDzjYvqJ-aMmKFcjqzv_-e8OZ5QG_TbpZ-nuCu6s,2590
|
194
194
|
airbyte_cdk/sources/declarative/requesters/request_path.py,sha256=S3MeFvcaQrMbOkSY2W2VbXLNomqt_3eXqVd9ZhgNwUs,299
|
195
195
|
airbyte_cdk/sources/declarative/requesters/requester.py,sha256=T6tMx_Bx4iT-0YVjY7IzgRil-gaIu9n01b1iwpTh3Ek,5516
|
@@ -408,9 +408,9 @@ airbyte_cdk/utils/slice_hasher.py,sha256=EDxgROHDbfG-QKQb59m7h_7crN1tRiawdf5uU7G
|
|
408
408
|
airbyte_cdk/utils/spec_schema_transformations.py,sha256=-5HTuNsnDBAhj-oLeQXwpTGA0HdcjFOf2zTEMUTTg_Y,816
|
409
409
|
airbyte_cdk/utils/stream_status_utils.py,sha256=ZmBoiy5HVbUEHAMrUONxZvxnvfV9CesmQJLDTAIWnWw,1171
|
410
410
|
airbyte_cdk/utils/traced_exception.py,sha256=C8uIBuCL_E4WnBAOPSxBicD06JAldoN9fGsQDp463OY,6292
|
411
|
-
airbyte_cdk-6.48.
|
412
|
-
airbyte_cdk-6.48.
|
413
|
-
airbyte_cdk-6.48.
|
414
|
-
airbyte_cdk-6.48.
|
415
|
-
airbyte_cdk-6.48.
|
416
|
-
airbyte_cdk-6.48.
|
411
|
+
airbyte_cdk-6.48.4.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
|
412
|
+
airbyte_cdk-6.48.4.dist-info/LICENSE_SHORT,sha256=aqF6D1NcESmpn-cqsxBtszTEnHKnlsp8L4x9wAh3Nxg,55
|
413
|
+
airbyte_cdk-6.48.4.dist-info/METADATA,sha256=hXMMplkbvREua_CfMKw1NaOIGbD43pmoz1tURhdTUWg,6343
|
414
|
+
airbyte_cdk-6.48.4.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
415
|
+
airbyte_cdk-6.48.4.dist-info/entry_points.txt,sha256=AKWbEkHfpzzk9nF9tqBUaw1MbvTM4mGtEzmZQm0ZWvM,139
|
416
|
+
airbyte_cdk-6.48.4.dist-info/RECORD,,
|
{airbyte_cdk-6.48.2.post1.dev14733922675.dist-info → airbyte_cdk-6.48.4.dist-info}/LICENSE.txt
RENAMED
File without changes
|
{airbyte_cdk-6.48.2.post1.dev14733922675.dist-info → airbyte_cdk-6.48.4.dist-info}/LICENSE_SHORT
RENAMED
File without changes
|
File without changes
|
{airbyte_cdk-6.48.2.post1.dev14733922675.dist-info → airbyte_cdk-6.48.4.dist-info}/entry_points.txt
RENAMED
File without changes
|