Skip to content

Commit 608502d

Browse files
committed
feat: add --components-path arg to CLI commands
1 parent 1aa2a71 commit 608502d

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

airbyte_cdk/cli/source_declarative_manifest/_run.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ def create_declarative_source(
191191
f"but got type: {type(config['__injected_declarative_manifest'])}"
192192
)
193193

194+
# Load custom components if provided - this will register them in sys.modules
195+
_parse_components_from_args(args)
196+
194197
return ConcurrentDeclarativeSource(
195198
config=config,
196199
catalog=catalog,
@@ -266,6 +269,63 @@ def _parse_manifest_from_args(args: list[str]) -> dict[str, Any] | None:
266269
return None
267270

268271

272+
def _register_components_from_file(filepath: str) -> None:
273+
"""Load and register components from a Python file for CLI usage.
274+
275+
This is a special case for CLI usage that bypasses the checksum validation
276+
since the user is explicitly providing the file to execute.
277+
"""
278+
import importlib.util
279+
import sys
280+
281+
# Use Python's import mechanism to properly load the module
282+
components_path = Path(filepath)
283+
284+
# Standard module names that the rest of the system expects
285+
module_name = "components"
286+
sdm_module_name = "source_declarative_manifest.components"
287+
288+
# Create module spec
289+
spec = importlib.util.spec_from_file_location(module_name, components_path)
290+
if spec is None or spec.loader is None:
291+
raise ImportError(f"Could not load module from {components_path}")
292+
293+
# Create module and execute code
294+
module = importlib.util.module_from_spec(spec)
295+
296+
# Register the module BEFORE executing its code
297+
# This is critical for features like dataclasses that look up the module
298+
sys.modules[module_name] = module
299+
sys.modules[sdm_module_name] = module
300+
301+
# Now execute the module code
302+
spec.loader.exec_module(module)
303+
304+
305+
def _parse_components_from_args(args: list[str]) -> bool:
306+
"""Loads and registers the custom components.py module if it exists.
307+
308+
This function imports the components module from a provided path
309+
and registers it in sys.modules so it can be found by the source.
310+
311+
Returns True if components were registered, False otherwise.
312+
"""
313+
parsed_args = AirbyteEntrypoint.parse_args(args)
314+
315+
# Safely check if components_path is provided in the args
316+
if hasattr(parsed_args, "components_path") and parsed_args.components_path:
317+
try:
318+
# Use our CLI-specific function that bypasses checksum validation
319+
_register_components_from_file(parsed_args.components_path)
320+
return True
321+
except Exception as error:
322+
raise ValueError(
323+
f"Failed to load components from {parsed_args.components_path}: {error}"
324+
)
325+
326+
return False
327+
328+
269329
def run() -> None:
270330
args: list[str] = sys.argv[1:]
271331
handle_command(args)

airbyte_cdk/entrypoint.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ def parse_args(args: List[str]) -> argparse.Namespace:
9090
required=False,
9191
help="path to the YAML manifest file to inject into the config",
9292
)
93+
check_parser.add_argument(
94+
"--components-path",
95+
type=str,
96+
required=False,
97+
help="path to the custom components file, if it exists",
98+
)
9399

94100
# discover
95101
discover_parser = subparsers.add_parser(
@@ -107,6 +113,12 @@ def parse_args(args: List[str]) -> argparse.Namespace:
107113
required=False,
108114
help="path to the YAML manifest file to inject into the config",
109115
)
116+
discover_parser.add_argument(
117+
"--components-path",
118+
type=str,
119+
required=False,
120+
help="path to the custom components file, if it exists",
121+
)
110122

111123
# read
112124
read_parser = subparsers.add_parser(
@@ -132,6 +144,12 @@ def parse_args(args: List[str]) -> argparse.Namespace:
132144
required=False,
133145
help="path to the YAML manifest file to inject into the config",
134146
)
147+
read_parser.add_argument(
148+
"--components-path",
149+
type=str,
150+
required=False,
151+
help="path to the custom components file, if it exists",
152+
)
135153

136154
return main_parser.parse_args(args)
137155

0 commit comments

Comments
 (0)