airbyte-cdk 6.48.0__py3-none-any.whl → 6.48.1__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/connector_builder/connector_builder_handler.py +11 -0
- airbyte_cdk/manifest_migrations/README.md +73 -0
- airbyte_cdk/manifest_migrations/__init__.py +3 -0
- airbyte_cdk/manifest_migrations/exceptions.py +12 -0
- airbyte_cdk/manifest_migrations/manifest_migration.py +134 -0
- airbyte_cdk/manifest_migrations/migration_handler.py +163 -0
- airbyte_cdk/manifest_migrations/migrations/__init__.py +4 -0
- airbyte_cdk/manifest_migrations/migrations/http_requester_path_to_url.py +57 -0
- airbyte_cdk/manifest_migrations/migrations/http_requester_request_body_json_data_to_request_body.py +51 -0
- airbyte_cdk/manifest_migrations/migrations/http_requester_url_base_to_url.py +41 -0
- airbyte_cdk/manifest_migrations/migrations/registry.yaml +22 -0
- airbyte_cdk/manifest_migrations/migrations_registry.py +76 -0
- airbyte_cdk/sources/declarative/manifest_declarative_source.py +26 -7
- airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +8 -8
- {airbyte_cdk-6.48.0.dist-info → airbyte_cdk-6.48.1.dist-info}/METADATA +1 -1
- {airbyte_cdk-6.48.0.dist-info → airbyte_cdk-6.48.1.dist-info}/RECORD +20 -9
- {airbyte_cdk-6.48.0.dist-info → airbyte_cdk-6.48.1.dist-info}/LICENSE.txt +0 -0
- {airbyte_cdk-6.48.0.dist-info → airbyte_cdk-6.48.1.dist-info}/LICENSE_SHORT +0 -0
- {airbyte_cdk-6.48.0.dist-info → airbyte_cdk-6.48.1.dist-info}/WHEEL +0 -0
- {airbyte_cdk-6.48.0.dist-info → airbyte_cdk-6.48.1.dist-info}/entry_points.txt +0 -0
@@ -56,6 +56,16 @@ def get_limits(config: Mapping[str, Any]) -> TestLimits:
|
|
56
56
|
return TestLimits(max_records, max_pages_per_slice, max_slices, max_streams)
|
57
57
|
|
58
58
|
|
59
|
+
def should_migrate_manifest(config: Mapping[str, Any]) -> bool:
|
60
|
+
"""
|
61
|
+
Determines whether the manifest should be migrated,
|
62
|
+
based on the presence of the "__should_migrate" key in the config.
|
63
|
+
|
64
|
+
This flag is set by the UI.
|
65
|
+
"""
|
66
|
+
return config.get("__should_migrate", False)
|
67
|
+
|
68
|
+
|
59
69
|
def should_normalize_manifest(config: Mapping[str, Any]) -> bool:
|
60
70
|
"""
|
61
71
|
Check if the manifest should be normalized.
|
@@ -71,6 +81,7 @@ def create_source(config: Mapping[str, Any], limits: TestLimits) -> ManifestDecl
|
|
71
81
|
config=config,
|
72
82
|
emit_connector_builder_messages=True,
|
73
83
|
source_config=manifest,
|
84
|
+
migrate_manifest=should_migrate_manifest(config),
|
74
85
|
normalize_manifest=should_normalize_manifest(config),
|
75
86
|
component_factory=ModelToComponentFactory(
|
76
87
|
emit_connector_builder_messages=True,
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# Manifest Migrations
|
2
|
+
|
3
|
+
This directory contains the logic and registry for manifest migrations in the Airbyte CDK. Migrations are used to update or transform manifest components to newer formats or schemas as the CDK evolves.
|
4
|
+
|
5
|
+
## Adding a New Migration
|
6
|
+
|
7
|
+
1. **Create a Migration File:**
|
8
|
+
- Add a new Python file in the `migrations/` subdirectory.
|
9
|
+
- Name the file using the pattern: `<description_of_the_migration>.py`.
|
10
|
+
- Example: `http_requester_url_base_to_url.py`
|
11
|
+
- The filename should be unique and descriptive.
|
12
|
+
|
13
|
+
2. **Define the Migration Class:**
|
14
|
+
- The migration class must inherit from `ManifestMigration`.
|
15
|
+
- Name the class using a descriptive name (e.g., `HttpRequesterUrlBaseToUrl`).
|
16
|
+
- Implement the following methods:
|
17
|
+
- `should_migrate(self, manifest: ManifestType) -> bool`
|
18
|
+
- `migrate(self, manifest: ManifestType) -> None`
|
19
|
+
- `validate(self, manifest: ManifestType) -> bool`
|
20
|
+
|
21
|
+
3. **Register the Migration:**
|
22
|
+
- Open `migrations/registry.yaml`.
|
23
|
+
- Add an entry under the appropriate version, or create a new version section if needed.
|
24
|
+
- Each migration entry should include:
|
25
|
+
- `name`: The filename (without `.py`)
|
26
|
+
- `order`: The order in which this migration should be applied for the version
|
27
|
+
- `description`: A short description of the migration
|
28
|
+
|
29
|
+
Example:
|
30
|
+
```yaml
|
31
|
+
manifest_migrations:
|
32
|
+
- version: 6.45.2
|
33
|
+
migrations:
|
34
|
+
- name: http_requester_url_base_to_url
|
35
|
+
order: 1
|
36
|
+
description: |
|
37
|
+
This migration updates the `url_base` field in the `HttpRequester` component spec to `url`.
|
38
|
+
```
|
39
|
+
|
40
|
+
4. **Testing:**
|
41
|
+
- Ensure your migration is covered by unit tests.
|
42
|
+
- Tests should verify both `should_migrate`, `migrate`, and `validate` behaviors.
|
43
|
+
|
44
|
+
## Migration Discovery
|
45
|
+
|
46
|
+
- Migrations are discovered and registered automatically based on the entries in `migrations/registry.yaml`.
|
47
|
+
- Do not modify the migration registry in code manually.
|
48
|
+
- If you need to skip certain component types, use the `NON_MIGRATABLE_TYPES` list in `manifest_migration.py`.
|
49
|
+
|
50
|
+
## Example Migration Skeleton
|
51
|
+
|
52
|
+
```python
|
53
|
+
from airbyte_cdk.manifest_migrations.manifest_migration import TYPE_TAG, ManifestMigration, ManifestType
|
54
|
+
|
55
|
+
class ExampleMigration(ManifestMigration):
|
56
|
+
component_type = "ExampleComponent"
|
57
|
+
original_key = "old_key"
|
58
|
+
replacement_key = "new_key"
|
59
|
+
|
60
|
+
def should_migrate(self, manifest: ManifestType) -> bool:
|
61
|
+
return manifest[TYPE_TAG] == self.component_type and self.original_key in manifest
|
62
|
+
|
63
|
+
def migrate(self, manifest: ManifestType) -> None:
|
64
|
+
manifest[self.replacement_key] = manifest[self.original_key]
|
65
|
+
manifest.pop(self.original_key, None)
|
66
|
+
|
67
|
+
def validate(self, manifest: ManifestType) -> bool:
|
68
|
+
return self.replacement_key in manifest and self.original_key not in manifest
|
69
|
+
```
|
70
|
+
|
71
|
+
---
|
72
|
+
|
73
|
+
For more details, see the docstrings in `manifest_migration.py` and the examples in the `migrations/` folder.
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
3
|
+
#
|
4
|
+
|
5
|
+
|
6
|
+
class ManifestMigrationException(Exception):
|
7
|
+
"""
|
8
|
+
Raised when a migration error occurs in the manifest.
|
9
|
+
"""
|
10
|
+
|
11
|
+
def __init__(self, message: str) -> None:
|
12
|
+
super().__init__(f"Failed to migrate the manifest: {message}")
|
@@ -0,0 +1,134 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
3
|
+
#
|
4
|
+
|
5
|
+
|
6
|
+
from abc import ABC, abstractmethod
|
7
|
+
from dataclasses import asdict, dataclass
|
8
|
+
from typing import Any, Dict
|
9
|
+
|
10
|
+
ManifestType = Dict[str, Any]
|
11
|
+
|
12
|
+
|
13
|
+
TYPE_TAG = "type"
|
14
|
+
|
15
|
+
NON_MIGRATABLE_TYPES = [
|
16
|
+
# more info here: https://github.com/airbytehq/airbyte-internal-issues/issues/12423
|
17
|
+
"DynamicDeclarativeStream",
|
18
|
+
]
|
19
|
+
|
20
|
+
|
21
|
+
@dataclass
|
22
|
+
class MigrationTrace:
|
23
|
+
"""
|
24
|
+
This class represents a migration that has been applied to the manifest.
|
25
|
+
It contains information about the migration, including the version it was applied from,
|
26
|
+
the version it was applied to, and the time it was applied.
|
27
|
+
"""
|
28
|
+
|
29
|
+
from_version: str
|
30
|
+
to_version: str
|
31
|
+
migration: str
|
32
|
+
migrated_at: str
|
33
|
+
|
34
|
+
def as_dict(self) -> Dict[str, Any]:
|
35
|
+
return asdict(self)
|
36
|
+
|
37
|
+
|
38
|
+
class ManifestMigration(ABC):
|
39
|
+
"""
|
40
|
+
Base class for manifest migrations.
|
41
|
+
This class provides a framework for migrating manifest components.
|
42
|
+
It defines the structure for migration classes, including methods for checking if a migration is needed,
|
43
|
+
performing the migration, and validating the migration.
|
44
|
+
"""
|
45
|
+
|
46
|
+
def __init__(self) -> None:
|
47
|
+
self.is_migrated: bool = False
|
48
|
+
|
49
|
+
@abstractmethod
|
50
|
+
def should_migrate(self, manifest: ManifestType) -> bool:
|
51
|
+
"""
|
52
|
+
Check if the manifest should be migrated.
|
53
|
+
|
54
|
+
:param manifest: The manifest to potentially migrate
|
55
|
+
|
56
|
+
:return: true if the manifest is of the expected format and should be migrated. False otherwise.
|
57
|
+
"""
|
58
|
+
|
59
|
+
@abstractmethod
|
60
|
+
def migrate(self, manifest: ManifestType) -> None:
|
61
|
+
"""
|
62
|
+
Migrate the manifest. Assumes should_migrate(manifest) returned True.
|
63
|
+
|
64
|
+
:param manifest: The manifest to migrate
|
65
|
+
"""
|
66
|
+
|
67
|
+
@abstractmethod
|
68
|
+
def validate(self, manifest: ManifestType) -> bool:
|
69
|
+
"""
|
70
|
+
Validate the manifest to ensure the migration was successfully applied.
|
71
|
+
|
72
|
+
:param manifest: The manifest to validate
|
73
|
+
"""
|
74
|
+
|
75
|
+
def _is_component(self, obj: Dict[str, Any]) -> bool:
|
76
|
+
"""
|
77
|
+
Check if the object is a component.
|
78
|
+
|
79
|
+
:param obj: The object to check
|
80
|
+
:return: True if the object is a component, False otherwise
|
81
|
+
"""
|
82
|
+
return TYPE_TAG in obj.keys()
|
83
|
+
|
84
|
+
def _is_migratable_type(self, obj: Dict[str, Any]) -> bool:
|
85
|
+
"""
|
86
|
+
Check if the object is a migratable component,
|
87
|
+
based on the Type of the component and the migration version.
|
88
|
+
|
89
|
+
:param obj: The object to check
|
90
|
+
:return: True if the object is a migratable component, False otherwise
|
91
|
+
"""
|
92
|
+
return obj[TYPE_TAG] not in NON_MIGRATABLE_TYPES
|
93
|
+
|
94
|
+
def _process_manifest(self, obj: Any) -> None:
|
95
|
+
"""
|
96
|
+
Recursively processes a manifest object, migrating components that match the migration criteria.
|
97
|
+
|
98
|
+
This method traverses the entire manifest structure (dictionaries and lists) and applies
|
99
|
+
migrations to components that:
|
100
|
+
1. Have a type tag
|
101
|
+
2. Are not in the list of non-migratable types
|
102
|
+
3. Meet the conditions defined in the should_migrate method
|
103
|
+
|
104
|
+
Parameters:
|
105
|
+
obj (Any): The object to process, which can be a dictionary, list, or any other type.
|
106
|
+
Dictionary objects are checked for component type tags and potentially migrated.
|
107
|
+
List objects have each of their items processed recursively.
|
108
|
+
Other types are ignored.
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
None, since we process the manifest in place.
|
112
|
+
"""
|
113
|
+
if isinstance(obj, dict):
|
114
|
+
# Check if the object is a component
|
115
|
+
if self._is_component(obj):
|
116
|
+
# Check if the object is allowed to be migrated
|
117
|
+
if not self._is_migratable_type(obj):
|
118
|
+
return
|
119
|
+
|
120
|
+
# Check if the object should be migrated
|
121
|
+
if self.should_migrate(obj):
|
122
|
+
# Perform the migration, if needed
|
123
|
+
self.migrate(obj)
|
124
|
+
# validate the migration
|
125
|
+
self.is_migrated = self.validate(obj)
|
126
|
+
|
127
|
+
# Process all values in the dictionary
|
128
|
+
for value in list(obj.values()):
|
129
|
+
self._process_manifest(value)
|
130
|
+
|
131
|
+
elif isinstance(obj, list):
|
132
|
+
# Process all items in the list
|
133
|
+
for item in obj:
|
134
|
+
self._process_manifest(item)
|
@@ -0,0 +1,163 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
3
|
+
#
|
4
|
+
|
5
|
+
|
6
|
+
import copy
|
7
|
+
import logging
|
8
|
+
from datetime import datetime, timezone
|
9
|
+
from typing import Type
|
10
|
+
|
11
|
+
from packaging.version import Version
|
12
|
+
|
13
|
+
from airbyte_cdk.manifest_migrations.exceptions import (
|
14
|
+
ManifestMigrationException,
|
15
|
+
)
|
16
|
+
from airbyte_cdk.manifest_migrations.manifest_migration import (
|
17
|
+
ManifestMigration,
|
18
|
+
ManifestType,
|
19
|
+
MigrationTrace,
|
20
|
+
)
|
21
|
+
from airbyte_cdk.manifest_migrations.migrations_registry import (
|
22
|
+
MANIFEST_MIGRATIONS,
|
23
|
+
)
|
24
|
+
|
25
|
+
METADATA_TAG = "metadata"
|
26
|
+
MANIFEST_VERSION_TAG = "version"
|
27
|
+
APPLIED_MIGRATIONS_TAG = "applied_migrations"
|
28
|
+
|
29
|
+
LOGGER = logging.getLogger("airbyte.cdk.manifest_migrations")
|
30
|
+
|
31
|
+
|
32
|
+
class ManifestMigrationHandler:
|
33
|
+
"""
|
34
|
+
This class is responsible for handling migrations in the manifest.
|
35
|
+
"""
|
36
|
+
|
37
|
+
def __init__(self, manifest: ManifestType) -> None:
|
38
|
+
self._manifest = manifest
|
39
|
+
self._migrated_manifest: ManifestType = copy.deepcopy(self._manifest)
|
40
|
+
|
41
|
+
def apply_migrations(self) -> ManifestType:
|
42
|
+
"""
|
43
|
+
Apply all registered migrations to the manifest.
|
44
|
+
|
45
|
+
This method iterates through all migrations in the migrations registry and applies
|
46
|
+
them sequentially to the current manifest. If any migration fails with a
|
47
|
+
ManifestMigrationException, the original unmodified manifest is returned instead.
|
48
|
+
|
49
|
+
Returns:
|
50
|
+
ManifestType: The migrated manifest if all migrations succeeded, or the original
|
51
|
+
manifest if any migration failed.
|
52
|
+
"""
|
53
|
+
try:
|
54
|
+
manifest_version = self._get_manifest_version()
|
55
|
+
for migration_version, migrations in MANIFEST_MIGRATIONS.items():
|
56
|
+
for migration_cls in migrations:
|
57
|
+
self._handle_migration(migration_cls, manifest_version, migration_version)
|
58
|
+
return self._migrated_manifest
|
59
|
+
except ManifestMigrationException:
|
60
|
+
# if any errors occur we return the original resolved manifest
|
61
|
+
return self._manifest
|
62
|
+
|
63
|
+
def _handle_migration(
|
64
|
+
self,
|
65
|
+
migration_class: Type[ManifestMigration],
|
66
|
+
manifest_version: str,
|
67
|
+
migration_version: str,
|
68
|
+
) -> None:
|
69
|
+
"""
|
70
|
+
Handles a single manifest migration by instantiating the migration class and processing the manifest.
|
71
|
+
|
72
|
+
Args:
|
73
|
+
migration_class (Type[ManifestMigration]): The migration class to apply to the manifest.
|
74
|
+
|
75
|
+
Raises:
|
76
|
+
ManifestMigrationException: If the migration process encounters any errors.
|
77
|
+
"""
|
78
|
+
try:
|
79
|
+
migration_instance = migration_class()
|
80
|
+
if self._version_is_valid_for_migration(manifest_version, migration_version):
|
81
|
+
migration_instance._process_manifest(self._migrated_manifest)
|
82
|
+
if migration_instance.is_migrated:
|
83
|
+
# set the updated manifest version, after migration has been applied
|
84
|
+
self._set_manifest_version(migration_version)
|
85
|
+
self._set_migration_trace(migration_class, manifest_version, migration_version)
|
86
|
+
else:
|
87
|
+
LOGGER.info(
|
88
|
+
f"Manifest migration: `{self._get_migration_name(migration_class)}` is not supported for the given manifest version `{manifest_version}`.",
|
89
|
+
)
|
90
|
+
except Exception as e:
|
91
|
+
raise ManifestMigrationException(str(e)) from e
|
92
|
+
|
93
|
+
def _get_migration_name(self, migration_class: Type[ManifestMigration]) -> str:
|
94
|
+
"""
|
95
|
+
Get the name of the migration instance.
|
96
|
+
|
97
|
+
Returns:
|
98
|
+
str: The name of the migration.
|
99
|
+
"""
|
100
|
+
return migration_class.__name__
|
101
|
+
|
102
|
+
def _get_manifest_version(self) -> str:
|
103
|
+
"""
|
104
|
+
Get the manifest version from the manifest.
|
105
|
+
|
106
|
+
:param manifest: The manifest to get the version from
|
107
|
+
:return: The manifest version
|
108
|
+
"""
|
109
|
+
return str(self._migrated_manifest.get(MANIFEST_VERSION_TAG, "0.0.0"))
|
110
|
+
|
111
|
+
def _version_is_valid_for_migration(
|
112
|
+
self,
|
113
|
+
manifest_version: str,
|
114
|
+
migration_version: str,
|
115
|
+
) -> bool:
|
116
|
+
"""
|
117
|
+
Checks if the given manifest version is less than or equal to the specified migration version.
|
118
|
+
|
119
|
+
Args:
|
120
|
+
manifest_version (str): The version of the manifest to check.
|
121
|
+
migration_version (str): The migration version to compare against.
|
122
|
+
|
123
|
+
Returns:
|
124
|
+
bool: True if the manifest version is less than or equal to the migration version, False otherwise.
|
125
|
+
"""
|
126
|
+
return Version(manifest_version) <= Version(migration_version)
|
127
|
+
|
128
|
+
def _set_manifest_version(self, version: str) -> None:
|
129
|
+
"""
|
130
|
+
Set the manifest version in the manifest.
|
131
|
+
|
132
|
+
:param version: The version to set
|
133
|
+
"""
|
134
|
+
self._migrated_manifest[MANIFEST_VERSION_TAG] = version
|
135
|
+
|
136
|
+
def _set_migration_trace(
|
137
|
+
self,
|
138
|
+
migration_instance: Type[ManifestMigration],
|
139
|
+
manifest_version: str,
|
140
|
+
migration_version: str,
|
141
|
+
) -> None:
|
142
|
+
"""
|
143
|
+
Set the migration trace in the manifest, under the `metadata.applied_migrations` property object.
|
144
|
+
|
145
|
+
:param migration_instance: The migration instance to set
|
146
|
+
:param manifest_version: The manifest version before migration
|
147
|
+
:param migration_version: The manifest version after migration
|
148
|
+
"""
|
149
|
+
|
150
|
+
if METADATA_TAG not in self._migrated_manifest:
|
151
|
+
self._migrated_manifest[METADATA_TAG] = {}
|
152
|
+
if APPLIED_MIGRATIONS_TAG not in self._migrated_manifest[METADATA_TAG]:
|
153
|
+
self._migrated_manifest[METADATA_TAG][APPLIED_MIGRATIONS_TAG] = []
|
154
|
+
|
155
|
+
migration_trace = MigrationTrace(
|
156
|
+
from_version=manifest_version,
|
157
|
+
to_version=migration_version,
|
158
|
+
migration=self._get_migration_name(migration_instance),
|
159
|
+
migrated_at=datetime.now(tz=timezone.utc).isoformat(),
|
160
|
+
).as_dict()
|
161
|
+
|
162
|
+
if migration_version not in self._migrated_manifest[METADATA_TAG][APPLIED_MIGRATIONS_TAG]:
|
163
|
+
self._migrated_manifest[METADATA_TAG][APPLIED_MIGRATIONS_TAG].append(migration_trace)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
3
|
+
#
|
4
|
+
|
5
|
+
|
6
|
+
from urllib.parse import urljoin
|
7
|
+
|
8
|
+
from airbyte_cdk.manifest_migrations.manifest_migration import (
|
9
|
+
TYPE_TAG,
|
10
|
+
ManifestMigration,
|
11
|
+
ManifestType,
|
12
|
+
)
|
13
|
+
from airbyte_cdk.sources.types import EmptyString
|
14
|
+
|
15
|
+
|
16
|
+
class HttpRequesterPathToUrl(ManifestMigration):
|
17
|
+
"""
|
18
|
+
This migration is responsible for migrating the `path` key to `url` in the HttpRequester component.
|
19
|
+
The `path` key is expected to be a relative path, and the `url` key is expected to be a full URL.
|
20
|
+
The migration will concatenate the `url_base` and `path` to form a full URL.
|
21
|
+
"""
|
22
|
+
|
23
|
+
component_type = "HttpRequester"
|
24
|
+
original_key = "path"
|
25
|
+
replacement_key = "url"
|
26
|
+
|
27
|
+
def should_migrate(self, manifest: ManifestType) -> bool:
|
28
|
+
return manifest[TYPE_TAG] == self.component_type and self.original_key in list(
|
29
|
+
manifest.keys()
|
30
|
+
)
|
31
|
+
|
32
|
+
def migrate(self, manifest: ManifestType) -> None:
|
33
|
+
original_key_value = manifest.get(self.original_key, EmptyString).lstrip("/")
|
34
|
+
replacement_key_value = manifest[self.replacement_key]
|
35
|
+
|
36
|
+
# return a full-url if provided directly from interpolation context
|
37
|
+
if original_key_value == EmptyString or original_key_value is None:
|
38
|
+
manifest[self.replacement_key] = replacement_key_value
|
39
|
+
manifest.pop(self.original_key, None)
|
40
|
+
else:
|
41
|
+
# since we didn't provide a full-url, the url_base might not have a trailing slash
|
42
|
+
# so we join the url_base and path correctly
|
43
|
+
if not replacement_key_value.endswith("/"):
|
44
|
+
replacement_key_value += "/"
|
45
|
+
|
46
|
+
manifest[self.replacement_key] = urljoin(replacement_key_value, original_key_value)
|
47
|
+
manifest.pop(self.original_key, None)
|
48
|
+
|
49
|
+
def validate(self, manifest: ManifestType) -> bool:
|
50
|
+
"""
|
51
|
+
Validate the migration by checking if the `url` key is present and the `path` key is not.
|
52
|
+
"""
|
53
|
+
return (
|
54
|
+
self.replacement_key in manifest
|
55
|
+
and self.original_key not in manifest
|
56
|
+
and manifest[self.replacement_key] is not None
|
57
|
+
)
|
airbyte_cdk/manifest_migrations/migrations/http_requester_request_body_json_data_to_request_body.py
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
3
|
+
#
|
4
|
+
|
5
|
+
|
6
|
+
from airbyte_cdk.manifest_migrations.manifest_migration import (
|
7
|
+
TYPE_TAG,
|
8
|
+
ManifestMigration,
|
9
|
+
ManifestType,
|
10
|
+
)
|
11
|
+
|
12
|
+
|
13
|
+
class HttpRequesterRequestBodyJsonDataToRequestBody(ManifestMigration):
|
14
|
+
"""
|
15
|
+
This migration is responsible for migrating the `request_body_json` and `request_body_data` keys
|
16
|
+
to a unified `request_body` key in the HttpRequester component.
|
17
|
+
The migration will copy the value of either original key to `request_body` and remove the original key.
|
18
|
+
"""
|
19
|
+
|
20
|
+
component_type = "HttpRequester"
|
21
|
+
|
22
|
+
body_json_key = "request_body_json"
|
23
|
+
body_data_key = "request_body_data"
|
24
|
+
original_keys = (body_json_key, body_data_key)
|
25
|
+
|
26
|
+
replacement_key = "request_body"
|
27
|
+
|
28
|
+
def should_migrate(self, manifest: ManifestType) -> bool:
|
29
|
+
return manifest[TYPE_TAG] == self.component_type and any(
|
30
|
+
key in list(manifest.keys()) for key in self.original_keys
|
31
|
+
)
|
32
|
+
|
33
|
+
def migrate(self, manifest: ManifestType) -> None:
|
34
|
+
for key in self.original_keys:
|
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)
|
41
|
+
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)
|
47
|
+
|
48
|
+
def validate(self, manifest: ManifestType) -> bool:
|
49
|
+
return self.replacement_key in manifest and all(
|
50
|
+
key not in manifest for key in self.original_keys
|
51
|
+
)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
3
|
+
#
|
4
|
+
|
5
|
+
|
6
|
+
from airbyte_cdk.manifest_migrations.manifest_migration import (
|
7
|
+
TYPE_TAG,
|
8
|
+
ManifestMigration,
|
9
|
+
ManifestType,
|
10
|
+
)
|
11
|
+
|
12
|
+
|
13
|
+
class HttpRequesterUrlBaseToUrl(ManifestMigration):
|
14
|
+
"""
|
15
|
+
This migration is responsible for migrating the `url_base` key to `url` in the HttpRequester component.
|
16
|
+
The `url_base` key is expected to be a base URL, and the `url` key is expected to be a full URL.
|
17
|
+
The migration will copy the value of `url_base` to `url`.
|
18
|
+
"""
|
19
|
+
|
20
|
+
component_type = "HttpRequester"
|
21
|
+
original_key = "url_base"
|
22
|
+
replacement_key = "url"
|
23
|
+
|
24
|
+
def should_migrate(self, manifest: ManifestType) -> bool:
|
25
|
+
return manifest[TYPE_TAG] == self.component_type and self.original_key in list(
|
26
|
+
manifest.keys()
|
27
|
+
)
|
28
|
+
|
29
|
+
def migrate(self, manifest: ManifestType) -> None:
|
30
|
+
manifest[self.replacement_key] = manifest[self.original_key]
|
31
|
+
manifest.pop(self.original_key, None)
|
32
|
+
|
33
|
+
def validate(self, manifest: ManifestType) -> bool:
|
34
|
+
"""
|
35
|
+
Validate the migration by checking if the `url` key is present and the `url_base` key is not.
|
36
|
+
"""
|
37
|
+
return (
|
38
|
+
self.replacement_key in manifest
|
39
|
+
and self.original_key not in manifest
|
40
|
+
and manifest[self.replacement_key] is not None
|
41
|
+
)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
3
|
+
#
|
4
|
+
|
5
|
+
manifest_migrations:
|
6
|
+
- version: 6.47.1
|
7
|
+
migrations:
|
8
|
+
- name: http_requester_url_base_to_url
|
9
|
+
order: 1
|
10
|
+
description: |
|
11
|
+
This migration updates the `url_base` field in the `http_requester` spec to `url`.
|
12
|
+
The `url_base` field is deprecated and will be removed in a future version.
|
13
|
+
- name: http_requester_path_to_url
|
14
|
+
order: 2
|
15
|
+
description: |
|
16
|
+
This migration updates the `path` field in the `http_requester` spec to `url`.
|
17
|
+
The `path` field is deprecated and will be removed in a future version.
|
18
|
+
- name: http_requester_request_body_json_data_to_request_body
|
19
|
+
order: 3
|
20
|
+
description: |
|
21
|
+
This migration updates the `request_body_json_data` field in the `http_requester` spec to `request_body`.
|
22
|
+
The `request_body_json_data` field is deprecated and will be removed in a future version.
|
@@ -0,0 +1,76 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
3
|
+
#
|
4
|
+
|
5
|
+
|
6
|
+
import importlib
|
7
|
+
import inspect
|
8
|
+
import os
|
9
|
+
from pathlib import Path
|
10
|
+
from types import ModuleType
|
11
|
+
from typing import Dict, List, Type
|
12
|
+
|
13
|
+
import yaml
|
14
|
+
|
15
|
+
from airbyte_cdk.manifest_migrations.manifest_migration import (
|
16
|
+
ManifestMigration,
|
17
|
+
)
|
18
|
+
|
19
|
+
DiscoveredMigrations = Dict[str, List[Type[ManifestMigration]]]
|
20
|
+
|
21
|
+
MIGRATIONS_PATH = Path(__file__).parent / "migrations"
|
22
|
+
REGISTRY_PATH = MIGRATIONS_PATH / "registry.yaml"
|
23
|
+
|
24
|
+
|
25
|
+
def _find_migration_module(name: str) -> str:
|
26
|
+
"""
|
27
|
+
Finds the migration module by name in the migrations directory.
|
28
|
+
The name should match the file name of the migration module (without the .py extension).
|
29
|
+
Raises ImportError if the module is not found.
|
30
|
+
"""
|
31
|
+
|
32
|
+
for migration_file in os.listdir(MIGRATIONS_PATH):
|
33
|
+
migration_name = name + ".py"
|
34
|
+
if migration_file == migration_name:
|
35
|
+
return migration_file.replace(".py", "")
|
36
|
+
|
37
|
+
raise ImportError(f"Migration module '{name}' not found in {MIGRATIONS_PATH}.")
|
38
|
+
|
39
|
+
|
40
|
+
def _get_migration_class(module: ModuleType) -> Type[ManifestMigration]:
|
41
|
+
"""
|
42
|
+
Returns the ManifestMigration subclass defined in the module.
|
43
|
+
"""
|
44
|
+
for _, obj in inspect.getmembers(module, inspect.isclass):
|
45
|
+
if issubclass(obj, ManifestMigration):
|
46
|
+
return obj
|
47
|
+
|
48
|
+
raise ImportError(f"No ManifestMigration subclass found in module {module.__name__}.")
|
49
|
+
|
50
|
+
|
51
|
+
def _discover_migrations() -> DiscoveredMigrations:
|
52
|
+
"""
|
53
|
+
Discovers and returns a list of ManifestMigration subclasses in the order specified by registry.yaml.
|
54
|
+
"""
|
55
|
+
with open(REGISTRY_PATH, "r") as f:
|
56
|
+
registry = yaml.safe_load(f)
|
57
|
+
migrations: DiscoveredMigrations = {}
|
58
|
+
# Iterate through the registry and import the migration classes
|
59
|
+
# based on the version and order specified in the registry.yaml
|
60
|
+
for version_entry in registry.get("manifest_migrations", []):
|
61
|
+
migration_version = version_entry.get("version", "0.0.0")
|
62
|
+
if not migration_version in migrations:
|
63
|
+
migrations[migration_version] = []
|
64
|
+
|
65
|
+
for migration in sorted(version_entry.get("migrations", []), key=lambda m: m["order"]):
|
66
|
+
module = importlib.import_module(
|
67
|
+
f"airbyte_cdk.manifest_migrations.migrations.{_find_migration_module(migration['name'])}"
|
68
|
+
)
|
69
|
+
migration_class = _get_migration_class(module)
|
70
|
+
migrations[migration_version].append(migration_class)
|
71
|
+
|
72
|
+
return migrations
|
73
|
+
|
74
|
+
|
75
|
+
# registered migrations
|
76
|
+
MANIFEST_MIGRATIONS: DiscoveredMigrations = _discover_migrations()
|
@@ -15,6 +15,9 @@ from jsonschema.exceptions import ValidationError
|
|
15
15
|
from jsonschema.validators import validate
|
16
16
|
from packaging.version import InvalidVersion, Version
|
17
17
|
|
18
|
+
from airbyte_cdk.manifest_migrations.migration_handler import (
|
19
|
+
ManifestMigrationHandler,
|
20
|
+
)
|
18
21
|
from airbyte_cdk.models import (
|
19
22
|
AirbyteConnectionStatus,
|
20
23
|
AirbyteMessage,
|
@@ -91,6 +94,7 @@ class ManifestDeclarativeSource(DeclarativeSource):
|
|
91
94
|
debug: bool = False,
|
92
95
|
emit_connector_builder_messages: bool = False,
|
93
96
|
component_factory: Optional[ModelToComponentFactory] = None,
|
97
|
+
migrate_manifest: Optional[bool] = False,
|
94
98
|
normalize_manifest: Optional[bool] = False,
|
95
99
|
) -> None:
|
96
100
|
"""
|
@@ -104,12 +108,11 @@ class ManifestDeclarativeSource(DeclarativeSource):
|
|
104
108
|
"""
|
105
109
|
self.logger = logging.getLogger(f"airbyte.{self.name}")
|
106
110
|
self._should_normalize = normalize_manifest
|
111
|
+
self._should_migrate = migrate_manifest
|
107
112
|
self._declarative_component_schema = _get_declarative_component_schema()
|
108
113
|
# If custom components are needed, locate and/or register them.
|
109
114
|
self.components_module: ModuleType | None = get_registered_components_module(config=config)
|
110
|
-
#
|
111
|
-
self._source_config = self._preprocess_manifest(dict(source_config))
|
112
|
-
|
115
|
+
# set additional attributes
|
113
116
|
self._debug = debug
|
114
117
|
self._emit_connector_builder_messages = emit_connector_builder_messages
|
115
118
|
self._constructor = (
|
@@ -126,11 +129,12 @@ class ManifestDeclarativeSource(DeclarativeSource):
|
|
126
129
|
)
|
127
130
|
self._config = config or {}
|
128
131
|
|
132
|
+
# resolve all components in the manifest
|
133
|
+
self._source_config = self._pre_process_manifest(dict(source_config))
|
129
134
|
# validate resolved manifest against the declarative component schema
|
130
135
|
self._validate_source()
|
131
|
-
|
132
136
|
# apply additional post-processing to the manifest
|
133
|
-
self.
|
137
|
+
self._post_process_manifest()
|
134
138
|
|
135
139
|
@property
|
136
140
|
def resolved_manifest(self) -> Mapping[str, Any]:
|
@@ -145,7 +149,7 @@ class ManifestDeclarativeSource(DeclarativeSource):
|
|
145
149
|
"""
|
146
150
|
return self._source_config
|
147
151
|
|
148
|
-
def
|
152
|
+
def _pre_process_manifest(self, manifest: Dict[str, Any]) -> Dict[str, Any]:
|
149
153
|
"""
|
150
154
|
Preprocesses the provided manifest dictionary by resolving any manifest references.
|
151
155
|
|
@@ -169,12 +173,14 @@ class ManifestDeclarativeSource(DeclarativeSource):
|
|
169
173
|
|
170
174
|
return propagated_manifest
|
171
175
|
|
172
|
-
def
|
176
|
+
def _post_process_manifest(self) -> None:
|
173
177
|
"""
|
174
178
|
Post-processes the manifest after validation.
|
175
179
|
This method is responsible for any additional modifications or transformations needed
|
176
180
|
after the manifest has been validated and before it is used in the source.
|
177
181
|
"""
|
182
|
+
# apply manifest migration, if required
|
183
|
+
self._migrate_manifest()
|
178
184
|
# apply manifest normalization, if required
|
179
185
|
self._normalize_manifest()
|
180
186
|
|
@@ -190,6 +196,19 @@ class ManifestDeclarativeSource(DeclarativeSource):
|
|
190
196
|
normalizer = ManifestNormalizer(self._source_config, self._declarative_component_schema)
|
191
197
|
self._source_config = normalizer.normalize()
|
192
198
|
|
199
|
+
def _migrate_manifest(self) -> None:
|
200
|
+
"""
|
201
|
+
This method is used to migrate the manifest. It should be called after the manifest has been validated.
|
202
|
+
The migration is done in place, so the original manifest is modified.
|
203
|
+
|
204
|
+
The original manifest is returned if any error occurs during migration.
|
205
|
+
"""
|
206
|
+
if self._should_migrate:
|
207
|
+
manifest_migrator = ManifestMigrationHandler(self._source_config)
|
208
|
+
self._source_config = manifest_migrator.apply_migrations()
|
209
|
+
# validate migrated manifest against the declarative component schema
|
210
|
+
self._validate_source()
|
211
|
+
|
193
212
|
def _fix_source_type(self, manifest: Dict[str, Any]) -> Dict[str, Any]:
|
194
213
|
"""
|
195
214
|
Fix the source type in the manifest. This is necessary because the source type is not always set in the manifest.
|
@@ -2833,13 +2833,9 @@ class ModelToComponentFactory:
|
|
2833
2833
|
else None
|
2834
2834
|
)
|
2835
2835
|
|
2836
|
-
|
2837
|
-
|
2838
|
-
|
2839
|
-
|
2840
|
-
assert model.transform_before_filtering is not None # for mypy
|
2841
|
-
|
2842
|
-
transform_before_filtering = model.transform_before_filtering
|
2836
|
+
transform_before_filtering = (
|
2837
|
+
False if model.transform_before_filtering is None else model.transform_before_filtering
|
2838
|
+
)
|
2843
2839
|
if client_side_incremental_sync:
|
2844
2840
|
record_filter = ClientSideIncrementalRecordFilterDecorator(
|
2845
2841
|
config=config,
|
@@ -2849,7 +2845,11 @@ class ModelToComponentFactory:
|
|
2849
2845
|
else None,
|
2850
2846
|
**client_side_incremental_sync,
|
2851
2847
|
)
|
2852
|
-
transform_before_filtering =
|
2848
|
+
transform_before_filtering = (
|
2849
|
+
True
|
2850
|
+
if model.transform_before_filtering is None
|
2851
|
+
else model.transform_before_filtering
|
2852
|
+
)
|
2853
2853
|
|
2854
2854
|
if model.schema_normalization is None:
|
2855
2855
|
# default to no schema normalization if not set
|
@@ -14,7 +14,7 @@ airbyte_cdk/config_observation.py,sha256=7SSPxtN0nXPkm4euGNcTTr1iLbwUL01jy-24V1H
|
|
14
14
|
airbyte_cdk/connector.py,sha256=bO23kdGRkl8XKFytOgrrWFc_VagteTHVEF6IsbizVkM,4224
|
15
15
|
airbyte_cdk/connector_builder/README.md,sha256=Hw3wvVewuHG9-QgsAq1jDiKuLlStDxKBz52ftyNRnBw,1665
|
16
16
|
airbyte_cdk/connector_builder/__init__.py,sha256=4Hw-PX1-VgESLF16cDdvuYCzGJtHntThLF4qIiULWeo,61
|
17
|
-
airbyte_cdk/connector_builder/connector_builder_handler.py,sha256=
|
17
|
+
airbyte_cdk/connector_builder/connector_builder_handler.py,sha256=OFTzxyfAevI3Um8fXTOLTgoCc4Sx9NzF0boqYkAATfM,6590
|
18
18
|
airbyte_cdk/connector_builder/main.py,sha256=j1pP5N8RsnvQZ4iYxhLdLEHsJ5Ui7IVFBUi6wYMGBkM,3839
|
19
19
|
airbyte_cdk/connector_builder/models.py,sha256=9pIZ98LW_d6fRS39VdnUOf3cxGt4TkC5MJ0_OrzcCRk,1578
|
20
20
|
airbyte_cdk/connector_builder/test_reader/__init__.py,sha256=iTwBMoI9vaJotEgpqZbFjlxRcbxXYypSVJ9YxeHk7wc,120
|
@@ -36,6 +36,17 @@ airbyte_cdk/destinations/vector_db_based/writer.py,sha256=nZ00xPiohElJmYktEZZIhr
|
|
36
36
|
airbyte_cdk/entrypoint.py,sha256=NRJv5BNZRSUEVTmNBa9N7ih6fW5sg4DwL0nkB9kI99Y,18570
|
37
37
|
airbyte_cdk/exception_handler.py,sha256=D_doVl3Dt60ASXlJsfviOCswxGyKF2q0RL6rif3fNks,2013
|
38
38
|
airbyte_cdk/logger.py,sha256=1cURbvawbunCAV178q-XhTHcbAQZTSf07WhU7U9AXWU,3744
|
39
|
+
airbyte_cdk/manifest_migrations/README.md,sha256=PvnbrW1gyzhlkeucd0YAOXcXVxi0xBUUynzs4DMqjDo,2942
|
40
|
+
airbyte_cdk/manifest_migrations/__init__.py,sha256=0eq9ic_6GGXMwzE31eAOSA7PLtBauMfgM9XshjYHF84,61
|
41
|
+
airbyte_cdk/manifest_migrations/exceptions.py,sha256=mmMZaCVEkYSGykVL5jKA0xsDWWkybRdQwnh9pGb7VG0,300
|
42
|
+
airbyte_cdk/manifest_migrations/manifest_migration.py,sha256=4ohLfbj2PeuPSgCMVbCArb0d-YdaZIllX4ieXQNiRRw,4420
|
43
|
+
airbyte_cdk/manifest_migrations/migration_handler.py,sha256=CF8in-Eb45TGzFBxEJrXSzqVr8Lgv0vqvZlbuz1rbQk,6096
|
44
|
+
airbyte_cdk/manifest_migrations/migrations/__init__.py,sha256=SJ7imfOgCRYOVaFkW2bVEnSUxbYPlkryWwYT2semsF0,62
|
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
|
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=56B09vbBlGWKF5KSB-3rashZLRiJmL8si_lmTokXWsc,960
|
49
|
+
airbyte_cdk/manifest_migrations/migrations_registry.py,sha256=zly2fwaOxDukqC7eowzrDlvhA2v71FjW74kDzvRXhSY,2619
|
39
50
|
airbyte_cdk/models/__init__.py,sha256=Et9wJWs5VOWynGbb-3aJRhsdAHAiLkNNLxdwqJAuqkw,2114
|
40
51
|
airbyte_cdk/models/airbyte_protocol.py,sha256=oZdKsZ7yPjUt9hvxdWNpxCtgjSV2RWhf4R9Np03sqyY,3613
|
41
52
|
airbyte_cdk/models/airbyte_protocol_serializers.py,sha256=s6SaFB2CMrG_7jTQGn_fhFbQ1FUxhCxf5kq2RWGHMVI,1749
|
@@ -116,7 +127,7 @@ airbyte_cdk/sources/declarative/interpolation/interpolated_string.py,sha256=CQkH
|
|
116
127
|
airbyte_cdk/sources/declarative/interpolation/interpolation.py,sha256=9IoeuWam3L6GyN10L6U8xNWXmkt9cnahSDNkez1OmFY,982
|
117
128
|
airbyte_cdk/sources/declarative/interpolation/jinja.py,sha256=UQeuS4Vpyp4hlOn-R3tRyeBX0e9IoV6jQ6gH-Jz8lY0,7182
|
118
129
|
airbyte_cdk/sources/declarative/interpolation/macros.py,sha256=UYSJ5gW7TkHALYnNvUnRP3RlyGwGuRMObF3BHuNzjJM,5320
|
119
|
-
airbyte_cdk/sources/declarative/manifest_declarative_source.py,sha256=
|
130
|
+
airbyte_cdk/sources/declarative/manifest_declarative_source.py,sha256=yPQ4Qsxt8rzgbj52TLVFJiSRJp4lbFF-H7dSsr88E58,22961
|
120
131
|
airbyte_cdk/sources/declarative/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
121
132
|
airbyte_cdk/sources/declarative/migrations/legacy_to_per_partition_state_migration.py,sha256=iemy3fKLczcU0-Aor7tx5jcT6DRedKMqyK7kCOp01hg,3924
|
122
133
|
airbyte_cdk/sources/declarative/migrations/state_migration.py,sha256=KWPjealMLKSMtajXgkdGgKg7EmTLR-CqqD7UIh0-eDU,794
|
@@ -128,7 +139,7 @@ airbyte_cdk/sources/declarative/parsers/custom_exceptions.py,sha256=wnRUP0Xeru9R
|
|
128
139
|
airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py,sha256=RUyFZS0zslLb7UfQrvqMC--k5CVLNSp7zHw6kbosvKE,9688
|
129
140
|
airbyte_cdk/sources/declarative/parsers/manifest_normalizer.py,sha256=laBy7ebjA-PiNwc-50U4FHvMqS_mmHvnabxgFs4CjGw,17069
|
130
141
|
airbyte_cdk/sources/declarative/parsers/manifest_reference_resolver.py,sha256=pJmg78vqE5VfUrF_KJnWjucQ4k9IWFULeAxHCowrHXE,6806
|
131
|
-
airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py,sha256=
|
142
|
+
airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py,sha256=YXR_cKEL6U-B6eji-FxvHFw_6V-bBAVmC4-M5IawXtQ,163774
|
132
143
|
airbyte_cdk/sources/declarative/partition_routers/__init__.py,sha256=TBC9AkGaUqHm2IKHMPN6punBIcY5tWGULowcLoAVkfw,1109
|
133
144
|
airbyte_cdk/sources/declarative/partition_routers/async_job_partition_router.py,sha256=VelO7zKqKtzMJ35jyFeg0ypJLQC0plqqIBNXoBW1G2E,3001
|
134
145
|
airbyte_cdk/sources/declarative/partition_routers/cartesian_product_stream_slicer.py,sha256=c5cuVFM6NFkuQqG8Z5IwkBuwDrvXZN1CunUOM_L0ezg,6892
|
@@ -396,9 +407,9 @@ airbyte_cdk/utils/slice_hasher.py,sha256=EDxgROHDbfG-QKQb59m7h_7crN1tRiawdf5uU7G
|
|
396
407
|
airbyte_cdk/utils/spec_schema_transformations.py,sha256=-5HTuNsnDBAhj-oLeQXwpTGA0HdcjFOf2zTEMUTTg_Y,816
|
397
408
|
airbyte_cdk/utils/stream_status_utils.py,sha256=ZmBoiy5HVbUEHAMrUONxZvxnvfV9CesmQJLDTAIWnWw,1171
|
398
409
|
airbyte_cdk/utils/traced_exception.py,sha256=C8uIBuCL_E4WnBAOPSxBicD06JAldoN9fGsQDp463OY,6292
|
399
|
-
airbyte_cdk-6.48.
|
400
|
-
airbyte_cdk-6.48.
|
401
|
-
airbyte_cdk-6.48.
|
402
|
-
airbyte_cdk-6.48.
|
403
|
-
airbyte_cdk-6.48.
|
404
|
-
airbyte_cdk-6.48.
|
410
|
+
airbyte_cdk-6.48.1.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
|
411
|
+
airbyte_cdk-6.48.1.dist-info/LICENSE_SHORT,sha256=aqF6D1NcESmpn-cqsxBtszTEnHKnlsp8L4x9wAh3Nxg,55
|
412
|
+
airbyte_cdk-6.48.1.dist-info/METADATA,sha256=scl_OXlgL349P2PgmdntFjkKVIBv9d_dPTIF7S2iKmA,6323
|
413
|
+
airbyte_cdk-6.48.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
414
|
+
airbyte_cdk-6.48.1.dist-info/entry_points.txt,sha256=AKWbEkHfpzzk9nF9tqBUaw1MbvTM4mGtEzmZQm0ZWvM,139
|
415
|
+
airbyte_cdk-6.48.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|