Skip to content

Commit b2a49d3

Browse files
author
Oleksandr Bazarnov
committed
added version checks for manifest migrations. Added auto-import for all migrations available
1 parent 9c87e5e commit b2a49d3

File tree

6 files changed

+98
-29
lines changed

6 files changed

+98
-29
lines changed

airbyte_cdk/sources/declarative/migrations/manifest/manifest_migration.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
22

3+
import re
34
from abc import abstractmethod
45
from typing import Any, Dict
56

@@ -34,6 +35,15 @@ def migrate(self, manifest: ManifestType) -> None:
3435
:param kwargs: Additional arguments for migration
3536
"""
3637

38+
@property
39+
def migration_version(self) -> str:
40+
"""
41+
Get the migration version.
42+
43+
:return: The migration version as a string
44+
"""
45+
return self._get_migration_version()
46+
3747
def _is_component(self, obj: Dict[str, Any]) -> bool:
3848
"""
3949
Check if the object is a component.
@@ -45,12 +55,16 @@ def _is_component(self, obj: Dict[str, Any]) -> bool:
4555

4656
def _is_migratable(self, obj: Dict[str, Any]) -> bool:
4757
"""
48-
Check if the object is a migratable component.
58+
Check if the object is a migratable component,
59+
based on the Type of the component and the migration version.
4960
5061
:param obj: The object to check
5162
:return: True if the object is a migratable component, False otherwise
5263
"""
53-
return obj[TYPE_TAG] not in NON_MIGRATABLE_TYPES
64+
return (
65+
obj[TYPE_TAG] not in NON_MIGRATABLE_TYPES
66+
and self._get_manifest_version(obj) <= self.migration_version
67+
)
5468

5569
def _process_manifest(self, obj: Any) -> None:
5670
"""
@@ -91,3 +105,33 @@ def _process_manifest(self, obj: Any) -> None:
91105
# Process all items in the list
92106
for item in obj:
93107
self._process_manifest(item)
108+
109+
def _get_manifest_version(self, manifest: ManifestType) -> str:
110+
"""
111+
Get the manifest version from the manifest.
112+
113+
:param manifest: The manifest to get the version from
114+
:return: The manifest version
115+
"""
116+
return manifest.get("version", "0.0.0")
117+
118+
def _get_migration_version(self) -> str:
119+
"""
120+
Get the migration version from the class name.
121+
The migration version is extracted from the class name using a regular expression.
122+
The expected format is "V_<major>_<minor>_<patch>_<migration_name>".
123+
124+
For example, "V_6_45_2_ManifestMigration_HttpRequesterPathToUrl" -> "6.45.2"
125+
126+
:return: The migration version as a string in the format "major.minor.patch"
127+
:raises ValueError: If the class name does not match the expected format
128+
"""
129+
130+
class_name = self.__class__.__name__
131+
migration_version = re.search(r"V_(\d+_\d+_\d+)", class_name)
132+
if migration_version:
133+
return migration_version.group(1).replace("_", ".")
134+
else:
135+
raise ValueError(
136+
f"Invalid migration class name, make sure the class name has the version (e.g `V_0_0_0_`): {class_name}"
137+
)

airbyte_cdk/sources/declarative/migrations/manifest/migration_handler.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
ManifestType,
1414
)
1515
from airbyte_cdk.sources.declarative.migrations.manifest.migrations_registry import (
16-
migrations_registry,
16+
MIGRATIONS,
1717
)
1818

1919

@@ -39,8 +39,8 @@ def apply_migrations(self) -> ManifestType:
3939
manifest if any migration failed.
4040
"""
4141
try:
42-
for migration_class in migrations_registry:
43-
self._handle_migration(migration_class)
42+
for migration_cls in MIGRATIONS:
43+
self._handle_migration(migration_cls)
4444
return self._migrated_manifest
4545
except ManifestMigrationException:
4646
# if any errors occur we return the original resolved manifest

airbyte_cdk/sources/declarative/migrations/manifest/migrations/http_requester_url_base_to_url_migration.py renamed to airbyte_cdk/sources/declarative/migrations/manifest/migrations/0_v6_45_2_http_requester_url_base_to_url_migration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
)
66

77

8-
class HttpRequesterUrlBaseToUrlMigration(ManifestMigration):
8+
class V_6_45_2_ManifestMigration_HttpRequesterUrlBaseToUrl(ManifestMigration):
99
"""
1010
This migration is responsible for migrating the `url_base` key to `url` in the HttpRequester component.
1111
The `url_base` key is expected to be a base URL, and the `url` key is expected to be a full URL.

airbyte_cdk/sources/declarative/migrations/manifest/migrations/http_requester_path_to_url_migration.py renamed to airbyte_cdk/sources/declarative/migrations/manifest/migrations/1_v6_45_2_http_requester_path_to_url_migration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from airbyte_cdk.sources.types import EmptyString
99

1010

11-
class HttpRequesterPathToUrlMigration(ManifestMigration):
11+
class V_6_45_2_ManifestMigration_HttpRequesterPathToUrl(ManifestMigration):
1212
"""
1313
This migration is responsible for migrating the `path` key to `url` in the HttpRequester component.
1414
The `path` key is expected to be a relative path, and the `url` key is expected to be a full URL.
Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +0,0 @@
1-
from airbyte_cdk.sources.declarative.migrations.manifest.migrations.http_requester_path_to_url_migration import (
2-
HttpRequesterPathToUrlMigration,
3-
)
4-
from airbyte_cdk.sources.declarative.migrations.manifest.migrations.http_requester_url_base_to_url_migration import (
5-
HttpRequesterUrlBaseToUrlMigration,
6-
)
7-
8-
__all__ = [
9-
"HttpRequesterUrlBaseToUrlMigration",
10-
"HttpRequesterPathToUrlMigration",
11-
]
Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,53 @@
1+
import importlib
2+
import inspect
3+
import pkgutil
4+
import re
5+
import sys
16
from typing import List, Type
27

8+
import airbyte_cdk.sources.declarative.migrations.manifest.migrations as migrations_pkg
39
from airbyte_cdk.sources.declarative.migrations.manifest.manifest_migration import (
410
ManifestMigration,
511
)
6-
from airbyte_cdk.sources.declarative.migrations.manifest.migrations import (
7-
HttpRequesterPathToUrlMigration,
8-
HttpRequesterUrlBaseToUrlMigration,
9-
)
1012

11-
# This is the registry of all the migrations that are available.
12-
# Add new migrations to the bottom of the list,
13-
# ( ! ) make sure the order of the migrations is correct.
14-
migrations_registry: List[Type[ManifestMigration]] = [
15-
HttpRequesterUrlBaseToUrlMigration,
16-
HttpRequesterPathToUrlMigration,
17-
]
13+
# Dynamically import all modules in the migrations package
14+
for _, module_name, is_pkg in pkgutil.iter_modules(migrations_pkg.__path__):
15+
if not is_pkg:
16+
importlib.import_module(f"{migrations_pkg.__name__}.{module_name}")
17+
18+
19+
def _migration_order_key(cls):
20+
# Extract the migration order from the module name, e.g., 0_v6_45_2_http_requester_url_base_to_url_migration
21+
# The order is the integer at the start of the module name, before the first underscore
22+
module_name = cls.__module__.split(".")[-1]
23+
match = re.match(r"(\d+)_", module_name)
24+
return int(match.group(1)) if match else float("inf")
25+
26+
27+
def _discover_migrations() -> List[Type[ManifestMigration]]:
28+
migration_classes = []
29+
for name, obj in inspect.getmembers(sys.modules[migrations_pkg.__name__], inspect.isclass):
30+
if (
31+
issubclass(obj, ManifestMigration)
32+
and obj is not ManifestMigration
33+
and obj not in migration_classes
34+
):
35+
migration_classes.append(obj)
36+
37+
for _, module_name, _ in pkgutil.iter_modules(migrations_pkg.__path__):
38+
module = sys.modules.get(f"{migrations_pkg.__name__}.{module_name}")
39+
if module:
40+
for name, obj in inspect.getmembers(module, inspect.isclass):
41+
if (
42+
issubclass(obj, ManifestMigration)
43+
and obj is not ManifestMigration
44+
and obj not in migration_classes
45+
):
46+
migration_classes.append(obj)
47+
48+
# Sort by migration order key
49+
migration_classes.sort(key=_migration_order_key)
50+
return migration_classes
51+
52+
53+
MIGRATIONS: List[Type[ManifestMigration]] = _discover_migrations()

0 commit comments

Comments
 (0)