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.
@@ -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
- Usage without pre-installing (stateless):
12
- pipx run airbyte-cdk secrets fetch ...
13
- uvx airbyte-cdk secrets fetch ...
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
- The 'fetch' command retrieves secrets from Google Secret Manager based on connector
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 resolve_connector_name_and_directory
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
- try:
72
- from google.cloud import secretmanager_v1 as secretmanager
73
- except ImportError:
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
- click.echo("Fetching secrets...")
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
- # Resolve connector name/directory
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
- secrets_dir.mkdir(parents=True, exist_ok=True)
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
- gitignore_path = secrets_dir / ".gitignore"
103
- gitignore_path.write_text("*")
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. Please set the GCP_GSM_CREDENTIALS environment variable."
341
+ "No Google Cloud credentials found. "
342
+ "Please set the `GCP_GSM_CREDENTIALS` environment variable."
110
343
  )
111
344
 
112
- client = secretmanager.SecretManagerServiceClient.from_service_account_info(
113
- json.loads(credentials_json)
345
+ return cast(
346
+ "secretmanager.SecretManagerServiceClient",
347
+ secretmanager.SecretManagerServiceClient.from_service_account_info(
348
+ json.loads(credentials_json)
349
+ ),
114
350
  )
115
351
 
116
- # List all secrets with the connector label
117
- parent = f"projects/{gcp_project_id}"
118
- filter_string = f"labels.{CONNECTOR_LABEL}={connector_name}"
119
- secrets = client.list_secrets(
120
- request=secretmanager.ListSecretsRequest(
121
- parent=parent,
122
- filter=filter_string,
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
- # Fetch and write secrets
127
- secret_count = 0
128
- for secret in secrets:
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
- if secret_count == 0:
145
- click.echo(f"No secrets found for connector: {connector_name}")
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
- __all__ = [
149
- "secrets_cli_group",
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
@@ -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
- manifest[self.replacement_key] = {
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
- manifest[self.replacement_key] = {
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)
@@ -3,7 +3,7 @@
3
3
  #
4
4
 
5
5
  manifest_migrations:
6
- - version: 6.48.2
6
+ - version: 6.48.3
7
7
  migrations:
8
8
  - name: http_requester_url_base_to_url
9
9
  order: 1
@@ -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
- # We've seen some APIs allowing empty client_secret so we will only log here
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/RequestBody"
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: RequestBodyJson
2061
+ - type: RequestBodyJsonObject
2059
2062
  value:
2060
2063
  sort_order: "ASC"
2061
2064
  sort_field: "CREATED_AT"
2062
- - type: RequestBodyJson
2065
+ - type: RequestBodyJsonObject
2063
2066
  value:
2064
2067
  key: "{{ config['value'] }}"
2065
- - type: RequestBodyJson
2068
+ - type: RequestBodyJsonObject
2066
2069
  value:
2067
2070
  sort:
2068
2071
  field: "updated_at"
2069
2072
  order: "ascending"
2070
- - type: RequestBodyData
2073
+ - type: RequestBodyPlainText
2071
2074
  value: "plain_text_body"
2072
- - type: RequestBodyData
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
- RequestBody:
4084
+ RequestBodyPlainText:
4085
+ title: Plain-text Body
4086
+ description: Request body value is sent as plain text
4077
4087
  type: object
4078
- description: The request body payload. Can be either URL encoded data or JSON.
4088
+ required:
4089
+ - type
4090
+ - value
4079
4091
  properties:
4080
4092
  type:
4081
- anyOf:
4082
- - type: string
4083
- enum: [RequestBodyData]
4084
- - type: string
4085
- enum: [RequestBodyJson]
4093
+ type: string
4094
+ enum: [RequestBodyPlainText]
4086
4095
  value:
4087
- anyOf:
4088
- - type: string
4089
- description: The request body payload as a string.
4090
- - type: object
4091
- description: The request body payload as a Non-JSON object (url-encoded data).
4092
- additionalProperties:
4093
- type: string
4094
- - type: object
4095
- description: The request body payload as a JSON object (json-encoded data).
4096
- additionalProperties: true
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 RequestBody(BaseModel):
1505
- type: Optional[Union[Literal["RequestBodyData"], Literal["RequestBodyJson"]]] = None
1506
- value: Optional[Union[str, Dict[str, str], Dict[str, Any]]] = None
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[RequestBody] = Field(
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": "RequestBodyJson",
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": "RequestBodyJson",
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": "RequestBodyData", "value": "plain_text_body"},
2351
+ {"type": "RequestBodyPlainText", "value": "plain_text_body"},
2322
2352
  {
2323
- "type": "RequestBodyData",
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
  )
@@ -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
- RequestBody,
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[RequestBody] = None
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: If both `request_body_data` and `request_body_json` are provided.
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 == "RequestBodyData":
100
+ if self.request_body.type == "RequestBodyUrlEncodedForm":
91
101
  self.request_body_data = self.request_body.value
92
- elif self.request_body.type == "RequestBodyJson":
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.2.post1.dev14733922675
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=AOHrMwEeKeEq9icDTu5Zrhw7CzC1lvoxFk1cA0aW3-M,5221
8
- airbyte_cdk/cli/airbyte_cdk/_util.py,sha256=kEvq7OkjDjn7G25gk15xTaMRp6gAWzD68e0PkZBADoY,1714
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=70md8yDu8SWl2JkkFcEs8kyXUbP0F_obIzyHsygyR9k,1777
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=pMG4JzHQyxvTjxhTZpzUXHb0BhwkY7w_6CtTwaUZ_K0,960
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=fAHR0oxKI1zHgyi3ct3WfQ9uDzE2_0rBo0-l3-3zJH4,14272
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=C_hjPvQHfNGNTt2rXp9nCl8T_tPZBoPx0D5yEBi6ZWg,165127
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=Vf8LSt-leRKOpxQVQsGxWpG2aq9s9yvZKi4DYbpmf0s,117147
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=TKerU1oGJJRGPk9_AXpKnaQVdNNIYpdLG1lK3HfaHS8,7741
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.2.post1.dev14733922675.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
412
- airbyte_cdk-6.48.2.post1.dev14733922675.dist-info/LICENSE_SHORT,sha256=aqF6D1NcESmpn-cqsxBtszTEnHKnlsp8L4x9wAh3Nxg,55
413
- airbyte_cdk-6.48.2.post1.dev14733922675.dist-info/METADATA,sha256=hyHulMaWAKNUkn465NanPYPLqM2HEVg0LTMHomPWZzY,6344
414
- airbyte_cdk-6.48.2.post1.dev14733922675.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
415
- airbyte_cdk-6.48.2.post1.dev14733922675.dist-info/entry_points.txt,sha256=AKWbEkHfpzzk9nF9tqBUaw1MbvTM4mGtEzmZQm0ZWvM,139
416
- airbyte_cdk-6.48.2.post1.dev14733922675.dist-info/RECORD,,
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,,