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.
Files changed (29) hide show
  1. airbyte_cdk/connector_builder/connector_builder_handler.py +11 -0
  2. airbyte_cdk/connector_builder/test_reader/reader.py +11 -0
  3. airbyte_cdk/manifest_migrations/README.md +73 -0
  4. airbyte_cdk/manifest_migrations/__init__.py +3 -0
  5. airbyte_cdk/manifest_migrations/exceptions.py +12 -0
  6. airbyte_cdk/manifest_migrations/manifest_migration.py +134 -0
  7. airbyte_cdk/manifest_migrations/migration_handler.py +163 -0
  8. airbyte_cdk/manifest_migrations/migrations/__init__.py +4 -0
  9. airbyte_cdk/manifest_migrations/migrations/http_requester_path_to_url.py +57 -0
  10. airbyte_cdk/manifest_migrations/migrations/http_requester_request_body_json_data_to_request_body.py +51 -0
  11. airbyte_cdk/manifest_migrations/migrations/http_requester_url_base_to_url.py +41 -0
  12. airbyte_cdk/manifest_migrations/migrations/registry.yaml +22 -0
  13. airbyte_cdk/manifest_migrations/migrations_registry.py +76 -0
  14. airbyte_cdk/sources/declarative/declarative_component_schema.yaml +80 -5
  15. airbyte_cdk/sources/declarative/declarative_source.py +10 -1
  16. airbyte_cdk/sources/declarative/interpolation/interpolated_nested_mapping.py +1 -1
  17. airbyte_cdk/sources/declarative/manifest_declarative_source.py +32 -7
  18. airbyte_cdk/sources/declarative/models/base_model_with_deprecations.py +144 -0
  19. airbyte_cdk/sources/declarative/models/declarative_component_schema.py +54 -5
  20. airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +66 -15
  21. airbyte_cdk/sources/declarative/requesters/http_requester.py +62 -17
  22. airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py +24 -2
  23. airbyte_cdk/sources/declarative/requesters/requester.py +12 -0
  24. {airbyte_cdk-6.48.0.dist-info → airbyte_cdk-6.48.2.dist-info}/METADATA +1 -1
  25. {airbyte_cdk-6.48.0.dist-info → airbyte_cdk-6.48.2.dist-info}/RECORD +29 -17
  26. {airbyte_cdk-6.48.0.dist-info → airbyte_cdk-6.48.2.dist-info}/LICENSE.txt +0 -0
  27. {airbyte_cdk-6.48.0.dist-info → airbyte_cdk-6.48.2.dist-info}/LICENSE_SHORT +0 -0
  28. {airbyte_cdk-6.48.0.dist-info → airbyte_cdk-6.48.2.dist-info}/WHEEL +0 -0
  29. {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,3 @@
1
+ #
2
+ # Copyright (c) 2025 Airbyte, Inc., all rights reserved.
3
+ #
@@ -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,4 @@
1
+ #
2
+ # Copyright (c) 2025 Airbyte, Inc., all rights reserved.
3
+ #
4
+
@@ -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
+ )
@@ -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.