machineconfig 6.47__py3-none-any.whl → 6.49__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of machineconfig might be problematic. Click here for more details.
- machineconfig/cluster/sessions_managers/utils/maker.py +1 -1
- machineconfig/scripts/python/cloud.py +3 -3
- machineconfig/scripts/python/croshell.py +6 -6
- machineconfig/scripts/python/devops.py +6 -6
- machineconfig/scripts/python/devops_navigator.py +1 -1
- machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
- machineconfig/scripts/python/ftpx.py +1 -1
- machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/cloud_copy.py +2 -2
- machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/cloud_sync.py +3 -3
- machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/helpers2.py +1 -1
- machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_config.py +3 -3
- machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_data.py +2 -2
- machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_nw.py +2 -2
- machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_repos.py +10 -10
- machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_self.py +4 -4
- machineconfig/scripts/python/{devops_helpers → helpers_devops}/devops_backup_retrieve.py +1 -1
- machineconfig/scripts/python/{devops_helpers → helpers_devops}/devops_update_repos.py +1 -1
- machineconfig/scripts/python/helpers_navigator/__init__.py +20 -0
- machineconfig/scripts/python/{helper_navigator → helpers_navigator}/command_builder.py +1 -1
- machineconfig/scripts/python/{helper_navigator → helpers_navigator}/command_detail.py +1 -1
- machineconfig/scripts/python/{helper_navigator → helpers_navigator}/command_tree.py +1 -1
- machineconfig/scripts/python/{helper_navigator → helpers_navigator}/main_app.py +5 -5
- machineconfig/scripts/python/{repos_helpers → helpers_repos}/action.py +1 -1
- machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +11 -6
- machineconfig/scripts/python/{repos_helpers → helpers_repos}/count_lines_frontend.py +2 -2
- machineconfig/scripts/python/{repos_helpers → helpers_repos}/entrypoint.py +2 -2
- machineconfig/scripts/python/interactive.py +3 -3
- machineconfig/scripts/python/nw/mount_nfs +1 -1
- machineconfig/scripts/python/sessions.py +1 -1
- machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
- machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +1 -1
- machineconfig/utils/code.py +1 -1
- machineconfig/utils/meta.py +132 -41
- machineconfig/utils/procs.py +10 -23
- machineconfig/utils/scheduler.py +6 -19
- machineconfig/utils/ssh.py +1 -1
- {machineconfig-6.47.dist-info → machineconfig-6.49.dist-info}/METADATA +1 -1
- {machineconfig-6.47.dist-info → machineconfig-6.49.dist-info}/RECORD +71 -72
- machineconfig/scripts/python/helper_navigator/__init__.py +0 -20
- machineconfig/scripts/python/sessions_helpers/__init__.py +0 -0
- /machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/__init__.py +0 -0
- /machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/cloud_helpers.py +0 -0
- /machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/cloud_mount.py +0 -0
- /machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/helpers5.py +0 -0
- /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/__init__.py +0 -0
- /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/crosh.py +0 -0
- /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/pomodoro.py +0 -0
- /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/scheduler.py +0 -0
- /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/start_slidev.py +0 -0
- /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/viewer.py +0 -0
- /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/viewer_template.py +0 -0
- /machineconfig/scripts/python/{devops_helpers → helpers_devops}/__init__.py +0 -0
- /machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_config_dotfile.py +0 -0
- /machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_share_server.py +0 -0
- /machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_terminal.py +0 -0
- /machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_utils.py +0 -0
- /machineconfig/scripts/python/{devops_helpers → helpers_devops}/devops_status.py +0 -0
- /machineconfig/scripts/python/{devops_helpers → helpers_devops}/themes/__init__.py +0 -0
- /machineconfig/scripts/python/{devops_helpers → helpers_devops}/themes/choose_pwsh_theme.ps1 +0 -0
- /machineconfig/scripts/python/{devops_helpers → helpers_devops}/themes/choose_wezterm_theme.py +0 -0
- /machineconfig/scripts/python/{helper_navigator → helpers_navigator}/data_models.py +0 -0
- /machineconfig/scripts/python/{helper_navigator → helpers_navigator}/search_bar.py +0 -0
- /machineconfig/scripts/python/{repos_helpers → helpers_repos}/clone.py +0 -0
- /machineconfig/scripts/python/{repos_helpers → helpers_repos}/count_lines.py +0 -0
- /machineconfig/scripts/python/{repos_helpers → helpers_repos}/record.py +0 -0
- /machineconfig/scripts/python/{repos_helpers → helpers_repos}/sync.py +0 -0
- /machineconfig/scripts/python/{repos_helpers → helpers_repos}/update.py +0 -0
- /machineconfig/scripts/python/{helpers_repos → helpers_sessions}/__init__.py +0 -0
- /machineconfig/scripts/python/{sessions_helpers → helpers_sessions}/sessions_multiprocess.py +0 -0
- {machineconfig-6.47.dist-info → machineconfig-6.49.dist-info}/WHEEL +0 -0
- {machineconfig-6.47.dist-info → machineconfig-6.49.dist-info}/entry_points.txt +0 -0
- {machineconfig-6.47.dist-info → machineconfig-6.49.dist-info}/top_level.txt +0 -0
machineconfig/utils/meta.py
CHANGED
|
@@ -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
|
|
8
|
+
from typing import ParamSpec
|
|
8
9
|
|
|
10
|
+
P = ParamSpec("P")
|
|
9
11
|
|
|
10
|
-
|
|
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
|
|
29
|
-
raise ValueError(f"Expected a function, got {type(func)}")
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
call_statement = _generate_call_statement(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -86,6 +90,17 @@ def _extract_imports(func: FunctionType) -> str:
|
|
|
86
90
|
if isinstance(node.value, ast.Name):
|
|
87
91
|
used_names.add(node.value.id)
|
|
88
92
|
|
|
93
|
+
for node in ast.walk(tree):
|
|
94
|
+
if isinstance(node, ast.FunctionDef):
|
|
95
|
+
for default in node.args.defaults + node.args.kw_defaults:
|
|
96
|
+
if default is not None:
|
|
97
|
+
for subnode in ast.walk(default):
|
|
98
|
+
if isinstance(subnode, ast.Name):
|
|
99
|
+
used_names.add(subnode.id)
|
|
100
|
+
elif isinstance(subnode, ast.Attribute):
|
|
101
|
+
if isinstance(subnode.value, ast.Name):
|
|
102
|
+
used_names.add(subnode.value.id)
|
|
103
|
+
|
|
89
104
|
for name in used_names:
|
|
90
105
|
if name in func_globals:
|
|
91
106
|
obj = func_globals[name]
|
|
@@ -97,7 +112,20 @@ def _extract_imports(func: FunctionType) -> str:
|
|
|
97
112
|
else:
|
|
98
113
|
import_statements.add(f"import {module_name} as {name}")
|
|
99
114
|
|
|
100
|
-
elif hasattr(obj, '__module__') and obj.__module__ != '__main__':
|
|
115
|
+
elif isinstance(obj, type) and hasattr(obj, '__module__') and obj.__module__ != '__main__':
|
|
116
|
+
try:
|
|
117
|
+
module_name = obj.__module__
|
|
118
|
+
obj_name = obj.__name__ if hasattr(obj, '__name__') else name
|
|
119
|
+
|
|
120
|
+
if module_name and module_name != 'builtins':
|
|
121
|
+
if obj_name == name:
|
|
122
|
+
import_statements.add(f"from {module_name} import {obj_name}")
|
|
123
|
+
else:
|
|
124
|
+
import_statements.add(f"from {module_name} import {obj_name} as {name}")
|
|
125
|
+
except AttributeError:
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
elif callable(obj) and not isinstance(obj, type) and hasattr(obj, '__module__') and obj.__module__ != '__main__':
|
|
101
129
|
try:
|
|
102
130
|
module_name = obj.__module__
|
|
103
131
|
obj_name = obj.__name__ if hasattr(obj, '__name__') else name
|
|
@@ -116,6 +144,7 @@ def _extract_imports(func: FunctionType) -> str:
|
|
|
116
144
|
def _extract_globals(func: FunctionType) -> str:
|
|
117
145
|
"""Extract global variables needed by the function."""
|
|
118
146
|
global_assignments: list[str] = []
|
|
147
|
+
needed_types: set[type] = set()
|
|
119
148
|
|
|
120
149
|
source = _get_function_source(func)
|
|
121
150
|
func_globals = func.__globals__
|
|
@@ -133,6 +162,17 @@ def _extract_globals(func: FunctionType) -> str:
|
|
|
133
162
|
if isinstance(node.value, ast.Name):
|
|
134
163
|
used_names.add(node.value.id)
|
|
135
164
|
|
|
165
|
+
for node in ast.walk(tree):
|
|
166
|
+
if isinstance(node, ast.FunctionDef):
|
|
167
|
+
for default in node.args.defaults + node.args.kw_defaults:
|
|
168
|
+
if default is not None:
|
|
169
|
+
for subnode in ast.walk(default):
|
|
170
|
+
if isinstance(subnode, ast.Name):
|
|
171
|
+
used_names.add(subnode.id)
|
|
172
|
+
elif isinstance(subnode, ast.Attribute):
|
|
173
|
+
if isinstance(subnode.value, ast.Name):
|
|
174
|
+
used_names.add(subnode.value.id)
|
|
175
|
+
|
|
136
176
|
for name in used_names:
|
|
137
177
|
if name in func_globals:
|
|
138
178
|
obj = func_globals[name]
|
|
@@ -140,27 +180,78 @@ def _extract_globals(func: FunctionType) -> str:
|
|
|
140
180
|
if not isinstance(obj, (ModuleType, FunctionType, type)):
|
|
141
181
|
if not (hasattr(obj, '__module__') and hasattr(obj, '__name__')):
|
|
142
182
|
try:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
183
|
+
obj_type = type(obj)
|
|
184
|
+
|
|
185
|
+
if obj_type.__name__ == 'PathExtended' and obj_type.__module__ == 'machineconfig.utils.path_extended':
|
|
186
|
+
global_assignments.append(f"{name} = PathExtended('{str(obj)}')")
|
|
187
|
+
if obj_type.__module__ not in ['builtins', '__main__']:
|
|
188
|
+
needed_types.add(obj_type)
|
|
189
|
+
else:
|
|
190
|
+
repr_str = repr(obj)
|
|
191
|
+
if len(repr_str) < 1000 and '\n' not in repr_str and all(ord(c) < 128 or c in [' ', '\n', '\t'] for c in repr_str):
|
|
192
|
+
global_assignments.append(f"{name} = {repr_str}")
|
|
193
|
+
if obj_type.__module__ not in ['builtins', '__main__']:
|
|
194
|
+
needed_types.add(obj_type)
|
|
195
|
+
else:
|
|
196
|
+
global_assignments.append(f"# Warning: Could not serialize global variable '{name}' (repr too complex or contains non-ASCII)")
|
|
197
|
+
except Exception as e:
|
|
198
|
+
global_assignments.append(f"# Warning: Could not serialize global variable '{name}': {e}")
|
|
156
199
|
|
|
157
|
-
|
|
200
|
+
result_parts: list[str] = []
|
|
158
201
|
|
|
159
|
-
|
|
160
|
-
|
|
202
|
+
if needed_types:
|
|
203
|
+
for obj_type in sorted(needed_types, key=lambda t: (t.__module__, t.__name__)):
|
|
204
|
+
module_name = obj_type.__module__
|
|
205
|
+
type_name = obj_type.__name__
|
|
206
|
+
result_parts.append(f"from {module_name} import {type_name}")
|
|
207
|
+
result_parts.append("")
|
|
161
208
|
|
|
162
|
-
|
|
163
|
-
arg_parts.append(f"{key}={repr(value)}")
|
|
209
|
+
result_parts.extend(global_assignments)
|
|
164
210
|
|
|
211
|
+
return "\n".join(result_parts)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _prepare_call_kwargs(func: FunctionType, call_with_kwargs: Mapping[str, object] | None) -> dict[str, object] | None:
|
|
215
|
+
if call_with_kwargs is None:
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
normalized_kwargs = dict(call_with_kwargs)
|
|
219
|
+
|
|
220
|
+
if not normalized_kwargs:
|
|
221
|
+
return {}
|
|
222
|
+
|
|
223
|
+
signature = inspect.signature(func)
|
|
224
|
+
positional_only = [parameter.name for parameter in signature.parameters.values() if parameter.kind is inspect.Parameter.POSITIONAL_ONLY]
|
|
225
|
+
|
|
226
|
+
if positional_only:
|
|
227
|
+
joined = ", ".join(positional_only)
|
|
228
|
+
raise ValueError(f"""Cannot call {func.__name__} with positional-only parameters: {joined}""")
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
signature.bind(**normalized_kwargs)
|
|
232
|
+
except TypeError as error:
|
|
233
|
+
raise ValueError(f"""Invalid call_with_kwargs for {func.__name__}: {error}""") from error
|
|
234
|
+
|
|
235
|
+
return normalized_kwargs
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _generate_call_statement(func: FunctionType, kwargs: dict[str, object]) -> str:
|
|
239
|
+
"""Generate a function call statement with the given keyword arguments."""
|
|
240
|
+
if not kwargs:
|
|
241
|
+
return f"{func.__name__}()"
|
|
242
|
+
|
|
243
|
+
arg_parts: list[str] = [f"{key}={repr(value)}" for key, value in kwargs.items()]
|
|
165
244
|
args_str = ", ".join(arg_parts)
|
|
166
245
|
return f"{func.__name__}({args_str})"
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
if __name__ == "__main__":
|
|
249
|
+
# Example usage
|
|
250
|
+
a = True
|
|
251
|
+
b = 3
|
|
252
|
+
def func(no_copy_assets: bool = a):
|
|
253
|
+
from machineconfig.scripts.python.helpers_devops.cli_self import update
|
|
254
|
+
update(no_copy_assets=no_copy_assets)
|
|
255
|
+
script = function_to_script(func, call_with_kwargs=None)
|
|
256
|
+
print(script)
|
|
257
|
+
pass
|
machineconfig/utils/procs.py
CHANGED
|
@@ -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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
machineconfig/utils/scheduler.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
from typing import Callable, Optional, Union, Any, Protocol, List, TypeVar
|
|
3
4
|
import logging
|
|
4
5
|
import time
|
|
5
6
|
from datetime import datetime, timezone, timedelta
|
|
6
7
|
from machineconfig.utils.io import from_pickle
|
|
7
|
-
from machineconfig.utils.path_extended import PathExtended
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class LoggerTemplate(Protocol):
|
|
@@ -147,7 +147,6 @@ class Scheduler:
|
|
|
147
147
|
T2 = TypeVar("T2")
|
|
148
148
|
def to_pickle(obj: Any, path: Path) -> None:
|
|
149
149
|
import pickle
|
|
150
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
151
150
|
path.write_bytes(pickle.dumps(obj))
|
|
152
151
|
|
|
153
152
|
|
|
@@ -160,15 +159,12 @@ class CacheMemory[T]():
|
|
|
160
159
|
self.time_produced = datetime.now()
|
|
161
160
|
self.logger = logger
|
|
162
161
|
self.expire = expire
|
|
163
|
-
self.name = name if isinstance(name, str) else
|
|
164
|
-
self.last_call_is_fresh = False
|
|
165
|
-
|
|
162
|
+
self.name = name if isinstance(name, str) else self.source_func.__name__
|
|
166
163
|
@property
|
|
167
164
|
def age(self) -> timedelta:
|
|
168
165
|
return datetime.now() - self.time_produced
|
|
169
166
|
|
|
170
167
|
def __call__(self, fresh: bool = False) -> T:
|
|
171
|
-
self.last_call_is_fresh = False
|
|
172
168
|
if fresh or not hasattr(self, "cache"):
|
|
173
169
|
why = "There was an explicit fresh order." if fresh else "Previous cache never existed."
|
|
174
170
|
t0 = time.time()
|
|
@@ -177,7 +173,6 @@ class CacheMemory[T]():
|
|
|
177
173
|
ℹ️ Reason: {why}""")
|
|
178
174
|
self.cache = self.source_func()
|
|
179
175
|
self.logger.warning(f"⏱️ Cache population took {time.time() - t0:.2f} seconds.")
|
|
180
|
-
self.last_call_is_fresh = True
|
|
181
176
|
self.time_produced = datetime.now()
|
|
182
177
|
else:
|
|
183
178
|
age = self.age
|
|
@@ -189,7 +184,6 @@ class CacheMemory[T]():
|
|
|
189
184
|
t0 = time.time()
|
|
190
185
|
self.cache = self.source_func()
|
|
191
186
|
self.logger.warning(f"⏱️ Cache population took {time.time() - t0:.2f} seconds.")
|
|
192
|
-
self.last_call_is_fresh = True
|
|
193
187
|
self.time_produced = datetime.now()
|
|
194
188
|
else:
|
|
195
189
|
self.logger.warning(f"""
|
|
@@ -212,21 +206,19 @@ class Cache[T](): # This class helps to accelrate access to latest data coming
|
|
|
212
206
|
) -> None:
|
|
213
207
|
self.cache: T
|
|
214
208
|
self.source_func = source_func # function which when called returns a fresh object to be frozen.
|
|
215
|
-
self.path:
|
|
209
|
+
self.path: Path = path
|
|
210
|
+
_ = self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
216
211
|
self.time_produced = datetime.now() # if path is None else
|
|
217
212
|
self.save = saver
|
|
218
213
|
self.reader = reader
|
|
219
214
|
self.logger = logger
|
|
220
215
|
self.expire = expire
|
|
221
|
-
self.name = name if isinstance(name, str) else
|
|
222
|
-
self.last_call_is_fresh = False
|
|
223
|
-
|
|
216
|
+
self.name = name if isinstance(name, str) else self.source_func.__name__
|
|
224
217
|
@property
|
|
225
218
|
def age(self):
|
|
226
219
|
"""Throws AttributeError if called before cache is populated and path doesn't exists"""
|
|
227
220
|
return datetime.now() - self.time_produced
|
|
228
221
|
def __call__(self, fresh: bool = False) -> T:
|
|
229
|
-
self.last_call_is_fresh = False
|
|
230
222
|
if fresh or not hasattr(self, "cache"): # populate cache for the first time
|
|
231
223
|
if not fresh and self.path.exists():
|
|
232
224
|
age = datetime.now() - datetime.fromtimestamp(self.path.stat().st_mtime)
|
|
@@ -245,10 +237,8 @@ class Cache[T](): # This class helps to accelrate access to latest data coming
|
|
|
245
237
|
t0 = time.time()
|
|
246
238
|
self.cache = self.source_func()
|
|
247
239
|
self.logger.warning(f"⏱️ Cache population took {time.time() - t0:.2f} seconds.")
|
|
248
|
-
self.last_call_is_fresh = True
|
|
249
240
|
self.time_produced = datetime.now()
|
|
250
|
-
|
|
251
|
-
# self.save(self.cache, self.path)
|
|
241
|
+
self.save(self.cache, self.path)
|
|
252
242
|
return self.cache
|
|
253
243
|
return self(fresh=False) # may be the cache is old ==> check that by passing it through the logic again.
|
|
254
244
|
else:
|
|
@@ -261,7 +251,6 @@ class Cache[T](): # This class helps to accelrate access to latest data coming
|
|
|
261
251
|
t0 = time.time()
|
|
262
252
|
self.cache = self.source_func() # fresh data.
|
|
263
253
|
self.logger.warning(f"⏱️ Cache population took {time.time() - t0:.2f} seconds.")
|
|
264
|
-
self.last_call_is_fresh = True
|
|
265
254
|
self.time_produced = datetime.now()
|
|
266
255
|
self.save(self.cache, self.path)
|
|
267
256
|
else: # cache exists
|
|
@@ -277,7 +266,6 @@ class Cache[T](): # This class helps to accelrate access to latest data coming
|
|
|
277
266
|
t0 = time.time()
|
|
278
267
|
self.cache = self.source_func()
|
|
279
268
|
self.logger.warning(f"⏱️ Cache population took {time.time() - t0:.2f} seconds.")
|
|
280
|
-
self.last_call_is_fresh = True
|
|
281
269
|
self.time_produced = datetime.now()
|
|
282
270
|
self.save(self.cache, self.path)
|
|
283
271
|
else:
|
|
@@ -294,5 +282,4 @@ class Cache[T](): # This class helps to accelrate access to latest data coming
|
|
|
294
282
|
def decorator(source_func: Callable[[], T2]) -> Cache["T2"]:
|
|
295
283
|
res = Cache(source_func=source_func, expire=expire, logger=logger, path=path, name=name, reader=reader, saver=saver)
|
|
296
284
|
return res
|
|
297
|
-
|
|
298
285
|
return decorator
|
machineconfig/utils/ssh.py
CHANGED
|
@@ -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.
|
|
9
|
+
MACHINECONFIG_VERSION = "machineconfig>=6.49"
|
|
10
10
|
DEFAULT_PICKLE_SUBDIR = "tmp_results/tmp_scripts/ssh"
|
|
11
11
|
|
|
12
12
|
|