machineconfig 2.0__py3-none-any.whl → 2.1__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 (235) hide show
  1. machineconfig/cluster/cloud_manager.py +0 -3
  2. machineconfig/cluster/data_transfer.py +0 -1
  3. machineconfig/cluster/file_manager.py +0 -1
  4. machineconfig/cluster/job_params.py +0 -3
  5. machineconfig/cluster/loader_runner.py +0 -3
  6. machineconfig/cluster/remote_machine.py +0 -1
  7. machineconfig/cluster/script_notify_upon_completion.py +0 -1
  8. machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +3 -5
  9. machineconfig/cluster/sessions_managers/archive/session_managers.py +0 -1
  10. machineconfig/cluster/sessions_managers/enhanced_command_runner.py +17 -57
  11. machineconfig/cluster/sessions_managers/wt_local.py +36 -110
  12. machineconfig/cluster/sessions_managers/wt_local_manager.py +42 -112
  13. machineconfig/cluster/sessions_managers/wt_remote.py +23 -30
  14. machineconfig/cluster/sessions_managers/wt_remote_manager.py +20 -62
  15. machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +10 -15
  16. machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +27 -127
  17. machineconfig/cluster/sessions_managers/wt_utils/remote_executor.py +10 -43
  18. machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +22 -101
  19. machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +11 -39
  20. machineconfig/cluster/sessions_managers/zellij_local.py +49 -102
  21. machineconfig/cluster/sessions_managers/zellij_local_manager.py +34 -78
  22. machineconfig/cluster/sessions_managers/zellij_remote.py +17 -24
  23. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +7 -13
  24. machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +4 -2
  25. machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +6 -6
  26. machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +18 -88
  27. machineconfig/cluster/sessions_managers/zellij_utils/remote_executor.py +2 -6
  28. machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +12 -40
  29. machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +3 -2
  30. machineconfig/cluster/templates/cli_click.py +0 -1
  31. machineconfig/cluster/templates/cli_gooey.py +0 -2
  32. machineconfig/cluster/templates/cli_trogon.py +0 -1
  33. machineconfig/cluster/templates/run_cloud.py +0 -1
  34. machineconfig/cluster/templates/run_cluster.py +0 -1
  35. machineconfig/cluster/templates/run_remote.py +0 -1
  36. machineconfig/cluster/templates/utils.py +26 -10
  37. machineconfig/jobs/__pycache__/__init__.cpython-313.pyc +0 -0
  38. machineconfig/jobs/linux/msc/cli_agents.sh +16 -0
  39. machineconfig/jobs/python/check_installations.py +1 -0
  40. machineconfig/jobs/python/create_bootable_media.py +0 -2
  41. machineconfig/jobs/python/python_ve_symlink.py +9 -11
  42. machineconfig/jobs/python/tasks.py +0 -1
  43. machineconfig/jobs/python/vscode/api.py +5 -5
  44. machineconfig/jobs/python/vscode/link_ve.py +13 -14
  45. machineconfig/jobs/python/vscode/select_interpreter.py +21 -22
  46. machineconfig/jobs/python/vscode/sync_code.py +9 -13
  47. machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  48. machineconfig/jobs/python_custom_installers/archive/ngrok.py +13 -13
  49. machineconfig/jobs/python_custom_installers/dev/aider.py +7 -15
  50. machineconfig/jobs/python_custom_installers/dev/alacritty.py +9 -18
  51. machineconfig/jobs/python_custom_installers/dev/brave.py +10 -19
  52. machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +8 -15
  53. machineconfig/jobs/python_custom_installers/dev/code.py +14 -21
  54. machineconfig/jobs/python_custom_installers/dev/cursor.py +3 -14
  55. machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +8 -7
  56. machineconfig/jobs/python_custom_installers/dev/espanso.py +15 -19
  57. machineconfig/jobs/python_custom_installers/dev/goes.py +5 -12
  58. machineconfig/jobs/python_custom_installers/dev/lvim.py +9 -17
  59. machineconfig/jobs/python_custom_installers/dev/nerdfont.py +12 -19
  60. machineconfig/jobs/python_custom_installers/dev/redis.py +12 -20
  61. machineconfig/jobs/python_custom_installers/dev/wezterm.py +12 -19
  62. machineconfig/jobs/python_custom_installers/dev/winget.py +5 -23
  63. machineconfig/jobs/python_custom_installers/docker.py +12 -21
  64. machineconfig/jobs/python_custom_installers/gh.py +11 -19
  65. machineconfig/jobs/python_custom_installers/hx.py +32 -16
  66. machineconfig/jobs/python_custom_installers/warp-cli.py +12 -20
  67. machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  68. machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  69. machineconfig/jobs/windows/archive/archive_pygraphviz.ps1 +1 -1
  70. machineconfig/jobs/windows/msc/cli_agents.bat +0 -0
  71. machineconfig/jobs/windows/msc/cli_agents.ps1 +0 -0
  72. machineconfig/jobs/windows/start_terminal.ps1 +1 -1
  73. machineconfig/profile/create.py +29 -22
  74. machineconfig/profile/create_hardlinks.py +26 -19
  75. machineconfig/profile/shell.py +51 -28
  76. machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  77. machineconfig/scripts/cloud/init.sh +2 -2
  78. machineconfig/scripts/linux/checkout_versions +1 -1
  79. machineconfig/scripts/linux/choose_wezterm_theme +1 -1
  80. machineconfig/scripts/linux/cloud_copy +1 -1
  81. machineconfig/scripts/linux/cloud_manager +1 -1
  82. machineconfig/scripts/linux/cloud_mount +1 -1
  83. machineconfig/scripts/linux/cloud_repo_sync +1 -1
  84. machineconfig/scripts/linux/cloud_sync +1 -1
  85. machineconfig/scripts/linux/croshell +1 -1
  86. machineconfig/scripts/linux/devops +4 -6
  87. machineconfig/scripts/linux/fire +1 -1
  88. machineconfig/scripts/linux/fire_agents +3 -2
  89. machineconfig/scripts/linux/ftpx +1 -1
  90. machineconfig/scripts/linux/gh_models +1 -1
  91. machineconfig/scripts/linux/kill_process +1 -1
  92. machineconfig/scripts/linux/mcinit +1 -1
  93. machineconfig/scripts/linux/repos +1 -1
  94. machineconfig/scripts/linux/scheduler +1 -1
  95. machineconfig/scripts/linux/start_slidev +1 -1
  96. machineconfig/scripts/linux/start_terminals +1 -1
  97. machineconfig/scripts/linux/url2md +1 -1
  98. machineconfig/scripts/linux/warp-cli.sh +122 -0
  99. machineconfig/scripts/linux/wifi_conn +1 -1
  100. machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
  101. machineconfig/scripts/python/__pycache__/croshell.cpython-313.pyc +0 -0
  102. machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
  103. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
  104. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
  105. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
  106. machineconfig/scripts/python/ai/__init__.py +0 -0
  107. machineconfig/scripts/python/ai/__pycache__/__init__.cpython-313.pyc +0 -0
  108. machineconfig/scripts/python/ai/__pycache__/generate_files.cpython-313.pyc +0 -0
  109. machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-313.pyc +0 -0
  110. machineconfig/scripts/python/ai/generate_files.py +84 -0
  111. machineconfig/scripts/python/ai/instructions/python/dev.instructions.md +2 -2
  112. machineconfig/scripts/python/ai/mcinit.py +7 -3
  113. machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +10 -5
  114. machineconfig/scripts/python/cloud_copy.py +1 -1
  115. machineconfig/scripts/python/cloud_mount.py +1 -1
  116. machineconfig/scripts/python/cloud_repo_sync.py +4 -4
  117. machineconfig/scripts/python/croshell.py +5 -3
  118. machineconfig/scripts/python/devops_add_identity.py +1 -1
  119. machineconfig/scripts/python/devops_add_ssh_key.py +1 -1
  120. machineconfig/scripts/python/devops_backup_retrieve.py +1 -1
  121. machineconfig/scripts/python/devops_update_repos.py +140 -52
  122. machineconfig/scripts/python/dotfile.py +1 -1
  123. machineconfig/scripts/python/fire_agents.py +28 -9
  124. machineconfig/scripts/python/fire_jobs.py +3 -4
  125. machineconfig/scripts/python/ftpx.py +2 -1
  126. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
  127. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
  128. machineconfig/scripts/python/helpers/helpers2.py +2 -2
  129. machineconfig/scripts/python/helpers/helpers4.py +1 -2
  130. machineconfig/scripts/python/helpers/repo_sync_helpers.py +1 -1
  131. machineconfig/scripts/python/mount_nfs.py +1 -1
  132. machineconfig/scripts/python/mount_ssh.py +1 -1
  133. machineconfig/scripts/python/repos.py +1 -1
  134. machineconfig/scripts/python/start_slidev.py +1 -1
  135. machineconfig/scripts/python/wsl_windows_transfer.py +1 -1
  136. machineconfig/scripts/windows/checkout_version.ps1 +1 -3
  137. machineconfig/scripts/windows/choose_wezterm_theme.ps1 +1 -3
  138. machineconfig/scripts/windows/cloud_copy.ps1 +2 -6
  139. machineconfig/scripts/windows/cloud_manager.ps1 +1 -1
  140. machineconfig/scripts/windows/cloud_repo_sync.ps1 +1 -2
  141. machineconfig/scripts/windows/cloud_sync.ps1 +2 -2
  142. machineconfig/scripts/windows/croshell.ps1 +2 -2
  143. machineconfig/scripts/windows/devops.ps1 +1 -4
  144. machineconfig/scripts/windows/dotfile.ps1 +1 -3
  145. machineconfig/scripts/windows/fire.ps1 +1 -1
  146. machineconfig/scripts/windows/ftpx.ps1 +2 -2
  147. machineconfig/scripts/windows/gpt.ps1 +1 -1
  148. machineconfig/scripts/windows/kill_process.ps1 +1 -2
  149. machineconfig/scripts/windows/mcinit.ps1 +1 -1
  150. machineconfig/scripts/windows/mount_nfs.ps1 +1 -1
  151. machineconfig/scripts/windows/mount_ssh.ps1 +1 -1
  152. machineconfig/scripts/windows/pomodoro.ps1 +1 -1
  153. machineconfig/scripts/windows/py2exe.ps1 +1 -3
  154. machineconfig/scripts/windows/repos.ps1 +1 -1
  155. machineconfig/scripts/windows/scheduler.ps1 +1 -1
  156. machineconfig/scripts/windows/snapshot.ps1 +2 -2
  157. machineconfig/scripts/windows/start_slidev.ps1 +1 -1
  158. machineconfig/scripts/windows/start_terminals.ps1 +1 -1
  159. machineconfig/scripts/windows/wifi_conn.ps1 +1 -1
  160. machineconfig/scripts/windows/wsl_windows_transfer.ps1 +1 -3
  161. machineconfig/settings/lf/linux/lfrc +1 -1
  162. machineconfig/settings/linters/.ruff_cache/.gitignore +2 -0
  163. machineconfig/settings/linters/.ruff_cache/CACHEDIR.TAG +1 -0
  164. machineconfig/settings/lvim/windows/archive/config_additional.lua +1 -1
  165. machineconfig/settings/svim/linux/init.toml +1 -1
  166. machineconfig/settings/svim/windows/init.toml +1 -1
  167. machineconfig/setup_linux/web_shortcuts/croshell.sh +0 -54
  168. machineconfig/setup_linux/web_shortcuts/interactive.sh +6 -6
  169. machineconfig/setup_windows/web_shortcuts/all.ps1 +2 -2
  170. machineconfig/setup_windows/web_shortcuts/ascii_art.ps1 +1 -1
  171. machineconfig/setup_windows/web_shortcuts/croshell.ps1 +1 -1
  172. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +5 -5
  173. machineconfig/setup_windows/wt_and_pwsh/install_fonts.ps1 +51 -15
  174. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +66 -12
  175. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +44 -36
  176. machineconfig/utils/ai/generate_file_checklist.py +8 -10
  177. machineconfig/utils/ai/url2md.py +4 -2
  178. machineconfig/utils/cloud/onedrive/setup_oauth.py +1 -0
  179. machineconfig/utils/cloud/onedrive/transaction.py +63 -98
  180. machineconfig/utils/code.py +60 -39
  181. machineconfig/utils/installer.py +27 -33
  182. machineconfig/utils/installer_utils/installer_abc.py +8 -7
  183. machineconfig/utils/installer_utils/installer_class.py +149 -70
  184. machineconfig/utils/links.py +22 -11
  185. machineconfig/utils/notifications.py +197 -0
  186. machineconfig/utils/options.py +29 -23
  187. machineconfig/utils/path.py +13 -6
  188. machineconfig/utils/path_reduced.py +485 -216
  189. machineconfig/utils/procs.py +47 -41
  190. machineconfig/utils/scheduling.py +0 -1
  191. machineconfig/utils/ssh.py +157 -76
  192. machineconfig/utils/terminal.py +82 -37
  193. machineconfig/utils/utils.py +12 -10
  194. machineconfig/utils/utils2.py +38 -48
  195. machineconfig/utils/utils5.py +183 -116
  196. machineconfig/utils/ve.py +9 -4
  197. {machineconfig-2.0.dist-info → machineconfig-2.1.dist-info}/METADATA +3 -2
  198. {machineconfig-2.0.dist-info → machineconfig-2.1.dist-info}/RECORD +200 -217
  199. machineconfig/jobs/__pycache__/__init__.cpython-311.pyc +0 -0
  200. machineconfig/jobs/python/__pycache__/__init__.cpython-311.pyc +0 -0
  201. machineconfig/jobs/python/__pycache__/python_ve_symlink.cpython-311.pyc +0 -0
  202. machineconfig/jobs/python/archive/python_tools.txt +0 -12
  203. machineconfig/jobs/python/vscode/__pycache__/select_interpreter.cpython-311.pyc +0 -0
  204. machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  205. machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  206. machineconfig/jobs/python_generic_installers/update.py +0 -3
  207. machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  208. machineconfig/profile/__pycache__/__init__.cpython-311.pyc +0 -0
  209. machineconfig/profile/__pycache__/create.cpython-311.pyc +0 -0
  210. machineconfig/profile/__pycache__/shell.cpython-311.pyc +0 -0
  211. machineconfig/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  212. machineconfig/scripts/linux/activate_ve +0 -87
  213. machineconfig/scripts/python/__pycache__/__init__.cpython-311.pyc +0 -0
  214. machineconfig/scripts/python/__pycache__/cloud_copy.cpython-311.pyc +0 -0
  215. machineconfig/scripts/python/__pycache__/cloud_mount.cpython-311.pyc +0 -0
  216. machineconfig/scripts/python/__pycache__/cloud_sync.cpython-311.pyc +0 -0
  217. machineconfig/scripts/python/__pycache__/croshell.cpython-311.pyc +0 -0
  218. machineconfig/scripts/python/__pycache__/devops.cpython-311.pyc +0 -0
  219. machineconfig/scripts/python/__pycache__/devops_backup_retrieve.cpython-311.pyc +0 -0
  220. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-311.pyc +0 -0
  221. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-311.pyc +0 -0
  222. machineconfig/scripts/python/__pycache__/fire_agents.cpython-311.pyc +0 -0
  223. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-311.pyc +0 -0
  224. machineconfig/scripts/python/__pycache__/get_zellij_cmd.cpython-311.pyc +0 -0
  225. machineconfig/scripts/python/__pycache__/repos.cpython-311.pyc +0 -0
  226. machineconfig/scripts/python/ai/__pycache__/init.cpython-311.pyc +0 -0
  227. machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-311.pyc +0 -0
  228. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-311.pyc +0 -0
  229. machineconfig/scripts/python/helpers/__pycache__/cloud_helpers.cpython-311.pyc +0 -0
  230. machineconfig/scripts/python/helpers/__pycache__/helpers2.cpython-311.pyc +0 -0
  231. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-311.pyc +0 -0
  232. machineconfig/scripts/python/helpers/__pycache__/repo_sync_helpers.cpython-311.pyc +0 -0
  233. machineconfig/scripts/windows/activate_ve.ps1 +0 -54
  234. {machineconfig-2.0.dist-info → machineconfig-2.1.dist-info}/WHEEL +0 -0
  235. {machineconfig-2.0.dist-info → machineconfig-2.1.dist-info}/top_level.txt +0 -0
@@ -1,31 +1,44 @@
1
-
2
- from typing import Callable, Optional, Union, Any, NoReturn, TypeVar, Protocol, List
1
+ from pathlib import Path
2
+ from typing import Callable, Optional, Union, Any, NoReturn, TypeVar, Protocol, List, Generic
3
3
  import logging
4
4
  import time
5
5
  from datetime import datetime, timezone, timedelta
6
+ from machineconfig.utils.path_reduced import PathExtended as PathExtended
6
7
 
7
8
 
8
9
  class LoggerTemplate(Protocol):
9
10
  handlers: List[logging.Handler]
11
+
10
12
  def debug(self, msg: str) -> None:
11
13
  pass # 10
14
+
12
15
  def info(self, msg: str) -> None:
13
16
  pass # 20
17
+
14
18
  def warning(self, msg: str) -> None:
15
19
  pass # 30
20
+
16
21
  def error(self, msg: str) -> None:
17
22
  pass # 40
23
+
18
24
  def critical(self, msg: str) -> None:
19
25
  pass # 50
26
+
20
27
  def fatal(self, msg: str) -> None:
21
28
  pass # 50
22
29
 
23
30
 
24
31
  class Scheduler:
25
- def __init__(self, routine: Callable[['Scheduler'], Any], wait_ms: int,
26
- logger: LoggerTemplate, sess_stats: Optional[Callable[['Scheduler'], dict[str, Any]]] = None,
27
- exception_handler: Optional[Callable[[Union[Exception, KeyboardInterrupt], str, 'Scheduler'], Any]] = None,
28
- max_cycles: int = 1_000_000_000, records: Optional[list[list[Any]]] = None):
32
+ def __init__(
33
+ self,
34
+ routine: Callable[["Scheduler"], Any],
35
+ wait_ms: int,
36
+ logger: LoggerTemplate,
37
+ sess_stats: Optional[Callable[["Scheduler"], dict[str, Any]]] = None,
38
+ exception_handler: Optional[Callable[[Union[Exception, KeyboardInterrupt], str, "Scheduler"], Any]] = None,
39
+ max_cycles: int = 1_000_000_000,
40
+ records: Optional[list[list[Any]]] = None,
41
+ ):
29
42
  self.routine = routine # main routine to be repeated every `wait` time period
30
43
  self.logger = logger
31
44
  self.exception_handler = exception_handler if exception_handler is not None else self.default_exception_handler
@@ -35,8 +48,11 @@ class Scheduler:
35
48
  self.max_cycles: int = max_cycles
36
49
  self.sess_start_utc_ms: int
37
50
  self.sess_stats = sess_stats or (lambda _sched: {})
38
- def __repr__(self): return f"Scheduler with {self.cycle} cycles ran so far. Last cycle was at {self.sess_start_utc_ms}."
39
- def run(self, max_cycles: Optional[int]=None, until_ms: Optional[int]=None):
51
+
52
+ def __repr__(self):
53
+ return f"Scheduler with {self.cycle} cycles ran so far. Last cycle was at {self.sess_start_utc_ms}."
54
+
55
+ def run(self, max_cycles: Optional[int] = None, until_ms: Optional[int] = None):
40
56
  if max_cycles is not None:
41
57
  self.max_cycles = max_cycles
42
58
  if until_ms is None:
@@ -54,33 +70,46 @@ class Scheduler:
54
70
  time2_ms = time.time_ns() // 1_000_000
55
71
  time_left_ms = int(self.wait_ms - (time2_ms - time1_ms)) # 4- Conclude Message
56
72
  self.cycle += 1
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)
58
- try: time.sleep(time_left_ms*0.001 if time_left_ms > 0 else 0.0) # # 5- Sleep. consider replacing by Asyncio.sleep
73
+ 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)
74
+ try:
75
+ time.sleep(time_left_ms * 0.001 if time_left_ms > 0 else 0.0) # # 5- Sleep. consider replacing by Asyncio.sleep
59
76
  except KeyboardInterrupt as ex:
60
77
  self.exception_handler(ex, "sleep", self)
61
78
  return # that's probably the only kind of exception that can rise during sleep.
62
79
  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})")
80
+
63
81
  def get_records_df(self) -> List[dict[str, Any]]:
64
82
  columns = ["start", "finish", "duration", "cycles", "termination reason"] + list(self.sess_stats(self).keys())
65
83
  return [dict(zip(columns, row)) for row in self.records]
84
+
66
85
  def record_session_end(self, reason: str):
67
86
  end_time_ms = time.time_ns() // 1_000_000
68
87
  duration_ms = end_time_ms - self.sess_start_utc_ms
69
88
  sess_stats = self.sess_stats(self)
70
- self.records.append([self.sess_start_utc_ms, end_time_ms, duration_ms, self.cycle, reason,
71
- # self.logger.file_path
72
- ] + list(sess_stats.values()))
89
+ self.records.append(
90
+ [
91
+ self.sess_start_utc_ms,
92
+ end_time_ms,
93
+ duration_ms,
94
+ self.cycle,
95
+ reason,
96
+ # self.logger.file_path
97
+ ]
98
+ + list(sess_stats.values())
99
+ )
73
100
  records_df = self.get_records_df()
74
101
  total_cycles = sum(row["cycles"] for row in records_df)
75
- summ = {"start time": f"{str(self.sess_start_utc_ms)}",
76
- "finish time": f"{str(end_time_ms)}.",
77
- "duration": f"{str(duration_ms)} | wait time {self.wait_ms/1_000: 0.1f}s",
78
- "cycles ran": f"{self.cycle} | Lifetime cycles = {total_cycles}",
79
- "termination reason": reason,
80
- # "logfile": self.logger.file_path
81
- }
102
+ summ = {
103
+ "start time": f"{str(self.sess_start_utc_ms)}",
104
+ "finish time": f"{str(end_time_ms)}.",
105
+ "duration": f"{str(duration_ms)} | wait time {self.wait_ms / 1_000: 0.1f}s",
106
+ "cycles ran": f"{self.cycle} | Lifetime cycles = {total_cycles}",
107
+ "termination reason": reason,
108
+ # "logfile": self.logger.file_path
109
+ }
82
110
  summ.update(sess_stats)
83
111
  from machineconfig.utils.utils2 import get_repr
112
+
84
113
  tmp = get_repr(summ)
85
114
  self.logger.critical("\n--> Scheduler has finished running a session. \n" + tmp + "\n" + "-" * 100)
86
115
  # Format records as table
@@ -105,109 +134,147 @@ class Scheduler:
105
134
  table_str = "No records available."
106
135
  self.logger.critical("\n--> Logger history.\n" + table_str)
107
136
  return self
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.
137
+
138
+ 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.
109
139
  print(sched)
110
140
  self.record_session_end(reason=f"during {during}, " + str(ex))
111
141
  self.logger.fatal(str(ex))
112
142
  raise ex
113
143
 
114
144
 
115
- T = TypeVar('T')
116
- T2 = TypeVar('T2')
145
+ T = TypeVar("T")
146
+ T2 = TypeVar("T2")
147
+
148
+
117
149
  class PrintFunc(Protocol):
118
150
  def __call__(self, msg: str) -> Union[NoReturn, None]: ...
119
151
 
152
+ def to_pickle(obj: Any, path: Path) -> None:
153
+ import pickle
154
+ path.parent.mkdir(parents=True, exist_ok=True)
155
+ path.write_bytes(pickle.dumps(obj))
156
+ def from_pickle(path: Path) -> Any:
157
+ import pickle
158
+ return pickle.loads(path.read_bytes())
159
+
120
160
 
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
161
+ class Cache(Generic[T]): # This class helps to accelrate access to latest data coming from expensive function. The class has two flavours, memory-based and disk-based variants."""
162
+ # source_func: Callable[[], T]
163
+ def __init__(self, source_func: Callable[[], T],
164
+ expire: timedelta, logger: Optional[PrintFunc] = None, path: Optional[Path] = None,
165
+ saver: Callable[[T, Path], Any] = to_pickle, reader: Callable[[Path], T] = from_pickle, name: Optional[str] = None) -> None:
166
+ self.cache: T
167
+ self.source_func = source_func # function which when called returns a fresh object to be frozen.
168
+ self.path: Optional[PathExtended] = PathExtended(path) if path is not None else None # if path is passed, it will function as disk-based flavour.
169
+ self.time_produced = datetime.now() # if path is None else
170
+ self.save = saver
171
+ self.reader = reader
172
+ self.logger = logger
173
+ self.expire = expire
174
+ self.name = name if isinstance(name, str) else str(self.source_func)
175
+ self.last_call_is_fresh = False
176
+ @property
177
+ def age(self):
178
+ """Throws AttributeError if called before cache is populated and path doesn't exists"""
179
+ if self.path is None: # memory-based cache.
180
+ return datetime.now() - self.time_produced
181
+ return datetime.now() - datetime.fromtimestamp(self.path.stat().st_mtime)
182
+ # def __setstate__(self, state: dict[str, Any]) -> None:
183
+ # self.__dict__.update(state)
184
+ # self.path = P.home() / self.path if self.path is not None else self.path
185
+ # def __getstate__(self) -> dict[str, Any]:
186
+ # state = self.__dict__.copy()
187
+ # state["path"] = self.path.rel2home() if self.path is not None else state["path"]
188
+ # return state # With this implementation, instances can be pickled and loaded up in different machine and still works.
189
+ def __call__(self, fresh: bool = False) -> T:
190
+ self.last_call_is_fresh = False
191
+ if fresh or not hasattr(self, "cache"): # populate cache for the first time
192
+ if not fresh and self.path is not None and self.path.exists():
193
+ age = datetime.now() - datetime.fromtimestamp(self.path.stat().st_mtime)
194
+ msg1 = f"""
195
+ đŸ“Ļ ════════════════════ CACHE OPERATION ════════════════════
196
+ 🔄 {self.name} cache: Reading cached values from `{self.path}`
197
+ âąī¸ Lag = {age}
198
+ ════════════════════════════════════════════════════════════
199
+ """
200
+ try:
201
+ self.cache = self.reader(self.path)
202
+ except Exception as ex:
203
+ if self.logger:
204
+ msg2 = f"""
205
+ ❌ ════════════════════ CACHE ERROR ════════════════════
206
+ âš ī¸ {self.name} cache: Cache file is corrupted
207
+ 🔍 Error: {ex}
208
+ ════════════════════════════════════════════════════════
209
+ """
210
+ self.logger(msg1 + msg2)
211
+ self.cache = self.source_func()
212
+ self.last_call_is_fresh = True
213
+ self.time_produced = datetime.now()
214
+ # if self.path is not None:
215
+ # self.save(self.cache, self.path)
216
+ return self.cache
217
+ return self(fresh=False) # may be the cache is old ==> check that by passing it through the logic again.
218
+ else:
219
+ if self.logger:
220
+ # Previous cache never existed or there was an explicit fresh order.
221
+ why = "There was an explicit fresh order." if fresh else "Previous cache never existed or is corrupted."
222
+ self.logger(f"""
223
+ 🆕 ════════════════════ NEW CACHE ════════════════════
224
+ 🔄 {self.name} cache: Populating fresh cache from source func
225
+ â„šī¸ Reason: {why}
226
+ ════════════════════════════════════════════════════════
227
+ """)
228
+ self.cache = self.source_func() # fresh data.
229
+ self.last_call_is_fresh = True
230
+ self.time_produced = datetime.now()
231
+ if self.path is not None: self.save(self.cache, self.path)
232
+ else: # cache exists
233
+ try: age = self.age
234
+ except AttributeError: # path doesn't exist (may be deleted) ==> need to repopulate cache form source_func.
235
+ return self(fresh=True)
236
+ if age > self.expire:
237
+ if self.logger:
238
+ self.logger(f"""
239
+ 🔄 ════════════════════ CACHE UPDATE ════════════════════
240
+ âš ī¸ {self.name} cache: Updating cache from source func
241
+ âąī¸ Age = {age} > {self.expire}
242
+ ════════════════════════════════════════════════════════""")
243
+ self.cache = self.source_func()
244
+ self.last_call_is_fresh = True
245
+ self.time_produced = datetime.now()
246
+ if self.path is not None: self.save(self.cache, self.path)
247
+ else:
248
+ if self.logger:
249
+ self.logger(f"""
250
+ ✅ ════════════════════ USING CACHE ════════════════════
251
+ đŸ“Ļ {self.name} cache: Using cached values
252
+ âąī¸ Lag = {age}
253
+ ════════════════════════════════════════════════════════""")
254
+ return self.cache
255
+ @staticmethod
256
+ def as_decorator(expire: timedelta, logger: Optional[PrintFunc] = None, path: Optional[Path] = None,
257
+ saver: Callable[[T2, Path], Any] = to_pickle,
258
+ reader: Callable[[Path], T2] = from_pickle,
259
+ name: Optional[str] = None): # -> Callable[..., 'Cache[T2]']:
260
+ def decorator(source_func: Callable[[], T2]) -> Cache['T2']:
261
+ res = Cache(source_func=source_func, expire=expire, logger=logger, path=path, name=name, reader=reader, saver=saver)
262
+ return res
263
+ return decorator
264
+ def from_cloud(self, cloud: str, rel2home: bool = True, root: Optional[str] = None):
265
+ assert self.path is not None
266
+ exists = self.path.exists()
267
+ exists_but_old = exists and ((datetime.now() - datetime.fromtimestamp(self.path.stat().st_mtime)) > self.expire)
268
+ if not exists or exists_but_old:
269
+ returned_path = self.path.from_cloud(cloud=cloud, rel2home=rel2home, root=root)
270
+ if returned_path is None and not exists:
271
+ raise FileNotFoundError(f"❌ Failed to get @ {self.path}. Build the cache first with signed API.")
272
+ elif returned_path is None and exists and self.logger is not None:
273
+ self.logger(f"""
274
+ âš ī¸ ════════════════════ CLOUD FETCH WARNING ════════════════════
275
+ 🔄 Failed to get fresh data from cloud
276
+ đŸ“Ļ Using old cache @ {self.path}
277
+ ════════════════════════════════════════════════════════════════""")
278
+ else:
279
+ pass # maybe we don't need to fetch it from cloud, if its too hot
280
+ return self.reader(self.path)
machineconfig/utils/ve.py CHANGED
@@ -1,4 +1,4 @@
1
- from machineconfig.utils.path_reduced import P as PathExtended
1
+ from machineconfig.utils.path_reduced import PathExtended as PathExtended
2
2
  from machineconfig.utils.utils2 import read_ini
3
3
  import platform
4
4
  from typing import Optional
@@ -37,6 +37,7 @@ def get_ve_path_and_ipython_profile(init_path: PathExtended) -> tuple[Optional[s
37
37
 
38
38
  def get_repo_root(choice_file: str) -> Optional[str]:
39
39
  from git import Repo, InvalidGitRepositoryError
40
+
40
41
  try:
41
42
  repo = Repo(PathExtended(choice_file), search_parent_directories=True)
42
43
  repo_root = str(repo.working_tree_dir) if repo.working_tree_dir else None
@@ -44,8 +45,12 @@ def get_repo_root(choice_file: str) -> Optional[str]:
44
45
  repo_root = None
45
46
  return repo_root
46
47
 
48
+
47
49
  def get_ve_activate_line(ve_root: str):
48
- if platform.system() == "Windows": activate_ve_line = f". {ve_root}/Scripts/activate.ps1"
49
- elif platform.system() in ["Linux", "Darwin"]: activate_ve_line = f". {ve_root}/bin/activate"
50
- else: raise NotImplementedError(f"Platform {platform.system()} not supported.")
50
+ if platform.system() == "Windows":
51
+ activate_ve_line = f". {ve_root}/Scripts/activate.ps1"
52
+ elif platform.system() in ["Linux", "Darwin"]:
53
+ activate_ve_line = f". {ve_root}/bin/activate"
54
+ else:
55
+ raise NotImplementedError(f"Platform {platform.system()} not supported.")
51
56
  return activate_ve_line
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: machineconfig
3
- Version: 2.0
3
+ Version: 2.1
4
4
  Summary: Dotfiles management package
5
5
  Author-email: Alex Al-Saffar <programmer@usa.com>
6
6
  License: Apache 2.0
@@ -30,6 +30,7 @@ Requires-Dist: joblib>=1.5.2
30
30
  Requires-Dist: randomname>=0.2.1
31
31
  Requires-Dist: cryptography>=44.0.2
32
32
  Requires-Dist: tenacity>=9.1.2
33
+ Requires-Dist: markdown>=3.9
33
34
  Provides-Extra: windows
34
35
  Requires-Dist: pywin32; extra == "windows"
35
36
  Provides-Extra: docs
@@ -98,7 +99,7 @@ iwr https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/mac
98
99
  (iwr bit.ly/cfgwt).Content | iex
99
100
  . $HOME/code/machineconfig/src/machineconfig/setup_windows/symlinks.ps1
100
101
 
101
- & "$HOME\venvs\ve\Scripts\activate.ps1"
102
+ & "$HOME\code\machineconfig\.venv\Scripts\activate.ps1"
102
103
  python -m fire machineconfig.profile.create main2 --choice=all
103
104
  deactivate
104
105