machineconfig 6.52__py3-none-any.whl → 6.54__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 +9 -12
- machineconfig/scripts/python/helpers_devops/cli_utils.py +43 -0
- machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +31 -27
- machineconfig/scripts/python/helpers_repos/sync.py +5 -5
- machineconfig/utils/code.py +37 -85
- machineconfig/utils/installer.py +1 -1
- machineconfig/utils/meta.py +5 -251
- machineconfig/utils/scheduling.py +0 -1
- machineconfig/utils/ssh.py +50 -49
- {machineconfig-6.52.dist-info → machineconfig-6.54.dist-info}/METADATA +1 -1
- {machineconfig-6.52.dist-info → machineconfig-6.54.dist-info}/RECORD +14 -14
- {machineconfig-6.52.dist-info → machineconfig-6.54.dist-info}/WHEEL +0 -0
- {machineconfig-6.52.dist-info → machineconfig-6.54.dist-info}/entry_points.txt +0 -0
- {machineconfig-6.52.dist-info → machineconfig-6.54.dist-info}/top_level.txt +0 -0
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
|
|
2
2
|
from types import FunctionType
|
|
3
|
-
from typing import Literal
|
|
3
|
+
from typing import Optional, Literal
|
|
4
4
|
from machineconfig.utils.schemas.layouts.layout_types import TabConfig, LayoutConfig
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
-
def
|
|
8
|
-
from machineconfig.utils.meta import
|
|
9
|
-
py_script =
|
|
10
|
-
from machineconfig.utils.
|
|
11
|
-
py_script_path =
|
|
12
|
-
py_script_path.parent.mkdir(parents=True, exist_ok=True)
|
|
13
|
-
py_script_path.write_text(py_script, encoding="utf-8")
|
|
14
|
-
command_to_run = f"uv run --project $HOME/ {py_script_path}"
|
|
7
|
+
def get_fire_tab_using_uv(func: FunctionType, uv_with: Optional[list[str]], uv_project_dir: Optional[str]) -> tuple[TabConfig, Path]:
|
|
8
|
+
from machineconfig.utils.meta import lambda_to_defstring
|
|
9
|
+
py_script = lambda_to_defstring(lmb=lambda: func, in_global=True)
|
|
10
|
+
from machineconfig.utils.code import get_uv_command_executing_python_script
|
|
11
|
+
command_to_run, py_script_path = get_uv_command_executing_python_script(python_script=py_script, uv_with=uv_with, uv_project_dir=uv_project_dir)
|
|
15
12
|
tab_config: TabConfig = {
|
|
16
13
|
"command": command_to_run,
|
|
17
14
|
"startDir": "$HOME",
|
|
18
15
|
"tabName": func.__name__
|
|
19
16
|
}
|
|
20
17
|
return tab_config, py_script_path
|
|
21
|
-
def
|
|
18
|
+
def get_fire_tab_using_fire(func: FunctionType):
|
|
22
19
|
import inspect
|
|
23
20
|
path = Path(inspect.getfile(func))
|
|
24
21
|
path_relative = path.relative_to(Path.home())
|
|
@@ -36,10 +33,10 @@ def make_layout_from_functions(functions: list[FunctionType], tab_configs: list[
|
|
|
36
33
|
for a_func in functions:
|
|
37
34
|
match method:
|
|
38
35
|
case "script":
|
|
39
|
-
tab_config, artifact_files_1 =
|
|
36
|
+
tab_config, artifact_files_1 = get_fire_tab_using_uv(a_func, uv_with=None, uv_project_dir=None)
|
|
40
37
|
artifact_files = [artifact_files_1]
|
|
41
38
|
case "fire":
|
|
42
|
-
tab_config =
|
|
39
|
+
tab_config = get_fire_tab_using_fire(a_func)
|
|
43
40
|
artifact_files = []
|
|
44
41
|
tabs2artifacts.append((tab_config, artifact_files))
|
|
45
42
|
list_of_tabs = [tab for tab, _ in tabs2artifacts] + tab_configs
|
|
@@ -78,8 +78,51 @@ def download(
|
|
|
78
78
|
raise typer.Exit(code=1)
|
|
79
79
|
|
|
80
80
|
|
|
81
|
+
def merge_pdfs(
|
|
82
|
+
pdf1: Annotated[str, typer.Argument(..., help="Path to the first PDF file.")],
|
|
83
|
+
pdf2: Annotated[str, typer.Argument(..., help="Path to the second PDF file.")],
|
|
84
|
+
output: Annotated[Optional[str], typer.Option("--output", "-o", help="Output merged PDF file path.")] = None,
|
|
85
|
+
compress: Annotated[bool, typer.Option("--compress", "-c", help="Compress the output PDF.")] = False,
|
|
86
|
+
) -> None:
|
|
87
|
+
def merge_pdfs_internal(pdf1: str, pdf2: str, output: Optional[str], compress: bool) -> None:
|
|
88
|
+
from pypdf import PdfReader, PdfWriter
|
|
89
|
+
writer = PdfWriter()
|
|
90
|
+
for pdf_path in [pdf1, pdf2]:
|
|
91
|
+
reader = PdfReader(pdf_path)
|
|
92
|
+
for page in reader.pages:
|
|
93
|
+
writer.add_page(page)
|
|
94
|
+
output_path = output if output else "merged.pdf"
|
|
95
|
+
if compress:
|
|
96
|
+
try:
|
|
97
|
+
for p in writer.pages:
|
|
98
|
+
try:
|
|
99
|
+
# PageObject.compress_content_streams exists in pypdf
|
|
100
|
+
p.compress_content_streams()
|
|
101
|
+
except Exception:
|
|
102
|
+
# best-effort: ignore per-page compression failures
|
|
103
|
+
continue
|
|
104
|
+
except Exception:
|
|
105
|
+
pass
|
|
106
|
+
try:
|
|
107
|
+
writer.compress_identical_objects()
|
|
108
|
+
except Exception:
|
|
109
|
+
# non-fatal if this fails
|
|
110
|
+
pass
|
|
111
|
+
writer.write(output_path)
|
|
112
|
+
print(f"✅ Merged PDF saved to: {output_path}")
|
|
113
|
+
from machineconfig.utils.meta import lambda_to_defstring
|
|
114
|
+
code = lambda_to_defstring(lambda : merge_pdfs_internal(pdf1=pdf1, pdf2=pdf2, output=output, compress=compress), in_global=True)
|
|
115
|
+
import tempfile
|
|
116
|
+
tmp_py_file = Path(tempfile.mkstemp(suffix=".py")[1])
|
|
117
|
+
tmp_py_file.write_text(code, encoding="utf-8")
|
|
118
|
+
from machineconfig.utils.code import run_shell_script
|
|
119
|
+
run_shell_script(f"""uv run --with pypdf {tmp_py_file} """)
|
|
120
|
+
|
|
121
|
+
|
|
81
122
|
def get_app() -> typer.Typer:
|
|
82
123
|
app = typer.Typer(help="🛠️ [u] utilities operations", no_args_is_help=True, add_help_option=False, add_completion=False)
|
|
83
124
|
app.command(name="download", no_args_is_help=True, help="[d] Download a file from a URL and optionally decompress it.")(download)
|
|
84
125
|
app.command(name="d", no_args_is_help=True, hidden=True)(download)
|
|
126
|
+
app.command(name="merge-pdfs", no_args_is_help=True, help="[m] Merge two PDF files into one.")(merge_pdfs)
|
|
127
|
+
app.command(name="m", no_args_is_help=True, hidden=True)(merge_pdfs)
|
|
85
128
|
return app
|
|
@@ -6,7 +6,7 @@ import typer
|
|
|
6
6
|
from machineconfig.utils.path_extended import PathExtended
|
|
7
7
|
from machineconfig.utils.terminal import Response
|
|
8
8
|
from machineconfig.utils.source_of_truth import CONFIG_ROOT, DEFAULTS_PATH
|
|
9
|
-
from machineconfig.utils.code import
|
|
9
|
+
from machineconfig.utils.code import get_uv_command_executing_python_script
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
import platform
|
|
12
12
|
import subprocess
|
|
@@ -76,10 +76,17 @@ git pull originEnc master
|
|
|
76
76
|
|
|
77
77
|
"""
|
|
78
78
|
|
|
79
|
-
if Path.home().joinpath("code/machineconfig").exists():
|
|
80
|
-
|
|
79
|
+
if Path.home().joinpath("code/machineconfig").exists():
|
|
80
|
+
uv_project_dir = f"""{str(Path.home().joinpath("code/machineconfig"))}"""
|
|
81
|
+
uv_with = None
|
|
82
|
+
else:
|
|
83
|
+
uv_with = ["machineconfig>=6.52"]
|
|
84
|
+
uv_project_dir = None
|
|
85
|
+
|
|
86
|
+
import tempfile
|
|
87
|
+
shell_path = Path(tempfile.mkstemp(suffix=".ps1" if platform.system() == "Windows" else ".sh")[1])
|
|
88
|
+
shell_path.write_text(script, encoding="utf-8")
|
|
81
89
|
|
|
82
|
-
shell_path = write_shell_script_to_file(shell_script=script)
|
|
83
90
|
command = f". {shell_path}"
|
|
84
91
|
if platform.system() == "Windows":
|
|
85
92
|
completed = subprocess.run(["powershell", "-Command", command], capture_output=True, check=False, text=True)
|
|
@@ -100,20 +107,12 @@ git pull originEnc master
|
|
|
100
107
|
|
|
101
108
|
# ================================================================================
|
|
102
109
|
option1 = "Delete remote copy and push local:"
|
|
103
|
-
from machineconfig.utils.meta import
|
|
110
|
+
from machineconfig.utils.meta import lambda_to_defstring
|
|
104
111
|
def func2(remote_repo: str, local_repo: str, cloud: str):
|
|
105
112
|
from machineconfig.scripts.python.helpers_repos.sync import delete_remote_repo_copy_and_push_local
|
|
106
113
|
delete_remote_repo_copy_and_push_local(remote_repo=remote_repo, local_repo=local_repo, cloud=cloud)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
# from machineconfig.scripts.python.helpers_repos.sync import delete_remote_repo_copy_and_push_local
|
|
110
|
-
# delete_remote_repo_copy_and_push_local(remote_repo=remote_repo, local_repo=local_repo, cloud=cloud)
|
|
111
|
-
# return "done"
|
|
112
|
-
# program_1_py = function_to_script(func=func2, call_with_kwargs=None)
|
|
113
|
-
program_1_py = function_to_script(func=func2, call_with_kwargs={"remote_repo": str(repo_remote_root), "local_repo": str(repo_local_root), "cloud": cloud_resolved})
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
shell_file_1 = get_shell_file_executing_python_script(python_script=program_1_py, ve_path=None, executable=executable)
|
|
114
|
+
program_1_py = lambda_to_defstring(lambda: func2(remote_repo=str(repo_remote_root), local_repo=str(repo_local_root), cloud=str(cloud_resolved)), in_global=True)
|
|
115
|
+
uv_command_1, _pyfile1 = get_uv_command_executing_python_script(python_script=program_1_py, uv_with=uv_with, uv_project_dir=uv_project_dir)
|
|
117
116
|
# ================================================================================
|
|
118
117
|
option2 = "Delete local repo and replace it with remote copy:"
|
|
119
118
|
program_2 = f"""
|
|
@@ -126,15 +125,19 @@ sudo chmod 600 $HOME/.ssh/*
|
|
|
126
125
|
sudo chmod 700 $HOME/.ssh
|
|
127
126
|
sudo chmod +x $HOME/dotfiles/scripts/linux -R
|
|
128
127
|
"""
|
|
129
|
-
|
|
128
|
+
import tempfile
|
|
129
|
+
shell_file_2 = Path(tempfile.mkstemp(suffix=".ps1" if platform.system() == "Windows" else ".sh")[1])
|
|
130
|
+
shell_file_2.write_text(program_2, encoding="utf-8")
|
|
131
|
+
|
|
130
132
|
# ================================================================================
|
|
131
133
|
option3 = "Inspect repos:"
|
|
132
134
|
def func(repo_local_root: str, repo_remote_root: str):
|
|
133
135
|
from machineconfig.scripts.python.helpers_repos.sync import inspect_repos
|
|
134
136
|
inspect_repos(repo_local_root=repo_local_root, repo_remote_root=repo_remote_root)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
# program_3_py = function_to_script(func=func, call_with_kwargs={"repo_local_root": str(repo_local_root), "repo_remote_root": str(repo_remote_root)})
|
|
138
|
+
# shell_file_3 = get_shell_file_executing_python_script(python_script=program_3_py, ve_path=None, executable=executable)
|
|
139
|
+
program_3_py = lambda_to_defstring(lambda: func(repo_local_root=str(repo_local_root), repo_remote_root=str(repo_remote_root)), in_global=True)
|
|
140
|
+
uv_command_3, _pyfile3 = get_uv_command_executing_python_script(python_script=program_3_py, uv_with=uv_with, uv_project_dir=uv_project_dir)
|
|
138
141
|
# ================================================================================
|
|
139
142
|
|
|
140
143
|
option4 = "Remove problematic rclone file from repo and replace with remote:"
|
|
@@ -143,16 +146,17 @@ rm $HOME/dotfiles/creds/rclone/rclone.conf
|
|
|
143
146
|
cp $HOME/.config/machineconfig/remote/dotfiles/creds/rclone/rclone.conf $HOME/dotfiles/creds/rclone
|
|
144
147
|
cd $HOME/dotfiles
|
|
145
148
|
git commit -am "finished merging"
|
|
146
|
-
|
|
149
|
+
{uv_command_1}
|
|
147
150
|
"""
|
|
148
|
-
shell_file_4 =
|
|
151
|
+
shell_file_4 = Path(tempfile.mkstemp(suffix=".ps1" if platform.system() == "Windows" else ".sh")[1])
|
|
152
|
+
shell_file_4.write_text(program_4, encoding="utf-8")
|
|
149
153
|
# ================================================================================
|
|
150
154
|
|
|
151
155
|
console.print(Panel("🔄 RESOLVE MERGE CONFLICT\nChoose an option to resolve the conflict:", title_align="left", border_style="blue"))
|
|
152
156
|
|
|
153
|
-
print(f"• {option1:75} 👉 {
|
|
157
|
+
print(f"• {option1:75} 👉 {uv_command_1}")
|
|
154
158
|
print(f"• {option2:75} 👉 {shell_file_2}")
|
|
155
|
-
print(f"• {option3:75} 👉 {
|
|
159
|
+
print(f"• {option3:75} 👉 {uv_command_3}")
|
|
156
160
|
print(f"• {option4:75} 👉 {shell_file_4}")
|
|
157
161
|
print("\n\n")
|
|
158
162
|
|
|
@@ -162,21 +166,21 @@ git commit -am "finished merging"
|
|
|
162
166
|
import questionary
|
|
163
167
|
choice = questionary.select("Choose one option:", choices=[option1, option2, option3, option4]).ask()
|
|
164
168
|
if choice == option1:
|
|
165
|
-
program_content =
|
|
169
|
+
program_content = uv_command_1
|
|
166
170
|
elif choice == option2:
|
|
167
171
|
program_content = program_2
|
|
168
172
|
elif choice == option3:
|
|
169
|
-
program_content =
|
|
173
|
+
program_content = uv_command_3
|
|
170
174
|
elif choice == option4:
|
|
171
175
|
program_content = program_4
|
|
172
176
|
else:
|
|
173
177
|
raise NotImplementedError(f"Choice {choice} not implemented.")
|
|
174
178
|
case "push-local-merge":
|
|
175
|
-
program_content =
|
|
179
|
+
program_content = uv_command_1
|
|
176
180
|
case "overwrite-local":
|
|
177
181
|
program_content = program_2
|
|
178
182
|
case "stop-on-conflict":
|
|
179
|
-
program_content =
|
|
183
|
+
program_content = uv_command_3
|
|
180
184
|
case "remove-rclone-conflict":
|
|
181
185
|
program_content = program_4
|
|
182
186
|
case _:
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from machineconfig.utils.path_extended import PathExtended
|
|
2
|
-
from machineconfig.utils.code import write_shell_script_to_file
|
|
3
2
|
import platform
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
from rich.console import Console
|
|
@@ -56,11 +55,12 @@ def inspect_repos(repo_local_root: str, repo_remote_root: str):
|
|
|
56
55
|
|
|
57
56
|
if platform.system() == "Windows":
|
|
58
57
|
program = get_wt_cmd(wd1=PathExtended(repo_local_root), wd2=PathExtended(repo_local_root))
|
|
59
|
-
write_shell_script_to_file(shell_script=program)
|
|
60
|
-
return None
|
|
61
58
|
elif platform.system() in ["Linux", "Darwin"]:
|
|
62
59
|
program = get_zellij_cmd(wd1=PathExtended(repo_local_root), wd2=PathExtended(repo_remote_root))
|
|
63
|
-
write_shell_script_to_file(shell_script=program)
|
|
64
|
-
return None
|
|
65
60
|
else:
|
|
66
61
|
raise NotImplementedError(f"Platform {platform.system()} not implemented.")
|
|
62
|
+
import tempfile
|
|
63
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix=".sh" if platform.system() != "Windows" else ".ps1", delete=False, encoding='utf-8') as temp_file:
|
|
64
|
+
temp_file.write(program)
|
|
65
|
+
temp_script_path = PathExtended(temp_file.name)
|
|
66
|
+
console.print(Panel(f"🚀 Launching repo inspection tool...\n\n[blue]{temp_script_path}[/blue]", border_style="blue"))
|
machineconfig/utils/code.py
CHANGED
|
@@ -1,95 +1,18 @@
|
|
|
1
|
+
|
|
1
2
|
import atexit
|
|
2
|
-
import platform
|
|
3
|
+
# import platform
|
|
3
4
|
from typing import Optional
|
|
4
5
|
import subprocess
|
|
5
|
-
from rich.console import Console
|
|
6
|
-
from rich.panel import Panel
|
|
7
|
-
from rich.syntax import Syntax
|
|
8
|
-
|
|
9
6
|
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
|
|
7
|
+
from machineconfig.utils.path_extended import PathExtended
|
|
8
|
+
from pathlib import Path
|
|
35
9
|
|
|
36
10
|
|
|
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:
|
|
11
|
+
def print_code(code: str, lexer: str, desc: str, subtitle: str = ""):
|
|
12
|
+
import platform
|
|
45
13
|
from rich.console import Console
|
|
46
14
|
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 = ""):
|
|
15
|
+
from rich.syntax import Syntax
|
|
93
16
|
if lexer == "shell":
|
|
94
17
|
if platform.system() == "Windows":
|
|
95
18
|
lexer = "powershell"
|
|
@@ -101,8 +24,32 @@ def print_code(code: str, lexer: str, desc: str, subtitle: str = ""):
|
|
|
101
24
|
console.print(Panel(Syntax(code=code, lexer=lexer), title=f"📄 {desc}", subtitle=subtitle), style="bold red")
|
|
102
25
|
|
|
103
26
|
|
|
27
|
+
def get_uv_command_executing_python_script(python_script: str, uv_with: Optional[list[str]], uv_project_dir: Optional[str]) -> tuple[str, Path]:
|
|
28
|
+
python_file = PathExtended.tmp().joinpath("tmp_scripts", "python", randstr() + ".py")
|
|
29
|
+
python_file.parent.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
if uv_with is not None and len(uv_with) > 0:
|
|
31
|
+
uv_with.append("rich")
|
|
32
|
+
uv_with_arg = "--with " + '"' + ",".join(uv_with) + '"'
|
|
33
|
+
else:
|
|
34
|
+
uv_with_arg = "--with rich"
|
|
35
|
+
if uv_project_dir is not None:
|
|
36
|
+
uv_project_dir_arg = "--project" + f' "{uv_project_dir}"'
|
|
37
|
+
else:
|
|
38
|
+
uv_project_dir_arg = ""
|
|
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.write_text(print_code_string + "\n" + python_script, encoding="utf-8")
|
|
42
|
+
shell_script = f"""uv run {uv_with_arg} {uv_project_dir_arg} {str(python_file)} """
|
|
43
|
+
return shell_script, python_file
|
|
44
|
+
|
|
45
|
+
|
|
104
46
|
def run_shell_script(script: str, display_script: bool = True, clean_env: bool = False):
|
|
105
47
|
import tempfile
|
|
48
|
+
import platform
|
|
49
|
+
from rich.console import Console
|
|
50
|
+
from rich.panel import Panel
|
|
51
|
+
from rich.syntax import Syntax
|
|
52
|
+
|
|
106
53
|
if platform.system() == "Windows":
|
|
107
54
|
suffix = ".ps1"
|
|
108
55
|
lexer = "powershell"
|
|
@@ -112,6 +59,7 @@ def run_shell_script(script: str, display_script: bool = True, clean_env: bool =
|
|
|
112
59
|
with tempfile.NamedTemporaryFile(mode='w', suffix=suffix, delete=False, encoding='utf-8') as temp_file:
|
|
113
60
|
temp_file.write(script)
|
|
114
61
|
temp_script_path = PathExtended(temp_file.name)
|
|
62
|
+
|
|
115
63
|
console = Console()
|
|
116
64
|
if display_script:
|
|
117
65
|
from rich.syntax import Syntax
|
|
@@ -138,8 +86,12 @@ def run_shell_script(script: str, display_script: bool = True, clean_env: bool =
|
|
|
138
86
|
|
|
139
87
|
|
|
140
88
|
def run_shell_script_after_exit(script: str, display_script: bool = True) -> None:
|
|
89
|
+
import platform
|
|
90
|
+
from rich.console import Console
|
|
91
|
+
from rich.panel import Panel
|
|
92
|
+
from rich.syntax import Syntax
|
|
93
|
+
|
|
141
94
|
console = Console()
|
|
142
|
-
|
|
143
95
|
def execute_script_at_exit() -> None:
|
|
144
96
|
if platform.system() == "Windows":
|
|
145
97
|
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
|
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
|
machineconfig/utils/ssh.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from typing import Callable, Optional, Any, Union
|
|
2
2
|
import os
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
import platform
|
|
4
5
|
import rich.console
|
|
5
6
|
from machineconfig.utils.terminal import Response, MACHINE
|
|
6
7
|
from machineconfig.utils.accessories import pprint
|
|
7
8
|
|
|
8
|
-
UV_RUN_CMD = "$HOME/.local/bin/uv run"
|
|
9
|
+
UV_RUN_CMD = "$HOME/.local/bin/uv run" if platform.system() != "Windows" else """& "$env:USERPROFILE/.local/bin/uv" run"""
|
|
9
10
|
MACHINECONFIG_VERSION = "machineconfig>=6.52"
|
|
10
11
|
DEFAULT_PICKLE_SUBDIR = "tmp_results/tmp_scripts/ssh"
|
|
11
12
|
|
|
@@ -176,7 +177,6 @@ class SSH:
|
|
|
176
177
|
res.output.returncode = os.system(command)
|
|
177
178
|
return res
|
|
178
179
|
|
|
179
|
-
|
|
180
180
|
def run_shell(self, command: str, verbose_output: bool, description: str, strict_stderr: bool, strict_return_code: bool) -> Response:
|
|
181
181
|
raw = self.ssh.exec_command(command)
|
|
182
182
|
res = Response(stdin=raw[0], stdout=raw[1], stderr=raw[2], cmd=command, desc=description) # type: ignore
|
|
@@ -186,31 +186,30 @@ class SSH:
|
|
|
186
186
|
res.capture().print_if_unsuccessful(desc=description, strict_err=strict_stderr, strict_returncode=strict_return_code, assert_success=False)
|
|
187
187
|
self.terminal_responses.append(res)
|
|
188
188
|
return res
|
|
189
|
-
def run_py(self, python_code: str,
|
|
189
|
+
def run_py(self, python_code: str, uv_with: Optional[list[str]], uv_project_dir: Optional[str],
|
|
190
190
|
description: str, verbose_output: bool, strict_stderr: bool, strict_return_code: bool) -> Response:
|
|
191
191
|
from machineconfig.utils.accessories import randstr
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
self.copy_from_here(source_path=
|
|
196
|
-
if len(
|
|
197
|
-
with_clause = " --with " + '"'
|
|
192
|
+
py_path = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/runpy_{randstr()}.py")
|
|
193
|
+
py_path.parent.mkdir(parents=True, exist_ok=True)
|
|
194
|
+
py_path.write_text(python_code, encoding="utf-8")
|
|
195
|
+
self.copy_from_here(source_path=py_path, target_path=None, compress_with_zip=False, recursive=False, overwrite_existing=False)
|
|
196
|
+
if uv_with is not None and len(uv_with) > 0:
|
|
197
|
+
with_clause = " --with " + '"' + ",".join(uv_with) + '"'
|
|
198
198
|
else:
|
|
199
199
|
with_clause = ""
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
else:
|
|
206
|
-
venv_export = f"VIRTUAL_ENV={venv_path}"
|
|
207
|
-
uv_cmd = venv_export + " " + uv_cmd
|
|
200
|
+
if uv_project_dir is not None:
|
|
201
|
+
with_clause += f" --project {uv_project_dir}"
|
|
202
|
+
else:
|
|
203
|
+
with_clause += ""
|
|
204
|
+
uv_cmd = f"""{UV_RUN_CMD} {with_clause} python {py_path.relative_to(Path.home())}"""
|
|
208
205
|
return self.run_shell(command=uv_cmd, verbose_output=verbose_output, description=description or f"run_py on {self.get_remote_repr(add_machine=False)}", strict_stderr=strict_stderr, strict_return_code=strict_return_code)
|
|
209
206
|
|
|
210
|
-
def run_py_func(self, func: Callable[..., Any],
|
|
211
|
-
from machineconfig.utils.meta import
|
|
212
|
-
command =
|
|
213
|
-
return self.run_py(python_code=command,
|
|
207
|
+
def run_py_func(self, func: Callable[..., Any], uv_with: Optional[list[str]], uv_project_dir: Optional[str]) -> Response:
|
|
208
|
+
from machineconfig.utils.meta import lambda_to_defstring
|
|
209
|
+
command = lambda_to_defstring(lmb=lambda: func, in_global=True)
|
|
210
|
+
return self.run_py(python_code=command, uv_with=uv_with, uv_project_dir=uv_project_dir,
|
|
211
|
+
description=f"run_py_func {func.__name__} on {self.get_remote_repr(add_machine=False)}",
|
|
212
|
+
verbose_output=True, strict_stderr=True, strict_return_code=True)
|
|
214
213
|
|
|
215
214
|
def _simple_sftp_get(self, remote_path: str, local_path: Path) -> None:
|
|
216
215
|
"""Simple SFTP get without any recursion or path expansion - for internal use only."""
|
|
@@ -238,11 +237,14 @@ class SSH:
|
|
|
238
237
|
json_result_path.write_text(json.dumps(result_path_posix, indent=2), encoding="utf-8")
|
|
239
238
|
print(json_result_path.as_posix())
|
|
240
239
|
return result_path_posix
|
|
241
|
-
from machineconfig.utils.meta import
|
|
240
|
+
from machineconfig.utils.meta import lambda_to_defstring
|
|
242
241
|
from machineconfig.utils.accessories import randstr
|
|
243
242
|
remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
|
|
244
|
-
command = function_to_script(func=create_target_dir, call_with_kwargs={"target_dir_path": Path(target_path).as_posix(), "overwrite": overwrite_existing, "json_output_path": remote_json_output})
|
|
245
|
-
|
|
243
|
+
# command = function_to_script(func=create_target_dir, call_with_kwargs={"target_dir_path": Path(target_path).as_posix(), "overwrite": overwrite_existing, "json_output_path": remote_json_output})
|
|
244
|
+
command = lambda_to_defstring(lmb=lambda: create_target_dir(target_dir_path=str(target_path), overwrite=overwrite_existing, json_output_path=remote_json_output), in_global=True)
|
|
245
|
+
response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None,
|
|
246
|
+
description=f"Creating target directory `{Path(target_path).parent.as_posix()}` @ {self.get_remote_repr(add_machine=False)}",
|
|
247
|
+
verbose_output=False, strict_stderr=False, strict_return_code=False)
|
|
246
248
|
remote_json_path = response.op.strip()
|
|
247
249
|
if not remote_json_path:
|
|
248
250
|
raise RuntimeError(f"Failed to create target directory {target_path} - no response from remote")
|
|
@@ -323,11 +325,10 @@ class SSH:
|
|
|
323
325
|
with zipfile.ZipFile(archive_path, "r") as archive_handle:
|
|
324
326
|
archive_handle.extractall(extraction_directory)
|
|
325
327
|
archive_path.unlink()
|
|
326
|
-
from machineconfig.utils.meta import
|
|
327
|
-
command =
|
|
328
|
-
_resp = self.run_py(python_code=command,
|
|
328
|
+
from machineconfig.utils.meta import lambda_to_defstring
|
|
329
|
+
command = lambda_to_defstring(lmb=lambda: unzip_archive(zip_file_path=remotepath.as_posix(), overwrite_flag=overwrite_existing), in_global=True)
|
|
330
|
+
_resp = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description=f"UNZIPPING {remotepath.as_posix()}", verbose_output=False, strict_stderr=True, strict_return_code=True)
|
|
329
331
|
source_obj.unlink()
|
|
330
|
-
print("\n")
|
|
331
332
|
return source_obj
|
|
332
333
|
|
|
333
334
|
def _check_remote_is_dir(self, source_path: Union[str, Path]) -> bool:
|
|
@@ -342,11 +343,11 @@ class SSH:
|
|
|
342
343
|
print(json_result_path.as_posix())
|
|
343
344
|
return is_directory
|
|
344
345
|
|
|
345
|
-
from machineconfig.utils.meta import
|
|
346
|
+
from machineconfig.utils.meta import lambda_to_defstring
|
|
346
347
|
from machineconfig.utils.accessories import randstr
|
|
347
348
|
remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
|
|
348
|
-
command =
|
|
349
|
-
response = self.run_py(python_code=command,
|
|
349
|
+
command = lambda_to_defstring(lmb=lambda: check_is_dir(path_to_check=str(source_path), json_output_path=remote_json_output), in_global=True)
|
|
350
|
+
response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description=f"Check if source `{source_path}` is a dir", verbose_output=False, strict_stderr=False, strict_return_code=False)
|
|
350
351
|
remote_json_path = response.op.strip()
|
|
351
352
|
if not remote_json_path:
|
|
352
353
|
raise RuntimeError(f"Failed to check if {source_path} is directory - no response from remote")
|
|
@@ -376,11 +377,11 @@ class SSH:
|
|
|
376
377
|
print(json_result_path.as_posix())
|
|
377
378
|
return expanded_path_posix
|
|
378
379
|
|
|
379
|
-
from machineconfig.utils.meta import
|
|
380
|
+
from machineconfig.utils.meta import lambda_to_defstring
|
|
380
381
|
from machineconfig.utils.accessories import randstr
|
|
381
382
|
remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
|
|
382
|
-
command =
|
|
383
|
-
response = self.run_py(python_code=command,
|
|
383
|
+
command = lambda_to_defstring(lmb=lambda: expand_source(path_to_expand=str(source_path), json_output_path=remote_json_output), in_global=True)
|
|
384
|
+
response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description="Resolving source path by expanding user", verbose_output=False, strict_stderr=False, strict_return_code=False)
|
|
384
385
|
remote_json_path = response.op.strip()
|
|
385
386
|
if not remote_json_path:
|
|
386
387
|
raise RuntimeError(f"Could not resolve source path {source_path} - no response from remote")
|
|
@@ -425,11 +426,11 @@ class SSH:
|
|
|
425
426
|
print(json_result_path.as_posix())
|
|
426
427
|
return file_paths_list
|
|
427
428
|
|
|
428
|
-
from machineconfig.utils.meta import
|
|
429
|
+
from machineconfig.utils.meta import lambda_to_defstring
|
|
429
430
|
from machineconfig.utils.accessories import randstr
|
|
430
431
|
remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
|
|
431
|
-
command =
|
|
432
|
-
response = self.run_py(python_code=command,
|
|
432
|
+
command = lambda_to_defstring(lmb=lambda: search_files(directory_path=expanded_source, json_output_path=remote_json_output), in_global=True)
|
|
433
|
+
response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description="Searching for files in source", verbose_output=False, strict_stderr=False, strict_return_code=False)
|
|
433
434
|
remote_json_path = response.op.strip()
|
|
434
435
|
if not remote_json_path:
|
|
435
436
|
raise RuntimeError(f"Could not resolve source path {source} - no response from remote")
|
|
@@ -463,11 +464,11 @@ class SSH:
|
|
|
463
464
|
except ValueError:
|
|
464
465
|
raise RuntimeError(f"Source path must be relative to home directory: {source_absolute_path}")
|
|
465
466
|
|
|
466
|
-
from machineconfig.utils.meta import
|
|
467
|
+
from machineconfig.utils.meta import lambda_to_defstring
|
|
467
468
|
from machineconfig.utils.accessories import randstr
|
|
468
469
|
remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
|
|
469
|
-
command =
|
|
470
|
-
response = self.run_py(python_code=command,
|
|
470
|
+
command = lambda_to_defstring(lmb=lambda: collapse_to_home_dir(absolute_path=expanded_source, json_output_path=remote_json_output), in_global=True)
|
|
471
|
+
response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description="Finding default target via relative source path", verbose_output=False, strict_stderr=False, strict_return_code=False)
|
|
471
472
|
remote_json_path_dir = response.op.strip()
|
|
472
473
|
if not remote_json_path_dir:
|
|
473
474
|
raise RuntimeError("Could not resolve target path - no response from remote")
|
|
@@ -515,11 +516,11 @@ class SSH:
|
|
|
515
516
|
print(json_result_path.as_posix())
|
|
516
517
|
return zip_file_path
|
|
517
518
|
|
|
518
|
-
from machineconfig.utils.meta import
|
|
519
|
+
from machineconfig.utils.meta import lambda_to_defstring
|
|
519
520
|
from machineconfig.utils.accessories import randstr
|
|
520
521
|
remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
|
|
521
|
-
command =
|
|
522
|
-
response = self.run_py(python_code=command,
|
|
522
|
+
command = lambda_to_defstring(lmb=lambda: zip_source(path_to_zip=expanded_source, json_output_path=remote_json_output), in_global=True)
|
|
523
|
+
response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description=f"Zipping source file {source}", verbose_output=False, strict_stderr=False, strict_return_code=False)
|
|
523
524
|
remote_json_path = response.op.strip()
|
|
524
525
|
if not remote_json_path:
|
|
525
526
|
raise RuntimeError(f"Could not zip {source} - no response from remote")
|
|
@@ -554,11 +555,11 @@ class SSH:
|
|
|
554
555
|
except ValueError:
|
|
555
556
|
raise RuntimeError(f"Source path must be relative to home directory: {source_absolute_path}")
|
|
556
557
|
|
|
557
|
-
from machineconfig.utils.meta import
|
|
558
|
+
from machineconfig.utils.meta import lambda_to_defstring
|
|
558
559
|
from machineconfig.utils.accessories import randstr
|
|
559
560
|
remote_json_output = Path.home().joinpath(f"{DEFAULT_PICKLE_SUBDIR}/return_{randstr()}.json").as_posix()
|
|
560
|
-
command =
|
|
561
|
-
response = self.run_py(python_code=command,
|
|
561
|
+
command = lambda_to_defstring(lmb=lambda: collapse_to_home(absolute_path=expanded_source, json_output_path=remote_json_output), in_global=True)
|
|
562
|
+
response = self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description="Finding default target via relative source path", verbose_output=False, strict_stderr=False, strict_return_code=False)
|
|
562
563
|
remote_json_path = response.op.strip()
|
|
563
564
|
if not remote_json_path:
|
|
564
565
|
raise RuntimeError("Could not resolve target path - no response from remote")
|
|
@@ -612,9 +613,9 @@ class SSH:
|
|
|
612
613
|
else:
|
|
613
614
|
file_or_dir_path.unlink()
|
|
614
615
|
|
|
615
|
-
from machineconfig.utils.meta import
|
|
616
|
-
command =
|
|
617
|
-
self.run_py(python_code=command,
|
|
616
|
+
from machineconfig.utils.meta import lambda_to_defstring
|
|
617
|
+
command = lambda_to_defstring(lmb=lambda: delete_temp_zip(path_to_delete=expanded_source), in_global=True)
|
|
618
|
+
self.run_py(python_code=command, uv_with=[MACHINECONFIG_VERSION], uv_project_dir=None, description="Cleaning temp zip files @ remote.", verbose_output=False, strict_stderr=True, strict_return_code=True)
|
|
618
619
|
|
|
619
620
|
print("\n")
|
|
620
621
|
return target_obj
|
|
@@ -25,7 +25,7 @@ machineconfig/cluster/sessions_managers/zellij_remote_manager.py,sha256=xzih0y8_
|
|
|
25
25
|
machineconfig/cluster/sessions_managers/helpers/enhanced_command_runner.py,sha256=3vcQVg-HHa_WTxBGPtKMAdoSqJVa2EO5KAtrY8a6I3c,5264
|
|
26
26
|
machineconfig/cluster/sessions_managers/helpers/load_balancer_helper.py,sha256=i5TRittC1IWTgMZNyG8AR5qq-3WrGp3xgIx2m5ktT7g,7526
|
|
27
27
|
machineconfig/cluster/sessions_managers/utils/load_balancer.py,sha256=Y4RQmhROY6o7JXSJXRrBTkoAuEmu1gvmvN_7JKPw5sc,3178
|
|
28
|
-
machineconfig/cluster/sessions_managers/utils/maker.py,sha256=
|
|
28
|
+
machineconfig/cluster/sessions_managers/utils/maker.py,sha256=GPt1A17LcU7hsrG_OHoy8jmD3HTrzJR5rPFAa70xn60,2102
|
|
29
29
|
machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py,sha256=OA50j16uUS9ZTjL38TLuR3jufIOln_EszMZpbWyejTo,6972
|
|
30
30
|
machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py,sha256=Mitm7mKiKl5lT0OiEUHAqVg2Q21RjsKO1-hpJTHJ5lM,15196
|
|
31
31
|
machineconfig/cluster/sessions_managers/wt_utils/remote_executor.py,sha256=lApUy67_WhfaBXqt0meZSx_QvwiXjN0YLdyE3c7kP_s,6744
|
|
@@ -186,7 +186,7 @@ machineconfig/scripts/python/helpers_devops/cli_repos.py,sha256=Xwkv1adqHZvTfRSP
|
|
|
186
186
|
machineconfig/scripts/python/helpers_devops/cli_self.py,sha256=-IyJpuw8Wnttls8y2vcNNkxtI9jUveROMPT-eudOoC4,6171
|
|
187
187
|
machineconfig/scripts/python/helpers_devops/cli_share_server.py,sha256=q9pFJ6AxPuygMr3onMNOKEuuQHbVE_6Qoyo7xRT5FX0,4196
|
|
188
188
|
machineconfig/scripts/python/helpers_devops/cli_terminal.py,sha256=k_PzXaiGyE0vXr0Ii1XcJz2A7UvyPJrR31TRWt4RKRI,6019
|
|
189
|
-
machineconfig/scripts/python/helpers_devops/cli_utils.py,sha256=
|
|
189
|
+
machineconfig/scripts/python/helpers_devops/cli_utils.py,sha256=FcvC_795dKxZQM7hL4uRzmhysKi9YV-v9qu2Gb_Fun8,5622
|
|
190
190
|
machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py,sha256=Dn8luB6QJzxKiiFSC-NMqiYddWZoca3A8eOjMYZDzTc,5598
|
|
191
191
|
machineconfig/scripts/python/helpers_devops/devops_status.py,sha256=PJVPhfhXq8der6Xd-_fjZfnizfM-RGfJApkRGhGBmNo,20525
|
|
192
192
|
machineconfig/scripts/python/helpers_devops/devops_update_repos.py,sha256=kSln8_-Wn7Qu0NaKdt-QTN_bBVyTIAWHH8xVYKK-vCM,10133
|
|
@@ -222,14 +222,14 @@ machineconfig/scripts/python/helpers_navigator/main_app.py,sha256=R1vOBMUKaiFHOg
|
|
|
222
222
|
machineconfig/scripts/python/helpers_navigator/search_bar.py,sha256=kDi8Jhxap8wdm7YpDBtfhwcPnSqDPFrV2LqbcSBWMT4,414
|
|
223
223
|
machineconfig/scripts/python/helpers_repos/action.py,sha256=9AxWy8mB9HFeV5t11-qD_l-KA5jkUmm0pXVKT1L6-Qk,14894
|
|
224
224
|
machineconfig/scripts/python/helpers_repos/clone.py,sha256=UULEG5xJuXlPGU0nqXH6U45jA9DOFqLw8B4iPytCwOQ,5471
|
|
225
|
-
machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py,sha256=
|
|
225
|
+
machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py,sha256=Ewl-flfosUO3jL23ixKcysKjR149ySlNe22W_2osmPA,10432
|
|
226
226
|
machineconfig/scripts/python/helpers_repos/count_lines.py,sha256=Q5c7b-DxvTlQmljoic7niTuiAVyFlwYvkVQ7uRJHiTo,16009
|
|
227
227
|
machineconfig/scripts/python/helpers_repos/count_lines_frontend.py,sha256=epbXDwuXe_9jXaX3KA2JlG1sPXybdGq1NX4dRljHpF8,607
|
|
228
228
|
machineconfig/scripts/python/helpers_repos/entrypoint.py,sha256=WYEFGUJp9HWImlFjbs_hiFZrUqM_KEYm5VvSUjWd04I,2810
|
|
229
229
|
machineconfig/scripts/python/helpers_repos/grource.py,sha256=oJj1-gqlkV3Z_BrIOXRmtzoXcuBl0xTYfulJ5D0srOc,14656
|
|
230
230
|
machineconfig/scripts/python/helpers_repos/record.py,sha256=FQo0swuJZOp0I2XGK-t1OQU4zJHmQ2z9zTpDD30Tmg4,11001
|
|
231
231
|
machineconfig/scripts/python/helpers_repos/secure_repo.py,sha256=fW_GyHqWrpnf7nexHojfWHv4eLBa71IhVL_LSVMyGnE,1115
|
|
232
|
-
machineconfig/scripts/python/helpers_repos/sync.py,sha256=
|
|
232
|
+
machineconfig/scripts/python/helpers_repos/sync.py,sha256=P0P7Dog2uFDvwxcLP3YHPwm6AtvCm6QOz1BLqw53xOo,3259
|
|
233
233
|
machineconfig/scripts/python/helpers_repos/update.py,sha256=cUIMUMm-50HrY6fzxSMZnFplhToVjVPZMm1j_otTha4,11060
|
|
234
234
|
machineconfig/scripts/python/helpers_sessions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
235
235
|
machineconfig/scripts/python/helpers_sessions/sessions_multiprocess.py,sha256=zLssrCT3WRReiK0AFwctELN_o_svKypagUwJj0nT6i4,3122
|
|
@@ -390,20 +390,20 @@ machineconfig/setup_windows/wt_and_pwsh/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J
|
|
|
390
390
|
machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py,sha256=ogxJnwpdcpH7N6dFJu95UCNoGYirZKQho_3X0F_hmXs,6791
|
|
391
391
|
machineconfig/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
392
392
|
machineconfig/utils/accessories.py,sha256=W_9dLzjwNTW5JQk_pe3B2ijQ1nA2-8Kdg2r7VBtzgQs,4340
|
|
393
|
-
machineconfig/utils/code.py,sha256=
|
|
394
|
-
machineconfig/utils/installer.py,sha256=
|
|
393
|
+
machineconfig/utils/code.py,sha256=jf9RbO0daBkUUuj6jyoTY-0judpBZ4Wfp7i8PZoxzuY,5344
|
|
394
|
+
machineconfig/utils/installer.py,sha256=UzI_DtTcKbgvkAkWkNLAPUtx-RVqITHCpvZyLiCpD9g,10377
|
|
395
395
|
machineconfig/utils/io.py,sha256=4dSieoqZO8Vvi4vW8lLoITDHBvmFp4dtl3kyeZHQ6Co,2528
|
|
396
396
|
machineconfig/utils/links.py,sha256=KM6vIn3hag9FYEzLSHP5MAM9tU_RStw2mCq2_OvmmZA,23672
|
|
397
|
-
machineconfig/utils/meta.py,sha256
|
|
397
|
+
machineconfig/utils/meta.py,sha256=-ki5JxPZoPhg5omM2YChUNExNx5mOoX-a2w0rx51NBE,7453
|
|
398
398
|
machineconfig/utils/notifications.py,sha256=tuXIudcip0tEioG-bm8BbLr3FMDve4f6BktlznBhKxM,9013
|
|
399
399
|
machineconfig/utils/options.py,sha256=vUO4Kej-vDOv64wHr2HNDyu6PATURpjd7xp6N8OOoJg,7083
|
|
400
400
|
machineconfig/utils/path_extended.py,sha256=WyJwoHnXdvSQQJ-yrxTX78FpqYmgVeKDYpNEB9UsRck,53223
|
|
401
401
|
machineconfig/utils/path_helper.py,sha256=0e3Xh3BAEv27oqcezNeVLHJllGmLEgLH4T1l90m-650,8014
|
|
402
402
|
machineconfig/utils/procs.py,sha256=YPA_vEYQGwPd_o_Lc6nOTBo5BrB1tSs8PJ42XiGpenM,10957
|
|
403
403
|
machineconfig/utils/scheduler.py,sha256=44CASABJg3epccxhAwv2CX7TVgZh6zVy3K4vqHKTuf4,14228
|
|
404
|
-
machineconfig/utils/scheduling.py,sha256=
|
|
404
|
+
machineconfig/utils/scheduling.py,sha256=6x5zLA7sY5gohrEtN6zGrXIqNFasMoyBfwLcOjrjiME,11109
|
|
405
405
|
machineconfig/utils/source_of_truth.py,sha256=ZAnCRltiM07ig--P6g9_6nEAvNFC4X4ERFTVcvpIYsE,764
|
|
406
|
-
machineconfig/utils/ssh.py,sha256=
|
|
406
|
+
machineconfig/utils/ssh.py,sha256=eRNatbyhxnMFSzzEEnbuORiVnQVUiAYVhVohIYuEu-Q,39310
|
|
407
407
|
machineconfig/utils/terminal.py,sha256=IlmOByfQG-vjhaFFxxzU5rWzP5_qUzmalRfuey3PAmc,11801
|
|
408
408
|
machineconfig/utils/tst.py,sha256=6u1GI49NdcpxH2BYGAusNfY5q9G_ytCGVzFM5b6HYpM,674
|
|
409
409
|
machineconfig/utils/upgrade_packages.py,sha256=mSFyKvB3JhHte_x1dtmEgrJZCAXgTUQoaJUSx1OXQ3Y,4145
|
|
@@ -432,8 +432,8 @@ machineconfig/utils/schemas/installer/installer_types.py,sha256=QClRY61QaduBPJoS
|
|
|
432
432
|
machineconfig/utils/schemas/layouts/layout_types.py,sha256=TcqlZdGVoH8htG5fHn1KWXhRdPueAcoyApppZsPAPto,2020
|
|
433
433
|
machineconfig/utils/schemas/repos/repos_types.py,sha256=ECVr-3IVIo8yjmYmVXX2mnDDN1SLSwvQIhx4KDDQHBQ,405
|
|
434
434
|
machineconfig/utils/ssh_utils/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
435
|
-
machineconfig-6.
|
|
436
|
-
machineconfig-6.
|
|
437
|
-
machineconfig-6.
|
|
438
|
-
machineconfig-6.
|
|
439
|
-
machineconfig-6.
|
|
435
|
+
machineconfig-6.54.dist-info/METADATA,sha256=FhrF7ZV12PTSmzsuC6JizwhGhEvBC4r2zrzaX7fvheg,2928
|
|
436
|
+
machineconfig-6.54.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
437
|
+
machineconfig-6.54.dist-info/entry_points.txt,sha256=M0jwN_brZdXWhmNVeXLvdKxfkv8WhhXFZYcuKBA9qnk,418
|
|
438
|
+
machineconfig-6.54.dist-info/top_level.txt,sha256=porRtB8qms8fOIUJgK-tO83_FeH6Bpe12oUVC670teA,14
|
|
439
|
+
machineconfig-6.54.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|