machineconfig 2.1__py3-none-any.whl → 2.3__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/enhanced_command_runner.py +0 -2
- machineconfig/cluster/sessions_managers/layout_types.py +29 -0
- machineconfig/cluster/sessions_managers/wt_local.py +68 -62
- machineconfig/cluster/sessions_managers/wt_local_manager.py +51 -22
- machineconfig/cluster/sessions_managers/wt_remote.py +30 -108
- machineconfig/cluster/sessions_managers/wt_remote_manager.py +14 -11
- machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +33 -37
- machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +22 -17
- machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +59 -10
- machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +16 -14
- machineconfig/cluster/sessions_managers/zellij_local.py +75 -57
- machineconfig/cluster/sessions_managers/zellij_local_manager.py +51 -23
- machineconfig/cluster/sessions_managers/zellij_remote.py +47 -27
- machineconfig/cluster/sessions_managers/zellij_remote_manager.py +13 -12
- machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +14 -10
- machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +31 -15
- machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +47 -21
- machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +1 -1
- machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +8 -7
- machineconfig/cluster/templates/utils.py +0 -35
- machineconfig/jobs/python/check_installations.py +1 -1
- machineconfig/jobs/python_custom_installers/dev/code.py +0 -13
- machineconfig/jobs/python_generic_installers/config.json +1 -1
- machineconfig/profile/create.py +13 -4
- machineconfig/profile/create_hardlinks.py +3 -1
- machineconfig/profile/shell.py +8 -7
- machineconfig/scripts/__init__.py +0 -2
- machineconfig/scripts/linux/devops +6 -4
- machineconfig/scripts/python/ai/generate_files.py +14 -15
- machineconfig/scripts/python/ai/mcinit.py +8 -5
- machineconfig/scripts/python/archive/tmate_conn.py +5 -5
- machineconfig/scripts/python/archive/tmate_start.py +7 -7
- machineconfig/scripts/python/choose_wezterm_theme.py +35 -32
- machineconfig/scripts/python/cloud_copy.py +22 -13
- machineconfig/scripts/python/cloud_mount.py +35 -23
- machineconfig/scripts/python/cloud_repo_sync.py +38 -25
- machineconfig/scripts/python/cloud_sync.py +4 -4
- machineconfig/scripts/python/croshell.py +37 -28
- machineconfig/scripts/python/devops.py +46 -27
- machineconfig/scripts/python/devops_add_identity.py +15 -25
- machineconfig/scripts/python/devops_add_ssh_key.py +7 -7
- machineconfig/scripts/python/devops_backup_retrieve.py +17 -15
- machineconfig/scripts/python/devops_devapps_install.py +26 -20
- machineconfig/scripts/python/devops_update_repos.py +142 -57
- machineconfig/scripts/python/dotfile.py +16 -14
- machineconfig/scripts/python/fire_agents.py +30 -23
- machineconfig/scripts/python/fire_jobs.py +86 -98
- machineconfig/scripts/python/fire_jobs_args_helper.py +84 -0
- machineconfig/scripts/python/fire_jobs_layout_helper.py +66 -0
- machineconfig/scripts/python/ftpx.py +24 -14
- machineconfig/scripts/python/get_zellij_cmd.py +8 -7
- machineconfig/scripts/python/helpers/cloud_helpers.py +33 -28
- machineconfig/scripts/python/helpers/helpers2.py +25 -14
- machineconfig/scripts/python/helpers/helpers4.py +44 -31
- machineconfig/scripts/python/helpers/helpers5.py +1 -1
- machineconfig/scripts/python/helpers/repo_sync_helpers.py +31 -9
- machineconfig/scripts/python/mount_nfs.py +8 -15
- machineconfig/scripts/python/mount_nw_drive.py +10 -5
- machineconfig/scripts/python/mount_ssh.py +8 -6
- machineconfig/scripts/python/repos.py +215 -57
- machineconfig/scripts/python/snapshot.py +0 -1
- machineconfig/scripts/python/start_slidev.py +10 -5
- machineconfig/scripts/python/start_terminals.py +22 -16
- machineconfig/scripts/python/viewer_template.py +0 -1
- machineconfig/scripts/python/wifi_conn.py +49 -76
- machineconfig/scripts/python/wsl_windows_transfer.py +8 -6
- machineconfig/settings/lf/linux/lfrc +1 -0
- machineconfig/setup_linux/web_shortcuts/croshell.sh +5 -0
- machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
- machineconfig/setup_linux/web_shortcuts/ssh.sh +0 -4
- machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +3 -12
- machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +1 -1
- machineconfig/utils/code.py +2 -3
- machineconfig/utils/installer.py +2 -2
- machineconfig/utils/installer_utils/installer_abc.py +2 -4
- machineconfig/utils/installer_utils/installer_class.py +6 -4
- machineconfig/utils/links.py +103 -33
- machineconfig/utils/notifications.py +52 -38
- machineconfig/utils/options.py +14 -21
- machineconfig/utils/path.py +12 -12
- machineconfig/utils/path_reduced.py +239 -200
- machineconfig/utils/procs.py +1 -1
- machineconfig/utils/source_of_truth.py +27 -0
- machineconfig/utils/ssh.py +9 -19
- machineconfig/utils/terminal.py +4 -2
- machineconfig/utils/upgrade_packages.py +91 -0
- machineconfig/utils/utils2.py +1 -2
- machineconfig/utils/utils5.py +23 -11
- machineconfig/utils/ve.py +4 -1
- {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/METADATA +13 -13
- {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/RECORD +105 -121
- machineconfig-2.3.dist-info/entry_points.txt +2 -0
- machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +0 -59
- machineconfig/cluster/sessions_managers/archive/session_managers.py +0 -183
- machineconfig/cluster/sessions_managers/demo_rich_zellij.py +0 -0
- machineconfig/jobs/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/croshell.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/generate_files.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-313.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
- machineconfig/setup_linux/web_shortcuts/all.sh +0 -48
- machineconfig/setup_linux/web_shortcuts/update_system.sh +0 -48
- machineconfig/utils/utils.py +0 -97
- /machineconfig/cluster/{cloud_manager.py → remote/cloud_manager.py} +0 -0
- /machineconfig/cluster/{data_transfer.py → remote/data_transfer.py} +0 -0
- /machineconfig/cluster/{distribute.py → remote/distribute.py} +0 -0
- /machineconfig/cluster/{file_manager.py → remote/file_manager.py} +0 -0
- /machineconfig/cluster/{job_params.py → remote/job_params.py} +0 -0
- /machineconfig/cluster/{loader_runner.py → remote/loader_runner.py} +0 -0
- /machineconfig/cluster/{remote_machine.py → remote/remote_machine.py} +0 -0
- /machineconfig/cluster/{script_execution.py → remote/script_execution.py} +0 -0
- /machineconfig/cluster/{script_notify_upon_completion.py → remote/script_notify_upon_completion.py} +0 -0
- /machineconfig/{cluster/sessions_managers/archive/__init__.py → scripts/python/fire_jobs_streamlit_helper.py} +0 -0
- /machineconfig/setup_linux/web_shortcuts/{tmp.sh → android.sh} +0 -0
- {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/WHEEL +0 -0
- {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/top_level.txt +0 -0
machineconfig/utils/ssh.py
CHANGED
|
@@ -5,6 +5,7 @@ import rich.console
|
|
|
5
5
|
from machineconfig.utils.terminal import Terminal, Response, MACHINE
|
|
6
6
|
from machineconfig.utils.path_reduced import PathExtended, PLike, OPLike
|
|
7
7
|
from machineconfig.utils.utils2 import pprint
|
|
8
|
+
# from machineconfig.utils.ve import get_ve_activate_line
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
@dataclass
|
|
@@ -95,7 +96,6 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
95
96
|
self.ssh.load_system_host_keys()
|
|
96
97
|
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
97
98
|
pprint(dict(host=self.host, hostname=self.hostname, username=self.username, password="***", port=self.port, key_filename=self.sshkey, ve=self.ve), title="SSHing To")
|
|
98
|
-
|
|
99
99
|
sock = paramiko.ProxyCommand(self.proxycommand) if self.proxycommand is not None else None
|
|
100
100
|
try:
|
|
101
101
|
if pwd is None:
|
|
@@ -109,7 +109,6 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
109
109
|
rich.console.Console().print_exception()
|
|
110
110
|
self.pwd = getpass.getpass(f"Enter password for {self.username}@{self.hostname}: ")
|
|
111
111
|
self.ssh.connect(hostname=self.hostname, username=self.username, password=self.pwd, port=self.port, key_filename=self.sshkey, compress=self.compress, sock=sock, allow_agent=False, look_for_keys=False) # type: ignore
|
|
112
|
-
|
|
113
112
|
try:
|
|
114
113
|
self.sftp: Optional[paramiko.SFTPClient] = self.ssh.open_sftp()
|
|
115
114
|
except Exception as err:
|
|
@@ -129,14 +128,6 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
129
128
|
self._remote_machine: Optional[MACHINE] = None
|
|
130
129
|
self.terminal_responses: list[Response] = []
|
|
131
130
|
self.platform = platform
|
|
132
|
-
self.remote_env_cmd = rf"""~/code/machineconfig/{self.ve}/Scripts/Activate.ps1""" if self.get_remote_machine() == "Windows" else rf"""source ~/code/machineconfig/{self.ve}/bin/activate"""
|
|
133
|
-
self.local_env_cmd = rf"""~/code/machineconfig/{self.ve}/Scripts/Activate.ps1""" if self.platform.system() == "Windows" else rf"""source ~/code/machineconfig/{self.ve}/bin/activate""" # works for both cmd and pwsh
|
|
134
|
-
|
|
135
|
-
def __getstate__(self):
|
|
136
|
-
return {attr: self.__getattribute__(attr) for attr in ["username", "hostname", "host", "port", "sshkey", "compress", "pwd", "ve"]}
|
|
137
|
-
|
|
138
|
-
def __setstate__(self, state: dict[str, Any]):
|
|
139
|
-
SSH(**state)
|
|
140
131
|
|
|
141
132
|
def get_remote_machine(self) -> MACHINE:
|
|
142
133
|
if self._remote_machine is None:
|
|
@@ -158,8 +149,8 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
158
149
|
|
|
159
150
|
def get_remote_distro(self):
|
|
160
151
|
if self._remote_distro is None:
|
|
161
|
-
|
|
162
|
-
|
|
152
|
+
res = self.run("""~/.local/bin/uv run --with distro python -c "import distro; print(distro.name(pretty=True))" """)
|
|
153
|
+
self._remote_distro = res.op_if_successfull_or_default() or ""
|
|
163
154
|
return self._remote_distro
|
|
164
155
|
|
|
165
156
|
def restart_computer(self):
|
|
@@ -196,9 +187,7 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
196
187
|
def get_ssh_conn_str(self, cmd: str = ""):
|
|
197
188
|
return "ssh " + (f" -i {self.sshkey}" if self.sshkey else "") + self.get_remote_repr().replace(":", " -p ") + (f" -t {cmd} " if cmd != "" else " ")
|
|
198
189
|
|
|
199
|
-
|
|
200
|
-
def run(self, cmd: str, verbose: bool = True, desc: str = "", strict_err: bool = False, strict_returncode: bool = False, env_prefix: bool = False) -> Response: # most central method.
|
|
201
|
-
cmd = (self.remote_env_cmd + "; " + cmd) if env_prefix else cmd
|
|
190
|
+
def run(self, cmd: str, verbose: bool = True, desc: str = "", strict_err: bool = False, strict_returncode: bool = False) -> Response:
|
|
202
191
|
raw = self.ssh.exec_command(cmd)
|
|
203
192
|
res = Response(stdin=raw[0], stdout=raw[1], stderr=raw[2], cmd=cmd, desc=desc) # type: ignore
|
|
204
193
|
if not verbose:
|
|
@@ -212,15 +201,14 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
212
201
|
assert '"' not in cmd, 'Avoid using `"` in your command. I dont know how to handle this when passing is as command to python in pwsh command.'
|
|
213
202
|
if not return_obj:
|
|
214
203
|
return self.run(
|
|
215
|
-
cmd=f"""
|
|
204
|
+
cmd=f"""uv run --with machineconfig -c "{Terminal.get_header(wdir=None, toolbox=True)}{cmd}\n""" + '"', desc=desc or f"run_py on {self.get_remote_repr()}", verbose=verbose, strict_err=strict_err, strict_returncode=strict_returncode
|
|
216
205
|
)
|
|
217
206
|
assert "obj=" in cmd, "The command sent to run_py must have `obj=` statement if return_obj is set to True"
|
|
218
207
|
source_file = self.run_py(f"""{cmd}\npath = Save.pickle(obj=obj, path=P.tmpfile(suffix='.pkl'))\nprint(path)""", desc=desc, verbose=verbose, strict_err=True, strict_returncode=True).op.split("\n")[-1]
|
|
219
208
|
res = self.copy_to_here(source=source_file, target=PathExtended.tmpfile(suffix=".pkl"))
|
|
220
209
|
import pickle
|
|
221
210
|
|
|
222
|
-
|
|
223
|
-
return pickle.loads(res_bytes)
|
|
211
|
+
return pickle.loads(res.read_bytes())
|
|
224
212
|
|
|
225
213
|
def copy_from_here(self, source: PLike, target: OPLike = None, z: bool = False, r: bool = False, overwrite: bool = False, init: bool = True) -> Union[PathExtended, list[PathExtended]]:
|
|
226
214
|
if init:
|
|
@@ -239,7 +227,9 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
|
|
|
239
227
|
source_list: list[PathExtended] = source_obj.search("*", folders=False, files=True, r=True)
|
|
240
228
|
remote_root = (
|
|
241
229
|
self.run_py(
|
|
242
|
-
f"path=P(r'{PathExtended(target).as_posix()}').expanduser()\n{'path.delete(sure=True)' if overwrite else ''}\nprint(path.create())",
|
|
230
|
+
f"path=P(r'{PathExtended(target).as_posix()}').expanduser()\n{'path.delete(sure=True)' if overwrite else ''}\nprint(path.create())",
|
|
231
|
+
desc=f"Creating Target directory `{PathExtended(target).as_posix()}` @ {self.get_remote_repr()}",
|
|
232
|
+
verbose=False,
|
|
243
233
|
).op
|
|
244
234
|
or ""
|
|
245
235
|
)
|
machineconfig/utils/terminal.py
CHANGED
|
@@ -143,7 +143,8 @@ class Terminal:
|
|
|
143
143
|
def run_script(self, script: str, shell: SHELLS = "default", verbose: bool = False):
|
|
144
144
|
if self.machine == "Linux":
|
|
145
145
|
script = "#!/bin/bash" + "\n" + script # `source` is only available in bash.
|
|
146
|
-
script_file = PathExtended.tmpfile(name="tmp_shell_script", suffix=".ps1" if self.machine == "Windows" else ".sh", folder="tmp_scripts")
|
|
146
|
+
script_file = PathExtended.tmpfile(name="tmp_shell_script", suffix=".ps1" if self.machine == "Windows" else ".sh", folder="tmp_scripts")
|
|
147
|
+
script_file.write_text(script, newline={"Windows": None, "Linux": "\n"}[self.machine])
|
|
147
148
|
if shell == "default":
|
|
148
149
|
if self.machine == "Windows":
|
|
149
150
|
start_cmd = "powershell" # default shell on Windows is cmd which is not very useful. (./source is not available)
|
|
@@ -188,7 +189,8 @@ class Terminal:
|
|
|
188
189
|
{f"cd {wdir}" if wdir is not None else ""}
|
|
189
190
|
{"ipython" if ipython else "python"} {"-i" if interactive else ""} {py_script}
|
|
190
191
|
"""
|
|
191
|
-
|
|
192
|
+
shell_path = PathExtended.tmpfile(name="tmp_shell_script", suffix=".sh" if self.machine == "Linux" else ".ps1", folder="tmp_scripts/shell")
|
|
193
|
+
shell_path.write_text(shell_script)
|
|
192
194
|
if shell is None and self.machine == "Windows":
|
|
193
195
|
shell = "pwsh"
|
|
194
196
|
window = "start" if new_window and self.machine == "Windows" else ""
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generate uv add commands from pyproject.toml dependency groups.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import tomllib
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def generate_uv_add_commands(pyproject_path: Path, output_path: Path) -> None:
|
|
11
|
+
"""
|
|
12
|
+
Generate uv add commands for each dependency group in pyproject.toml.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
pyproject_path: Path to the pyproject.toml file
|
|
16
|
+
output_path: Path where to write the uv add commands
|
|
17
|
+
"""
|
|
18
|
+
# Read pyproject.toml
|
|
19
|
+
with open(pyproject_path, "rb") as f:
|
|
20
|
+
pyproject_data: dict[str, Any] = tomllib.load(f)
|
|
21
|
+
|
|
22
|
+
commands: list[str] = []
|
|
23
|
+
|
|
24
|
+
# Handle main dependencies (no group)
|
|
25
|
+
if "project" in pyproject_data and "dependencies" in pyproject_data["project"]:
|
|
26
|
+
main_deps = pyproject_data["project"]["dependencies"]
|
|
27
|
+
if main_deps:
|
|
28
|
+
# Extract package names without version constraints
|
|
29
|
+
package_names = [extract_package_name(dep) for dep in main_deps]
|
|
30
|
+
commands.append(f"uv add {' '.join(package_names)}")
|
|
31
|
+
|
|
32
|
+
# Handle optional dependencies as groups
|
|
33
|
+
if "project" in pyproject_data and "optional-dependencies" in pyproject_data["project"]:
|
|
34
|
+
optional_deps = pyproject_data["project"]["optional-dependencies"]
|
|
35
|
+
for group_name, deps in optional_deps.items():
|
|
36
|
+
if deps:
|
|
37
|
+
package_names = [extract_package_name(dep) for dep in deps]
|
|
38
|
+
commands.append(f"uv add {' '.join(package_names)} --group {group_name}")
|
|
39
|
+
|
|
40
|
+
# Handle dependency-groups (like dev)
|
|
41
|
+
if "dependency-groups" in pyproject_data:
|
|
42
|
+
dep_groups = pyproject_data["dependency-groups"]
|
|
43
|
+
for group_name, deps in dep_groups.items():
|
|
44
|
+
if deps:
|
|
45
|
+
package_names = [extract_package_name(dep) for dep in deps]
|
|
46
|
+
if group_name == "dev":
|
|
47
|
+
commands.append(f"uv add {' '.join(package_names)} --dev")
|
|
48
|
+
else:
|
|
49
|
+
commands.append(f"uv add {' '.join(package_names)} --group {group_name}")
|
|
50
|
+
|
|
51
|
+
# Write commands to output file
|
|
52
|
+
with open(output_path, "w") as f:
|
|
53
|
+
for command in commands:
|
|
54
|
+
f.write(command + "\n")
|
|
55
|
+
|
|
56
|
+
print(f"Generated {len(commands)} uv add commands in {output_path}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def extract_package_name(dependency_spec: str) -> str:
|
|
60
|
+
"""
|
|
61
|
+
Extract package name from dependency specification.
|
|
62
|
+
|
|
63
|
+
Examples:
|
|
64
|
+
"rich>=14.0.0" -> "rich"
|
|
65
|
+
"requests>=2.32.5" -> "requests"
|
|
66
|
+
"pywin32" -> "pywin32"
|
|
67
|
+
"package[extra]>=1.0" -> "package"
|
|
68
|
+
"""
|
|
69
|
+
# Handle extras like "package[extra]>=1.0" first
|
|
70
|
+
if "[" in dependency_spec:
|
|
71
|
+
dependency_spec = dependency_spec.split("[")[0].strip()
|
|
72
|
+
|
|
73
|
+
# Split on common version operators and take the first part
|
|
74
|
+
for operator in [">=", "<=", "==", "!=", ">", "<", "~=", "===", "@"]:
|
|
75
|
+
if operator in dependency_spec:
|
|
76
|
+
return dependency_spec.split(operator)[0].strip()
|
|
77
|
+
|
|
78
|
+
# Return as-is if no version constraint found
|
|
79
|
+
return dependency_spec.strip()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
if __name__ == "__main__":
|
|
83
|
+
# Example usage
|
|
84
|
+
current_dir = Path.cwd()
|
|
85
|
+
pyproject_file = current_dir / "pyproject.toml"
|
|
86
|
+
output_file = current_dir / "uv_add_commands.txt"
|
|
87
|
+
|
|
88
|
+
if pyproject_file.exists():
|
|
89
|
+
generate_uv_add_commands(pyproject_file, output_file)
|
|
90
|
+
else:
|
|
91
|
+
print(f"pyproject.toml not found at {pyproject_file}")
|
machineconfig/utils/utils2.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
from typing import Optional, Any
|
|
3
|
-
# import time
|
|
4
|
-
# from typing import Callable, Literal, TypeVar, ParamSpec
|
|
5
3
|
|
|
6
4
|
|
|
7
5
|
def randstr(length: int = 10, lower: bool = True, upper: bool = True, digits: bool = True, punctuation: bool = False, safe: bool = False, noun: bool = False) -> str:
|
|
@@ -51,6 +49,7 @@ def read_toml(path: "Path"):
|
|
|
51
49
|
|
|
52
50
|
def pprint(obj: dict[Any, Any], title: str) -> None:
|
|
53
51
|
from rich import inspect
|
|
52
|
+
|
|
54
53
|
inspect(type("TempStruct", (object,), obj)(), value=False, title=title, docs=False, dunder=False, sort=False)
|
|
55
54
|
|
|
56
55
|
|
machineconfig/utils/utils5.py
CHANGED
|
@@ -149,20 +149,25 @@ T2 = TypeVar("T2")
|
|
|
149
149
|
class PrintFunc(Protocol):
|
|
150
150
|
def __call__(self, msg: str) -> Union[NoReturn, None]: ...
|
|
151
151
|
|
|
152
|
+
|
|
152
153
|
def to_pickle(obj: Any, path: Path) -> None:
|
|
153
154
|
import pickle
|
|
155
|
+
|
|
154
156
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
155
157
|
path.write_bytes(pickle.dumps(obj))
|
|
158
|
+
|
|
159
|
+
|
|
156
160
|
def from_pickle(path: Path) -> Any:
|
|
157
161
|
import pickle
|
|
162
|
+
|
|
158
163
|
return pickle.loads(path.read_bytes())
|
|
159
164
|
|
|
160
165
|
|
|
161
166
|
class Cache(Generic[T]): # This class helps to accelrate access to latest data coming from expensive function. The class has two flavours, memory-based and disk-based variants."""
|
|
162
167
|
# source_func: Callable[[], T]
|
|
163
|
-
def __init__(
|
|
164
|
-
|
|
165
|
-
|
|
168
|
+
def __init__(
|
|
169
|
+
self, source_func: Callable[[], T], expire: timedelta, logger: Optional[PrintFunc] = None, path: Optional[Path] = None, saver: Callable[[T, Path], Any] = to_pickle, reader: Callable[[Path], T] = from_pickle, name: Optional[str] = None
|
|
170
|
+
) -> None:
|
|
166
171
|
self.cache: T
|
|
167
172
|
self.source_func = source_func # function which when called returns a fresh object to be frozen.
|
|
168
173
|
self.path: Optional[PathExtended] = PathExtended(path) if path is not None else None # if path is passed, it will function as disk-based flavour.
|
|
@@ -173,12 +178,14 @@ class Cache(Generic[T]): # This class helps to accelrate access to latest data
|
|
|
173
178
|
self.expire = expire
|
|
174
179
|
self.name = name if isinstance(name, str) else str(self.source_func)
|
|
175
180
|
self.last_call_is_fresh = False
|
|
181
|
+
|
|
176
182
|
@property
|
|
177
183
|
def age(self):
|
|
178
184
|
"""Throws AttributeError if called before cache is populated and path doesn't exists"""
|
|
179
185
|
if self.path is None: # memory-based cache.
|
|
180
186
|
return datetime.now() - self.time_produced
|
|
181
187
|
return datetime.now() - datetime.fromtimestamp(self.path.stat().st_mtime)
|
|
188
|
+
|
|
182
189
|
# def __setstate__(self, state: dict[str, Any]) -> None:
|
|
183
190
|
# self.__dict__.update(state)
|
|
184
191
|
# self.path = P.home() / self.path if self.path is not None else self.path
|
|
@@ -228,9 +235,11 @@ class Cache(Generic[T]): # This class helps to accelrate access to latest data
|
|
|
228
235
|
self.cache = self.source_func() # fresh data.
|
|
229
236
|
self.last_call_is_fresh = True
|
|
230
237
|
self.time_produced = datetime.now()
|
|
231
|
-
if self.path is not None:
|
|
238
|
+
if self.path is not None:
|
|
239
|
+
self.save(self.cache, self.path)
|
|
232
240
|
else: # cache exists
|
|
233
|
-
try:
|
|
241
|
+
try:
|
|
242
|
+
age = self.age
|
|
234
243
|
except AttributeError: # path doesn't exist (may be deleted) ==> need to repopulate cache form source_func.
|
|
235
244
|
return self(fresh=True)
|
|
236
245
|
if age > self.expire:
|
|
@@ -243,7 +252,8 @@ class Cache(Generic[T]): # This class helps to accelrate access to latest data
|
|
|
243
252
|
self.cache = self.source_func()
|
|
244
253
|
self.last_call_is_fresh = True
|
|
245
254
|
self.time_produced = datetime.now()
|
|
246
|
-
if self.path is not None:
|
|
255
|
+
if self.path is not None:
|
|
256
|
+
self.save(self.cache, self.path)
|
|
247
257
|
else:
|
|
248
258
|
if self.logger:
|
|
249
259
|
self.logger(f"""
|
|
@@ -252,15 +262,17 @@ class Cache(Generic[T]): # This class helps to accelrate access to latest data
|
|
|
252
262
|
⏱️ Lag = {age}
|
|
253
263
|
════════════════════════════════════════════════════════""")
|
|
254
264
|
return self.cache
|
|
265
|
+
|
|
255
266
|
@staticmethod
|
|
256
|
-
def as_decorator(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def decorator(source_func: Callable[[], T2]) -> Cache['T2']:
|
|
267
|
+
def as_decorator(
|
|
268
|
+
expire: timedelta, logger: Optional[PrintFunc] = None, path: Optional[Path] = None, saver: Callable[[T2, Path], Any] = to_pickle, reader: Callable[[Path], T2] = from_pickle, name: Optional[str] = None
|
|
269
|
+
): # -> Callable[..., 'Cache[T2]']:
|
|
270
|
+
def decorator(source_func: Callable[[], T2]) -> Cache["T2"]:
|
|
261
271
|
res = Cache(source_func=source_func, expire=expire, logger=logger, path=path, name=name, reader=reader, saver=saver)
|
|
262
272
|
return res
|
|
273
|
+
|
|
263
274
|
return decorator
|
|
275
|
+
|
|
264
276
|
def from_cloud(self, cloud: str, rel2home: bool = True, root: Optional[str] = None):
|
|
265
277
|
assert self.path is not None
|
|
266
278
|
exists = self.path.exists()
|
machineconfig/utils/ve.py
CHANGED
|
@@ -13,7 +13,10 @@ def get_ve_path_and_ipython_profile(init_path: PathExtended) -> tuple[Optional[s
|
|
|
13
13
|
if tmp.joinpath(".ve.ini").exists():
|
|
14
14
|
ini = read_ini(tmp.joinpath(".ve.ini"))
|
|
15
15
|
if ve_path is None:
|
|
16
|
-
|
|
16
|
+
try:
|
|
17
|
+
ve_path = ini["specs"]["ve_path"]
|
|
18
|
+
except KeyError:
|
|
19
|
+
raise KeyError(f".ve.ini file at {tmp.joinpath('.ve.ini')} is missing the 've_path' key in the 'specs' section.")
|
|
17
20
|
print(f"🐍 Using Virtual Environment: {ve_path}. This is based on this file {tmp.joinpath('.ve.ini')}")
|
|
18
21
|
if ipy_profile is None:
|
|
19
22
|
ipy_profile = ini["specs"]["ipy_profile"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: machineconfig
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3
|
|
4
4
|
Summary: Dotfiles management package
|
|
5
5
|
Author-email: Alex Al-Saffar <programmer@usa.com>
|
|
6
6
|
License: Apache 2.0
|
|
@@ -11,26 +11,26 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
12
12
|
Requires-Python: >=3.13
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
|
-
Requires-Dist:
|
|
14
|
+
Requires-Dist: cryptography>=44.0.2
|
|
15
|
+
Requires-Dist: fire>=0.7.0
|
|
16
|
+
Requires-Dist: gitpython>=3.1.44
|
|
17
|
+
Requires-Dist: joblib>=1.5.2
|
|
18
|
+
Requires-Dist: markdown>=3.9
|
|
15
19
|
Requires-Dist: paramiko>=3.5.1
|
|
16
20
|
Requires-Dist: psutil>=7.0.0
|
|
17
|
-
Requires-Dist: fire>=0.7.0
|
|
18
21
|
Requires-Dist: pydantic>=2.11.3
|
|
19
|
-
Requires-Dist: gitpython>=3.1.44
|
|
20
22
|
Requires-Dist: pyfzf>=0.3.1
|
|
21
|
-
Requires-Dist:
|
|
23
|
+
Requires-Dist: pyjson5>=1.6.9
|
|
22
24
|
Requires-Dist: pytz>=2025.2
|
|
23
|
-
Requires-Dist: tomli>=2.2.1
|
|
24
|
-
Requires-Dist: toml>=0.10.2
|
|
25
25
|
Requires-Dist: pyyaml>=6.0.2
|
|
26
|
-
Requires-Dist: pyjson5>=1.6.9
|
|
27
|
-
Requires-Dist: requests>=2.32.5
|
|
28
|
-
Requires-Dist: tqdm>=4.67.1
|
|
29
|
-
Requires-Dist: joblib>=1.5.2
|
|
30
26
|
Requires-Dist: randomname>=0.2.1
|
|
31
|
-
Requires-Dist:
|
|
27
|
+
Requires-Dist: rclone-python>=0.1.23
|
|
28
|
+
Requires-Dist: requests>=2.32.5
|
|
29
|
+
Requires-Dist: rich>=14.0.0
|
|
32
30
|
Requires-Dist: tenacity>=9.1.2
|
|
33
|
-
Requires-Dist:
|
|
31
|
+
Requires-Dist: toml>=0.10.2
|
|
32
|
+
Requires-Dist: tomli>=2.2.1
|
|
33
|
+
Requires-Dist: tqdm>=4.67.1
|
|
34
34
|
Provides-Extra: windows
|
|
35
35
|
Requires-Dist: pywin32; extra == "windows"
|
|
36
36
|
Provides-Extra: docs
|