machineconfig 3.83__py3-none-any.whl → 3.86__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 (35) hide show
  1. machineconfig/cluster/sessions_managers/zellij_local.py +2 -2
  2. machineconfig/cluster/sessions_managers/zellij_remote.py +2 -2
  3. machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +2 -2
  4. machineconfig/jobs/python/vscode/api.py +8 -7
  5. machineconfig/scripts/python/ai/initai.py +1 -11
  6. machineconfig/scripts/python/cloud_copy.py +33 -27
  7. machineconfig/scripts/python/cloud_manager.py +0 -2
  8. machineconfig/scripts/python/cloud_mount.py +13 -10
  9. machineconfig/scripts/python/cloud_sync.py +30 -28
  10. machineconfig/scripts/python/croshell.py +46 -53
  11. machineconfig/scripts/python/dotfile.py +16 -16
  12. machineconfig/scripts/python/fire_agents.py +4 -5
  13. machineconfig/scripts/python/fire_jobs.py +30 -14
  14. machineconfig/scripts/python/fire_jobs_args_helper.py +43 -15
  15. machineconfig/scripts/python/fire_jobs_layout_helper.py +24 -16
  16. machineconfig/scripts/python/helpers/helpers4.py +0 -2
  17. machineconfig/scripts/python/repos.py +10 -88
  18. machineconfig/scripts/python/repos_helper_action.py +335 -0
  19. machineconfig/scripts/python/scheduler.py +0 -1
  20. machineconfig/scripts/python/share_terminal.py +41 -0
  21. machineconfig/scripts/python/start_slidev.py +15 -13
  22. machineconfig/scripts/python/start_terminals.py +19 -19
  23. machineconfig/scripts/python/t4.py +17 -0
  24. machineconfig/scripts/python/wifi_conn.py +16 -14
  25. machineconfig/scripts/python/wsl_windows_transfer.py +23 -29
  26. machineconfig/utils/accessories.py +45 -7
  27. machineconfig/utils/ai/generate_file_checklist.py +17 -17
  28. {machineconfig-3.83.dist-info → machineconfig-3.86.dist-info}/METADATA +1 -1
  29. {machineconfig-3.83.dist-info → machineconfig-3.86.dist-info}/RECORD +32 -32
  30. {machineconfig-3.83.dist-info → machineconfig-3.86.dist-info}/entry_points.txt +5 -5
  31. machineconfig/cluster/templates/cli_gooey.py +0 -115
  32. machineconfig/jobs/python/vscode/link_ve.py +0 -63
  33. machineconfig/jobs/python/vscode/select_interpreter.py +0 -87
  34. {machineconfig-3.83.dist-info → machineconfig-3.86.dist-info}/WHEEL +0 -0
  35. {machineconfig-3.83.dist-info → machineconfig-3.86.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,6 @@ fire
8
8
  """
9
9
 
10
10
  from machineconfig.scripts.python.helpers.helpers4 import search_for_files_of_interest
11
- from machineconfig.scripts.python.helpers.helpers4 import convert_kwargs_to_fire_kwargs_str
12
11
  from machineconfig.scripts.python.helpers.helpers4 import parse_pyfile
13
12
  from machineconfig.scripts.python.helpers.helpers4 import get_import_module_code
14
13
  from machineconfig.utils.ve import get_ve_activate_line, get_ve_path_and_ipython_profile
@@ -17,7 +16,7 @@ from machineconfig.utils.path_helper import match_file_name, sanitize_path
17
16
 
18
17
  from machineconfig.utils.path_extended import PathExtended as PathExtended
19
18
  from machineconfig.utils.accessories import get_repo_root, randstr
20
- from machineconfig.scripts.python.fire_jobs_args_helper import FireJobArgs, extract_kwargs
19
+ from machineconfig.scripts.python.fire_jobs_args_helper import FireJobArgs, extract_kwargs, parse_fire_args_from_context
21
20
  import platform
22
21
  from typing import Optional, Annotated
23
22
  from pathlib import Path
@@ -26,7 +25,7 @@ import typer
26
25
  # import os
27
26
 
28
27
 
29
- def route(args: FireJobArgs) -> None:
28
+ def route(args: FireJobArgs, fire_args: str = "") -> None:
30
29
  if args.layout:
31
30
  from machineconfig.scripts.python.fire_jobs_layout_helper import handle_layout_args
32
31
 
@@ -51,7 +50,7 @@ def route(args: FireJobArgs) -> None:
51
50
  ipy_profile = "default"
52
51
 
53
52
  if choice_file.suffix == ".py":
54
- kwargs = extract_kwargs(args)
53
+ kwargs = extract_kwargs(args) # This now returns empty dict, but kept for compatibility
55
54
  activate_ve_line = get_ve_activate_line(ve_root=args.ve or ve_root_from_file or "$HOME/code/machineconfig/.venv")
56
55
  else:
57
56
  activate_ve_line = ""
@@ -217,8 +216,7 @@ except ImportError as _ex:
217
216
  # both selected function and kwargs are mentioned in the made up script, therefore no need for fire module.
218
217
  command = f"{exe} {choice_file} "
219
218
  elif choice_function is not None:
220
- kwargs_str = convert_kwargs_to_fire_kwargs_str(kwargs)
221
- command = f"{exe} -m fire {choice_file} {choice_function} {kwargs_str}"
219
+ command = f"{exe} -m fire {choice_file} {choice_function} {fire_args}"
222
220
  elif args.streamlit:
223
221
  # for .streamlit config to work, it needs to be in the current directory.
224
222
  if args.holdDirectory:
@@ -230,8 +228,7 @@ except ImportError as _ex:
230
228
  command = rf""" cd /d {choice_file.parent} & {exe} {choice_file.name} """
231
229
  else:
232
230
  if choice_file.suffix == "":
233
- kwargs_raw = " ".join(args.kw) if args.kw is not None else ""
234
- command = f"{exe} {choice_file} {kwargs_raw}"
231
+ command = f"{exe} {choice_file} {fire_args}"
235
232
  else:
236
233
  # command = f"cd {choice_file.parent}\n{exe} {choice_file.name}\ncd {PathExtended.cwd()}"
237
234
  command = f"{exe} {choice_file} "
@@ -330,7 +327,8 @@ python -m machineconfig.cluster.templates.cli_click --file {choice_file} """
330
327
 
331
328
 
332
329
  def main(
333
- path: Annotated[str, typer.Argument(help="The directory containing the jobs")] = ".",
330
+ ctx: typer.Context,
331
+ path: Annotated[str, typer.Argument(help="Path to the Python file to run")] = ".",
334
332
  function: Annotated[Optional[str], typer.Argument(help="Function to run")] = None,
335
333
  ve: Annotated[str, typer.Option("--ve", "-v", help="Virtual environment name")] = "",
336
334
  cmd: Annotated[bool, typer.Option("--cmd", "-B", help="Create a cmd fire command to launch the job asynchronously")] = False,
@@ -351,10 +349,13 @@ def main(
351
349
  Nprocess: Annotated[int, typer.Option("--Nprocess", "-p", help="Number of processes to use")] = 1,
352
350
  zellij_tab: Annotated[Optional[str], typer.Option("--zellij_tab", "-z", help="Open in a new zellij tab")] = None,
353
351
  watch: Annotated[bool, typer.Option("--watch", "-w", help="Watch the file for changes")] = False,
354
- kw: Annotated[Optional[list[str]], typer.Option("--kw", help="Keyword arguments to pass to the function in the form of k1 v1 k2 v2 ... (meaning k1=v1, k2=v2, etc)")] = None,
355
352
  layout: Annotated[bool, typer.Option("--layout", "-L", help="Use layout configuration (Zellij Or WindowsTerminal)")] = False,
356
353
  ) -> None:
357
354
  """Main function to process fire jobs arguments."""
355
+
356
+ # Get Fire arguments from context
357
+ fire_args = parse_fire_args_from_context(ctx)
358
+
358
359
  args = FireJobArgs(
359
360
  path=path,
360
361
  function=function,
@@ -377,16 +378,31 @@ def main(
377
378
  Nprocess=Nprocess,
378
379
  zellij_tab=zellij_tab,
379
380
  watch=watch,
380
- kw=kw,
381
381
  layout=layout,
382
382
  )
383
- route(args)
383
+ try:
384
+ route(args, fire_args)
385
+ except SystemExit:
386
+ # Re-raise SystemExit to preserve exit codes and allow clean exits
387
+ raise
388
+ except Exception as e:
389
+ # For other exceptions, print clean error message and exit
390
+ import sys
391
+ print(f"❌ Error: {e}", file=sys.stderr)
392
+ sys.exit(1)
384
393
 
385
394
 
386
395
  def main_from_parser():
387
- typer.run(main)
396
+ # from trogon.typer import init_tui
397
+ # from trogon.typer import init_tui
398
+ from typer import Typer
399
+ app = Typer(add_completion=False)
400
+ app.command(context_settings={"allow_extra_args": True, "allow_interspersed_args": False})(main)
401
+ # typer.run(main)
402
+ # init_tui(app)
403
+ app()
388
404
 
389
405
 
390
406
  if __name__ == "__main__":
391
407
  # options, func_args = parse_pyfile(file_path="C:/Users/aalsaf01/code/machineconfig/myresources/crocodile/core.py")
392
- pass
408
+ main_from_parser()
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass
2
- from typing import Optional
2
+ from typing import Optional, Any
3
3
 
4
4
 
5
5
  @dataclass
@@ -27,21 +27,49 @@ class FireJobArgs:
27
27
  Nprocess: int = 1
28
28
  zellij_tab: Optional[str] = None
29
29
  watch: bool = False
30
- kw: Optional[list[str]] = None
31
30
  layout: bool = False
32
31
 
33
32
 
34
33
  def extract_kwargs(args: FireJobArgs) -> dict[str, object]:
35
- str2obj = {"True": True, "False": False, "None": None}
36
- if args.kw is not None:
37
- assert len(args.kw) % 2 == 0, f"args.kw must be a list of even length. Got {len(args.kw)}"
38
- kwargs = dict(zip(args.kw[::2], args.kw[1::2]))
39
- kwargs: dict[str, object]
40
- for key, value in kwargs.items():
41
- if value in str2obj:
42
- kwargs[key] = str2obj[str(value)]
43
- if args.function is None: # if user passed arguments and forgot to pass function, then assume they want to run the main function.
44
- args.choose_function = True
45
- else:
46
- kwargs = {}
47
- return kwargs
34
+ """Extract kwargs from command line using -- separator.
35
+
36
+ Returns empty dict since kwargs are now parsed directly from sys.argv
37
+ using the -- separator pattern in the main function.
38
+ """
39
+ return {}
40
+
41
+
42
+ def parse_fire_args_from_argv() -> str:
43
+ """Parse arguments after -- separator for Fire compatibility.
44
+
45
+ Returns:
46
+ String of Fire-compatible arguments to append to command
47
+ """
48
+ import sys
49
+
50
+ if '--' in sys.argv:
51
+ separator_index = sys.argv.index('--')
52
+ fire_args = sys.argv[separator_index + 1:]
53
+ # Join all Fire arguments - they should already be in Fire format
54
+ return ' '.join(fire_args) if fire_args else ''
55
+
56
+ return ''
57
+
58
+
59
+ def parse_fire_args_from_context(ctx: Any) -> str:
60
+ """Parse Fire arguments from typer context.
61
+
62
+ Args:
63
+ ctx: Typer context containing raw arguments
64
+
65
+ Returns:
66
+ String of Fire-compatible arguments to append to command
67
+ """
68
+ # Get remaining args that weren't consumed by typer
69
+ if hasattr(ctx, 'args') and ctx.args:
70
+ args = ctx.args
71
+ # Filter out the -- separator if present
72
+ if args and args[0] == '--':
73
+ args = args[1:]
74
+ return ' '.join(args)
75
+ return ''
@@ -10,34 +10,39 @@ if TYPE_CHECKING:
10
10
  from machineconfig.scripts.python.fire_jobs_args_helper import FireJobArgs
11
11
 
12
12
 
13
- def select_layout(layouts_json_file: Path, layout_name: Optional[str]):
13
+ def select_layout(layouts_json_file: Path, layouts_name: Optional[list[str]]) -> list[LayoutConfig]:
14
14
  import json
15
-
16
15
  layout_file: LayoutsFile = json.loads(layouts_json_file.read_text(encoding="utf-8"))
17
16
  if len(layout_file["layouts"]) == 0:
18
17
  raise ValueError(f"No layouts found in {layouts_json_file}")
19
- if layout_name is None:
18
+ if layouts_name is None:
20
19
  options = [layout["layoutName"] for layout in layout_file["layouts"]]
21
20
  from machineconfig.utils.options import choose_from_options
22
-
23
- layout_name = choose_from_options(multi=False, options=options, prompt="Choose a layout configuration:", fzf=True, msg="Choose one option")
24
- print(f"Selected layout: {layout_name}")
25
- layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"] == layout_name), None)
26
- if layout_chosen is None:
27
- layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"].lower() == layout_name.lower()), None)
28
- if layout_chosen is None:
29
- available_layouts = [layout["layoutName"] for layout in layout_file["layouts"]]
30
- raise ValueError(f"Layout '{layout_name}' not found. Available layouts: {available_layouts}")
31
- return layout_chosen
21
+ layouts_name = choose_from_options(multi=True, options=options, prompt="Choose a layout configuration:", fzf=True, msg="Choose one option")
22
+ print(f"Selected layout(s): {layouts_name}")
23
+ # layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"] == layouts_name), None)
24
+ # if layout_chosen is None:
25
+ # layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"].lower() == layouts_name.lower()), None)
26
+ # if layout_chosen is None:
27
+ # available_layouts = [layout["layoutName"] for layout in layout_file["layouts"]]
28
+ # raise ValueError(f"Layout '{layouts_name}' not found. Available layouts: {available_layouts}")
29
+ layouts_chosen: list[LayoutConfig] = []
30
+ for name in layouts_name:
31
+ layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"] == name), None)
32
+ if layout_chosen is None:
33
+ layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"].lower() == name.lower()), None)
34
+ if layout_chosen is None:
35
+ available_layouts = [layout["layoutName"] for layout in layout_file["layouts"]]
36
+ raise ValueError(f"Layout '{name}' not found. Available layouts: {available_layouts}")
37
+ layouts_chosen.append(layout_chosen)
38
+ return layouts_chosen
32
39
 
33
40
 
34
41
  def launch_layout(layout_config: LayoutConfig) -> Optional[Exception]:
35
42
  import platform
36
-
37
43
  if platform.system() == "Linux" or platform.system() == "Darwin":
38
44
  print("🧑‍💻 Launching layout using Zellij terminal multiplexer...")
39
45
  from machineconfig.cluster.sessions_managers.zellij_local import run_zellij_layout
40
-
41
46
  run_zellij_layout(layout_config=layout_config)
42
47
  elif platform.system() == "Windows":
43
48
  print("🧑‍💻 Launching layout using Windows Terminal...")
@@ -63,4 +68,7 @@ def handle_layout_args(args: "FireJobArgs") -> None:
63
68
  choice_file = PathExtended(choice_file)
64
69
  else:
65
70
  choice_file = path_obj
66
- launch_layout(layout_config=select_layout(layouts_json_file=choice_file, layout_name=args.function))
71
+ if args.function is None: layouts_name = None
72
+ else: layouts_name = args.function.split(",")
73
+ for a_layout_config in select_layout(layouts_json_file=choice_file, layouts_name=layouts_name):
74
+ launch_layout(layout_config=a_layout_config)
@@ -2,9 +2,7 @@ from typing import Any, Callable, Optional
2
2
  import inspect
3
3
  import os
4
4
 
5
- # import argparse
6
5
  from machineconfig.utils.path_extended import PathExtended as PathExtended
7
- # from machineconfig.utils.utils import choose_ssh_host
8
6
 
9
7
 
10
8
  def search_for_files_of_interest(path_obj: PathExtended):
@@ -8,80 +8,13 @@ in the event that username@github.com is not mentioned in the remote url.
8
8
  from machineconfig.utils.io import read_ini
9
9
  from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
10
10
  from machineconfig.utils.path_extended import PathExtended as PathExtended
11
- from machineconfig.utils.accessories import randstr
12
- from machineconfig.scripts.python.repos_helper_update import update_repository
13
11
  from machineconfig.scripts.python.repos_helper_record import main as record_repos
14
12
  from machineconfig.scripts.python.repos_helper_clone import clone_repos
13
+ from machineconfig.scripts.python.repos_helper_action import perform_git_operations
15
14
 
16
15
  import typer
17
- from enum import Enum
18
16
  from typing import Annotated, Optional
19
17
 
20
- from rich import print as pprint
21
-
22
-
23
- class GitAction(Enum):
24
- commit = "commit"
25
- push = "push"
26
- pull = "pull"
27
-
28
-
29
- def git_action(path: PathExtended, action: GitAction, mess: Optional[str] = None, r: bool = False, auto_sync: bool = True) -> bool:
30
- """Perform git actions using Python instead of shell scripts. Returns True if successful."""
31
- from git.exc import InvalidGitRepositoryError
32
- from git.repo import Repo
33
-
34
- try:
35
- repo = Repo(str(path), search_parent_directories=False)
36
- except InvalidGitRepositoryError:
37
- pprint(f"⚠️ Skipping {path} because it is not a git repository.")
38
- if r:
39
- results = [git_action(path=sub_path, action=action, mess=mess, r=r, auto_sync=auto_sync) for sub_path in path.search()]
40
- return all(results) # Return True only if all recursive operations succeeded
41
- else:
42
- return False
43
-
44
- print(f">>>>>>>>> 🔧{action} - {path}")
45
-
46
- try:
47
- if action == GitAction.commit:
48
- if mess is None:
49
- mess = "auto_commit_" + randstr()
50
-
51
- # Check if there are changes to commit
52
- if repo.is_dirty() or repo.untracked_files:
53
- repo.git.add(A=True) # Stage all changes
54
- repo.index.commit(mess)
55
- print(f"✅ Committed changes with message: {mess}")
56
- return True
57
- else:
58
- print("ℹ️ No changes to commit")
59
- return True
60
-
61
- elif action == GitAction.push:
62
- success = True
63
- for remote in repo.remotes:
64
- try:
65
- print(f"🚀 Pushing to {remote.url}")
66
- remote.push(repo.active_branch.name)
67
- print(f"✅ Pushed to {remote.name}")
68
- except Exception as e:
69
- print(f"❌ Failed to push to {remote.name}: {e}")
70
- success = False
71
- return success
72
-
73
- elif action == GitAction.pull:
74
- # Use the enhanced update function with uv sync support
75
- update_repository(repo, auto_sync=auto_sync, allow_password_prompt=False)
76
- print("✅ Pull completed")
77
- return True
78
-
79
- except Exception as e:
80
- print(f"❌ Error performing {action} on {path}: {e}")
81
- return False
82
-
83
- return True
84
-
85
18
 
86
19
  def main(
87
20
  directory: Annotated[str, typer.Argument(help="📁 Folder containing repos to record or a specs JSON file to follow.")] = "",
@@ -105,9 +38,7 @@ def main(
105
38
  repos_root = PathExtended.home().joinpath("code") # it is a positional argument, can never be empty.
106
39
  else:
107
40
  repos_root = PathExtended(directory).expanduser().absolute()
108
-
109
41
  auto_sync = not no_sync # Enable auto sync by default, disable with --no-sync
110
-
111
42
  if record:
112
43
  save_path = record_repos(repos_root=repos_root)
113
44
  if cloud is not None:
@@ -115,7 +46,6 @@ def main(
115
46
 
116
47
  elif clone or checkout or checkout_to_branch:
117
48
  print("\n📥 Cloning or checking out repositories...")
118
- print(">>>>>>>>> Cloning Repos")
119
49
  if not repos_root.exists() or repos_root.name != "repos.json":
120
50
  repos_root = PathExtended(CONFIG_PATH).joinpath("repos").joinpath(repos_root.rel2home()).joinpath("repos.json")
121
51
  if not repos_root.exists():
@@ -130,23 +60,15 @@ def main(
130
60
  clone_repos(spec_path=repos_root, preferred_remote=None, checkout_branch_flag=checkout_to_branch, checkout_commit_flag=checkout)
131
61
 
132
62
  elif all or commit or pull or push:
133
- print(f"\n🔄 Performing Git actions on repositories @ `{repos_root}`...")
134
- overall_success = True
135
- for a_path in repos_root.search("*"):
136
- print(f"{('Handling ' + str(a_path)).center(80, '-')}")
137
- path_success = True
138
- if pull or all:
139
- path_success = git_action(path=a_path, action=GitAction.pull, r=recursive, auto_sync=auto_sync) and path_success
140
- if commit or all:
141
- path_success = git_action(a_path, action=GitAction.commit, r=recursive, auto_sync=auto_sync) and path_success
142
- if push or all:
143
- path_success = git_action(a_path, action=GitAction.push, r=recursive, auto_sync=auto_sync) and path_success
144
- overall_success = overall_success and path_success
145
-
146
- if overall_success:
147
- print("✅ All git operations completed successfully")
148
- else:
149
- print("⚠️ Some git operations encountered issues")
63
+ # Use the new helper function for git operations
64
+ perform_git_operations(
65
+ repos_root=repos_root,
66
+ pull=pull or all,
67
+ commit=commit or all,
68
+ push=push or all,
69
+ recursive=recursive,
70
+ auto_sync=auto_sync
71
+ )
150
72
  else:
151
73
  print("❌ No action specified. Try passing --push, --pull, --commit, or --all.")
152
74