airbyte-cdk 6.48.0__py3-none-any.whl → 6.48.2__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/connector_builder/test_reader/reader.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/declarative_component_schema.yaml +80 -5
- airbyte_cdk/sources/declarative/declarative_source.py +10 -1
- airbyte_cdk/sources/declarative/interpolation/interpolated_nested_mapping.py +1 -1
- airbyte_cdk/sources/declarative/manifest_declarative_source.py +32 -7
- airbyte_cdk/sources/declarative/models/base_model_with_deprecations.py +144 -0
- airbyte_cdk/sources/declarative/models/declarative_component_schema.py +54 -5
- airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +66 -15
- airbyte_cdk/sources/declarative/requesters/http_requester.py +62 -17
- airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py +24 -2
- airbyte_cdk/sources/declarative/requesters/requester.py +12 -0
- {airbyte_cdk-6.48.0.dist-info → airbyte_cdk-6.48.2.dist-info}/METADATA +1 -1
- {airbyte_cdk-6.48.0.dist-info → airbyte_cdk-6.48.2.dist-info}/RECORD +29 -17
- {airbyte_cdk-6.48.0.dist-info → airbyte_cdk-6.48.2.dist-info}/LICENSE.txt +0 -0
- {airbyte_cdk-6.48.0.dist-info → airbyte_cdk-6.48.2.dist-info}/LICENSE_SHORT +0 -0
- {airbyte_cdk-6.48.0.dist-info → airbyte_cdk-6.48.2.dist-info}/WHEEL +0 -0
- {airbyte_cdk-6.48.0.dist-info → airbyte_cdk-6.48.2.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,
|
@@ -4,6 +4,7 @@
|
|
4
4
|
|
5
5
|
|
6
6
|
import logging
|
7
|
+
from math import log
|
7
8
|
from typing import Any, ClassVar, Dict, Iterator, List, Mapping, Optional, Union
|
8
9
|
|
9
10
|
from airbyte_cdk.connector_builder.models import (
|
@@ -112,11 +113,16 @@ class TestReader:
|
|
112
113
|
record_limit = self._check_record_limit(record_limit)
|
113
114
|
# The connector builder currently only supports reading from a single stream at a time
|
114
115
|
stream = source.streams(config)[0]
|
116
|
+
|
117
|
+
# get any deprecation warnings during the component creation
|
118
|
+
deprecation_warnings: List[LogMessage] = source.deprecation_warnings()
|
119
|
+
|
115
120
|
schema_inferrer = SchemaInferrer(
|
116
121
|
self._pk_to_nested_and_composite_field(stream.primary_key),
|
117
122
|
self._cursor_field_to_nested_and_composite_field(stream.cursor_field),
|
118
123
|
)
|
119
124
|
datetime_format_inferrer = DatetimeFormatInferrer()
|
125
|
+
|
120
126
|
message_group = get_message_groups(
|
121
127
|
self._read_stream(source, config, configured_catalog, state),
|
122
128
|
schema_inferrer,
|
@@ -127,6 +133,10 @@ class TestReader:
|
|
127
133
|
slices, log_messages, auxiliary_requests, latest_config_update = self._categorise_groups(
|
128
134
|
message_group
|
129
135
|
)
|
136
|
+
|
137
|
+
# extend log messages with deprecation warnings
|
138
|
+
log_messages.extend(deprecation_warnings)
|
139
|
+
|
130
140
|
schema, log_messages = self._get_infered_schema(
|
131
141
|
configured_catalog, schema_inferrer, log_messages
|
132
142
|
)
|
@@ -269,6 +279,7 @@ class TestReader:
|
|
269
279
|
auxiliary_requests = []
|
270
280
|
latest_config_update: Optional[AirbyteControlMessage] = None
|
271
281
|
|
282
|
+
# process the message groups first
|
272
283
|
for message_group in message_groups:
|
273
284
|
match message_group:
|
274
285
|
case AirbyteLogMessage():
|
@@ -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.48.2
|
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.
|