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.

Files changed (155) hide show
  1. machineconfig/__init__.py +8 -5
  2. machineconfig/jobs/python/check_installations.py +173 -163
  3. machineconfig/jobs/python/checkout_version.py +117 -0
  4. machineconfig/jobs/python/create_bootable_media.py +14 -14
  5. machineconfig/jobs/python/create_zellij_template.py +59 -56
  6. machineconfig/jobs/python/python_cargo_build_share.py +50 -45
  7. machineconfig/jobs/python/python_ve_symlink.py +20 -18
  8. machineconfig/jobs/python/tasks.py +4 -0
  9. machineconfig/jobs/script_installer/azure_data_studio.py +22 -0
  10. machineconfig/jobs/script_installer/bypass_paywall.py +23 -0
  11. machineconfig/jobs/script_installer/code.py +34 -0
  12. machineconfig/jobs/script_installer/docker_desktop.py +41 -0
  13. machineconfig/jobs/script_installer/ngrok.py +29 -0
  14. machineconfig/jobs/{python_linux_installers → script_installer}/skim.py +21 -19
  15. machineconfig/jobs/script_installer/wezterm.py +34 -0
  16. machineconfig/profile/create.py +107 -200
  17. machineconfig/profile/shell.py +127 -0
  18. machineconfig/scripts/__init__.py +6 -6
  19. machineconfig/scripts/python/cloud_copy.py +93 -0
  20. machineconfig/scripts/python/cloud_manager.py +38 -0
  21. machineconfig/scripts/python/cloud_mount.py +115 -52
  22. machineconfig/scripts/python/cloud_repo_sync.py +154 -114
  23. machineconfig/scripts/python/cloud_sync.py +261 -79
  24. machineconfig/scripts/python/croshell.py +151 -0
  25. machineconfig/scripts/python/devops.py +119 -87
  26. machineconfig/scripts/python/devops_add_identity.py +27 -23
  27. machineconfig/scripts/python/devops_add_ssh_key.py +70 -55
  28. machineconfig/scripts/python/devops_backup_retrieve.py +52 -46
  29. machineconfig/scripts/python/devops_devapps_install.py +120 -91
  30. machineconfig/scripts/python/devops_update_repos.py +82 -68
  31. machineconfig/scripts/python/dotfile.py +42 -38
  32. machineconfig/scripts/python/fire_jobs.py +351 -98
  33. machineconfig/scripts/python/ftpx.py +82 -0
  34. machineconfig/scripts/python/mount_nfs.py +54 -3
  35. machineconfig/scripts/python/mount_nw_drive.py +31 -0
  36. machineconfig/scripts/python/mount_ssh.py +44 -20
  37. machineconfig/scripts/python/onetimeshare.py +60 -51
  38. machineconfig/scripts/python/pomodoro.py +41 -37
  39. machineconfig/scripts/python/repos.py +195 -128
  40. machineconfig/scripts/python/scheduler.py +52 -0
  41. machineconfig/scripts/python/snapshot.py +21 -21
  42. machineconfig/scripts/python/start_slidev.py +104 -0
  43. machineconfig/scripts/python/start_terminals.py +97 -0
  44. machineconfig/scripts/python/wifi_conn.py +90 -71
  45. machineconfig/scripts/python/{transfer_wsl_win.py → wsl_windows_transfer.py} +47 -39
  46. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +44 -48
  47. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +136 -130
  48. machineconfig/utils/installer.py +251 -0
  49. machineconfig/utils/procs.py +114 -64
  50. machineconfig/utils/scheduling.py +188 -0
  51. machineconfig/utils/utils.py +353 -249
  52. machineconfig/utils/ve.py +222 -0
  53. {machineconfig-1.5.dist-info → machineconfig-1.8.dist-info}/METADATA +140 -110
  54. machineconfig-1.8.dist-info/RECORD +70 -0
  55. {machineconfig-1.5.dist-info → machineconfig-1.8.dist-info}/WHEEL +1 -1
  56. machineconfig/jobs/python/python_linux_installers_all.py +0 -73
  57. machineconfig/jobs/python/python_ve_installer.py +0 -73
  58. machineconfig/jobs/python/python_windows_installers_all.py +0 -23
  59. machineconfig/jobs/python_generic_installers/archive/nvim.py +0 -15
  60. machineconfig/jobs/python_generic_installers/archive/strongbox.py +0 -32
  61. machineconfig/jobs/python_generic_installers/archive/vtm.py +0 -25
  62. machineconfig/jobs/python_generic_installers/broot.py +0 -39
  63. machineconfig/jobs/python_generic_installers/browsh.py +0 -25
  64. machineconfig/jobs/python_generic_installers/bw.py +0 -26
  65. machineconfig/jobs/python_generic_installers/chatgpt.py +0 -24
  66. machineconfig/jobs/python_generic_installers/cpufetch.py +0 -23
  67. machineconfig/jobs/python_generic_installers/delta.py +0 -59
  68. machineconfig/jobs/python_generic_installers/dev/__init__.py +0 -0
  69. machineconfig/jobs/python_generic_installers/dev/autogpt.py +0 -28
  70. machineconfig/jobs/python_generic_installers/dev/bw.py +0 -29
  71. machineconfig/jobs/python_generic_installers/dev/evcxr.py +0 -22
  72. machineconfig/jobs/python_generic_installers/dev/kondo.py +0 -21
  73. machineconfig/jobs/python_generic_installers/dev/lvim.py +0 -60
  74. machineconfig/jobs/python_generic_installers/dev/ngrok.py +0 -35
  75. machineconfig/jobs/python_generic_installers/dev/opencommit.py +0 -21
  76. machineconfig/jobs/python_generic_installers/dev/qrcp.py +0 -25
  77. machineconfig/jobs/python_generic_installers/dev/qrscan.py +0 -16
  78. machineconfig/jobs/python_generic_installers/dev/rust-analyzer.py +0 -24
  79. machineconfig/jobs/python_generic_installers/dev/termscp.py +0 -23
  80. machineconfig/jobs/python_generic_installers/dev/tldr.py +0 -25
  81. machineconfig/jobs/python_generic_installers/dev/tokei.py +0 -24
  82. machineconfig/jobs/python_generic_installers/diskonaut.py +0 -26
  83. machineconfig/jobs/python_generic_installers/dua.py +0 -21
  84. machineconfig/jobs/python_generic_installers/evcxr.py +0 -21
  85. machineconfig/jobs/python_generic_installers/gitui.py +0 -23
  86. machineconfig/jobs/python_generic_installers/gopass.py +0 -19
  87. machineconfig/jobs/python_generic_installers/helix.py +0 -45
  88. machineconfig/jobs/python_generic_installers/kondo.py +0 -20
  89. machineconfig/jobs/python_generic_installers/lf.py +0 -25
  90. machineconfig/jobs/python_generic_installers/lvim.py +0 -34
  91. machineconfig/jobs/python_generic_installers/mprocs.py +0 -20
  92. machineconfig/jobs/python_generic_installers/navi.py +0 -20
  93. machineconfig/jobs/python_generic_installers/ots.py +0 -26
  94. machineconfig/jobs/python_generic_installers/ouch.py +0 -25
  95. machineconfig/jobs/python_generic_installers/pomodoro.py +0 -19
  96. machineconfig/jobs/python_generic_installers/procs.py +0 -20
  97. machineconfig/jobs/python_generic_installers/qrcp.py +0 -22
  98. machineconfig/jobs/python_generic_installers/qrscan.py +0 -14
  99. machineconfig/jobs/python_generic_installers/rclone.py +0 -21
  100. machineconfig/jobs/python_generic_installers/rust-analyzer.py +0 -24
  101. machineconfig/jobs/python_generic_installers/tere.py +0 -26
  102. machineconfig/jobs/python_generic_installers/termscp.py +0 -23
  103. machineconfig/jobs/python_generic_installers/tldr.py +0 -24
  104. machineconfig/jobs/python_generic_installers/tokei.py +0 -21
  105. machineconfig/jobs/python_generic_installers/vtm.py +0 -26
  106. machineconfig/jobs/python_generic_installers/watchexec.py +0 -21
  107. machineconfig/jobs/python_linux_installers/archive/__init__.py +0 -0
  108. machineconfig/jobs/python_linux_installers/archive/ranger.py +0 -18
  109. machineconfig/jobs/python_linux_installers/bandwhich.py +0 -11
  110. machineconfig/jobs/python_linux_installers/bottom.py +0 -17
  111. machineconfig/jobs/python_linux_installers/btop.py +0 -17
  112. machineconfig/jobs/python_linux_installers/dev/bandwhich.py +0 -13
  113. machineconfig/jobs/python_linux_installers/dev/bytehound.py +0 -20
  114. machineconfig/jobs/python_linux_installers/dev/nnn.py +0 -21
  115. machineconfig/jobs/python_linux_installers/gotty.py +0 -16
  116. machineconfig/jobs/python_linux_installers/joshuto.py +0 -28
  117. machineconfig/jobs/python_linux_installers/mcfly.py +0 -12
  118. machineconfig/jobs/python_linux_installers/nnn.py +0 -18
  119. machineconfig/jobs/python_linux_installers/topgrade.py +0 -15
  120. machineconfig/jobs/python_linux_installers/viu.py +0 -19
  121. machineconfig/jobs/python_linux_installers/xplr.py +0 -22
  122. machineconfig/jobs/python_linux_installers/zellij.py +0 -31
  123. machineconfig/jobs/python_windows_installers/archive/ntop.py +0 -20
  124. machineconfig/jobs/python_windows_installers/bat.py +0 -16
  125. machineconfig/jobs/python_windows_installers/boxes.py +0 -19
  126. machineconfig/jobs/python_windows_installers/bypass_paywall.py +0 -18
  127. machineconfig/jobs/python_windows_installers/dev/bypass_paywall.py +0 -21
  128. machineconfig/jobs/python_windows_installers/dev/obs_background_removal_plugin.py +0 -20
  129. machineconfig/jobs/python_windows_installers/fd.py +0 -17
  130. machineconfig/jobs/python_windows_installers/fzf.py +0 -19
  131. machineconfig/jobs/python_windows_installers/obs_background_removal_plugin.py +0 -19
  132. machineconfig/jobs/python_windows_installers/rg.py +0 -15
  133. machineconfig/jobs/python_windows_installers/ugrep.py +0 -14
  134. machineconfig/jobs/python_windows_installers/zoomit.py +0 -20
  135. machineconfig/jobs/python_windows_installers/zoxide.py +0 -20
  136. machineconfig/profile/fix_shell_profiles.py +0 -8
  137. machineconfig/scripts/python/archive/__init__.py +0 -0
  138. machineconfig/scripts/python/archive/bu_gdrive_rx.py +0 -41
  139. machineconfig/scripts/python/archive/bu_gdrive_sx.py +0 -40
  140. machineconfig/scripts/python/archive/bu_onedrive_rx.py +0 -59
  141. machineconfig/scripts/python/archive/bu_onedrive_sx.py +0 -45
  142. machineconfig/scripts/python/chatgpt.py +0 -28
  143. machineconfig/scripts/python/choose_ohmybash_theme.py +0 -25
  144. machineconfig/scripts/python/choose_ohmyposh_theme.py +0 -40
  145. machineconfig/scripts/python/cloud_rx.py +0 -42
  146. machineconfig/scripts/python/cloud_sx.py +0 -40
  147. machineconfig/scripts/python/ftprx.py +0 -37
  148. machineconfig/scripts/python/ftpsx.py +0 -36
  149. machineconfig/scripts/python/im2text.py +0 -15
  150. machineconfig/scripts/python/tmate_conn.py +0 -28
  151. machineconfig/scripts/python/tmate_start.py +0 -31
  152. machineconfig/utils/to_exe.py +0 -7
  153. machineconfig-1.5.dist-info/RECORD +0 -147
  154. /machineconfig/jobs/{python_generic_installers/archive → script_installer}/__init__.py +0 -0
  155. {machineconfig-1.5.dist-info → machineconfig-1.8.dist-info}/top_level.txt +0 -0
@@ -1,64 +1,114 @@
1
-
2
- import psutil
3
- import pandas as pd
4
- from pytz import timezone
5
- from machineconfig.utils.utils import display_options
6
-
7
-
8
- pd.options.display.max_rows = 10000
9
-
10
-
11
- class ProcessManager:
12
- def __init__(self):
13
- process_info = []
14
- for proc in psutil.process_iter():
15
- try:
16
- mem_usage_mb = proc.memory_info().rss / (1024 * 1024)
17
- process_info.append([proc.pid, proc.name(), proc.username(), proc.cpu_percent(), mem_usage_mb, proc.status(), proc.create_time()])
18
- except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): pass
19
- df = pd.DataFrame(process_info)
20
- df.columns = ['pid', 'name', 'username', 'cpu_percent', 'memory_usage_mb', 'status', 'create_time']
21
- df['create_time'] = pd.to_datetime(df['create_time'], unit='s', utc=True).apply(lambda x: x.tz_convert(timezone('Australia/Adelaide')))
22
- df = df.sort_values(by='memory_usage_mb', ascending=False).reset_index(drop=True)
23
- self.df = df
24
-
25
- def choose_and_kill(self):
26
- options = str(self.df).split("\n")[1:]
27
- res = display_options(options=str(self.df).split("\n"), msg="", fzf=True, multi=True)
28
- indices = [options.index(val) for val in res]
29
- sub_df = self.df.iloc[indices]
30
- print(self.df)
31
- print(sub_df)
32
- self.kill(pids=sub_df.pid) if input("Confirm kill? y/[n] ").lower() == "y" else print("Not killing")
33
-
34
- def filter_and_kill(self, name=None):
35
- df_sub = self.df.query(f"name == '{name}' ").sort_values(by='create_time', ascending=True)
36
- self.kill(pids=df_sub.pid)
37
-
38
- def kill(self, names: list or None = None, pids: list or None = None):
39
- if names is None and pids is None:
40
- raise ValueError('names and pids cannot both be None')
41
- if names is None: names = []
42
- if pids is None: pids = []
43
- for name in names:
44
- rows = self.df[self.df['name'] == name]
45
- if len(rows) > 0:
46
- for pid in rows['pid'].values:
47
- psutil.Process(pid).kill()
48
- age = pd.Timestamp.now(tz="Australia/Adeliade") - pd.to_datetime(rows["create_time"].values[0], unit="s", utc=True).tz_convert(timezone("Australia/Adelaide"))
49
- print(f'Killed process {name} with pid {pid}. It lived {age}.')
50
- else:
51
- print(f'No process named {name} found')
52
- for pid in pids:
53
- try:
54
- proc = psutil.Process(pid)
55
- proc.kill()
56
- age = pd.Timestamp.now(tz="Australia/Adelaide") - pd.to_datetime(proc.create_time(), unit="s", utc=True).tz_convert(timezone("Australia/Adelaide"))
57
- print(f'Killed process with pid {pid} and name {proc.name()}. It lived {age}.')
58
- except psutil.NoSuchProcess:
59
- print(f'No process with pid {pid} found')
60
-
61
-
62
- if __name__ == '__main__':
63
- pass
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