machineconfig 6.45__py3-none-any.whl → 6.48__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 (72) hide show
  1. machineconfig/scripts/python/cloud.py +3 -3
  2. machineconfig/scripts/python/croshell.py +8 -6
  3. machineconfig/scripts/python/devops.py +6 -6
  4. machineconfig/scripts/python/devops_navigator.py +1 -1
  5. machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
  6. machineconfig/scripts/python/ftpx.py +1 -1
  7. machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/cloud_copy.py +2 -2
  8. machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/cloud_sync.py +3 -3
  9. machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/helpers2.py +1 -1
  10. machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_config.py +3 -3
  11. machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_data.py +2 -2
  12. machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_nw.py +2 -2
  13. machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_repos.py +10 -10
  14. machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_self.py +4 -4
  15. machineconfig/scripts/python/{devops_helpers → helpers_devops}/devops_backup_retrieve.py +1 -1
  16. machineconfig/scripts/python/{devops_helpers → helpers_devops}/devops_update_repos.py +1 -1
  17. machineconfig/scripts/python/helpers_navigator/__init__.py +20 -0
  18. machineconfig/scripts/python/{helper_navigator → helpers_navigator}/command_builder.py +1 -1
  19. machineconfig/scripts/python/{helper_navigator → helpers_navigator}/command_detail.py +1 -1
  20. machineconfig/scripts/python/{helper_navigator → helpers_navigator}/command_tree.py +1 -1
  21. machineconfig/scripts/python/{helper_navigator → helpers_navigator}/main_app.py +5 -5
  22. machineconfig/scripts/python/{repos_helpers → helpers_repos}/action.py +1 -1
  23. machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +3 -3
  24. machineconfig/scripts/python/{repos_helpers → helpers_repos}/count_lines_frontend.py +2 -2
  25. machineconfig/scripts/python/{repos_helpers → helpers_repos}/entrypoint.py +2 -2
  26. machineconfig/scripts/python/interactive.py +3 -3
  27. machineconfig/scripts/python/nw/mount_nfs +1 -1
  28. machineconfig/scripts/python/sessions.py +1 -1
  29. machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
  30. machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
  31. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +1 -1
  32. machineconfig/utils/code.py +28 -67
  33. machineconfig/utils/meta.py +67 -34
  34. machineconfig/utils/procs.py +10 -23
  35. machineconfig/utils/scheduler.py +30 -36
  36. machineconfig/utils/ssh.py +1 -1
  37. {machineconfig-6.45.dist-info → machineconfig-6.48.dist-info}/METADATA +1 -1
  38. {machineconfig-6.45.dist-info → machineconfig-6.48.dist-info}/RECORD +70 -71
  39. machineconfig/scripts/python/helper_navigator/__init__.py +0 -20
  40. machineconfig/scripts/python/sessions_helpers/__init__.py +0 -0
  41. /machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/__init__.py +0 -0
  42. /machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/cloud_helpers.py +0 -0
  43. /machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/cloud_mount.py +0 -0
  44. /machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/helpers5.py +0 -0
  45. /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/__init__.py +0 -0
  46. /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/crosh.py +0 -0
  47. /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/pomodoro.py +0 -0
  48. /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/scheduler.py +0 -0
  49. /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/start_slidev.py +0 -0
  50. /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/viewer.py +0 -0
  51. /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/viewer_template.py +0 -0
  52. /machineconfig/scripts/python/{devops_helpers → helpers_devops}/__init__.py +0 -0
  53. /machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_config_dotfile.py +0 -0
  54. /machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_share_server.py +0 -0
  55. /machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_terminal.py +0 -0
  56. /machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_utils.py +0 -0
  57. /machineconfig/scripts/python/{devops_helpers → helpers_devops}/devops_status.py +0 -0
  58. /machineconfig/scripts/python/{devops_helpers → helpers_devops}/themes/__init__.py +0 -0
  59. /machineconfig/scripts/python/{devops_helpers → helpers_devops}/themes/choose_pwsh_theme.ps1 +0 -0
  60. /machineconfig/scripts/python/{devops_helpers → helpers_devops}/themes/choose_wezterm_theme.py +0 -0
  61. /machineconfig/scripts/python/{helper_navigator → helpers_navigator}/data_models.py +0 -0
  62. /machineconfig/scripts/python/{helper_navigator → helpers_navigator}/search_bar.py +0 -0
  63. /machineconfig/scripts/python/{repos_helpers → helpers_repos}/clone.py +0 -0
  64. /machineconfig/scripts/python/{repos_helpers → helpers_repos}/count_lines.py +0 -0
  65. /machineconfig/scripts/python/{repos_helpers → helpers_repos}/record.py +0 -0
  66. /machineconfig/scripts/python/{repos_helpers → helpers_repos}/sync.py +0 -0
  67. /machineconfig/scripts/python/{repos_helpers → helpers_repos}/update.py +0 -0
  68. /machineconfig/scripts/python/{helpers_repos → helpers_sessions}/__init__.py +0 -0
  69. /machineconfig/scripts/python/{sessions_helpers → helpers_sessions}/sessions_multiprocess.py +0 -0
  70. {machineconfig-6.45.dist-info → machineconfig-6.48.dist-info}/WHEEL +0 -0
  71. {machineconfig-6.45.dist-info → machineconfig-6.48.dist-info}/entry_points.txt +0 -0
  72. {machineconfig-6.45.dist-info → machineconfig-6.48.dist-info}/top_level.txt +0 -0
@@ -1,15 +1,14 @@
1
+ import atexit
1
2
  import platform
2
3
  from typing import Optional
3
4
  import subprocess
4
- import os
5
- # import time
6
5
  from rich.console import Console
7
6
  from rich.panel import Panel
8
7
  from rich.syntax import Syntax
9
8
 
10
9
  from machineconfig.utils.accessories import randstr
11
10
  from machineconfig.utils.ve import get_ve_activate_line
12
- from machineconfig.utils.path_extended import PathExtended
11
+ from machineconfig.utils.path_extended import PathExtended # type: ignore[import-not-found]
13
12
 
14
13
 
15
14
  def get_shell_script_executing_python_file(python_file: str, func: Optional[str], ve_path: Optional[str], executable: Optional[str], strict_execution: bool = True):
@@ -138,71 +137,33 @@ def run_shell_script(script: str, display_script: bool = True, clean_env: bool =
138
137
  return proc
139
138
 
140
139
 
141
- def run_shell_script_after_exit(script: str, check_interval: float = 0.1, display_script: bool = True) -> subprocess.Popen[bytes]:
142
- current_pid = os.getpid()
140
+ def run_shell_script_after_exit(script: str, display_script: bool = True) -> None:
143
141
  console = Console()
144
142
 
145
- if platform.system() == "Windows":
146
- monitor_script = f"""$ErrorActionPreference = "Stop"
147
- $targetPid = {current_pid}
148
- $checkInterval = {check_interval}
149
-
150
- Write-Host "🔍 Monitoring process PID: $targetPid"
151
-
152
- while ($true) {{
153
- $process = Get-Process -Id $targetPid -ErrorAction SilentlyContinue
154
- if (-not $process) {{
155
- Write-Host "✅ Process $targetPid has exited. Running script..."
156
- break
157
- }}
158
- Start-Sleep -Seconds $checkInterval
159
- }}
160
-
161
- # Execute the provided script
162
- {script}
163
- """
164
- suffix = ".ps1"
165
- cmd = ["powershell", "-ExecutionPolicy", "Bypass", "-NoProfile", "-WindowStyle", "Hidden", "-File"]
166
- else:
167
- monitor_script = f"""#!/bin/bash
168
- target_pid={current_pid}
169
- check_interval={check_interval}
170
-
171
- echo "🔍 Monitoring process PID: $target_pid"
172
-
173
- while kill -0 $target_pid 2>/dev/null; do
174
- sleep $check_interval
175
- done
176
-
177
- echo "✅ Process $target_pid has exited. Running script..."
178
-
179
- # Execute the provided script
180
- {script}
181
- """
182
- suffix = ".sh"
183
- cmd = ["bash"]
184
-
185
- monitor_script_path = PathExtended.tmp().joinpath("tmp_scripts", "monitor", randstr() + suffix)
186
- monitor_script_path.parent.mkdir(parents=True, exist_ok=True)
187
- monitor_script_path.write_text(monitor_script, encoding="utf-8")
188
-
189
- if display_script:
190
- lexer = "powershell" if platform.system() == "Windows" else "bash"
191
- console.print(Panel(Syntax(code=monitor_script, lexer=lexer), title=f"📄 Monitor script @ {monitor_script_path}", subtitle="Will run after current process exits"), style="bold yellow")
192
-
193
- if platform.system() != "Windows":
194
- monitor_script_path.chmod(0o755)
195
-
196
- cmd.append(str(monitor_script_path))
197
-
198
- if platform.system() == "Windows":
199
- creation_flags = subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS # type: ignore
200
- process = subprocess.Popen(cmd, creationflags=creation_flags, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL)
201
- else:
202
- process = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, start_new_session=True)
203
- if display_script:
204
- console.print(f"🚀 [green]Monitor process started with PID:[/green] [blue]{process.pid}[/blue]")
205
- console.print(f"📍 [yellow]Watching PID:[/yellow] [blue]{current_pid}[/blue]")
143
+ def execute_script_at_exit() -> None:
144
+ if platform.system() == "Windows":
145
+ suffix = ".ps1"
146
+ lexer = "powershell"
147
+ else:
148
+ suffix = ".sh"
149
+ lexer = "bash"
150
+
151
+ script_path = PathExtended.tmp().joinpath("tmp_scripts", "exit", randstr() + suffix)
152
+ script_path.parent.mkdir(parents=True, exist_ok=True)
153
+ script_path.write_text(script, encoding="utf-8")
154
+
155
+ if display_script:
156
+ console.print(Panel(Syntax(code=script, lexer=lexer), title=f"📄 Exit script @ {script_path}", subtitle="Running at exit"), style="bold yellow")
157
+
158
+ if platform.system() != "Windows":
159
+ script_path.chmod(0o755)
160
+
161
+ if platform.system() == "Windows":
162
+ subprocess.run(["powershell", "-ExecutionPolicy", "Bypass", "-NoProfile", "-File", str(script_path)], check=False)
163
+ else:
164
+ subprocess.run(["bash", str(script_path)], check=False)
165
+
166
+ script_path.unlink(missing_ok=True)
206
167
 
207
- return process
168
+ atexit.register(execute_script_at_exit)
208
169
 
@@ -3,11 +3,14 @@
3
3
  import ast
4
4
  import inspect
5
5
  import textwrap
6
+ from collections.abc import Callable, Mapping
6
7
  from types import FunctionType, ModuleType
7
- from typing import Any
8
+ from typing import ParamSpec
8
9
 
10
+ P = ParamSpec("P")
9
11
 
10
- def function_to_script(func: FunctionType, call_with_args: tuple[Any, ...] | None = None, call_with_kwargs: dict[str, Any] | None = None) -> str:
12
+
13
+ def function_to_script(func: Callable[P, object], call_with_kwargs: Mapping[str, object] | None) -> str:
11
14
  """Convert a function to a standalone executable Python script.
12
15
 
13
16
  This function analyzes a given function and generates a complete Python script
@@ -16,7 +19,6 @@ def function_to_script(func: FunctionType, call_with_args: tuple[Any, ...] | Non
16
19
 
17
20
  Args:
18
21
  func: The function to convert to a script
19
- call_with_args: Optional tuple of positional arguments to call the function with
20
22
  call_with_kwargs: Optional dict of keyword arguments to call the function with
21
23
 
22
24
  Returns:
@@ -25,35 +27,37 @@ def function_to_script(func: FunctionType, call_with_args: tuple[Any, ...] | Non
25
27
  Raises:
26
28
  ValueError: If the function cannot be inspected or analyzed
27
29
  """
28
- if not callable(func) or not hasattr(func, '__code__'):
29
- raise ValueError(f"Expected a function, got {type(func)}")
30
-
31
- call_with_args = call_with_args or ()
32
- call_with_kwargs = call_with_kwargs or {}
33
-
34
- imports = _extract_imports(func)
35
- globals_needed = _extract_globals(func)
36
- source_code = _get_function_source(func)
37
- call_statement = _generate_call_statement(func, call_with_args, call_with_kwargs)
38
-
30
+ if not isinstance(func, FunctionType):
31
+ raise ValueError(f"""Expected a Python function, got {type(func)}""")
32
+
33
+ python_func = func
34
+
35
+ imports = _extract_imports(python_func)
36
+ globals_needed = _extract_globals(python_func)
37
+ source_code = _get_function_source(python_func).rstrip()
38
+ validated_kwargs = _prepare_call_kwargs(python_func, call_with_kwargs)
39
+ call_statement = _generate_call_statement(python_func, validated_kwargs) if validated_kwargs is not None else None
40
+
39
41
  script_parts: list[str] = []
40
-
42
+
41
43
  if imports:
42
44
  script_parts.append(imports)
43
- script_parts.append("")
44
-
45
+
45
46
  if globals_needed:
47
+ if script_parts:
48
+ script_parts.append("")
46
49
  script_parts.append(globals_needed)
50
+
51
+ if script_parts:
47
52
  script_parts.append("")
48
-
53
+
49
54
  script_parts.append(source_code)
50
- script_parts.append("")
51
-
52
- if call_statement:
55
+
56
+ if call_statement is not None:
53
57
  script_parts.append("")
54
58
  script_parts.append("if __name__ == '__main__':")
55
59
  script_parts.append(f" {call_statement}")
56
-
60
+
57
61
  return "\n".join(script_parts)
58
62
 
59
63
 
@@ -149,18 +153,47 @@ def _extract_globals(func: FunctionType) -> str:
149
153
  return "\n".join(global_assignments)
150
154
 
151
155
 
152
- def _generate_call_statement(func: FunctionType, args: tuple[Any, ...], kwargs: dict[str, Any]) -> str:
153
- """Generate a function call statement with the given arguments."""
154
- if not args and not kwargs:
156
+ def _prepare_call_kwargs(func: FunctionType, call_with_kwargs: Mapping[str, object] | None) -> dict[str, object] | None:
157
+ if call_with_kwargs is None:
158
+ return None
159
+
160
+ normalized_kwargs = dict(call_with_kwargs)
161
+
162
+ if not normalized_kwargs:
163
+ return {}
164
+
165
+ signature = inspect.signature(func)
166
+ positional_only = [parameter.name for parameter in signature.parameters.values() if parameter.kind is inspect.Parameter.POSITIONAL_ONLY]
167
+
168
+ if positional_only:
169
+ joined = ", ".join(positional_only)
170
+ raise ValueError(f"""Cannot call {func.__name__} with positional-only parameters: {joined}""")
171
+
172
+ try:
173
+ signature.bind(**normalized_kwargs)
174
+ except TypeError as error:
175
+ raise ValueError(f"""Invalid call_with_kwargs for {func.__name__}: {error}""") from error
176
+
177
+ return normalized_kwargs
178
+
179
+
180
+ def _generate_call_statement(func: FunctionType, kwargs: dict[str, object]) -> str:
181
+ """Generate a function call statement with the given keyword arguments."""
182
+ if not kwargs:
155
183
  return f"{func.__name__}()"
156
-
157
- arg_parts: list[str] = []
158
-
159
- for arg in args:
160
- arg_parts.append(repr(arg))
161
-
162
- for key, value in kwargs.items():
163
- arg_parts.append(f"{key}={repr(value)}")
164
-
184
+
185
+ arg_parts: list[str] = [f"{key}={repr(value)}" for key, value in kwargs.items()]
165
186
  args_str = ", ".join(arg_parts)
166
187
  return f"{func.__name__}({args_str})"
188
+
189
+
190
+ if __name__ == "__main__":
191
+ # Example usage
192
+ a = True
193
+ b = 3
194
+ def func(no_copy_assets: bool = a):
195
+ from machineconfig.scripts.python.helpers_devops.cli_self import update
196
+ update(no_copy_assets=no_copy_assets)
197
+ script = function_to_script(func, call_with_kwargs=None)
198
+ print(script)
199
+ pass
@@ -106,51 +106,45 @@ class ProcessManager:
106
106
  """Format process data as table string for display."""
107
107
  if not self.data:
108
108
  return ""
109
-
110
109
  # Create header
111
110
  _headers = ["Command", "PID", "Name", "Username", "CPU%", "Memory(MB)", "Status", "Create Time"]
112
111
  header_line = f"{'Command':<50} {'PID':<8} {'Name':<20} {'Username':<12} {'CPU%':<8} {'Memory(MB)':<12} {'Status':<12} {'Create Time':<20}"
113
112
  separator = "-" * len(header_line)
114
-
115
113
  lines = [header_line, separator]
116
-
117
114
  for process in self.data:
118
115
  # Format create_time as string
119
116
  create_time_str = process["create_time"].strftime("%Y-%m-%d %H:%M:%S")
120
117
  # Truncate command if too long
121
118
  command = process["command"][:47] + "..." if len(process["command"]) > 50 else process["command"]
122
-
123
119
  line = f"{command:<50} {process['pid']:<8} {process['name'][:19]:<20} {process['username'][:11]:<12} {process['cpu_percent']:<8.1f} {process['memory_usage_mb']:<12.2f} {process['status'][:11]:<12} {create_time_str:<20}"
124
120
  lines.append(line)
125
-
126
121
  return "\n".join(lines)
127
122
 
128
123
  def choose_and_kill(self):
129
124
  # header for interactive process selection
130
125
  title = "🎯 INTERACTIVE PROCESS SELECTION AND TERMINATION"
131
126
  console.print(Panel(title, title="[bold blue]Process Info[/bold blue]", border_style="blue"))
132
-
133
127
  # Format data as table for display
134
128
  formatted_data = self._format_process_table()
135
- options = formatted_data.split("\n")[1:] # Skip header
136
- res = choose_from_options(options=formatted_data.split("\n"), msg="📋 Select processes to manage:", fzf=True, multi=True)
137
- indices = [options.index(val) for val in res]
129
+ all_lines = formatted_data.split("\n")
130
+ header_and_separator = all_lines[:2] # First two lines: header and separator
131
+ options = all_lines[2:] # Skip header and separator, only process lines
132
+ res = choose_from_options(options=all_lines, msg="📋 Select processes to manage:", fzf=True, multi=True)
133
+ # Filter out header and separator if they were selected
134
+ selected_lines = [line for line in res if line not in header_and_separator]
135
+ indices = [options.index(val) for val in selected_lines]
138
136
  selected_processes = [self.data[i] for i in indices]
139
-
140
137
  print("\n📊 All Processes:")
141
138
  print(formatted_data)
142
139
  print("\n🎯 Selected Processes:")
143
140
  for process in selected_processes:
144
141
  print(f"PID: {process['pid']}, Name: {process['name']}, Memory: {process['memory_usage_mb']:.2f}MB")
145
-
146
142
  for idx, process in enumerate(selected_processes):
147
143
  pprint(dict(process), f"📌 Process {idx}")
148
-
149
144
  kill_all = input("\n⚠️ Confirm killing ALL selected processes? y/[n] ").lower() == "y"
150
145
  if kill_all:
151
146
  self.kill(pids=[p["pid"] for p in selected_processes])
152
147
  return
153
-
154
148
  kill_by_index = input("\n🔫 Kill by index? (enter numbers separated by spaces, e.g. '1 4') or [n] to cancel: ")
155
149
  if kill_by_index != "" and kill_by_index != "n":
156
150
  indices = [int(val) for val in kill_by_index.split(" ")]
@@ -164,12 +158,10 @@ class ProcessManager:
164
158
  # header for filtering processes by name
165
159
  title = "🔍 FILTERING AND TERMINATING PROCESSES BY NAME"
166
160
  console.print(Panel(title, title="[bold blue]Process Info[/bold blue]", border_style="blue"))
167
-
168
161
  # Filter processes by name
169
162
  filtered_processes = [p for p in self.data if p["name"] == name]
170
163
  # Sort by create_time (ascending)
171
164
  filtered_processes.sort(key=lambda x: x["create_time"])
172
-
173
165
  print(f"🎯 Found {len(filtered_processes)} processes matching name: '{name}'")
174
166
  self.kill(pids=[p["pid"] for p in filtered_processes])
175
167
  console.print(Panel("", title="[bold blue]Process Info[/bold blue]", border_style="blue"))
@@ -186,40 +178,35 @@ class ProcessManager:
186
178
  pids = []
187
179
  if commands is None:
188
180
  commands = []
189
-
190
181
  killed_count = 0
191
-
192
182
  for name in names:
193
183
  matching_processes = [p for p in self.data if p["name"] == name]
194
184
  if len(matching_processes) > 0:
195
185
  for process in matching_processes:
196
186
  psutil.Process(process["pid"]).kill()
197
- print(f"💀 Killed process {name} with PID {process['pid']}. It lived {get_age(process['create_time'])}. RIP 🪦💐")
187
+ print(f"💀 Killed process {name} with PID {process['pid']}. It lived {get_age(process['create_time'])}. RIP 💐")
198
188
  killed_count += 1
199
189
  else:
200
190
  print(f'❓ No process named "{name}" found')
201
-
202
191
  for pid in pids:
203
192
  try:
204
193
  proc = psutil.Process(pid)
205
194
  proc_name = proc.name()
206
195
  proc_lifetime = get_age(datetime.fromtimestamp(proc.create_time(), tz=None))
207
196
  proc.kill()
208
- print(f'💀 Killed process with PID {pid} and name "{proc_name}". It lived {proc_lifetime}. RIP 🪦💐')
197
+ print(f'💀 Killed process with PID {pid} and name "{proc_name}". It lived {proc_lifetime}. RIP 💐')
209
198
  killed_count += 1
210
199
  except psutil.NoSuchProcess:
211
200
  print(f"❓ No process with PID {pid} found")
212
-
213
201
  for command in commands:
214
202
  matching_processes = [p for p in self.data if command in p["command"]]
215
203
  if len(matching_processes) > 0:
216
204
  for process in matching_processes:
217
205
  psutil.Process(process["pid"]).kill()
218
- print(f'💀 Killed process with "{command}" in its command & PID {process["pid"]}. It lived {get_age(process["create_time"])}. RIP 🪦💐')
206
+ print(f'💀 Killed process with "{command}" in its command & PID {process["pid"]}. It lived {get_age(process["create_time"])}. RIP 💐')
219
207
  killed_count += 1
220
208
  else:
221
209
  print(f'❓ No process has "{command}" in its command.')
222
-
223
210
  console.print(Panel(f"✅ Termination complete: {killed_count} processes terminated", title="[bold blue]Process Info[/bold blue]", border_style="blue"))
224
211
 
225
212
 
@@ -170,35 +170,32 @@ class CacheMemory[T]():
170
170
  def __call__(self, fresh: bool = False) -> T:
171
171
  self.last_call_is_fresh = False
172
172
  if fresh or not hasattr(self, "cache"):
173
- if self.logger:
174
- why = "There was an explicit fresh order." if fresh else "Previous cache never existed."
175
- self.logger.warning(f"""
176
- 🆕 ════════════════════ NEW CACHE ════════════════════
177
- 🔄 {self.name} cache: Populating fresh cache from source func
178
- ℹ️ Reason: {why}
179
- ════════════════════════════════════════════════════════""")
173
+ why = "There was an explicit fresh order." if fresh else "Previous cache never existed."
174
+ t0 = time.time()
175
+ self.logger.warning(f"""
176
+ 🆕 ════════════════════ NEW {self.name} CACHE ════════════════════
177
+ ℹ️ Reason: {why}""")
180
178
  self.cache = self.source_func()
179
+ self.logger.warning(f"⏱️ Cache population took {time.time() - t0:.2f} seconds.")
181
180
  self.last_call_is_fresh = True
182
181
  self.time_produced = datetime.now()
183
182
  else:
184
183
  age = self.age
185
184
  if age > self.expire:
186
- if self.logger:
187
- self.logger.warning(f"""
185
+ self.logger.warning(f"""
188
186
  🔄 ════════════════════ CACHE UPDATE ════════════════════
189
187
  ⚠️ {self.name} cache: Updating cache from source func
190
- ⏱️ Age = {age} > {self.expire}
191
- ════════════════════════════════════════════════════════""")
188
+ ⏱️ Age = {age} > {self.expire}""")
189
+ t0 = time.time()
192
190
  self.cache = self.source_func()
191
+ self.logger.warning(f"⏱️ Cache population took {time.time() - t0:.2f} seconds.")
193
192
  self.last_call_is_fresh = True
194
193
  self.time_produced = datetime.now()
195
194
  else:
196
- if self.logger:
197
- self.logger.warning(f"""
195
+ self.logger.warning(f"""
198
196
  ✅ ════════════════════ USING CACHE ════════════════════
199
197
  📦 {self.name} cache: Using cached values
200
- ⏱️ Lag = {age}
201
- ════════════════════════════════════════════════════════""")
198
+ ⏱️ Lag = {age}""")
202
199
  return self.cache
203
200
 
204
201
  @staticmethod
@@ -236,19 +233,18 @@ class Cache[T](): # This class helps to accelrate access to latest data coming
236
233
  msg1 = f"""
237
234
  📦 ════════════════════ CACHE OPERATION ════════════════════
238
235
  🔄 {self.name} cache: Reading cached values from `{self.path}`
239
- ⏱️ Lag = {age}
240
- ════════════════════════════════════════════════════════════"""
236
+ ⏱️ Lag = {age}"""
241
237
  try:
242
238
  self.cache = self.reader(self.path)
243
239
  except Exception as ex:
244
- if self.logger:
245
- msg2 = f"""
240
+ msg2 = f"""
246
241
  ❌ ════════════════════ CACHE ERROR ════════════════════
247
242
  ⚠️ {self.name} cache: Cache file is corrupted
248
- 🔍 Error: {ex}
249
- ════════════════════════════════════════════════════════"""
250
- self.logger.warning(msg1 + msg2)
243
+ 🔍 Error: {ex}"""
244
+ self.logger.warning(msg1 + msg2)
245
+ t0 = time.time()
251
246
  self.cache = self.source_func()
247
+ self.logger.warning(f"⏱️ Cache population took {time.time() - t0:.2f} seconds.")
252
248
  self.last_call_is_fresh = True
253
249
  self.time_produced = datetime.now()
254
250
  # if self.path is not None:
@@ -256,15 +252,15 @@ class Cache[T](): # This class helps to accelrate access to latest data coming
256
252
  return self.cache
257
253
  return self(fresh=False) # may be the cache is old ==> check that by passing it through the logic again.
258
254
  else:
259
- if self.logger:
260
- # Previous cache never existed or there was an explicit fresh order.
261
- why = "There was an explicit fresh order." if fresh else "Previous cache never existed or is corrupted."
262
- self.logger.warning(f"""
255
+ # Previous cache never existed or there was an explicit fresh order.
256
+ why = "There was an explicit fresh order." if fresh else "Previous cache never existed or is corrupted."
257
+ self.logger.warning(f"""
263
258
  🆕 ════════════════════ NEW CACHE ════════════════════
264
259
  🔄 {self.name} cache: Populating fresh cache from source func
265
- ℹ️ Reason: {why}
266
- ════════════════════════════════════════════════════════""")
260
+ ℹ️ Reason: {why}""")
261
+ t0 = time.time()
267
262
  self.cache = self.source_func() # fresh data.
263
+ self.logger.warning(f"⏱️ Cache population took {time.time() - t0:.2f} seconds.")
268
264
  self.last_call_is_fresh = True
269
265
  self.time_produced = datetime.now()
270
266
  self.save(self.cache, self.path)
@@ -274,23 +270,21 @@ class Cache[T](): # This class helps to accelrate access to latest data coming
274
270
  except AttributeError: # path doesn't exist (may be deleted) ==> need to repopulate cache form source_func.
275
271
  return self(fresh=True)
276
272
  if age > self.expire:
277
- if self.logger:
278
- self.logger.warning(f"""
273
+ self.logger.warning(f"""
279
274
  🔄 ════════════════════ CACHE UPDATE ════════════════════
280
275
  ⚠️ {self.name} cache: Updating cache from source func
281
- ⏱️ Age = {age} > {self.expire}
282
- ════════════════════════════════════════════════════════""")
276
+ ⏱️ Age = {age} > {self.expire}""")
277
+ t0 = time.time()
283
278
  self.cache = self.source_func()
279
+ self.logger.warning(f"⏱️ Cache population took {time.time() - t0:.2f} seconds.")
284
280
  self.last_call_is_fresh = True
285
281
  self.time_produced = datetime.now()
286
282
  self.save(self.cache, self.path)
287
283
  else:
288
- if self.logger:
289
- self.logger.warning(f"""
284
+ self.logger.warning(f"""
290
285
  ✅ ════════════════════ USING CACHE ════════════════════
291
286
  📦 {self.name} cache: Using cached values
292
- ⏱️ Lag = {age}
293
- ════════════════════════════════════════════════════════""")
287
+ ⏱️ Lag = {age}""")
294
288
  return self.cache
295
289
 
296
290
  @staticmethod
@@ -6,7 +6,7 @@ from machineconfig.utils.terminal import Response, MACHINE
6
6
  from machineconfig.utils.accessories import pprint
7
7
 
8
8
  UV_RUN_CMD = "$HOME/.local/bin/uv run"
9
- MACHINECONFIG_VERSION = "machineconfig>=6.45"
9
+ MACHINECONFIG_VERSION = "machineconfig>=6.48"
10
10
  DEFAULT_PICKLE_SUBDIR = "tmp_results/tmp_scripts/ssh"
11
11
 
12
12
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: machineconfig
3
- Version: 6.45
3
+ Version: 6.48
4
4
  Summary: Dotfiles management package
5
5
  Author-email: Alex Al-Saffar <programmer@usa.com>
6
6
  License: Apache 2.0