machineconfig 1.5__py3-none-any.whl → 1.8__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/__init__.py +8 -5
- machineconfig/jobs/python/check_installations.py +173 -163
- machineconfig/jobs/python/checkout_version.py +117 -0
- machineconfig/jobs/python/create_bootable_media.py +14 -14
- machineconfig/jobs/python/create_zellij_template.py +59 -56
- machineconfig/jobs/python/python_cargo_build_share.py +50 -45
- machineconfig/jobs/python/python_ve_symlink.py +20 -18
- machineconfig/jobs/python/tasks.py +4 -0
- machineconfig/jobs/script_installer/azure_data_studio.py +22 -0
- machineconfig/jobs/script_installer/bypass_paywall.py +23 -0
- machineconfig/jobs/script_installer/code.py +34 -0
- machineconfig/jobs/script_installer/docker_desktop.py +41 -0
- machineconfig/jobs/script_installer/ngrok.py +29 -0
- machineconfig/jobs/{python_linux_installers → script_installer}/skim.py +21 -19
- machineconfig/jobs/script_installer/wezterm.py +34 -0
- machineconfig/profile/create.py +107 -200
- machineconfig/profile/shell.py +127 -0
- machineconfig/scripts/__init__.py +6 -6
- machineconfig/scripts/python/cloud_copy.py +93 -0
- machineconfig/scripts/python/cloud_manager.py +38 -0
- machineconfig/scripts/python/cloud_mount.py +115 -52
- machineconfig/scripts/python/cloud_repo_sync.py +154 -114
- machineconfig/scripts/python/cloud_sync.py +261 -79
- machineconfig/scripts/python/croshell.py +151 -0
- machineconfig/scripts/python/devops.py +119 -87
- machineconfig/scripts/python/devops_add_identity.py +27 -23
- machineconfig/scripts/python/devops_add_ssh_key.py +70 -55
- machineconfig/scripts/python/devops_backup_retrieve.py +52 -46
- machineconfig/scripts/python/devops_devapps_install.py +120 -91
- machineconfig/scripts/python/devops_update_repos.py +82 -68
- machineconfig/scripts/python/dotfile.py +42 -38
- machineconfig/scripts/python/fire_jobs.py +351 -98
- machineconfig/scripts/python/ftpx.py +82 -0
- machineconfig/scripts/python/mount_nfs.py +54 -3
- machineconfig/scripts/python/mount_nw_drive.py +31 -0
- machineconfig/scripts/python/mount_ssh.py +44 -20
- machineconfig/scripts/python/onetimeshare.py +60 -51
- machineconfig/scripts/python/pomodoro.py +41 -37
- machineconfig/scripts/python/repos.py +195 -128
- machineconfig/scripts/python/scheduler.py +52 -0
- machineconfig/scripts/python/snapshot.py +21 -21
- machineconfig/scripts/python/start_slidev.py +104 -0
- machineconfig/scripts/python/start_terminals.py +97 -0
- machineconfig/scripts/python/wifi_conn.py +90 -71
- machineconfig/scripts/python/{transfer_wsl_win.py → wsl_windows_transfer.py} +47 -39
- machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +44 -48
- machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +136 -130
- machineconfig/utils/installer.py +251 -0
- machineconfig/utils/procs.py +114 -64
- machineconfig/utils/scheduling.py +188 -0
- machineconfig/utils/utils.py +353 -249
- machineconfig/utils/ve.py +222 -0
- {machineconfig-1.5.dist-info → machineconfig-1.8.dist-info}/METADATA +140 -110
- machineconfig-1.8.dist-info/RECORD +70 -0
- {machineconfig-1.5.dist-info → machineconfig-1.8.dist-info}/WHEEL +1 -1
- machineconfig/jobs/python/python_linux_installers_all.py +0 -73
- machineconfig/jobs/python/python_ve_installer.py +0 -73
- machineconfig/jobs/python/python_windows_installers_all.py +0 -23
- machineconfig/jobs/python_generic_installers/archive/nvim.py +0 -15
- machineconfig/jobs/python_generic_installers/archive/strongbox.py +0 -32
- machineconfig/jobs/python_generic_installers/archive/vtm.py +0 -25
- machineconfig/jobs/python_generic_installers/broot.py +0 -39
- machineconfig/jobs/python_generic_installers/browsh.py +0 -25
- machineconfig/jobs/python_generic_installers/bw.py +0 -26
- machineconfig/jobs/python_generic_installers/chatgpt.py +0 -24
- machineconfig/jobs/python_generic_installers/cpufetch.py +0 -23
- machineconfig/jobs/python_generic_installers/delta.py +0 -59
- machineconfig/jobs/python_generic_installers/dev/__init__.py +0 -0
- machineconfig/jobs/python_generic_installers/dev/autogpt.py +0 -28
- machineconfig/jobs/python_generic_installers/dev/bw.py +0 -29
- machineconfig/jobs/python_generic_installers/dev/evcxr.py +0 -22
- machineconfig/jobs/python_generic_installers/dev/kondo.py +0 -21
- machineconfig/jobs/python_generic_installers/dev/lvim.py +0 -60
- machineconfig/jobs/python_generic_installers/dev/ngrok.py +0 -35
- machineconfig/jobs/python_generic_installers/dev/opencommit.py +0 -21
- machineconfig/jobs/python_generic_installers/dev/qrcp.py +0 -25
- machineconfig/jobs/python_generic_installers/dev/qrscan.py +0 -16
- machineconfig/jobs/python_generic_installers/dev/rust-analyzer.py +0 -24
- machineconfig/jobs/python_generic_installers/dev/termscp.py +0 -23
- machineconfig/jobs/python_generic_installers/dev/tldr.py +0 -25
- machineconfig/jobs/python_generic_installers/dev/tokei.py +0 -24
- machineconfig/jobs/python_generic_installers/diskonaut.py +0 -26
- machineconfig/jobs/python_generic_installers/dua.py +0 -21
- machineconfig/jobs/python_generic_installers/evcxr.py +0 -21
- machineconfig/jobs/python_generic_installers/gitui.py +0 -23
- machineconfig/jobs/python_generic_installers/gopass.py +0 -19
- machineconfig/jobs/python_generic_installers/helix.py +0 -45
- machineconfig/jobs/python_generic_installers/kondo.py +0 -20
- machineconfig/jobs/python_generic_installers/lf.py +0 -25
- machineconfig/jobs/python_generic_installers/lvim.py +0 -34
- machineconfig/jobs/python_generic_installers/mprocs.py +0 -20
- machineconfig/jobs/python_generic_installers/navi.py +0 -20
- machineconfig/jobs/python_generic_installers/ots.py +0 -26
- machineconfig/jobs/python_generic_installers/ouch.py +0 -25
- machineconfig/jobs/python_generic_installers/pomodoro.py +0 -19
- machineconfig/jobs/python_generic_installers/procs.py +0 -20
- machineconfig/jobs/python_generic_installers/qrcp.py +0 -22
- machineconfig/jobs/python_generic_installers/qrscan.py +0 -14
- machineconfig/jobs/python_generic_installers/rclone.py +0 -21
- machineconfig/jobs/python_generic_installers/rust-analyzer.py +0 -24
- machineconfig/jobs/python_generic_installers/tere.py +0 -26
- machineconfig/jobs/python_generic_installers/termscp.py +0 -23
- machineconfig/jobs/python_generic_installers/tldr.py +0 -24
- machineconfig/jobs/python_generic_installers/tokei.py +0 -21
- machineconfig/jobs/python_generic_installers/vtm.py +0 -26
- machineconfig/jobs/python_generic_installers/watchexec.py +0 -21
- machineconfig/jobs/python_linux_installers/archive/__init__.py +0 -0
- machineconfig/jobs/python_linux_installers/archive/ranger.py +0 -18
- machineconfig/jobs/python_linux_installers/bandwhich.py +0 -11
- machineconfig/jobs/python_linux_installers/bottom.py +0 -17
- machineconfig/jobs/python_linux_installers/btop.py +0 -17
- machineconfig/jobs/python_linux_installers/dev/bandwhich.py +0 -13
- machineconfig/jobs/python_linux_installers/dev/bytehound.py +0 -20
- machineconfig/jobs/python_linux_installers/dev/nnn.py +0 -21
- machineconfig/jobs/python_linux_installers/gotty.py +0 -16
- machineconfig/jobs/python_linux_installers/joshuto.py +0 -28
- machineconfig/jobs/python_linux_installers/mcfly.py +0 -12
- machineconfig/jobs/python_linux_installers/nnn.py +0 -18
- machineconfig/jobs/python_linux_installers/topgrade.py +0 -15
- machineconfig/jobs/python_linux_installers/viu.py +0 -19
- machineconfig/jobs/python_linux_installers/xplr.py +0 -22
- machineconfig/jobs/python_linux_installers/zellij.py +0 -31
- machineconfig/jobs/python_windows_installers/archive/ntop.py +0 -20
- machineconfig/jobs/python_windows_installers/bat.py +0 -16
- machineconfig/jobs/python_windows_installers/boxes.py +0 -19
- machineconfig/jobs/python_windows_installers/bypass_paywall.py +0 -18
- machineconfig/jobs/python_windows_installers/dev/bypass_paywall.py +0 -21
- machineconfig/jobs/python_windows_installers/dev/obs_background_removal_plugin.py +0 -20
- machineconfig/jobs/python_windows_installers/fd.py +0 -17
- machineconfig/jobs/python_windows_installers/fzf.py +0 -19
- machineconfig/jobs/python_windows_installers/obs_background_removal_plugin.py +0 -19
- machineconfig/jobs/python_windows_installers/rg.py +0 -15
- machineconfig/jobs/python_windows_installers/ugrep.py +0 -14
- machineconfig/jobs/python_windows_installers/zoomit.py +0 -20
- machineconfig/jobs/python_windows_installers/zoxide.py +0 -20
- machineconfig/profile/fix_shell_profiles.py +0 -8
- machineconfig/scripts/python/archive/__init__.py +0 -0
- machineconfig/scripts/python/archive/bu_gdrive_rx.py +0 -41
- machineconfig/scripts/python/archive/bu_gdrive_sx.py +0 -40
- machineconfig/scripts/python/archive/bu_onedrive_rx.py +0 -59
- machineconfig/scripts/python/archive/bu_onedrive_sx.py +0 -45
- machineconfig/scripts/python/chatgpt.py +0 -28
- machineconfig/scripts/python/choose_ohmybash_theme.py +0 -25
- machineconfig/scripts/python/choose_ohmyposh_theme.py +0 -40
- machineconfig/scripts/python/cloud_rx.py +0 -42
- machineconfig/scripts/python/cloud_sx.py +0 -40
- machineconfig/scripts/python/ftprx.py +0 -37
- machineconfig/scripts/python/ftpsx.py +0 -36
- machineconfig/scripts/python/im2text.py +0 -15
- machineconfig/scripts/python/tmate_conn.py +0 -28
- machineconfig/scripts/python/tmate_start.py +0 -31
- machineconfig/utils/to_exe.py +0 -7
- machineconfig-1.5.dist-info/RECORD +0 -147
- /machineconfig/jobs/{python_generic_installers/archive → script_installer}/__init__.py +0 -0
- {machineconfig-1.5.dist-info → machineconfig-1.8.dist-info}/top_level.txt +0 -0
machineconfig/utils/procs.py
CHANGED
|
@@ -1,64 +1,114 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
1
|
+
|
|
2
|
+
"""Procs
|
|
3
|
+
"""
|
|
4
|
+
import psutil
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from tqdm import tqdm
|
|
7
|
+
from pytz import timezone
|
|
8
|
+
from machineconfig.utils.utils import display_options
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
pd.options.display.max_rows = 10000
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_processes_accessing_file(path: str):
|
|
15
|
+
res: dict[int, list[str]] = {}
|
|
16
|
+
for proc in tqdm(psutil.process_iter()):
|
|
17
|
+
try:
|
|
18
|
+
files = proc.open_files()
|
|
19
|
+
except psutil.AccessDenied:
|
|
20
|
+
continue
|
|
21
|
+
tmp = [file.path for file in files if path in file.path]
|
|
22
|
+
if len(tmp) > 0:
|
|
23
|
+
res[proc.pid] = tmp
|
|
24
|
+
df = pd.DataFrame(res.items(), columns=['pid', 'files'])
|
|
25
|
+
return df
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def kill_process(name: str):
|
|
29
|
+
for proc in psutil.process_iter():
|
|
30
|
+
if proc.name() == name:
|
|
31
|
+
proc.kill()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ProcessManager:
|
|
35
|
+
def __init__(self):
|
|
36
|
+
process_info = []
|
|
37
|
+
for proc in tqdm(psutil.process_iter(), desc="Reading Processes"):
|
|
38
|
+
try:
|
|
39
|
+
mem_usage_mb = proc.memory_info().rss / (1024 * 1024)
|
|
40
|
+
process_info.append([proc.pid, proc.name(), proc.username(), proc.cpu_percent(), mem_usage_mb, proc.status(), proc.create_time(), " ".join(proc.cmdline())])
|
|
41
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): pass
|
|
42
|
+
df = pd.DataFrame(process_info)
|
|
43
|
+
df.columns = pd.Index(['pid', 'name', 'username', 'cpu_percent', 'memory_usage_mb', 'status', 'create_time', 'command'])
|
|
44
|
+
df['create_time'] = pd.to_datetime(df['create_time'], unit='s', utc=True).apply(lambda x: x.tz_convert(timezone('Australia/Adelaide')))
|
|
45
|
+
df = df.sort_values(by='memory_usage_mb', ascending=False).reset_index(drop=True)
|
|
46
|
+
self.df = df
|
|
47
|
+
|
|
48
|
+
def choose_and_kill(self):
|
|
49
|
+
options = str(self.df).split("\n")[1:]
|
|
50
|
+
res = display_options(options=str(self.df).split("\n"), msg="", fzf=True, multi=True)
|
|
51
|
+
indices = [options.index(val) for val in res]
|
|
52
|
+
sub_df = self.df.iloc[indices]
|
|
53
|
+
print(self.df)
|
|
54
|
+
print(sub_df)
|
|
55
|
+
from crocodile.core import Struct
|
|
56
|
+
for idx, (_, row) in enumerate(sub_df.iterrows()):
|
|
57
|
+
Struct(row.to_dict()).print(as_config=True, title=f"Process {idx}")
|
|
58
|
+
kill_all = input("🔪 Confirm killing ALL? y/[n] ").lower() == "y"
|
|
59
|
+
if kill_all:
|
|
60
|
+
self.kill(pids=sub_df.pid.to_list())
|
|
61
|
+
return
|
|
62
|
+
kill_by_index = input("🔫 Kill by index? 1 4 ... /[n] ")
|
|
63
|
+
if kill_by_index != "":
|
|
64
|
+
indices = [int(val) for val in kill_by_index.split(" ")]
|
|
65
|
+
sub_sub_df = sub_df.iloc[indices]
|
|
66
|
+
for idx2, row in sub_sub_df.iterrows():
|
|
67
|
+
Struct(row.to_dict()).print(as_config=True, title=f"Process {idx2}")
|
|
68
|
+
_ = self.kill(pids=sub_sub_df.pid.to_list()) if input("Confirm kill? y/[n] ").lower() == "y" else None
|
|
69
|
+
print("🫠🐔 Not killing any process.")
|
|
70
|
+
|
|
71
|
+
def filter_and_kill(self, name: Optional[str] = None):
|
|
72
|
+
_ = 20
|
|
73
|
+
df_sub = self.df.query(f"name == '{name}' ").sort_values(by='create_time', ascending=True)
|
|
74
|
+
self.kill(pids=df_sub.pid.to_list())
|
|
75
|
+
|
|
76
|
+
def kill(self, names: Optional[list[str]] = None, pids: Optional[list[int]] = None, commands: Optional[list[str]] = None):
|
|
77
|
+
if names is None and pids is None and commands is None:
|
|
78
|
+
raise ValueError('names, pids and commands cannot all be None')
|
|
79
|
+
if names is None: names = []
|
|
80
|
+
if pids is None: pids = []
|
|
81
|
+
if commands is None: commands = []
|
|
82
|
+
for name in names:
|
|
83
|
+
rows = self.df[self.df['name'] == name]
|
|
84
|
+
if len(rows) > 0:
|
|
85
|
+
for _idx, a_row in rows.iterrows():
|
|
86
|
+
psutil.Process(a_row.pid).kill()
|
|
87
|
+
print(f'💀 Killed process {name} with pid {a_row.pid}. It lived {get_age(a_row.create_time)}. RIP 🪦💐')
|
|
88
|
+
else: print(f'No process named {name} found')
|
|
89
|
+
for pid in pids:
|
|
90
|
+
try:
|
|
91
|
+
proc = psutil.Process(pid)
|
|
92
|
+
proc.kill()
|
|
93
|
+
print(f'💀 Killed process with pid {pid} and name {proc.name()}. It lived {get_age(proc.create_time())}. RIP 🪦💐')
|
|
94
|
+
except psutil.NoSuchProcess: print(f'No process with pid {pid} found')
|
|
95
|
+
for command in commands:
|
|
96
|
+
rows = self.df[self.df['command'].str.contains(command)]
|
|
97
|
+
if len(rows) > 0:
|
|
98
|
+
for _idx, a_row in rows.iterrows():
|
|
99
|
+
psutil.Process(a_row.pid).kill()
|
|
100
|
+
print(f'💀 Killed process with `{command}` in its command & pid = {a_row.pid}. It lived {get_age(a_row.create_time)}. RIP 🪦💐')
|
|
101
|
+
else: print(f'No process has `{command}` in its command.')
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_age(create_time: float):
|
|
105
|
+
try: age = pd.Timestamp.now(tz="Australia/Adelaide") - pd.to_datetime(create_time, unit="s", utc=True).tz_convert(timezone("Australia/Adelaide"))
|
|
106
|
+
except Exception as e:
|
|
107
|
+
try: age = pd.Timestamp.now() - pd.to_datetime(create_time, unit="s", utc=True).tz_localize(tz=None)
|
|
108
|
+
except Exception as ee: # type: ignore
|
|
109
|
+
return f"unknown due to {ee} and {e}"
|
|
110
|
+
return age
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == '__main__':
|
|
114
|
+
pass
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
"""Task scheduler
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from crocodile.file_management import P, Read, str2timedelta, Save
|
|
7
|
+
# from crocodile.meta import Terminal
|
|
8
|
+
from machineconfig.utils.utils import get_shell_script_executing_python_file
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from datetime import datetime, timedelta
|
|
11
|
+
import platform
|
|
12
|
+
import subprocess
|
|
13
|
+
from typing import Optional
|
|
14
|
+
# from crocodile.meta import Scheduler
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
SCHEDULER_DEFAULT_ROOT = P.home().joinpath("dotfiles/scripts/.scheduler")
|
|
18
|
+
SUCCESS = "success"
|
|
19
|
+
DEFAULT_CONFIG = """
|
|
20
|
+
[specs]
|
|
21
|
+
frequency = 30d
|
|
22
|
+
start = 2024-01-01 01:00
|
|
23
|
+
|
|
24
|
+
[runtime]
|
|
25
|
+
venv = ve
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Register:
|
|
30
|
+
def __init__(self, root: str):
|
|
31
|
+
self.root = P(root).expanduser().absolute()
|
|
32
|
+
|
|
33
|
+
def register_runtime(self, frequency_months: int = 1):
|
|
34
|
+
start, end = self.get_report_start_end_datetimes(frequency_months=frequency_months)
|
|
35
|
+
report = Report(name="runtime", start=start, end=end, status="success")
|
|
36
|
+
report.to_path(self.root.joinpath("runtime.ini"))
|
|
37
|
+
return report
|
|
38
|
+
|
|
39
|
+
def read_runtime(self):
|
|
40
|
+
return Report.from_path(self.root.joinpath("runtime.ini"))
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def format_date(date: datetime):
|
|
44
|
+
return str(date.year)[2:] + "-" + str(date.month).zfill(2)
|
|
45
|
+
@staticmethod
|
|
46
|
+
def get_report_start_end_datetimes(frequency_months: int):
|
|
47
|
+
now = datetime.now()
|
|
48
|
+
import numpy as np
|
|
49
|
+
chunks = np.arange(start=1, stop=12, step=frequency_months)
|
|
50
|
+
chunk_now_start_month: int = chunks[chunks <= now.month][-1]
|
|
51
|
+
chunk_now_start = datetime(year=now.year, month=chunk_now_start_month, day=1)
|
|
52
|
+
from dateutil.relativedelta import relativedelta
|
|
53
|
+
previous_chunk_start = chunk_now_start - relativedelta(months=frequency_months)
|
|
54
|
+
return previous_chunk_start, chunk_now_start
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class Report:
|
|
59
|
+
name: str
|
|
60
|
+
start: datetime
|
|
61
|
+
end: datetime
|
|
62
|
+
status: str
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def from_path(cls, path: P, return_default_if_not_found: bool = False):
|
|
66
|
+
if not path.exists():
|
|
67
|
+
if return_default_if_not_found:
|
|
68
|
+
return Report(name=path.parent.name, start=datetime(year=2000, month=1, day=1), end=datetime(year=2000, month=1, day=1), status="NA")
|
|
69
|
+
else:
|
|
70
|
+
raise ValueError(f"Could not find report at {path}")
|
|
71
|
+
ini = Read.ini(path)['report']
|
|
72
|
+
return cls(
|
|
73
|
+
name=ini["name"],
|
|
74
|
+
start=datetime.fromisoformat(ini["start"]),
|
|
75
|
+
end=datetime.fromisoformat(ini["end"]),
|
|
76
|
+
status=ini["status"],
|
|
77
|
+
)
|
|
78
|
+
def to_path(self, path: P):
|
|
79
|
+
Save.ini(path=path, obj={'report': {
|
|
80
|
+
'name': self.name,
|
|
81
|
+
'start': self.start.isoformat(),
|
|
82
|
+
'end': self.end.isoformat(),
|
|
83
|
+
'status': str(self.status),
|
|
84
|
+
}})
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class Task:
|
|
89
|
+
name: str
|
|
90
|
+
task_root: P
|
|
91
|
+
frequency: timedelta
|
|
92
|
+
start: datetime
|
|
93
|
+
venv: str
|
|
94
|
+
@property
|
|
95
|
+
def report_path(self):
|
|
96
|
+
return self.task_root.joinpath("report.ini")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def read_task_from_dir(path: P):
|
|
100
|
+
tasks_config = Read.ini(path.joinpath("config.ini"))
|
|
101
|
+
task = Task(name=path.name,
|
|
102
|
+
task_root=path,
|
|
103
|
+
frequency=str2timedelta(tasks_config["specs"]["frequency"]),
|
|
104
|
+
start=datetime.fromisoformat(tasks_config["specs"]["start"]),
|
|
105
|
+
venv=tasks_config["runtime"]["venv"],
|
|
106
|
+
# output_dir=P(a_task_section["output_dir"]).expanduser().absolute(),
|
|
107
|
+
)
|
|
108
|
+
return task
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def main(root: Optional[str] = None, ignore_conditions: bool = True):
|
|
112
|
+
if root is None: root_resolved = SCHEDULER_DEFAULT_ROOT
|
|
113
|
+
else: root_resolved = P(root).expanduser().absolute()
|
|
114
|
+
tasks_dirs = root_resolved.search(files=False, folders=True).filter(lambda x: x.joinpath("task.py").exists())
|
|
115
|
+
|
|
116
|
+
print(root_resolved)
|
|
117
|
+
tasks: list[Task] = []
|
|
118
|
+
for a_dir in tasks_dirs:
|
|
119
|
+
tasks.append(read_task_from_dir(a_dir))
|
|
120
|
+
|
|
121
|
+
from machineconfig.utils.utils import choose_multiple_options
|
|
122
|
+
import pandas as pd
|
|
123
|
+
df_res = pd.DataFrame([Report.from_path(path=a_task.report_path).__dict__ for a_task in tasks])
|
|
124
|
+
tasks_chosen_raw = choose_multiple_options(df_res.to_markdown().splitlines(), "Choose tasks to run")
|
|
125
|
+
tasks_chosen = [tasks[int(a_task_chosen.split("|")[1])] for a_task_chosen in tasks_chosen_raw]
|
|
126
|
+
|
|
127
|
+
result: list[Report] = []
|
|
128
|
+
for a_task in tasks_chosen:
|
|
129
|
+
if not ignore_conditions:
|
|
130
|
+
answer, report = should_task_run(a_task)
|
|
131
|
+
else:
|
|
132
|
+
answer, report = True, None
|
|
133
|
+
if answer: report = run_task(a_task)
|
|
134
|
+
else:
|
|
135
|
+
assert report is not None
|
|
136
|
+
result.append(report)
|
|
137
|
+
|
|
138
|
+
df_res = pd.DataFrame([r.__dict__ for r in result])
|
|
139
|
+
print(df_res)
|
|
140
|
+
# root_resolved.joinpath("task_report.md").write_text(df_res.to_markdown(), encoding="utf-8")
|
|
141
|
+
return ""
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def should_task_run(task: Task, tolerance_mins: int = 1440) -> tuple[bool, Optional[Report]]:
|
|
145
|
+
if not task.report_path.exists():
|
|
146
|
+
print(f"Task {task.name} has no record of being run before, running now...")
|
|
147
|
+
return True, None
|
|
148
|
+
old_report = Report.from_path(task.report_path)
|
|
149
|
+
time_since_execution = datetime.now() - old_report.end
|
|
150
|
+
if time_since_execution > task.frequency:
|
|
151
|
+
print(f"⚠️ Task {task.name} has not been run for {time_since_execution}, It is mean to run every {task.frequency}. running now if time is okay ...")
|
|
152
|
+
elif old_report.status != SUCCESS:
|
|
153
|
+
print(f"⚠️ Task {task.name} last run failed, running now if time is okay ...")
|
|
154
|
+
else:
|
|
155
|
+
print(f"Task `{task.name}` was run successfully {time_since_execution} ago, skipping...")
|
|
156
|
+
return False, old_report
|
|
157
|
+
|
|
158
|
+
suitable_run_time = task.start.time()
|
|
159
|
+
time_now = datetime.now().time()
|
|
160
|
+
min_diff = abs(suitable_run_time.hour - time_now.hour) * 60 + abs(suitable_run_time.minute - time_now.minute)
|
|
161
|
+
if not min_diff < tolerance_mins:
|
|
162
|
+
status = f"⌚ Time now is not suitable for running task {task.name} (Ideally, it should be run at {suitable_run_time})"
|
|
163
|
+
print(status)
|
|
164
|
+
return False, old_report
|
|
165
|
+
return True, old_report
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def run_task(task: Task) -> Report:
|
|
169
|
+
start_time = datetime.now()
|
|
170
|
+
|
|
171
|
+
shell_script = get_shell_script_executing_python_file(python_file=task.task_root.joinpath("task.py").str, ve_name=task.venv)
|
|
172
|
+
shell_script_root = P.tmp().joinpath(f"tmp_scripts/scheduler/{task.name}").create()
|
|
173
|
+
try:
|
|
174
|
+
if platform.system() == 'Windows':
|
|
175
|
+
shell_script = shell_script_root.joinpath("run.ps1").write_text(shell_script)
|
|
176
|
+
subprocess.run(['powershell', '-ExecutionPolicy', 'Unrestricted', shell_script], check=True)
|
|
177
|
+
elif platform.system() == 'Linux':
|
|
178
|
+
shell_script = shell_script_root.joinpath("run.sh").write_text(shell_script)
|
|
179
|
+
subprocess.run(['bash', shell_script], check=True)
|
|
180
|
+
else: res = f"Error: Unsupported platform {platform.system()}."
|
|
181
|
+
res = SUCCESS
|
|
182
|
+
except subprocess.CalledProcessError: res = f"Error: The script {shell_script_root} failed to run."
|
|
183
|
+
except Exception as e: res = f"Error: An unexpected error occurred while running the script {shell_script_root}: {e}"
|
|
184
|
+
|
|
185
|
+
end_time = datetime.now()
|
|
186
|
+
report = Report(name=task.name, start=start_time, end=end_time, status=res.replace('\n', '_NL_').strip().replace('=', '_eq_'))
|
|
187
|
+
report.to_path(task.report_path)
|
|
188
|
+
return report
|