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.

Files changed (30) hide show
  1. machineconfig/cluster/sessions_managers/utils/maker.py +3 -3
  2. machineconfig/jobs/installer/check_installations.py +0 -1
  3. machineconfig/jobs/installer/installer_data.json +17 -0
  4. machineconfig/scripts/python/croshell.py +4 -4
  5. machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
  6. machineconfig/scripts/python/fire_jobs.py +9 -7
  7. machineconfig/scripts/python/helpers_devops/cli_config.py +3 -2
  8. machineconfig/scripts/python/helpers_devops/cli_self.py +3 -3
  9. machineconfig/scripts/python/helpers_devops/cli_utils.py +43 -0
  10. machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +24 -20
  11. machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +1 -1
  12. machineconfig/scripts/python/helpers_repos/sync.py +5 -5
  13. machineconfig/scripts/python/nw/mount_nfs +1 -1
  14. machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
  15. machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
  16. machineconfig/setup_mac/__init__.py +17 -0
  17. machineconfig/setup_mac/apps.sh +73 -0
  18. machineconfig/setup_mac/ssh/openssh_setup.sh +114 -0
  19. machineconfig/setup_mac/uv.sh +36 -0
  20. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +1 -1
  21. machineconfig/utils/code.py +46 -84
  22. machineconfig/utils/installer.py +8 -3
  23. machineconfig/utils/meta.py +5 -251
  24. machineconfig/utils/scheduling.py +0 -1
  25. machineconfig/utils/ssh.py +46 -45
  26. {machineconfig-6.51.dist-info → machineconfig-6.53.dist-info}/METADATA +1 -1
  27. {machineconfig-6.51.dist-info → machineconfig-6.53.dist-info}/RECORD +30 -26
  28. {machineconfig-6.51.dist-info → machineconfig-6.53.dist-info}/WHEEL +0 -0
  29. {machineconfig-6.51.dist-info → machineconfig-6.53.dist-info}/entry_points.txt +0 -0
  30. {machineconfig-6.51.dist-info → machineconfig-6.53.dist-info}/top_level.txt +0 -0
@@ -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.ve import get_ve_activate_line
11
- from machineconfig.utils.path_extended import PathExtended # type: ignore[import-not-found]
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 get_shell_file_executing_python_script(python_script: str, ve_path: Optional[str], executable: Optional[str], verbose: bool = True):
38
- if verbose:
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
- console = Console()
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"
@@ -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, LIBRARY_ROOT
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
- options_system = parse_apps_installer_windows(LIBRARY_ROOT.joinpath("setup_windows/apps.ps1").read_text(encoding="utf-8"))
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
- options_system = parse_apps_installer_linux(LIBRARY_ROOT.joinpath("setup_linux/apps.sh").read_text(encoding="utf-8"))
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()
@@ -1,252 +1,14 @@
1
1
  """Metaprogramming utilities for analyzing and serializing Python functions."""
2
2
 
3
- import ast
4
- import inspect
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
@@ -1,7 +1,6 @@
1
1
  # """Task scheduler
2
2
  # """
3
3
 
4
- # from machineconfig.utils.utils import get_shell_script_executing_python_file
5
4
  # from machineconfig.utils.utils2 import read_ini
6
5
  # from dataclasses import dataclass
7
6
  # from datetime import datetime, timedelta