machineconfig 6.51__py3-none-any.whl → 6.53__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.
Potentially problematic release.
This version of machineconfig might be problematic. Click here for more details.
- machineconfig/cluster/sessions_managers/utils/maker.py +3 -3
- machineconfig/jobs/installer/check_installations.py +0 -1
- machineconfig/jobs/installer/installer_data.json +17 -0
- machineconfig/scripts/python/croshell.py +4 -4
- machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
- machineconfig/scripts/python/fire_jobs.py +9 -7
- machineconfig/scripts/python/helpers_devops/cli_config.py +3 -2
- machineconfig/scripts/python/helpers_devops/cli_self.py +3 -3
- machineconfig/scripts/python/helpers_devops/cli_utils.py +43 -0
- machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +24 -20
- machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +1 -1
- machineconfig/scripts/python/helpers_repos/sync.py +5 -5
- machineconfig/scripts/python/nw/mount_nfs +1 -1
- machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
- machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
- machineconfig/setup_mac/__init__.py +17 -0
- machineconfig/setup_mac/apps.sh +73 -0
- machineconfig/setup_mac/ssh/openssh_setup.sh +114 -0
- machineconfig/setup_mac/uv.sh +36 -0
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +1 -1
- machineconfig/utils/code.py +46 -84
- machineconfig/utils/installer.py +8 -3
- machineconfig/utils/meta.py +5 -251
- machineconfig/utils/scheduling.py +0 -1
- machineconfig/utils/ssh.py +46 -45
- {machineconfig-6.51.dist-info → machineconfig-6.53.dist-info}/METADATA +1 -1
- {machineconfig-6.51.dist-info → machineconfig-6.53.dist-info}/RECORD +30 -26
- {machineconfig-6.51.dist-info → machineconfig-6.53.dist-info}/WHEEL +0 -0
- {machineconfig-6.51.dist-info → machineconfig-6.53.dist-info}/entry_points.txt +0 -0
- {machineconfig-6.51.dist-info → machineconfig-6.53.dist-info}/top_level.txt +0 -0
machineconfig/utils/code.py
CHANGED
|
@@ -2,94 +2,16 @@ import atexit
|
|
|
2
2
|
import platform
|
|
3
3
|
from typing import Optional
|
|
4
4
|
import subprocess
|
|
5
|
-
from rich.console import Console
|
|
6
|
-
from rich.panel import Panel
|
|
7
|
-
from rich.syntax import Syntax
|
|
8
|
-
|
|
9
5
|
from machineconfig.utils.accessories import randstr
|
|
10
|
-
from machineconfig.utils.
|
|
11
|
-
from
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def get_shell_script_executing_python_file(python_file: str, func: Optional[str], ve_path: Optional[str], executable: Optional[str], strict_execution: bool = True):
|
|
15
|
-
if executable is None: exe_resolved = "python"
|
|
16
|
-
else: exe_resolved = executable
|
|
17
|
-
if func is None: exec_line = f"""{exe_resolved} {python_file}"""
|
|
18
|
-
else: exec_line = f"""{exe_resolved} -m fire {python_file} {func}"""
|
|
19
|
-
if ve_path is None: ve_activate_line = ""
|
|
20
|
-
else: ve_activate_line = get_ve_activate_line(ve_path)
|
|
21
|
-
shell_script = f"""
|
|
22
|
-
echo "Executing {exec_line}"
|
|
23
|
-
{ve_activate_line}
|
|
24
|
-
{exec_line}
|
|
25
|
-
deactivate || true
|
|
26
|
-
"""
|
|
27
|
-
if strict_execution:
|
|
28
|
-
if platform.system() == "Windows":
|
|
29
|
-
shell_script = """$ErrorActionPreference = "Stop" """ + "\n" + shell_script
|
|
30
|
-
if platform.system() in ["Linux", "Darwin"]:
|
|
31
|
-
shell_script = "set -e" + "\n" + shell_script
|
|
32
|
-
if platform.system() in ["Linux", "Darwin"]:
|
|
33
|
-
shell_script = "#!/bin/bash" + "\n" + shell_script # vs #!/usr/bin/env bash
|
|
34
|
-
return shell_script
|
|
6
|
+
from machineconfig.utils.path_extended import PathExtended
|
|
7
|
+
from pathlib import Path
|
|
35
8
|
|
|
36
9
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
python_script = f"""
|
|
40
|
-
code = r'''{python_script}'''
|
|
41
|
-
try:
|
|
42
|
-
from machineconfig.utils.utils import print_code
|
|
43
|
-
print_code(code=code, lexer="python", desc="Python Script")
|
|
44
|
-
except ImportError:
|
|
10
|
+
def print_code(code: str, lexer: str, desc: str, subtitle: str = ""):
|
|
11
|
+
import platform
|
|
45
12
|
from rich.console import Console
|
|
46
13
|
from rich.panel import Panel
|
|
47
|
-
|
|
48
|
-
console.print(Panel(f'''📜 PYTHON SCRIPT:\n\n{{code}}''', title="Python Script", expand=False))
|
|
49
|
-
""" + python_script
|
|
50
|
-
python_file = PathExtended.tmp().joinpath("tmp_scripts", "python", randstr() + ".py")
|
|
51
|
-
python_file.parent.mkdir(parents=True, exist_ok=True)
|
|
52
|
-
python_file.write_text(python_script, encoding="utf-8")
|
|
53
|
-
shell_script = get_shell_script_executing_python_file(python_file=str(python_file), func=None, ve_path=ve_path, executable=executable)
|
|
54
|
-
shell_file = write_shell_script_to_file(shell_script)
|
|
55
|
-
return shell_file
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def write_shell_script_to_file(shell_script: str):
|
|
59
|
-
if platform.system() in ["Linux", "Darwin"]:
|
|
60
|
-
suffix = ".sh"
|
|
61
|
-
elif platform.system() == "Windows":
|
|
62
|
-
suffix = ".ps1"
|
|
63
|
-
else:
|
|
64
|
-
raise NotImplementedError(f"Platform {platform.system()} not implemented.")
|
|
65
|
-
shell_file = PathExtended.tmp().joinpath("tmp_scripts", "shell", randstr() + suffix)
|
|
66
|
-
shell_file.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
-
shell_file.write_text(shell_script, encoding="utf-8")
|
|
68
|
-
return shell_file
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def write_shell_script_to_default_program_path(program: str, desc: str, preserve_cwd: bool, display: bool, execute: bool):
|
|
72
|
-
if preserve_cwd:
|
|
73
|
-
if platform.system() == "Windows":
|
|
74
|
-
program = "$orig_path = $pwd\n" + program + "\ncd $orig_path"
|
|
75
|
-
else:
|
|
76
|
-
program = 'orig_path=$(cd -- "." && pwd)\n' + program + '\ncd "$orig_path" || exit'
|
|
77
|
-
if display:
|
|
78
|
-
print_code(code=program, lexer="shell", desc=desc, subtitle="PROGRAM")
|
|
79
|
-
if execute:
|
|
80
|
-
result = subprocess.run(program, shell=True, capture_output=True, text=True)
|
|
81
|
-
success = result.returncode == 0 and result.stderr == ""
|
|
82
|
-
if not success:
|
|
83
|
-
print("❌ 🛠️ EXECUTION | Shell script running failed")
|
|
84
|
-
if result.stdout:
|
|
85
|
-
print(f"STDOUT: {result.stdout}")
|
|
86
|
-
if result.stderr:
|
|
87
|
-
print(f"STDERR: {result.stderr}")
|
|
88
|
-
print(f"Return code: {result.returncode}")
|
|
89
|
-
return None
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def print_code(code: str, lexer: str, desc: str, subtitle: str = ""):
|
|
14
|
+
from rich.syntax import Syntax
|
|
93
15
|
if lexer == "shell":
|
|
94
16
|
if platform.system() == "Windows":
|
|
95
17
|
lexer = "powershell"
|
|
@@ -101,8 +23,43 @@ def print_code(code: str, lexer: str, desc: str, subtitle: str = ""):
|
|
|
101
23
|
console.print(Panel(Syntax(code=code, lexer=lexer), title=f"📄 {desc}", subtitle=subtitle), style="bold red")
|
|
102
24
|
|
|
103
25
|
|
|
26
|
+
def get_shell_file_executing_python_script(python_script: str, uv_with: Optional[list[str]], uv_project_dir: Optional[str]) -> Path:
|
|
27
|
+
python_file = PathExtended.tmp().joinpath("tmp_scripts", "python", randstr() + ".py")
|
|
28
|
+
python_file.parent.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
python_file.write_text(python_script, encoding="utf-8")
|
|
30
|
+
if uv_with is not None and len(uv_with) > 0:
|
|
31
|
+
uv_with_arg = "--with " + '"' + ",".join(uv_with) + '"'
|
|
32
|
+
else:
|
|
33
|
+
uv_with_arg = ""
|
|
34
|
+
if uv_project_dir is not None:
|
|
35
|
+
uv_project_dir_arg = "--project" + f' "{uv_project_dir}"'
|
|
36
|
+
else:
|
|
37
|
+
uv_project_dir_arg = ""
|
|
38
|
+
|
|
39
|
+
from machineconfig.utils.meta import lambda_to_defstring
|
|
40
|
+
print_code_string = lambda_to_defstring(lambda: print_code(code=python_script, lexer="python", desc="Temporary Python Script", subtitle="Executing via shell script"), in_global=True)
|
|
41
|
+
python_file_tmp = PathExtended.tmp().joinpath("tmp_scripts", "python", randstr() + ".py")
|
|
42
|
+
python_file_tmp.parent.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
python_file_tmp.write_text(print_code_string, encoding="utf-8")
|
|
44
|
+
|
|
45
|
+
shell_script = f"""
|
|
46
|
+
uv run --with rich {python_file_tmp}
|
|
47
|
+
uv run {uv_with_arg} {uv_project_dir_arg} {str(python_file)}
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
shell_path = PathExtended.tmp().joinpath("tmp_scripts", "shell", randstr() + (".ps1" if platform.system() == "Windows" else ".sh"))
|
|
51
|
+
shell_path.parent.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
shell_path.write_text(shell_script, encoding="utf-8")
|
|
53
|
+
return shell_path
|
|
54
|
+
|
|
55
|
+
|
|
104
56
|
def run_shell_script(script: str, display_script: bool = True, clean_env: bool = False):
|
|
105
57
|
import tempfile
|
|
58
|
+
import platform
|
|
59
|
+
from rich.console import Console
|
|
60
|
+
from rich.panel import Panel
|
|
61
|
+
from rich.syntax import Syntax
|
|
62
|
+
|
|
106
63
|
if platform.system() == "Windows":
|
|
107
64
|
suffix = ".ps1"
|
|
108
65
|
lexer = "powershell"
|
|
@@ -112,6 +69,7 @@ def run_shell_script(script: str, display_script: bool = True, clean_env: bool =
|
|
|
112
69
|
with tempfile.NamedTemporaryFile(mode='w', suffix=suffix, delete=False, encoding='utf-8') as temp_file:
|
|
113
70
|
temp_file.write(script)
|
|
114
71
|
temp_script_path = PathExtended(temp_file.name)
|
|
72
|
+
|
|
115
73
|
console = Console()
|
|
116
74
|
if display_script:
|
|
117
75
|
from rich.syntax import Syntax
|
|
@@ -138,8 +96,12 @@ def run_shell_script(script: str, display_script: bool = True, clean_env: bool =
|
|
|
138
96
|
|
|
139
97
|
|
|
140
98
|
def run_shell_script_after_exit(script: str, display_script: bool = True) -> None:
|
|
99
|
+
import platform
|
|
100
|
+
from rich.console import Console
|
|
101
|
+
from rich.panel import Panel
|
|
102
|
+
from rich.syntax import Syntax
|
|
103
|
+
|
|
141
104
|
console = Console()
|
|
142
|
-
|
|
143
105
|
def execute_script_at_exit() -> None:
|
|
144
106
|
if platform.system() == "Windows":
|
|
145
107
|
suffix = ".ps1"
|
machineconfig/utils/installer.py
CHANGED
|
@@ -5,7 +5,7 @@ from machineconfig.utils.installer_utils.installer_class import Installer
|
|
|
5
5
|
from machineconfig.utils.schemas.installer.installer_types import InstallerData, InstallerDataFiles, get_normalized_arch, get_os_name, OPERATING_SYSTEMS, CPU_ARCHITECTURES
|
|
6
6
|
from machineconfig.jobs.installer.package_groups import PACKAGE_GROUPS, PACKAGE_GROUP2NAMES
|
|
7
7
|
from machineconfig.utils.path_extended import PathExtended
|
|
8
|
-
from machineconfig.utils.source_of_truth import INSTALL_VERSION_ROOT, LINUX_INSTALL_PATH
|
|
8
|
+
from machineconfig.utils.source_of_truth import INSTALL_VERSION_ROOT, LINUX_INSTALL_PATH
|
|
9
9
|
from machineconfig.utils.io import read_json
|
|
10
10
|
|
|
11
11
|
from rich.console import Console
|
|
@@ -123,9 +123,14 @@ def dynamically_extract_installers_system_groups_from_scripts():
|
|
|
123
123
|
res_final: list[InstallerData] = []
|
|
124
124
|
from platform import system
|
|
125
125
|
if system() == "Windows":
|
|
126
|
-
|
|
126
|
+
from machineconfig.setup_windows import APPS
|
|
127
|
+
options_system = parse_apps_installer_windows(APPS.read_text(encoding="utf-8"))
|
|
127
128
|
elif system() == "Linux" or system() == "Darwin":
|
|
128
|
-
|
|
129
|
+
from machineconfig.setup_linux import APPS
|
|
130
|
+
options_system = parse_apps_installer_linux(APPS.read_text(encoding="utf-8"))
|
|
131
|
+
elif system() == "Darwin":
|
|
132
|
+
from machineconfig.setup_mac import APPS
|
|
133
|
+
options_system = parse_apps_installer_linux(APPS.read_text(encoding="utf-8"))
|
|
129
134
|
else:
|
|
130
135
|
raise NotImplementedError(f"❌ System {system()} not supported")
|
|
131
136
|
os_name = get_os_name()
|
machineconfig/utils/meta.py
CHANGED
|
@@ -1,252 +1,14 @@
|
|
|
1
1
|
"""Metaprogramming utilities for analyzing and serializing Python functions."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import textwrap
|
|
6
|
-
from collections.abc import Callable, Mapping
|
|
7
|
-
from types import FunctionType, ModuleType
|
|
8
|
-
from typing import Any, ParamSpec
|
|
9
|
-
|
|
10
|
-
P = ParamSpec("P")
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def function_to_script(func: Callable[P, object], call_with_kwargs: Mapping[str, object] | None) -> str:
|
|
14
|
-
"""Convert a function to a standalone executable Python script.
|
|
15
|
-
|
|
16
|
-
This function analyzes a given function and generates a complete Python script
|
|
17
|
-
that includes all necessary imports, global variables, the function definition,
|
|
18
|
-
and optionally a function call.
|
|
19
|
-
|
|
20
|
-
Args:
|
|
21
|
-
func: The function to convert to a script
|
|
22
|
-
call_with_kwargs: Optional dict of keyword arguments to call the function with
|
|
23
|
-
|
|
24
|
-
Returns:
|
|
25
|
-
A complete Python script as a string that can be executed independently
|
|
26
|
-
|
|
27
|
-
Raises:
|
|
28
|
-
ValueError: If the function cannot be inspected or analyzed
|
|
29
|
-
"""
|
|
30
|
-
if not isinstance(func, FunctionType):
|
|
31
|
-
raise ValueError(f"""Expected a Python function, got {type(func)}""")
|
|
32
|
-
|
|
33
|
-
python_func = func
|
|
34
|
-
|
|
35
|
-
imports = _extract_imports(python_func)
|
|
36
|
-
globals_needed = _extract_globals(python_func)
|
|
37
|
-
source_code = _get_function_source(python_func).rstrip()
|
|
38
|
-
validated_kwargs = _prepare_call_kwargs(python_func, call_with_kwargs)
|
|
39
|
-
call_statement = _generate_call_statement(python_func, validated_kwargs) if validated_kwargs is not None else None
|
|
40
|
-
|
|
41
|
-
script_parts: list[str] = []
|
|
42
|
-
|
|
43
|
-
if imports:
|
|
44
|
-
script_parts.append(imports)
|
|
45
|
-
|
|
46
|
-
if globals_needed:
|
|
47
|
-
if script_parts:
|
|
48
|
-
script_parts.append("")
|
|
49
|
-
script_parts.append(globals_needed)
|
|
50
|
-
|
|
51
|
-
if script_parts:
|
|
52
|
-
script_parts.append("")
|
|
53
|
-
|
|
54
|
-
script_parts.append(source_code)
|
|
55
|
-
|
|
56
|
-
if call_statement is not None:
|
|
57
|
-
script_parts.append("")
|
|
58
|
-
script_parts.append("if __name__ == '__main__':")
|
|
59
|
-
script_parts.append(f" {call_statement}")
|
|
60
|
-
|
|
61
|
-
return "\n".join(script_parts)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def _get_function_source(func: FunctionType) -> str:
|
|
65
|
-
"""Extract the source code of a function."""
|
|
66
|
-
try:
|
|
67
|
-
source = inspect.getsource(func)
|
|
68
|
-
return textwrap.dedent(source)
|
|
69
|
-
except (OSError, TypeError) as e:
|
|
70
|
-
raise ValueError(f"Cannot get source code for function {func.__name__}: {e}") from e
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def _extract_imports(func: FunctionType) -> str:
|
|
74
|
-
"""Extract all import statements needed by the function."""
|
|
75
|
-
import_statements: set[str] = set()
|
|
76
|
-
|
|
77
|
-
source = _get_function_source(func)
|
|
78
|
-
func_globals = func.__globals__
|
|
79
|
-
|
|
80
|
-
try:
|
|
81
|
-
tree = ast.parse(source)
|
|
82
|
-
except SyntaxError as e:
|
|
83
|
-
raise ValueError(f"Failed to parse function source: {e}") from e
|
|
84
|
-
|
|
85
|
-
used_names: set[str] = set()
|
|
86
|
-
for node in ast.walk(tree):
|
|
87
|
-
if isinstance(node, ast.Name):
|
|
88
|
-
used_names.add(node.id)
|
|
89
|
-
elif isinstance(node, ast.Attribute):
|
|
90
|
-
if isinstance(node.value, ast.Name):
|
|
91
|
-
used_names.add(node.value.id)
|
|
92
|
-
|
|
93
|
-
for node in ast.walk(tree):
|
|
94
|
-
if isinstance(node, ast.FunctionDef):
|
|
95
|
-
for default in node.args.defaults + node.args.kw_defaults:
|
|
96
|
-
if default is not None:
|
|
97
|
-
for subnode in ast.walk(default):
|
|
98
|
-
if isinstance(subnode, ast.Name):
|
|
99
|
-
used_names.add(subnode.id)
|
|
100
|
-
elif isinstance(subnode, ast.Attribute):
|
|
101
|
-
if isinstance(subnode.value, ast.Name):
|
|
102
|
-
used_names.add(subnode.value.id)
|
|
103
|
-
|
|
104
|
-
for name in used_names:
|
|
105
|
-
if name in func_globals:
|
|
106
|
-
obj = func_globals[name]
|
|
107
|
-
|
|
108
|
-
if isinstance(obj, ModuleType):
|
|
109
|
-
module_name = obj.__name__
|
|
110
|
-
if name == module_name.split('.')[-1]:
|
|
111
|
-
import_statements.add(f"import {module_name}")
|
|
112
|
-
else:
|
|
113
|
-
import_statements.add(f"import {module_name} as {name}")
|
|
114
|
-
|
|
115
|
-
elif isinstance(obj, type) and hasattr(obj, '__module__') and obj.__module__ != '__main__':
|
|
116
|
-
try:
|
|
117
|
-
module_name = obj.__module__
|
|
118
|
-
obj_name = obj.__name__ if hasattr(obj, '__name__') else name
|
|
119
|
-
|
|
120
|
-
if module_name and module_name != 'builtins':
|
|
121
|
-
if obj_name == name:
|
|
122
|
-
import_statements.add(f"from {module_name} import {obj_name}")
|
|
123
|
-
else:
|
|
124
|
-
import_statements.add(f"from {module_name} import {obj_name} as {name}")
|
|
125
|
-
except AttributeError:
|
|
126
|
-
pass
|
|
127
|
-
|
|
128
|
-
elif callable(obj) and not isinstance(obj, type) and hasattr(obj, '__module__') and obj.__module__ != '__main__':
|
|
129
|
-
try:
|
|
130
|
-
module_name = obj.__module__
|
|
131
|
-
obj_name = obj.__name__ if hasattr(obj, '__name__') else name
|
|
132
|
-
|
|
133
|
-
if module_name and module_name != 'builtins':
|
|
134
|
-
if obj_name == name:
|
|
135
|
-
import_statements.add(f"from {module_name} import {obj_name}")
|
|
136
|
-
else:
|
|
137
|
-
import_statements.add(f"from {module_name} import {obj_name} as {name}")
|
|
138
|
-
except AttributeError:
|
|
139
|
-
pass
|
|
140
|
-
|
|
141
|
-
return "\n".join(sorted(import_statements))
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def _extract_globals(func: FunctionType) -> str:
|
|
145
|
-
"""Extract global variables needed by the function."""
|
|
146
|
-
global_assignments: list[str] = []
|
|
147
|
-
needed_types: set[type] = set()
|
|
148
|
-
|
|
149
|
-
source = _get_function_source(func)
|
|
150
|
-
func_globals = func.__globals__
|
|
151
|
-
|
|
152
|
-
try:
|
|
153
|
-
tree = ast.parse(source)
|
|
154
|
-
except SyntaxError as e:
|
|
155
|
-
raise ValueError(f"Failed to parse function source: {e}") from e
|
|
156
|
-
|
|
157
|
-
used_names: set[str] = set()
|
|
158
|
-
for node in ast.walk(tree):
|
|
159
|
-
if isinstance(node, ast.Name):
|
|
160
|
-
used_names.add(node.id)
|
|
161
|
-
elif isinstance(node, ast.Attribute):
|
|
162
|
-
if isinstance(node.value, ast.Name):
|
|
163
|
-
used_names.add(node.value.id)
|
|
164
|
-
|
|
165
|
-
for node in ast.walk(tree):
|
|
166
|
-
if isinstance(node, ast.FunctionDef):
|
|
167
|
-
for default in node.args.defaults + node.args.kw_defaults:
|
|
168
|
-
if default is not None:
|
|
169
|
-
for subnode in ast.walk(default):
|
|
170
|
-
if isinstance(subnode, ast.Name):
|
|
171
|
-
used_names.add(subnode.id)
|
|
172
|
-
elif isinstance(subnode, ast.Attribute):
|
|
173
|
-
if isinstance(subnode.value, ast.Name):
|
|
174
|
-
used_names.add(subnode.value.id)
|
|
175
|
-
|
|
176
|
-
for name in used_names:
|
|
177
|
-
if name in func_globals:
|
|
178
|
-
obj = func_globals[name]
|
|
179
|
-
|
|
180
|
-
if not isinstance(obj, (ModuleType, FunctionType, type)):
|
|
181
|
-
if not (hasattr(obj, '__module__') and hasattr(obj, '__name__')):
|
|
182
|
-
try:
|
|
183
|
-
obj_type = type(obj)
|
|
184
|
-
|
|
185
|
-
if obj_type.__name__ == 'PathExtended' and obj_type.__module__ == 'machineconfig.utils.path_extended':
|
|
186
|
-
global_assignments.append(f"{name} = PathExtended('{str(obj)}')")
|
|
187
|
-
if obj_type.__module__ not in ['builtins', '__main__']:
|
|
188
|
-
needed_types.add(obj_type)
|
|
189
|
-
else:
|
|
190
|
-
repr_str = repr(obj)
|
|
191
|
-
if len(repr_str) < 1000 and '\n' not in repr_str and all(ord(c) < 128 or c in [' ', '\n', '\t'] for c in repr_str):
|
|
192
|
-
global_assignments.append(f"{name} = {repr_str}")
|
|
193
|
-
if obj_type.__module__ not in ['builtins', '__main__']:
|
|
194
|
-
needed_types.add(obj_type)
|
|
195
|
-
else:
|
|
196
|
-
global_assignments.append(f"# Warning: Could not serialize global variable '{name}' (repr too complex or contains non-ASCII)")
|
|
197
|
-
except Exception as e:
|
|
198
|
-
global_assignments.append(f"# Warning: Could not serialize global variable '{name}': {e}")
|
|
199
|
-
|
|
200
|
-
result_parts: list[str] = []
|
|
201
|
-
|
|
202
|
-
if needed_types:
|
|
203
|
-
for obj_type in sorted(needed_types, key=lambda t: (t.__module__, t.__name__)):
|
|
204
|
-
module_name = obj_type.__module__
|
|
205
|
-
type_name = obj_type.__name__
|
|
206
|
-
result_parts.append(f"from {module_name} import {type_name}")
|
|
207
|
-
result_parts.append("")
|
|
208
|
-
|
|
209
|
-
result_parts.extend(global_assignments)
|
|
210
|
-
|
|
211
|
-
return "\n".join(result_parts)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
def _prepare_call_kwargs(func: FunctionType, call_with_kwargs: Mapping[str, object] | None) -> dict[str, object] | None:
|
|
215
|
-
if call_with_kwargs is None:
|
|
216
|
-
return None
|
|
217
|
-
|
|
218
|
-
normalized_kwargs = dict(call_with_kwargs)
|
|
219
|
-
|
|
220
|
-
if not normalized_kwargs:
|
|
221
|
-
return {}
|
|
222
|
-
|
|
223
|
-
signature = inspect.signature(func)
|
|
224
|
-
positional_only = [parameter.name for parameter in signature.parameters.values() if parameter.kind is inspect.Parameter.POSITIONAL_ONLY]
|
|
225
|
-
|
|
226
|
-
if positional_only:
|
|
227
|
-
joined = ", ".join(positional_only)
|
|
228
|
-
raise ValueError(f"""Cannot call {func.__name__} with positional-only parameters: {joined}""")
|
|
229
|
-
|
|
230
|
-
try:
|
|
231
|
-
signature.bind(**normalized_kwargs)
|
|
232
|
-
except TypeError as error:
|
|
233
|
-
raise ValueError(f"""Invalid call_with_kwargs for {func.__name__}: {error}""") from error
|
|
234
|
-
|
|
235
|
-
return normalized_kwargs
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
def _generate_call_statement(func: FunctionType, kwargs: dict[str, object]) -> str:
|
|
239
|
-
"""Generate a function call statement with the given keyword arguments."""
|
|
240
|
-
if not kwargs:
|
|
241
|
-
return f"{func.__name__}()"
|
|
242
|
-
|
|
243
|
-
arg_parts: list[str] = [f"{key}={repr(value)}" for key, value in kwargs.items()]
|
|
244
|
-
args_str = ", ".join(arg_parts)
|
|
245
|
-
return f"{func.__name__}({args_str})"
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import Any
|
|
246
5
|
|
|
247
6
|
|
|
248
7
|
def lambda_to_defstring(lmb: Callable[[], Any], in_global: bool = False) -> str:
|
|
249
8
|
"""
|
|
9
|
+
caveats: always use keyword arguments in the lambda call for best results.
|
|
10
|
+
return statement not allowed in the wrapped function (otherwise it can be put in the global space)
|
|
11
|
+
|
|
250
12
|
Given a no-arg lambda like `lambda: func(a=var1, b=var2)`,
|
|
251
13
|
return a string containing the full function definition of `func`
|
|
252
14
|
but with the defaults for the parameters provided in the call replaced
|
|
@@ -423,12 +185,4 @@ def lambda_to_defstring(lmb: Callable[[], Any], in_global: bool = False) -> str:
|
|
|
423
185
|
|
|
424
186
|
|
|
425
187
|
if __name__ == "__main__":
|
|
426
|
-
# Example usage
|
|
427
|
-
a = True
|
|
428
|
-
b = 3
|
|
429
|
-
def func(no_copy_assets: bool = a):
|
|
430
|
-
from machineconfig.scripts.python.helpers_devops.cli_self import update
|
|
431
|
-
update(no_copy_assets=no_copy_assets)
|
|
432
|
-
script = function_to_script(func, call_with_kwargs=None)
|
|
433
|
-
print(script)
|
|
434
188
|
pass
|