File size: 2,773 Bytes
de3f199
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import importlib.util
import json
from pathlib import Path
import subprocess
import sys


def import_and_call(folder: Path, action: str, **params) -> str | None:
    """Dynamically import Python modules and call the action function."""
    folder_str = str(folder)
    if folder_str not in sys.path:
        sys.path.insert(0, folder_str)

    # Find all Python files in the folder
    py_files = list(folder.glob("*.py"))

    for py_file in py_files:
        if py_file.name.startswith("__"):
            continue

        # Reuse an already-loaded module if present in sys.modules. This is
        # necessary for unittest.mock.patch to work — patch modifies the
        # attribute on the module object in sys.modules, so we must use that
        # same object rather than creating a fresh one each call.
        module_name = py_file.stem
        if module_name in sys.modules:
            module = sys.modules[module_name]
        else:
            try:
                spec = importlib.util.spec_from_file_location(module_name, py_file)
                if spec is None or spec.loader is None:
                    continue
                module = importlib.util.module_from_spec(spec)
                sys.modules[module_name] = module
                spec.loader.exec_module(module)
            except Exception:
                continue

        # If the function is here, call it. Do NOT swallow its exceptions —
        # surface them so the caller can see what went wrong instead of the
        # misleading "No executable found" fallback.
        if hasattr(module, action):
            func = getattr(module, action)
            try:
                return str(func(**params))
            except Exception as e:
                return f"Error calling {action}: {type(e).__name__}: {e}"

    return None


def run_script(folder: Path, action: str, **params) -> str | None:
    """Try to execute scripts as subprocess (fallback method)."""
    for script in [f"{action}.py", "run.py", f"{action}.sh"]:
        path = folder / script
        if not path.exists():
            continue

        try:
            # Use python to run .py scripts instead of direct execution
            if script.endswith(".py"):
                args = [sys.executable, str(path), action]
            else:
                args = [str(path), action]

            for k, v in params.items():
                args.extend([f"--{k}", str(v)])

            result = subprocess.run(
                args, capture_output=True, text=True, timeout=30, cwd=str(folder)
            )
            try:
                return json.loads(result.stdout)
            except json.JSONDecodeError:
                return None
        except Exception as e:
            return None
    return None