machineconfig 1.97__py3-none-any.whl â 2.0__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/cloud_manager.py +22 -26
- machineconfig/cluster/data_transfer.py +2 -2
- machineconfig/cluster/distribute.py +0 -2
- machineconfig/cluster/file_manager.py +4 -4
- machineconfig/cluster/job_params.py +1 -1
- machineconfig/cluster/loader_runner.py +8 -8
- machineconfig/cluster/remote_machine.py +4 -4
- machineconfig/cluster/script_execution.py +2 -2
- machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +1 -1
- machineconfig/cluster/sessions_managers/enhanced_command_runner.py +23 -23
- machineconfig/cluster/sessions_managers/wt_local.py +78 -76
- machineconfig/cluster/sessions_managers/wt_local_manager.py +91 -91
- machineconfig/cluster/sessions_managers/wt_remote.py +39 -39
- machineconfig/cluster/sessions_managers/wt_remote_manager.py +94 -91
- machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +56 -54
- machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +49 -49
- machineconfig/cluster/sessions_managers/wt_utils/remote_executor.py +18 -18
- machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +42 -42
- machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +36 -36
- machineconfig/cluster/sessions_managers/zellij_local.py +43 -46
- machineconfig/cluster/sessions_managers/zellij_local_manager.py +139 -120
- machineconfig/cluster/sessions_managers/zellij_remote.py +35 -35
- machineconfig/cluster/sessions_managers/zellij_remote_manager.py +33 -33
- machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +15 -15
- machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +25 -26
- machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +49 -49
- machineconfig/cluster/sessions_managers/zellij_utils/remote_executor.py +5 -5
- machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +15 -15
- machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +11 -11
- machineconfig/cluster/templates/utils.py +3 -3
- machineconfig/jobs/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/jobs/python/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/jobs/python/__pycache__/python_ve_symlink.cpython-311.pyc +0 -0
- machineconfig/jobs/python/check_installations.py +8 -9
- machineconfig/jobs/python/python_cargo_build_share.py +2 -2
- machineconfig/jobs/python/vscode/link_ve.py +7 -7
- machineconfig/jobs/python/vscode/select_interpreter.py +7 -7
- machineconfig/jobs/python/vscode/sync_code.py +5 -5
- machineconfig/jobs/python_custom_installers/archive/ngrok.py +2 -2
- machineconfig/jobs/python_custom_installers/dev/aider.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/alacritty.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/brave.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +5 -5
- machineconfig/jobs/python_custom_installers/dev/code.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/cursor.py +9 -9
- machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +4 -4
- machineconfig/jobs/python_custom_installers/dev/espanso.py +4 -4
- machineconfig/jobs/python_custom_installers/dev/goes.py +4 -4
- machineconfig/jobs/python_custom_installers/dev/lvim.py +4 -4
- machineconfig/jobs/python_custom_installers/dev/nerdfont.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/redis.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/wezterm.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/winget.py +27 -27
- machineconfig/jobs/python_custom_installers/docker.py +3 -3
- machineconfig/jobs/python_custom_installers/gh.py +7 -7
- machineconfig/jobs/python_custom_installers/hx.py +1 -1
- machineconfig/jobs/python_custom_installers/warp-cli.py +3 -3
- machineconfig/jobs/python_generic_installers/config.json +412 -389
- machineconfig/jobs/python_windows_installers/dev/config.json +1 -1
- machineconfig/logger.py +50 -0
- machineconfig/profile/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/profile/__pycache__/create.cpython-311.pyc +0 -0
- machineconfig/profile/__pycache__/shell.cpython-311.pyc +0 -0
- machineconfig/profile/create.py +23 -16
- machineconfig/profile/create_hardlinks.py +8 -8
- machineconfig/profile/shell.py +41 -37
- machineconfig/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/linux/devops +2 -2
- machineconfig/scripts/linux/fire +1 -0
- machineconfig/scripts/linux/fire_agents +0 -1
- machineconfig/scripts/linux/mcinit +1 -1
- machineconfig/scripts/python/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/croshell.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_agents.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_jobs.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/repos.cpython-311.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/init.cpython-311.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-311.pyc +0 -0
- machineconfig/scripts/python/ai/chatmodes/Thinking-Beast-Mode.chatmode.md +337 -0
- machineconfig/scripts/python/ai/chatmodes/Ultimate-Transparent-Thinking-Beast-Mode.chatmode.md +644 -0
- machineconfig/scripts/python/ai/chatmodes/deepResearch.chatmode.md +81 -0
- machineconfig/scripts/python/ai/configs/.gemini/settings.json +81 -0
- machineconfig/scripts/python/ai/instructions/python/dev.instructions.md +45 -0
- machineconfig/scripts/python/ai/mcinit.py +103 -0
- machineconfig/scripts/python/ai/prompts/allLintersAndTypeCheckers.prompt.md +5 -0
- machineconfig/scripts/python/ai/prompts/research-report-skeleton.prompt.md +38 -0
- machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +47 -0
- machineconfig/scripts/python/archive/tmate_conn.py +5 -5
- machineconfig/scripts/python/archive/tmate_start.py +3 -3
- machineconfig/scripts/python/choose_wezterm_theme.py +2 -2
- machineconfig/scripts/python/cloud_copy.py +19 -18
- machineconfig/scripts/python/cloud_mount.py +9 -7
- machineconfig/scripts/python/cloud_repo_sync.py +11 -11
- machineconfig/scripts/python/cloud_sync.py +1 -1
- machineconfig/scripts/python/croshell.py +14 -14
- machineconfig/scripts/python/devops.py +6 -6
- machineconfig/scripts/python/devops_add_identity.py +8 -6
- machineconfig/scripts/python/devops_add_ssh_key.py +18 -18
- machineconfig/scripts/python/devops_backup_retrieve.py +13 -13
- machineconfig/scripts/python/devops_devapps_install.py +3 -3
- machineconfig/scripts/python/devops_update_repos.py +1 -1
- machineconfig/scripts/python/dotfile.py +2 -2
- machineconfig/scripts/python/fire_agents.py +183 -41
- machineconfig/scripts/python/fire_jobs.py +17 -17
- machineconfig/scripts/python/ftpx.py +2 -2
- machineconfig/scripts/python/gh_models.py +94 -94
- machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/cloud_helpers.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers2.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/cloud_helpers.py +3 -3
- machineconfig/scripts/python/helpers/helpers2.py +1 -1
- machineconfig/scripts/python/helpers/helpers4.py +8 -6
- machineconfig/scripts/python/helpers/helpers5.py +7 -7
- machineconfig/scripts/python/helpers/repo_sync_helpers.py +1 -1
- machineconfig/scripts/python/mount_nfs.py +3 -2
- machineconfig/scripts/python/mount_nw_drive.py +4 -4
- machineconfig/scripts/python/mount_ssh.py +3 -2
- machineconfig/scripts/python/repos.py +8 -8
- machineconfig/scripts/python/scheduler.py +1 -1
- machineconfig/scripts/python/start_slidev.py +8 -7
- machineconfig/scripts/python/start_terminals.py +1 -1
- machineconfig/scripts/python/viewer.py +40 -40
- machineconfig/scripts/python/wifi_conn.py +65 -66
- machineconfig/scripts/python/wsl_windows_transfer.py +1 -1
- machineconfig/scripts/windows/mcinit.ps1 +1 -1
- machineconfig/settings/linters/.ruff.toml +2 -2
- machineconfig/settings/shells/ipy/profiles/default/startup/playext.py +71 -71
- machineconfig/settings/shells/wt/settings.json +8 -8
- machineconfig/setup_linux/web_shortcuts/tmp.sh +2 -0
- machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +10 -7
- machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +9 -7
- machineconfig/utils/ai/browser_user_wrapper.py +5 -5
- machineconfig/utils/ai/generate_file_checklist.py +11 -12
- machineconfig/utils/ai/url2md.py +1 -1
- machineconfig/utils/cloud/onedrive/setup_oauth.py +4 -4
- machineconfig/utils/cloud/onedrive/transaction.py +129 -129
- machineconfig/utils/code.py +13 -6
- machineconfig/utils/installer.py +51 -53
- machineconfig/utils/installer_utils/installer_abc.py +21 -10
- machineconfig/utils/installer_utils/installer_class.py +42 -16
- machineconfig/utils/io_save.py +3 -15
- machineconfig/utils/options.py +10 -3
- machineconfig/utils/path.py +5 -0
- machineconfig/utils/path_reduced.py +201 -149
- machineconfig/utils/procs.py +23 -23
- machineconfig/utils/scheduling.py +11 -12
- machineconfig/utils/ssh.py +270 -0
- machineconfig/utils/terminal.py +180 -0
- machineconfig/utils/utils.py +1 -2
- machineconfig/utils/utils2.py +43 -0
- machineconfig/utils/utils5.py +163 -34
- machineconfig/utils/ve.py +2 -2
- {machineconfig-1.97.dist-info â machineconfig-2.0.dist-info}/METADATA +13 -8
- {machineconfig-1.97.dist-info â machineconfig-2.0.dist-info}/RECORD +163 -149
- machineconfig/cluster/self_ssh.py +0 -57
- machineconfig/scripts/python/ai/init.py +0 -56
- machineconfig/scripts/python/ai/rules/python/dev.md +0 -31
- {machineconfig-1.97.dist-info â machineconfig-2.0.dist-info}/WHEEL +0 -0
- {machineconfig-1.97.dist-info â machineconfig-2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
|
|
2
|
+
from machineconfig.utils.path_reduced import P, OPLike
|
|
3
|
+
import subprocess
|
|
4
|
+
from typing import Any, BinaryIO, Optional, Union
|
|
5
|
+
import platform
|
|
6
|
+
import sys
|
|
7
|
+
import os
|
|
8
|
+
from typing import Literal, TypeAlias
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
SHELLS: TypeAlias = Literal["default", "cmd", "powershell", "pwsh", "bash"] # pwsh.exe is PowerShell (community) and powershell.exe is Windows Powershell (msft)
|
|
12
|
+
CONSOLE: TypeAlias = Literal["wt", "cmd"]
|
|
13
|
+
MACHINE: TypeAlias = Literal["Windows", "Linux", "Darwin"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class STD:
|
|
18
|
+
stdin: str
|
|
19
|
+
stdout: str
|
|
20
|
+
stderr: str
|
|
21
|
+
returncode: int
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Response:
|
|
25
|
+
@staticmethod
|
|
26
|
+
def from_completed_process(cp: subprocess.CompletedProcess[str]):
|
|
27
|
+
resp = Response(cmd=cp.args)
|
|
28
|
+
resp.output.stdout = cp.stdout
|
|
29
|
+
resp.output.stderr = cp.stderr
|
|
30
|
+
resp.output.returncode = cp.returncode
|
|
31
|
+
return resp
|
|
32
|
+
def __init__(self, stdin: Optional[BinaryIO] = None, stdout: Optional[BinaryIO] = None, stderr: Optional[BinaryIO] = None, cmd: Optional[str] = None, desc: str = ""):
|
|
33
|
+
self.std = dict(stdin=stdin, stdout=stdout, stderr=stderr)
|
|
34
|
+
self.output = STD(stdin="", stdout="", stderr="", returncode=0)
|
|
35
|
+
self.input = cmd
|
|
36
|
+
self.desc = desc # input command
|
|
37
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Optional[str]:
|
|
38
|
+
_ = args, kwargs
|
|
39
|
+
return self.op.rstrip() if type(self.op) is str else None
|
|
40
|
+
@property
|
|
41
|
+
def op(self) -> str: return self.output.stdout
|
|
42
|
+
@property
|
|
43
|
+
def ip(self) -> str: return self.output.stdin
|
|
44
|
+
@property
|
|
45
|
+
def err(self) -> str: return self.output.stderr
|
|
46
|
+
@property
|
|
47
|
+
def returncode(self) -> int: return self.output.returncode
|
|
48
|
+
def op2path(self, strict_returncode: bool = True, strict_err: bool = False) -> Union[P, None]:
|
|
49
|
+
if self.is_successful(strict_returcode=strict_returncode, strict_err=strict_err): return P(self.op.rstrip())
|
|
50
|
+
return None
|
|
51
|
+
def op_if_successfull_or_default(self, strict_returcode: bool = True, strict_err: bool = False) -> Optional[str]: return self.op if self.is_successful(strict_returcode=strict_returcode, strict_err=strict_err) else None
|
|
52
|
+
def is_successful(self, strict_returcode: bool = True, strict_err: bool = False) -> bool:
|
|
53
|
+
return ((self.returncode in {0, None}) if strict_returcode else True) and (self.err == "" if strict_err else True)
|
|
54
|
+
def capture(self):
|
|
55
|
+
for key in ["stdin", "stdout", "stderr"]:
|
|
56
|
+
val: Optional[BinaryIO] = self.std[key]
|
|
57
|
+
if val is not None and val.readable():
|
|
58
|
+
self.output.__dict__[key] = val.read().decode().rstrip()
|
|
59
|
+
return self
|
|
60
|
+
def print_if_unsuccessful(self, desc: str = "TERMINAL CMD", strict_err: bool = False, strict_returncode: bool = False, assert_success: bool = False):
|
|
61
|
+
success = self.is_successful(strict_err=strict_err, strict_returcode=strict_returncode)
|
|
62
|
+
if assert_success: assert success, self.print(capture=False, desc=desc)
|
|
63
|
+
if success:
|
|
64
|
+
print(f"â
{desc} completed successfully")
|
|
65
|
+
else:
|
|
66
|
+
self.print(capture=False, desc=desc)
|
|
67
|
+
return self
|
|
68
|
+
def print(self, desc: str = "TERMINAL CMD", capture: bool = True):
|
|
69
|
+
if capture: self.capture()
|
|
70
|
+
from rich import console
|
|
71
|
+
con = console.Console()
|
|
72
|
+
from rich.panel import Panel
|
|
73
|
+
from rich.text import Text # from rich.syntax import Syntax; syntax = Syntax(my_code, "python", theme="monokai", line_numbers=True)
|
|
74
|
+
tmp1 = Text("đĨ Input Command:\n")
|
|
75
|
+
tmp1.stylize("u bold blue")
|
|
76
|
+
tmp2 = Text("\nđ¤ Terminal Response:\n")
|
|
77
|
+
tmp2.stylize("u bold blue")
|
|
78
|
+
list_str = [f"{f' {idx} - {key} '}".center(40, "â") + f"\n{val}" for idx, (key, val) in enumerate(self.output.__dict__.items())]
|
|
79
|
+
txt = tmp1 + Text(str(self.input), style="white") + tmp2 + Text("\n".join(list_str), style="white")
|
|
80
|
+
con.print(Panel(txt, title=f"đĨī¸ {self.desc}", subtitle=f"đ {desc}", width=150, style="bold cyan on black"))
|
|
81
|
+
return self
|
|
82
|
+
|
|
83
|
+
# DEPRECATED: Use subprocess.run directly instead of Terminal class.
|
|
84
|
+
# The Terminal class has been replaced with inline subprocess calls to underlying primitives.
|
|
85
|
+
# This file is kept for reference but should not be used.
|
|
86
|
+
|
|
87
|
+
class Terminal:
|
|
88
|
+
def __init__(self, stdout: Optional[int] = subprocess.PIPE, stderr: Optional[int] = subprocess.PIPE, stdin: Optional[int] = subprocess.PIPE, elevated: bool = False):
|
|
89
|
+
self.machine: str = platform.system()
|
|
90
|
+
self.elevated: bool = elevated
|
|
91
|
+
self.stdout = stdout
|
|
92
|
+
self.stderr = stderr
|
|
93
|
+
self.stdin = stdin
|
|
94
|
+
# def set_std_system(self): self.stdout = sys.stdout; self.stderr = sys.stderr; self.stdin = sys.stdin
|
|
95
|
+
def set_std_pipe(self):
|
|
96
|
+
self.stdout = subprocess.PIPE
|
|
97
|
+
self.stderr = subprocess.PIPE
|
|
98
|
+
self.stdin = subprocess.PIPE
|
|
99
|
+
def set_std_null(self):
|
|
100
|
+
self.stdout, self.stderr, self.stdin = subprocess.DEVNULL, subprocess.DEVNULL, subprocess.DEVNULL # Equivalent to `echo 'foo' &> /dev/null`
|
|
101
|
+
def run(self, *cmds: str, shell: Optional[SHELLS] = "default", check: bool = False, ip: Optional[str] = None) -> Response: # Runs SYSTEM commands like subprocess.run
|
|
102
|
+
"""Blocking operation. Thus, if you start a shell via this method, it will run in the main and won't stop until you exit manually IF stdin is set to sys.stdin, otherwise it will run and close quickly. Other combinations of stdin, stdout can lead to funny behaviour like no output but accept input or opposite.
|
|
103
|
+
* This method is short for: res = subprocess.run("powershell command", capture_output=True, shell=True, text=True) and unlike os.system(cmd), subprocess.run(cmd) gives much more control over the output and input.
|
|
104
|
+
* `shell=True` loads up the profile of the shell called so more specific commands can be run. Importantly, on Windows, the `start` command becomes availalbe and new windows can be launched.
|
|
105
|
+
* `capture_output` prevents the stdout to redirect to the stdout of the script automatically, instead it will be stored in the Response object returned. # `capture_output=True` same as `stdout=subprocess.PIPE, stderr=subprocess.PIPE`"""
|
|
106
|
+
my_list = list(cmds) # `subprocess.Popen` (process open) is the most general command. Used here to create asynchronous job. `subprocess.run` is a thin wrapper around Popen that makes it wait until it finishes the task. `suprocess.call` is an archaic command for pre-Python-3.5.
|
|
107
|
+
if self.machine == "Windows" and shell in {"powershell", "pwsh"}: my_list = [shell, "-Command"] + my_list # alternatively, one can run "cmd"
|
|
108
|
+
if self.elevated is False or self.is_user_admin():
|
|
109
|
+
resp: subprocess.CompletedProcess[str] = subprocess.run(my_list, stderr=self.stderr, stdin=self.stdin, stdout=self.stdout, text=True, shell=True, check=check, input=ip)
|
|
110
|
+
else:
|
|
111
|
+
resp = __import__("ctypes").windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)
|
|
112
|
+
return Response.from_completed_process(resp)
|
|
113
|
+
def run_script(self, script: str, shell: SHELLS = "default", verbose: bool = False):
|
|
114
|
+
if self.machine == "Linux": script = "#!/bin/bash" + "\n" + script # `source` is only available in bash.
|
|
115
|
+
script_file = P.tmpfile(name="tmp_shell_script", suffix=".ps1" if self.machine == "Windows" else ".sh", folder="tmp_scripts").write_text(script, newline={"Windows": None, "Linux": "\n"}[self.machine])
|
|
116
|
+
if shell == "default":
|
|
117
|
+
if self.machine == "Windows":
|
|
118
|
+
start_cmd = "powershell" # default shell on Windows is cmd which is not very useful. (./source is not available)
|
|
119
|
+
full_command: Union[list[str], str] = [start_cmd, str(script_file)] # shell=True will cause this to be a string anyway (with space separation)
|
|
120
|
+
else:
|
|
121
|
+
start_cmd = "bash"
|
|
122
|
+
full_command = f"{start_cmd} {script_file}" # full_command = [start_cmd, str(script_file)]
|
|
123
|
+
else: full_command = f"{shell} {script_file}" # full_command = [shell, str(tmp_file)]
|
|
124
|
+
if verbose:
|
|
125
|
+
desc="Script to be executed:"
|
|
126
|
+
if platform.system() == "Windows": lexer = "powershell"
|
|
127
|
+
elif platform.system() == "Linux": lexer = "sh"
|
|
128
|
+
elif platform.system() == "Darwin": lexer = "sh" # macOS uses similar shell to Linux
|
|
129
|
+
else: raise NotImplementedError(f"Platform {platform.system()} not supported.")
|
|
130
|
+
from rich.console import Console
|
|
131
|
+
from rich.panel import Panel
|
|
132
|
+
from rich.syntax import Syntax
|
|
133
|
+
import rich.progress as pb
|
|
134
|
+
console = Console()
|
|
135
|
+
console.print(Panel(Syntax(code=script, lexer=lexer), title=f"đ {desc}"), style="bold red")
|
|
136
|
+
with pb.Progress(transient=True) as progress:
|
|
137
|
+
_task = progress.add_task(f"Running Script @ {script_file}", total=None)
|
|
138
|
+
resp = subprocess.run(full_command, stderr=self.stderr, stdin=self.stdin, stdout=self.stdout, text=True, shell=True, check=False)
|
|
139
|
+
else: resp = subprocess.run(full_command, stderr=self.stderr, stdin=self.stdin, stdout=self.stdout, text=True, shell=True, check=False)
|
|
140
|
+
return Response.from_completed_process(resp)
|
|
141
|
+
def run_py(self, script: str, wdir: OPLike = None, interactive: bool = True, ipython: bool = True, shell: Optional[str] = None, terminal: str = "", new_window: bool = True, header: bool = True): # async run, since sync run is meaningless.
|
|
142
|
+
script = (Terminal.get_header(wdir=wdir, toolbox=True) if header else "") + script + ("\nDisplayData.set_pandas_auto_width()\n" if terminal in {"wt", "powershell", "pwsh"} else "")
|
|
143
|
+
py_script = P.tmpfile(name="tmp_python_script", suffix=".py", folder="tmp_scripts/terminal")
|
|
144
|
+
py_script.write_text(f"""print(r'''{script}''')""" + "\n" + script)
|
|
145
|
+
print(f"""đ [ASYNC PYTHON SCRIPT] Script URI:
|
|
146
|
+
{py_script.absolute().as_uri()}""")
|
|
147
|
+
print("Script to be executed asyncronously: ", py_script.absolute().as_uri())
|
|
148
|
+
shell_script = f"""
|
|
149
|
+
{f'cd {wdir}' if wdir is not None else ''}
|
|
150
|
+
{'ipython' if ipython else 'python'} {'-i' if interactive else ''} {py_script}
|
|
151
|
+
"""
|
|
152
|
+
shell_script = P.tmpfile(name="tmp_shell_script", suffix=".sh" if self.machine == "Linux" else ".ps1", folder="tmp_scripts/shell").write_text(shell_script)
|
|
153
|
+
if shell is None and self.machine == "Windows": shell = "pwsh"
|
|
154
|
+
window = "start" if new_window and self.machine == "Windows" else ""
|
|
155
|
+
os.system(f"{window} {terminal} {shell} {shell_script}")
|
|
156
|
+
@staticmethod
|
|
157
|
+
def is_user_admin() -> bool: # adopted from: https://stackoverflow.com/questions/19672352/how-to-run-script-with-elevated-privilege-on-windows"""
|
|
158
|
+
if os.name == 'nt':
|
|
159
|
+
try: return __import__("ctypes").windll.shell32.IsUserAnAdmin()
|
|
160
|
+
except Exception:
|
|
161
|
+
import traceback
|
|
162
|
+
traceback.print_exc()
|
|
163
|
+
print("Admin check failed, assuming not an admin.")
|
|
164
|
+
return False
|
|
165
|
+
else:
|
|
166
|
+
return os.getuid() == 0 # Check for root on Posix
|
|
167
|
+
# @staticmethod
|
|
168
|
+
# def run_as_admin(file: PLike, params: Any, wait: bool = False):
|
|
169
|
+
# proce_info = install_n_import(library="win32com", package="pywin32", fromlist=["shell.shell.ShellExecuteEx"]).shell.shell.ShellExecuteEx(lpVerb='runas', lpFile=file, lpParameters=params)
|
|
170
|
+
# # TODO update PATH for this to take effect immediately.
|
|
171
|
+
# if wait: time.sleep(1)
|
|
172
|
+
# return proce_info
|
|
173
|
+
|
|
174
|
+
@staticmethod
|
|
175
|
+
def get_header(wdir: OPLike, toolbox: bool): return f"""
|
|
176
|
+
# >> Code prepended
|
|
177
|
+
{"from crocodile.toolbox import *" if toolbox else "# No toolbox import."}
|
|
178
|
+
{'''sys.path.insert(0, r'{wdir}') ''' if wdir is not None else "# No path insertion."}
|
|
179
|
+
# >> End of header, start of script passed
|
|
180
|
+
"""
|
machineconfig/utils/utils.py
CHANGED
|
@@ -3,7 +3,6 @@ Utils
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from machineconfig.utils.path_reduced import P as PathExtended
|
|
6
|
-
# import crocodile.environment as env
|
|
7
6
|
import machineconfig
|
|
8
7
|
from machineconfig.utils.options import check_tool_exists, choose_cloud_interactively, choose_multiple_options, choose_one_option, choose_ssh_host, display_options
|
|
9
8
|
from rich.console import Console
|
|
@@ -50,7 +49,7 @@ DEFAULTS_PATH = PathExtended.home().joinpath("dotfiles/machineconfig/defaults.in
|
|
|
50
49
|
|
|
51
50
|
|
|
52
51
|
|
|
53
|
-
def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool
|
|
52
|
+
def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool) -> bool:
|
|
54
53
|
dotfiles_path = str(PathExtended.home().joinpath("dotfiles"))
|
|
55
54
|
from git import Repo
|
|
56
55
|
repo = Repo(path=dotfiles_path)
|
machineconfig/utils/utils2.py
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Optional, Any
|
|
5
|
+
# import time
|
|
6
|
+
# from typing import Callable, Literal, TypeVar, ParamSpec
|
|
7
|
+
|
|
5
8
|
|
|
6
9
|
def randstr(length: int = 10, lower: bool = True, upper: bool = True, digits: bool = True, punctuation: bool = False, safe: bool = False, noun: bool = False) -> str:
|
|
7
10
|
if safe:
|
|
@@ -40,3 +43,43 @@ def pprint(obj: dict[Any, Any], title: str) -> None:
|
|
|
40
43
|
inspect(type("TempStruct", (object,), obj)(), value=False, title=title, docs=False, dunder=False, sort=False)
|
|
41
44
|
def get_repr(obj: dict[Any, Any], sep: str = "\n", justify: int = 15, quotes: bool = False):
|
|
42
45
|
return sep.join([f"{key:>{justify}} = {repr(val) if quotes else val}" for key, val in obj.items()])
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# T = TypeVar('T')
|
|
49
|
+
# PS = ParamSpec('PS')
|
|
50
|
+
|
|
51
|
+
# class RepeatUntilNoException:
|
|
52
|
+
# """
|
|
53
|
+
# Repeat function calling if it raised an exception and/or exceeded the timeout, for a maximum of `retry` times.
|
|
54
|
+
# * Alternative: `https://github.com/jd/tenacity`
|
|
55
|
+
# """
|
|
56
|
+
# def __init__(self, retry: int, sleep: float, timeout: Optional[float] = None, scaling: Literal["linear", "exponential"] = "exponential"):
|
|
57
|
+
# self.retry = retry
|
|
58
|
+
# self.sleep = sleep
|
|
59
|
+
# self.timeout = timeout
|
|
60
|
+
# self.scaling: Literal["linear", "exponential"] = scaling
|
|
61
|
+
# def __call__(self, func: Callable[PS, T]) -> Callable[PS, T]:
|
|
62
|
+
# from functools import wraps
|
|
63
|
+
# if self.timeout is not None:
|
|
64
|
+
# import warpt_time_decorator
|
|
65
|
+
# func = wrapt_timeout_decorator.timeout(self.timeout)(func)
|
|
66
|
+
# @wraps(wrapped=func)
|
|
67
|
+
# def wrapper(*args: PS.args, **kwargs: PS.kwargs):
|
|
68
|
+
# t0 = time.time()
|
|
69
|
+
# for idx in range(self.retry):
|
|
70
|
+
# try:
|
|
71
|
+
# return func(*args, **kwargs)
|
|
72
|
+
# except Exception as ex:
|
|
73
|
+
# match self.scaling:
|
|
74
|
+
# case "linear":
|
|
75
|
+
# sleep_time = self.sleep * (idx + 1)
|
|
76
|
+
# case "exponential":
|
|
77
|
+
# sleep_time = self.sleep * (idx + 1)**2
|
|
78
|
+
# print(f"""đĨ [RETRY] Function {func.__name__} call failed with error:
|
|
79
|
+
# {ex}
|
|
80
|
+
# Retry count: {idx}/{self.retry}. Sleeping for {sleep_time} seconds.
|
|
81
|
+
# Total elapsed time: {time.time() - t0:0.1f} seconds.""")
|
|
82
|
+
# print(f"""đĨ Robust call of `{func}` failed with ```{ex}```.\nretrying {idx}/{self.retry} more times after sleeping for {sleep_time} seconds.\nTotal wait time so far {time.time() - t0: 0.1f} seconds.""")
|
|
83
|
+
# time.sleep(sleep_time)
|
|
84
|
+
# raise RuntimeError(f"đĨ Robust call failed after {self.retry} retries and total wait time of {time.time() - t0: 0.1f} seconds.\n{func=}\n{args=}\n{kwargs=}")
|
|
85
|
+
# return wrapper
|
machineconfig/utils/utils5.py
CHANGED
|
@@ -1,39 +1,52 @@
|
|
|
1
1
|
|
|
2
|
-
from typing import Callable, Optional, Union, Any
|
|
3
|
-
|
|
4
|
-
from machineconfig.utils.utils2 import randstr, get_repr
|
|
2
|
+
from typing import Callable, Optional, Union, Any, NoReturn, TypeVar, Protocol, List
|
|
3
|
+
import logging
|
|
5
4
|
import time
|
|
6
|
-
from datetime import datetime, timezone
|
|
5
|
+
from datetime import datetime, timezone, timedelta
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LoggerTemplate(Protocol):
|
|
9
|
+
handlers: List[logging.Handler]
|
|
10
|
+
def debug(self, msg: str) -> None:
|
|
11
|
+
pass # 10
|
|
12
|
+
def info(self, msg: str) -> None:
|
|
13
|
+
pass # 20
|
|
14
|
+
def warning(self, msg: str) -> None:
|
|
15
|
+
pass # 30
|
|
16
|
+
def error(self, msg: str) -> None:
|
|
17
|
+
pass # 40
|
|
18
|
+
def critical(self, msg: str) -> None:
|
|
19
|
+
pass # 50
|
|
20
|
+
def fatal(self, msg: str) -> None:
|
|
21
|
+
pass # 50
|
|
7
22
|
|
|
8
23
|
|
|
9
24
|
class Scheduler:
|
|
10
|
-
def __init__(self, routine: Callable[['Scheduler'], Any],
|
|
11
|
-
|
|
25
|
+
def __init__(self, routine: Callable[['Scheduler'], Any], wait_ms: int,
|
|
26
|
+
logger: LoggerTemplate, sess_stats: Optional[Callable[['Scheduler'], dict[str, Any]]] = None,
|
|
12
27
|
exception_handler: Optional[Callable[[Union[Exception, KeyboardInterrupt], str, 'Scheduler'], Any]] = None,
|
|
13
|
-
|
|
14
|
-
sess_stats: Optional[Callable[['Scheduler'], dict[str, Any]]] = None,
|
|
15
|
-
max_cycles: int = 1_000_000_000,
|
|
16
|
-
records: Optional[list[list[Any]]] = None):
|
|
28
|
+
max_cycles: int = 1_000_000_000, records: Optional[list[list[Any]]] = None):
|
|
17
29
|
self.routine = routine # main routine to be repeated every `wait` time period
|
|
18
|
-
self.logger = logger
|
|
30
|
+
self.logger = logger
|
|
19
31
|
self.exception_handler = exception_handler if exception_handler is not None else self.default_exception_handler
|
|
20
32
|
self.records: list[list[Any]] = records if records is not None else []
|
|
21
33
|
self.wait_ms = wait_ms # wait period between routine cycles.
|
|
22
34
|
self.cycle: int = 0
|
|
23
35
|
self.max_cycles: int = max_cycles
|
|
24
|
-
self.
|
|
36
|
+
self.sess_start_utc_ms: int
|
|
25
37
|
self.sess_stats = sess_stats or (lambda _sched: {})
|
|
26
|
-
def __repr__(self): return f"Scheduler with {self.cycle} cycles ran so far. Last cycle was at {self.
|
|
38
|
+
def __repr__(self): return f"Scheduler with {self.cycle} cycles ran so far. Last cycle was at {self.sess_start_utc_ms}."
|
|
27
39
|
def run(self, max_cycles: Optional[int]=None, until_ms: Optional[int]=None):
|
|
28
40
|
if max_cycles is not None:
|
|
29
41
|
self.max_cycles = max_cycles
|
|
30
42
|
if until_ms is None:
|
|
31
|
-
until_ms =
|
|
32
|
-
self.
|
|
43
|
+
until_ms = 1_000_000_000_000_000
|
|
44
|
+
self.sess_start_utc_ms = time.time_ns() // 1_000_000
|
|
33
45
|
while (time.time_ns() // 1_000_000) < until_ms and self.cycle < self.max_cycles:
|
|
34
46
|
# 1- Time before Ops, and Opening Message
|
|
35
47
|
time1_ms = time.time_ns() // 1_000_000
|
|
36
|
-
|
|
48
|
+
duration = timedelta(milliseconds=time1_ms - self.sess_start_utc_ms)
|
|
49
|
+
self.logger.info(f"Starting Cycle {str(self.cycle).zfill(5)}. Total Run Time = {str(duration).split('.', maxsplit=1)[0]}. UTCđ {datetime.now(tz=timezone.utc).strftime('%d %H:%M:%S')}")
|
|
37
50
|
try:
|
|
38
51
|
self.routine(self)
|
|
39
52
|
except Exception as ex:
|
|
@@ -41,44 +54,160 @@ class Scheduler:
|
|
|
41
54
|
time2_ms = time.time_ns() // 1_000_000
|
|
42
55
|
time_left_ms = int(self.wait_ms - (time2_ms - time1_ms)) # 4- Conclude Message
|
|
43
56
|
self.cycle += 1
|
|
44
|
-
self.logger.info(f"Finishing Cycle {str(self.cycle - 1).zfill(5)} in {str((time2_ms - time1_ms)*0.001).split('.', maxsplit=1)[0]}s. Sleeping for {self.wait_ms*0.001:0.1f}s ({time_left_ms*0.001:0.
|
|
57
|
+
self.logger.info(f"Finishing Cycle {str(self.cycle - 1).zfill(5)} in {str((time2_ms - time1_ms)*0.001).split('.', maxsplit=1)[0]}s. Sleeping for {self.wait_ms*0.001:0.1f}s ({time_left_ms*0.001:0.1f}s left)\n" + "-" * 100)
|
|
45
58
|
try: time.sleep(time_left_ms*0.001 if time_left_ms > 0 else 0.0) # # 5- Sleep. consider replacing by Asyncio.sleep
|
|
46
59
|
except KeyboardInterrupt as ex:
|
|
47
60
|
self.exception_handler(ex, "sleep", self)
|
|
48
61
|
return # that's probably the only kind of exception that can rise during sleep.
|
|
49
62
|
self.record_session_end(reason=f"Reached maximum number of cycles ({self.max_cycles})" if self.cycle >= self.max_cycles else f"Reached due stop time ({until_ms})")
|
|
50
|
-
def get_records_df(self):
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return pl.DataFrame(self.records, schema=columns)
|
|
63
|
+
def get_records_df(self) -> List[dict[str, Any]]:
|
|
64
|
+
columns = ["start", "finish", "duration", "cycles", "termination reason"] + list(self.sess_stats(self).keys())
|
|
65
|
+
return [dict(zip(columns, row)) for row in self.records]
|
|
54
66
|
def record_session_end(self, reason: str):
|
|
55
|
-
import polars as pl
|
|
56
67
|
end_time_ms = time.time_ns() // 1_000_000
|
|
57
|
-
duration_ms = end_time_ms - self.
|
|
68
|
+
duration_ms = end_time_ms - self.sess_start_utc_ms
|
|
58
69
|
sess_stats = self.sess_stats(self)
|
|
59
|
-
self.records.append([self.
|
|
70
|
+
self.records.append([self.sess_start_utc_ms, end_time_ms, duration_ms, self.cycle, reason,
|
|
60
71
|
# self.logger.file_path
|
|
61
72
|
] + list(sess_stats.values()))
|
|
62
|
-
|
|
73
|
+
records_df = self.get_records_df()
|
|
74
|
+
total_cycles = sum(row["cycles"] for row in records_df)
|
|
75
|
+
summ = {"start time": f"{str(self.sess_start_utc_ms)}",
|
|
63
76
|
"finish time": f"{str(end_time_ms)}.",
|
|
64
77
|
"duration": f"{str(duration_ms)} | wait time {self.wait_ms/1_000: 0.1f}s",
|
|
65
|
-
"cycles ran": f"{self.cycle} | Lifetime cycles = {
|
|
78
|
+
"cycles ran": f"{self.cycle} | Lifetime cycles = {total_cycles}",
|
|
66
79
|
"termination reason": reason,
|
|
67
80
|
# "logfile": self.logger.file_path
|
|
68
81
|
}
|
|
69
82
|
summ.update(sess_stats)
|
|
83
|
+
from machineconfig.utils.utils2 import get_repr
|
|
70
84
|
tmp = get_repr(summ)
|
|
71
85
|
self.logger.critical("\n--> Scheduler has finished running a session. \n" + tmp + "\n" + "-" * 100)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
# Format records as table
|
|
87
|
+
if records_df:
|
|
88
|
+
headers = list(records_df[0].keys())
|
|
89
|
+
# Process start, finish, duration to strings without milliseconds
|
|
90
|
+
processed_records = []
|
|
91
|
+
for row in records_df:
|
|
92
|
+
processed = row.copy()
|
|
93
|
+
processed["start"] = str(row["start"]).split(".", maxsplit=1)[0]
|
|
94
|
+
processed["finish"] = str(row["finish"]).split(".", maxsplit=1)[0]
|
|
95
|
+
processed["duration"] = str(row["duration"]).split(".", maxsplit=1)[0]
|
|
96
|
+
processed_records.append(processed)
|
|
97
|
+
# Simple aligned table formatting
|
|
98
|
+
max_lengths = {col: max(len(str(row.get(col, ""))) for row in processed_records) for col in headers}
|
|
99
|
+
table_lines = ["| " + " | ".join(col.ljust(max_lengths[col]) for col in headers) + " |"]
|
|
100
|
+
table_lines.append("|" + "-+-".join("-" * max_lengths[col] for col in headers) + "|")
|
|
101
|
+
for row in processed_records:
|
|
102
|
+
table_lines.append("| " + " | ".join(str(row.get(col, "")).ljust(max_lengths[col]) for col in headers) + " |")
|
|
103
|
+
table_str = "\n".join(table_lines)
|
|
104
|
+
else:
|
|
105
|
+
table_str = "No records available."
|
|
106
|
+
self.logger.critical("\n--> Logger history.\n" + table_str)
|
|
79
107
|
return self
|
|
80
108
|
def default_exception_handler(self, ex: Union[Exception, KeyboardInterrupt], during: str, sched: 'Scheduler') -> None: # user decides on handling and continue, terminate, save checkpoint, etc. # Use signal library.
|
|
81
109
|
print(sched)
|
|
82
110
|
self.record_session_end(reason=f"during {during}, " + str(ex))
|
|
83
|
-
self.logger.
|
|
111
|
+
self.logger.fatal(str(ex))
|
|
84
112
|
raise ex
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
T = TypeVar('T')
|
|
116
|
+
T2 = TypeVar('T2')
|
|
117
|
+
class PrintFunc(Protocol):
|
|
118
|
+
def __call__(self, msg: str) -> Union[NoReturn, None]: ...
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# class CacheV2(Generic[T]):
|
|
122
|
+
# def __init__(self, source_func: Callable[[], T],
|
|
123
|
+
# expire_ms: int, logger: Optional[PrintFunc] = None, path: OPLike = None,
|
|
124
|
+
# saver: Callable[[T, PLike], Any] = Save.pickle, reader: Callable[[PLike], T] = Read.pickle, name: Optional[str] = None) -> None:
|
|
125
|
+
# self.cache: Optional[T] = None
|
|
126
|
+
# self.source_func = source_func
|
|
127
|
+
# self.path: Optional[P] = P(path) if path else None
|
|
128
|
+
# self.time_produced = time.time_ns() // 1_000_000
|
|
129
|
+
# self.save = saver
|
|
130
|
+
# self.reader = reader
|
|
131
|
+
# self.logger = logger
|
|
132
|
+
# self.expire = expire_ms # in milliseconds
|
|
133
|
+
# self.name = name if isinstance(name, str) else str(self.source_func)
|
|
134
|
+
# @property
|
|
135
|
+
# def age(self):
|
|
136
|
+
# if self.path is None:
|
|
137
|
+
# return time.time_ns() // 1_000_000 - self.time_produced
|
|
138
|
+
# return time.time_ns() // 1_000_000 - int(self.path.stat().st_mtime * 1000)
|
|
139
|
+
# def __setstate__(self, state: dict[str, Any]) -> None:
|
|
140
|
+
# self.__dict__.update(state)
|
|
141
|
+
# self.path = P.home() / self.path if self.path is not None else self.path
|
|
142
|
+
# def __getstate__(self) -> dict[str, Any]:
|
|
143
|
+
# state = self.__dict__.copy()
|
|
144
|
+
# state["path"] = self.path.relative_to(P.home()) if self.path is not None else state["path"]
|
|
145
|
+
# return state
|
|
146
|
+
# def __call__(self, fresh: bool = False) -> T:
|
|
147
|
+
# if fresh or self.cache is None:
|
|
148
|
+
# if not fresh and self.path is not None and self.path.exists():
|
|
149
|
+
# age = time.time_ns() // 1_000_000 - int(self.path.stat().st_mtime * 1000)
|
|
150
|
+
# msg1 = f"""
|
|
151
|
+
# đĻ ââââââââââââââââââââ CACHE V2 OPERATION ââââââââââââââââââââ
|
|
152
|
+
# đ {self.name} cache: Reading cached values from `{self.path}`
|
|
153
|
+
# âąī¸ Lag = {age} ms
|
|
154
|
+
# ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ"""
|
|
155
|
+
# try:
|
|
156
|
+
# self.cache = self.reader(self.path)
|
|
157
|
+
# except Exception as ex:
|
|
158
|
+
# if self.logger:
|
|
159
|
+
# msg2 = f"""
|
|
160
|
+
# â ââââââââââââââââââââ CACHE V2 ERROR ââââââââââââââââââââ
|
|
161
|
+
# â ī¸ {self.name} cache: Cache file is corrupted
|
|
162
|
+
# đ Error: {ex}
|
|
163
|
+
# ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ"""
|
|
164
|
+
# self.logger(msg1 + msg2)
|
|
165
|
+
# self.cache = self.source_func()
|
|
166
|
+
# self.save(self.cache, self.path)
|
|
167
|
+
# return self.cache
|
|
168
|
+
# return self(fresh=False)
|
|
169
|
+
# else:
|
|
170
|
+
# if self.logger:
|
|
171
|
+
# self.logger(f"""
|
|
172
|
+
# đ ââââââââââââââââââââ NEW CACHE V2 ââââââââââââââââââââ
|
|
173
|
+
# đ {self.name} cache: Populating fresh cache from source func
|
|
174
|
+
# âšī¸ Reason: Previous cache never existed or there was an explicit fresh order
|
|
175
|
+
# ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ""")
|
|
176
|
+
# self.cache = self.source_func()
|
|
177
|
+
# if self.path is None:
|
|
178
|
+
# self.time_produced = time.time_ns() // 1_000_000
|
|
179
|
+
# else:
|
|
180
|
+
# self.save(self.cache, self.path)
|
|
181
|
+
# else:
|
|
182
|
+
# try:
|
|
183
|
+
# age = self.age
|
|
184
|
+
# except AttributeError:
|
|
185
|
+
# self.cache = None
|
|
186
|
+
# return self(fresh=fresh)
|
|
187
|
+
# if age > self.expire:
|
|
188
|
+
# if self.logger:
|
|
189
|
+
# self.logger(f"""
|
|
190
|
+
# đ ââââââââââââââââââââ CACHE V2 UPDATE ââââââââââââââââââââ
|
|
191
|
+
# â ī¸ {self.name} cache: Updating cache from source func
|
|
192
|
+
# âąī¸ Age = {age} ms > {self.expire} ms
|
|
193
|
+
# ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ""")
|
|
194
|
+
# self.cache = self.source_func()
|
|
195
|
+
# if self.path is None:
|
|
196
|
+
# self.time_produced = time.time_ns() // 1_000_000
|
|
197
|
+
# else:
|
|
198
|
+
# self.save(self.cache, self.path)
|
|
199
|
+
# else:
|
|
200
|
+
# if self.logger:
|
|
201
|
+
# self.logger(f"""
|
|
202
|
+
# â
ââââââââââââââââââââ USING CACHE V2 ââââââââââââââââââââ
|
|
203
|
+
# đĻ {self.name} cache: Using cached values
|
|
204
|
+
# âąī¸ Lag = {age} ms
|
|
205
|
+
# ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ""")
|
|
206
|
+
# return self.cache
|
|
207
|
+
# @staticmethod
|
|
208
|
+
# def as_decorator(expire: int = 60000, logger: Optional[PrintFunc] = None, path: OPLike = None,
|
|
209
|
+
# name: Optional[str] = None):
|
|
210
|
+
# def decorator(source_func: Callable[[], T2]) -> CacheV2['T2']:
|
|
211
|
+
# res = CacheV2(source_func=source_func, expire_ms=expire, logger=logger, path=path, name=name)
|
|
212
|
+
# return res
|
|
213
|
+
# return decorator
|
machineconfig/utils/ve.py
CHANGED
|
@@ -19,10 +19,10 @@ def get_ve_path_and_ipython_profile(init_path: PathExtended) -> tuple[Optional[s
|
|
|
19
19
|
ipy_profile = ini["specs"]["ipy_profile"]
|
|
20
20
|
print(f"⨠Using IPython profile: {ipy_profile}")
|
|
21
21
|
if ipy_profile is None and tmp.joinpath(".ipy_profile").exists():
|
|
22
|
-
ipy_profile = tmp.joinpath(".ipy_profile").read_text().rstrip()
|
|
22
|
+
ipy_profile = tmp.joinpath(".ipy_profile").read_text(encoding="utf-8").rstrip()
|
|
23
23
|
print(f"⨠Using IPython profile: {ipy_profile}. This is based on this file {tmp.joinpath('.ipy_profile')}")
|
|
24
24
|
if ve_path is None and tmp.joinpath(".ve_path").exists():
|
|
25
|
-
ve_path = tmp.joinpath(".ve_path").read_text().rstrip().replace("\n", "")
|
|
25
|
+
ve_path = tmp.joinpath(".ve_path").read_text(encoding="utf-8").rstrip().replace("\n", "")
|
|
26
26
|
print(f"đŽ Using Virtual Environment found @ {tmp}/.ve_path: {ve_path}")
|
|
27
27
|
if ve_path is None and tmp.joinpath(".venv").exists():
|
|
28
28
|
print(f"đŽ Using Virtual Environment found @ {tmp}/.venv")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: machineconfig
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0
|
|
4
4
|
Summary: Dotfiles management package
|
|
5
5
|
Author-email: Alex Al-Saffar <programmer@usa.com>
|
|
6
6
|
License: Apache 2.0
|
|
@@ -9,22 +9,27 @@ Project-URL: Bug Tracker, https://github.com/thisismygitrepo/machineconfig/issue
|
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
12
|
-
Requires-Python: >=3.
|
|
12
|
+
Requires-Python: >=3.13
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
|
-
Requires-Dist: crocodile
|
|
15
14
|
Requires-Dist: rich>=14.0.0
|
|
16
15
|
Requires-Dist: paramiko>=3.5.1
|
|
17
16
|
Requires-Dist: psutil>=7.0.0
|
|
18
|
-
Requires-Dist: openai>=1.75.0
|
|
19
|
-
Requires-Dist: nbformat>=5.10.4
|
|
20
17
|
Requires-Dist: fire>=0.7.0
|
|
21
18
|
Requires-Dist: pydantic>=2.11.3
|
|
22
|
-
Requires-Dist: clipboard>=0.0.4
|
|
23
19
|
Requires-Dist: gitpython>=3.1.44
|
|
24
|
-
Requires-Dist: pudb>=2024.1.3
|
|
25
20
|
Requires-Dist: pyfzf>=0.3.1
|
|
26
|
-
Requires-Dist: call-function-with-timeout>=1.1.1
|
|
27
21
|
Requires-Dist: rclone-python>=0.1.23
|
|
22
|
+
Requires-Dist: pytz>=2025.2
|
|
23
|
+
Requires-Dist: tomli>=2.2.1
|
|
24
|
+
Requires-Dist: toml>=0.10.2
|
|
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
|
+
Requires-Dist: randomname>=0.2.1
|
|
31
|
+
Requires-Dist: cryptography>=44.0.2
|
|
32
|
+
Requires-Dist: tenacity>=9.1.2
|
|
28
33
|
Provides-Extra: windows
|
|
29
34
|
Requires-Dist: pywin32; extra == "windows"
|
|
30
35
|
Provides-Extra: docs
|