machineconfig 2.1__py3-none-any.whl → 2.2__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 (89) hide show
  1. machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +2 -1
  2. machineconfig/cluster/templates/utils.py +0 -35
  3. machineconfig/jobs/python/check_installations.py +1 -1
  4. machineconfig/jobs/python_custom_installers/dev/code.py +0 -13
  5. machineconfig/jobs/python_generic_installers/config.json +1 -1
  6. machineconfig/profile/create.py +10 -5
  7. machineconfig/profile/create_hardlinks.py +3 -1
  8. machineconfig/profile/shell.py +8 -7
  9. machineconfig/scripts/__init__.py +0 -2
  10. machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  11. machineconfig/scripts/linux/devops +6 -4
  12. machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
  13. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
  14. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
  15. machineconfig/scripts/python/__pycache__/fire_agents.cpython-313.pyc +0 -0
  16. machineconfig/scripts/python/ai/generate_files.py +14 -15
  17. machineconfig/scripts/python/ai/mcinit.py +8 -5
  18. machineconfig/scripts/python/archive/tmate_conn.py +5 -5
  19. machineconfig/scripts/python/archive/tmate_start.py +7 -7
  20. machineconfig/scripts/python/choose_wezterm_theme.py +35 -32
  21. machineconfig/scripts/python/cloud_copy.py +22 -13
  22. machineconfig/scripts/python/cloud_mount.py +35 -23
  23. machineconfig/scripts/python/cloud_repo_sync.py +38 -25
  24. machineconfig/scripts/python/cloud_sync.py +4 -4
  25. machineconfig/scripts/python/croshell.py +37 -28
  26. machineconfig/scripts/python/devops.py +45 -27
  27. machineconfig/scripts/python/devops_add_identity.py +15 -25
  28. machineconfig/scripts/python/devops_add_ssh_key.py +7 -7
  29. machineconfig/scripts/python/devops_backup_retrieve.py +17 -15
  30. machineconfig/scripts/python/devops_devapps_install.py +25 -20
  31. machineconfig/scripts/python/devops_update_repos.py +142 -57
  32. machineconfig/scripts/python/dotfile.py +16 -14
  33. machineconfig/scripts/python/fire_agents.py +24 -17
  34. machineconfig/scripts/python/fire_jobs.py +91 -55
  35. machineconfig/scripts/python/ftpx.py +24 -14
  36. machineconfig/scripts/python/get_zellij_cmd.py +8 -7
  37. machineconfig/scripts/python/helpers/cloud_helpers.py +33 -28
  38. machineconfig/scripts/python/helpers/helpers2.py +25 -14
  39. machineconfig/scripts/python/helpers/helpers4.py +44 -30
  40. machineconfig/scripts/python/helpers/helpers5.py +1 -1
  41. machineconfig/scripts/python/helpers/repo_sync_helpers.py +31 -9
  42. machineconfig/scripts/python/mount_nfs.py +8 -15
  43. machineconfig/scripts/python/mount_nw_drive.py +10 -5
  44. machineconfig/scripts/python/mount_ssh.py +8 -6
  45. machineconfig/scripts/python/repos.py +215 -57
  46. machineconfig/scripts/python/snapshot.py +0 -1
  47. machineconfig/scripts/python/start_slidev.py +10 -5
  48. machineconfig/scripts/python/start_terminals.py +22 -16
  49. machineconfig/scripts/python/viewer_template.py +0 -1
  50. machineconfig/scripts/python/wifi_conn.py +49 -75
  51. machineconfig/scripts/python/wsl_windows_transfer.py +8 -6
  52. machineconfig/settings/lf/linux/lfrc +1 -0
  53. machineconfig/setup_linux/web_shortcuts/croshell.sh +5 -0
  54. machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
  55. machineconfig/setup_linux/web_shortcuts/ssh.sh +0 -4
  56. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +3 -12
  57. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +1 -1
  58. machineconfig/utils/code.py +3 -3
  59. machineconfig/utils/installer.py +2 -2
  60. machineconfig/utils/installer_utils/installer_abc.py +3 -4
  61. machineconfig/utils/installer_utils/installer_class.py +6 -4
  62. machineconfig/utils/links.py +103 -33
  63. machineconfig/utils/notifications.py +52 -38
  64. machineconfig/utils/options.py +16 -23
  65. machineconfig/utils/path_reduced.py +239 -205
  66. machineconfig/utils/procs.py +1 -1
  67. machineconfig/utils/source_of_truth.py +27 -0
  68. machineconfig/utils/ssh.py +9 -29
  69. machineconfig/utils/terminal.py +4 -2
  70. machineconfig/utils/upgrade_packages.py +91 -0
  71. machineconfig/utils/utils2.py +1 -2
  72. machineconfig/utils/utils5.py +23 -11
  73. machineconfig/utils/ve.py +4 -1
  74. {machineconfig-2.1.dist-info → machineconfig-2.2.dist-info}/METADATA +13 -13
  75. {machineconfig-2.1.dist-info → machineconfig-2.2.dist-info}/RECORD +78 -86
  76. machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  77. machineconfig/scripts/python/__pycache__/croshell.cpython-313.pyc +0 -0
  78. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
  79. machineconfig/scripts/python/ai/__pycache__/__init__.cpython-313.pyc +0 -0
  80. machineconfig/scripts/python/ai/__pycache__/generate_files.cpython-313.pyc +0 -0
  81. machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-313.pyc +0 -0
  82. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
  83. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
  84. machineconfig/setup_linux/web_shortcuts/all.sh +0 -48
  85. machineconfig/setup_linux/web_shortcuts/update_system.sh +0 -48
  86. machineconfig/utils/utils.py +0 -97
  87. /machineconfig/setup_linux/web_shortcuts/{tmp.sh → android.sh} +0 -0
  88. {machineconfig-2.1.dist-info → machineconfig-2.2.dist-info}/WHEEL +0 -0
  89. {machineconfig-2.1.dist-info → machineconfig-2.2.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,7 @@ import rich.console
5
5
  from machineconfig.utils.terminal import Terminal, Response, MACHINE
6
6
  from machineconfig.utils.path_reduced import PathExtended, PLike, OPLike
7
7
  from machineconfig.utils.utils2 import pprint
8
+ # from machineconfig.utils.ve import get_ve_activate_line
8
9
 
9
10
 
10
11
  @dataclass
@@ -95,7 +96,6 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
95
96
  self.ssh.load_system_host_keys()
96
97
  self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
97
98
  pprint(dict(host=self.host, hostname=self.hostname, username=self.username, password="***", port=self.port, key_filename=self.sshkey, ve=self.ve), title="SSHing To")
98
-
99
99
  sock = paramiko.ProxyCommand(self.proxycommand) if self.proxycommand is not None else None
100
100
  try:
101
101
  if pwd is None:
@@ -109,7 +109,6 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
109
109
  rich.console.Console().print_exception()
110
110
  self.pwd = getpass.getpass(f"Enter password for {self.username}@{self.hostname}: ")
111
111
  self.ssh.connect(hostname=self.hostname, username=self.username, password=self.pwd, port=self.port, key_filename=self.sshkey, compress=self.compress, sock=sock, allow_agent=False, look_for_keys=False) # type: ignore
112
-
113
112
  try:
114
113
  self.sftp: Optional[paramiko.SFTPClient] = self.ssh.open_sftp()
115
114
  except Exception as err:
@@ -120,24 +119,13 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
120
119
  def view_bar(slf: Any, a: Any, b: Any):
121
120
  slf.total = int(b)
122
121
  slf.update(int(a - slf.n)) # update pbar with increment
123
-
124
122
  from tqdm import tqdm
125
-
126
123
  self.tqdm_wrap = type("TqdmWrap", (tqdm,), {"view_bar": view_bar})
127
124
  self._local_distro: Optional[str] = None
128
125
  self._remote_distro: Optional[str] = None
129
126
  self._remote_machine: Optional[MACHINE] = None
130
127
  self.terminal_responses: list[Response] = []
131
128
  self.platform = platform
132
- self.remote_env_cmd = rf"""~/code/machineconfig/{self.ve}/Scripts/Activate.ps1""" if self.get_remote_machine() == "Windows" else rf"""source ~/code/machineconfig/{self.ve}/bin/activate"""
133
- self.local_env_cmd = rf"""~/code/machineconfig/{self.ve}/Scripts/Activate.ps1""" if self.platform.system() == "Windows" else rf"""source ~/code/machineconfig/{self.ve}/bin/activate""" # works for both cmd and pwsh
134
-
135
- def __getstate__(self):
136
- return {attr: self.__getattribute__(attr) for attr in ["username", "hostname", "host", "port", "sshkey", "compress", "pwd", "ve"]}
137
-
138
- def __setstate__(self, state: dict[str, Any]):
139
- SSH(**state)
140
-
141
129
  def get_remote_machine(self) -> MACHINE:
142
130
  if self._remote_machine is None:
143
131
  if self.run("$env:OS", verbose=False, desc="Testing Remote OS Type").op == "Windows_NT" or self.run("echo %OS%", verbose=False, desc="Testing Remote OS Type Again").op == "Windows_NT":
@@ -150,16 +138,14 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
150
138
  if self._local_distro is None:
151
139
  command = """uv run --with distro python -c "import distro; print(distro.name(pretty=True))" """
152
140
  import subprocess
153
-
154
141
  res = subprocess.run(command, shell=True, capture_output=True, text=True).stdout.strip()
155
142
  self._local_distro = res
156
143
  return res
157
144
  return self._local_distro
158
-
159
145
  def get_remote_distro(self):
160
146
  if self._remote_distro is None:
161
- self._remote_distro = self.run_py("print(install_n_import('distro').name(pretty=True))", verbose=False).op_if_successfull_or_default() or ""
162
- # q.run("""~/.local/bin/uv run --with distro python -c "import distro; print(distro.name(pretty=True))" """)
147
+ res = self.run("""~/.local/bin/uv run --with distro python -c "import distro; print(distro.name(pretty=True))" """)
148
+ self._remote_distro = res.op_if_successfull_or_default() or ""
163
149
  return self._remote_distro
164
150
 
165
151
  def restart_computer(self):
@@ -192,13 +178,9 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
192
178
  res = Response(cmd=command)
193
179
  res.output.returncode = os.system(command)
194
180
  return res
195
-
196
181
  def get_ssh_conn_str(self, cmd: str = ""):
197
182
  return "ssh " + (f" -i {self.sshkey}" if self.sshkey else "") + self.get_remote_repr().replace(":", " -p ") + (f" -t {cmd} " if cmd != "" else " ")
198
-
199
- # def open_console(self, cmd: str = '', new_window: bool = True, terminal: Optional[str] = None, shell: str = "pwsh"): Terminal().run_async(*(self.get_ssh_conn_str(cmd=cmd).split(" ")), new_window=new_window, terminal=terminal, shell=shell)
200
- def run(self, cmd: str, verbose: bool = True, desc: str = "", strict_err: bool = False, strict_returncode: bool = False, env_prefix: bool = False) -> Response: # most central method.
201
- cmd = (self.remote_env_cmd + "; " + cmd) if env_prefix else cmd
183
+ def run(self, cmd: str, verbose: bool = True, desc: str = "", strict_err: bool = False, strict_returncode: bool = False) -> Response:
202
184
  raw = self.ssh.exec_command(cmd)
203
185
  res = Response(stdin=raw[0], stdout=raw[1], stderr=raw[2], cmd=cmd, desc=desc) # type: ignore
204
186
  if not verbose:
@@ -207,20 +189,16 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
207
189
  res.print()
208
190
  self.terminal_responses.append(res)
209
191
  return res
210
-
211
192
  def run_py(self, cmd: str, desc: str = "", return_obj: bool = False, verbose: bool = True, strict_err: bool = False, strict_returncode: bool = False) -> Union[Any, Response]:
212
193
  assert '"' not in cmd, 'Avoid using `"` in your command. I dont know how to handle this when passing is as command to python in pwsh command.'
213
194
  if not return_obj:
214
195
  return self.run(
215
- cmd=f"""{self.remote_env_cmd}; python -c "{Terminal.get_header(wdir=None, toolbox=True)}{cmd}\n""" + '"', desc=desc or f"run_py on {self.get_remote_repr()}", verbose=verbose, strict_err=strict_err, strict_returncode=strict_returncode
216
- )
196
+ cmd=f"""uv run --with machineconfig -c "{Terminal.get_header(wdir=None, toolbox=True)}{cmd}\n""" + '"', desc=desc or f"run_py on {self.get_remote_repr()}", verbose=verbose, strict_err=strict_err, strict_returncode=strict_returncode)
217
197
  assert "obj=" in cmd, "The command sent to run_py must have `obj=` statement if return_obj is set to True"
218
198
  source_file = self.run_py(f"""{cmd}\npath = Save.pickle(obj=obj, path=P.tmpfile(suffix='.pkl'))\nprint(path)""", desc=desc, verbose=verbose, strict_err=True, strict_returncode=True).op.split("\n")[-1]
219
199
  res = self.copy_to_here(source=source_file, target=PathExtended.tmpfile(suffix=".pkl"))
220
200
  import pickle
221
-
222
- res_bytes = res.read_bytes()
223
- return pickle.loads(res_bytes)
201
+ return pickle.loads(res.read_bytes())
224
202
 
225
203
  def copy_from_here(self, source: PLike, target: OPLike = None, z: bool = False, r: bool = False, overwrite: bool = False, init: bool = True) -> Union[PathExtended, list[PathExtended]]:
226
204
  if init:
@@ -239,7 +217,9 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
239
217
  source_list: list[PathExtended] = source_obj.search("*", folders=False, files=True, r=True)
240
218
  remote_root = (
241
219
  self.run_py(
242
- f"path=P(r'{PathExtended(target).as_posix()}').expanduser()\n{'path.delete(sure=True)' if overwrite else ''}\nprint(path.create())", desc=f"Creating Target directory `{PathExtended(target).as_posix()}` @ {self.get_remote_repr()}", verbose=False
220
+ f"path=P(r'{PathExtended(target).as_posix()}').expanduser()\n{'path.delete(sure=True)' if overwrite else ''}\nprint(path.create())",
221
+ desc=f"Creating Target directory `{PathExtended(target).as_posix()}` @ {self.get_remote_repr()}",
222
+ verbose=False,
243
223
  ).op
244
224
  or ""
245
225
  )
@@ -143,7 +143,8 @@ class Terminal:
143
143
  def run_script(self, script: str, shell: SHELLS = "default", verbose: bool = False):
144
144
  if self.machine == "Linux":
145
145
  script = "#!/bin/bash" + "\n" + script # `source` is only available in bash.
146
- script_file = PathExtended.tmpfile(name="tmp_shell_script", suffix=".ps1" if self.machine == "Windows" else ".sh", folder="tmp_scripts").write_text(script, newline={"Windows": None, "Linux": "\n"}[self.machine])
146
+ script_file = PathExtended.tmpfile(name="tmp_shell_script", suffix=".ps1" if self.machine == "Windows" else ".sh", folder="tmp_scripts")
147
+ script_file.write_text(script, newline={"Windows": None, "Linux": "\n"}[self.machine])
147
148
  if shell == "default":
148
149
  if self.machine == "Windows":
149
150
  start_cmd = "powershell" # default shell on Windows is cmd which is not very useful. (./source is not available)
@@ -188,7 +189,8 @@ class Terminal:
188
189
  {f"cd {wdir}" if wdir is not None else ""}
189
190
  {"ipython" if ipython else "python"} {"-i" if interactive else ""} {py_script}
190
191
  """
191
- shell_script = PathExtended.tmpfile(name="tmp_shell_script", suffix=".sh" if self.machine == "Linux" else ".ps1", folder="tmp_scripts/shell").write_text(shell_script)
192
+ shell_path = PathExtended.tmpfile(name="tmp_shell_script", suffix=".sh" if self.machine == "Linux" else ".ps1", folder="tmp_scripts/shell")
193
+ shell_path.write_text(shell_script)
192
194
  if shell is None and self.machine == "Windows":
193
195
  shell = "pwsh"
194
196
  window = "start" if new_window and self.machine == "Windows" else ""
@@ -0,0 +1,91 @@
1
+ """
2
+ Generate uv add commands from pyproject.toml dependency groups.
3
+ """
4
+
5
+ from pathlib import Path
6
+ import tomllib
7
+ from typing import Any
8
+
9
+
10
+ def generate_uv_add_commands(pyproject_path: Path, output_path: Path) -> None:
11
+ """
12
+ Generate uv add commands for each dependency group in pyproject.toml.
13
+
14
+ Args:
15
+ pyproject_path: Path to the pyproject.toml file
16
+ output_path: Path where to write the uv add commands
17
+ """
18
+ # Read pyproject.toml
19
+ with open(pyproject_path, "rb") as f:
20
+ pyproject_data: dict[str, Any] = tomllib.load(f)
21
+
22
+ commands: list[str] = []
23
+
24
+ # Handle main dependencies (no group)
25
+ if "project" in pyproject_data and "dependencies" in pyproject_data["project"]:
26
+ main_deps = pyproject_data["project"]["dependencies"]
27
+ if main_deps:
28
+ # Extract package names without version constraints
29
+ package_names = [extract_package_name(dep) for dep in main_deps]
30
+ commands.append(f"uv add {' '.join(package_names)}")
31
+
32
+ # Handle optional dependencies as groups
33
+ if "project" in pyproject_data and "optional-dependencies" in pyproject_data["project"]:
34
+ optional_deps = pyproject_data["project"]["optional-dependencies"]
35
+ for group_name, deps in optional_deps.items():
36
+ if deps:
37
+ package_names = [extract_package_name(dep) for dep in deps]
38
+ commands.append(f"uv add {' '.join(package_names)} --group {group_name}")
39
+
40
+ # Handle dependency-groups (like dev)
41
+ if "dependency-groups" in pyproject_data:
42
+ dep_groups = pyproject_data["dependency-groups"]
43
+ for group_name, deps in dep_groups.items():
44
+ if deps:
45
+ package_names = [extract_package_name(dep) for dep in deps]
46
+ if group_name == "dev":
47
+ commands.append(f"uv add {' '.join(package_names)} --dev")
48
+ else:
49
+ commands.append(f"uv add {' '.join(package_names)} --group {group_name}")
50
+
51
+ # Write commands to output file
52
+ with open(output_path, "w") as f:
53
+ for command in commands:
54
+ f.write(command + "\n")
55
+
56
+ print(f"Generated {len(commands)} uv add commands in {output_path}")
57
+
58
+
59
+ def extract_package_name(dependency_spec: str) -> str:
60
+ """
61
+ Extract package name from dependency specification.
62
+
63
+ Examples:
64
+ "rich>=14.0.0" -> "rich"
65
+ "requests>=2.32.5" -> "requests"
66
+ "pywin32" -> "pywin32"
67
+ "package[extra]>=1.0" -> "package"
68
+ """
69
+ # Handle extras like "package[extra]>=1.0" first
70
+ if "[" in dependency_spec:
71
+ dependency_spec = dependency_spec.split("[")[0].strip()
72
+
73
+ # Split on common version operators and take the first part
74
+ for operator in [">=", "<=", "==", "!=", ">", "<", "~=", "===", "@"]:
75
+ if operator in dependency_spec:
76
+ return dependency_spec.split(operator)[0].strip()
77
+
78
+ # Return as-is if no version constraint found
79
+ return dependency_spec.strip()
80
+
81
+
82
+ if __name__ == "__main__":
83
+ # Example usage
84
+ current_dir = Path.cwd()
85
+ pyproject_file = current_dir / "pyproject.toml"
86
+ output_file = current_dir / "uv_add_commands.txt"
87
+
88
+ if pyproject_file.exists():
89
+ generate_uv_add_commands(pyproject_file, output_file)
90
+ else:
91
+ print(f"pyproject.toml not found at {pyproject_file}")
@@ -1,7 +1,5 @@
1
1
  from pathlib import Path
2
2
  from typing import Optional, Any
3
- # import time
4
- # from typing import Callable, Literal, TypeVar, ParamSpec
5
3
 
6
4
 
7
5
  def randstr(length: int = 10, lower: bool = True, upper: bool = True, digits: bool = True, punctuation: bool = False, safe: bool = False, noun: bool = False) -> str:
@@ -51,6 +49,7 @@ def read_toml(path: "Path"):
51
49
 
52
50
  def pprint(obj: dict[Any, Any], title: str) -> None:
53
51
  from rich import inspect
52
+
54
53
  inspect(type("TempStruct", (object,), obj)(), value=False, title=title, docs=False, dunder=False, sort=False)
55
54
 
56
55
 
@@ -149,20 +149,25 @@ T2 = TypeVar("T2")
149
149
  class PrintFunc(Protocol):
150
150
  def __call__(self, msg: str) -> Union[NoReturn, None]: ...
151
151
 
152
+
152
153
  def to_pickle(obj: Any, path: Path) -> None:
153
154
  import pickle
155
+
154
156
  path.parent.mkdir(parents=True, exist_ok=True)
155
157
  path.write_bytes(pickle.dumps(obj))
158
+
159
+
156
160
  def from_pickle(path: Path) -> Any:
157
161
  import pickle
162
+
158
163
  return pickle.loads(path.read_bytes())
159
164
 
160
165
 
161
166
  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
167
  # 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:
168
+ def __init__(
169
+ self, source_func: Callable[[], T], expire: timedelta, logger: Optional[PrintFunc] = None, path: Optional[Path] = None, saver: Callable[[T, Path], Any] = to_pickle, reader: Callable[[Path], T] = from_pickle, name: Optional[str] = None
170
+ ) -> None:
166
171
  self.cache: T
167
172
  self.source_func = source_func # function which when called returns a fresh object to be frozen.
168
173
  self.path: Optional[PathExtended] = PathExtended(path) if path is not None else None # if path is passed, it will function as disk-based flavour.
@@ -173,12 +178,14 @@ class Cache(Generic[T]): # This class helps to accelrate access to latest data
173
178
  self.expire = expire
174
179
  self.name = name if isinstance(name, str) else str(self.source_func)
175
180
  self.last_call_is_fresh = False
181
+
176
182
  @property
177
183
  def age(self):
178
184
  """Throws AttributeError if called before cache is populated and path doesn't exists"""
179
185
  if self.path is None: # memory-based cache.
180
186
  return datetime.now() - self.time_produced
181
187
  return datetime.now() - datetime.fromtimestamp(self.path.stat().st_mtime)
188
+
182
189
  # def __setstate__(self, state: dict[str, Any]) -> None:
183
190
  # self.__dict__.update(state)
184
191
  # self.path = P.home() / self.path if self.path is not None else self.path
@@ -228,9 +235,11 @@ class Cache(Generic[T]): # This class helps to accelrate access to latest data
228
235
  self.cache = self.source_func() # fresh data.
229
236
  self.last_call_is_fresh = True
230
237
  self.time_produced = datetime.now()
231
- if self.path is not None: self.save(self.cache, self.path)
238
+ if self.path is not None:
239
+ self.save(self.cache, self.path)
232
240
  else: # cache exists
233
- try: age = self.age
241
+ try:
242
+ age = self.age
234
243
  except AttributeError: # path doesn't exist (may be deleted) ==> need to repopulate cache form source_func.
235
244
  return self(fresh=True)
236
245
  if age > self.expire:
@@ -243,7 +252,8 @@ class Cache(Generic[T]): # This class helps to accelrate access to latest data
243
252
  self.cache = self.source_func()
244
253
  self.last_call_is_fresh = True
245
254
  self.time_produced = datetime.now()
246
- if self.path is not None: self.save(self.cache, self.path)
255
+ if self.path is not None:
256
+ self.save(self.cache, self.path)
247
257
  else:
248
258
  if self.logger:
249
259
  self.logger(f"""
@@ -252,15 +262,17 @@ class Cache(Generic[T]): # This class helps to accelrate access to latest data
252
262
  ⏱️ Lag = {age}
253
263
  ════════════════════════════════════════════════════════""")
254
264
  return self.cache
265
+
255
266
  @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']:
267
+ def as_decorator(
268
+ expire: timedelta, logger: Optional[PrintFunc] = None, path: Optional[Path] = None, saver: Callable[[T2, Path], Any] = to_pickle, reader: Callable[[Path], T2] = from_pickle, name: Optional[str] = None
269
+ ): # -> Callable[..., 'Cache[T2]']:
270
+ def decorator(source_func: Callable[[], T2]) -> Cache["T2"]:
261
271
  res = Cache(source_func=source_func, expire=expire, logger=logger, path=path, name=name, reader=reader, saver=saver)
262
272
  return res
273
+
263
274
  return decorator
275
+
264
276
  def from_cloud(self, cloud: str, rel2home: bool = True, root: Optional[str] = None):
265
277
  assert self.path is not None
266
278
  exists = self.path.exists()
machineconfig/utils/ve.py CHANGED
@@ -13,7 +13,10 @@ def get_ve_path_and_ipython_profile(init_path: PathExtended) -> tuple[Optional[s
13
13
  if tmp.joinpath(".ve.ini").exists():
14
14
  ini = read_ini(tmp.joinpath(".ve.ini"))
15
15
  if ve_path is None:
16
- ve_path = ini["specs"]["ve_path"]
16
+ try:
17
+ ve_path = ini["specs"]["ve_path"]
18
+ except KeyError:
19
+ raise KeyError(f".ve.ini file at {tmp.joinpath('.ve.ini')} is missing the 've_path' key in the 'specs' section.")
17
20
  print(f"🐍 Using Virtual Environment: {ve_path}. This is based on this file {tmp.joinpath('.ve.ini')}")
18
21
  if ipy_profile is None:
19
22
  ipy_profile = ini["specs"]["ipy_profile"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: machineconfig
3
- Version: 2.1
3
+ Version: 2.2
4
4
  Summary: Dotfiles management package
5
5
  Author-email: Alex Al-Saffar <programmer@usa.com>
6
6
  License: Apache 2.0
@@ -11,26 +11,26 @@ Classifier: License :: OSI Approved :: Apache Software License
11
11
  Classifier: Operating System :: OS Independent
12
12
  Requires-Python: >=3.13
13
13
  Description-Content-Type: text/markdown
14
- Requires-Dist: rich>=14.0.0
14
+ Requires-Dist: cryptography>=44.0.2
15
+ Requires-Dist: fire>=0.7.0
16
+ Requires-Dist: gitpython>=3.1.44
17
+ Requires-Dist: joblib>=1.5.2
18
+ Requires-Dist: markdown>=3.9
15
19
  Requires-Dist: paramiko>=3.5.1
16
20
  Requires-Dist: psutil>=7.0.0
17
- Requires-Dist: fire>=0.7.0
18
21
  Requires-Dist: pydantic>=2.11.3
19
- Requires-Dist: gitpython>=3.1.44
20
22
  Requires-Dist: pyfzf>=0.3.1
21
- Requires-Dist: rclone-python>=0.1.23
23
+ Requires-Dist: pyjson5>=1.6.9
22
24
  Requires-Dist: pytz>=2025.2
23
- Requires-Dist: tomli>=2.2.1
24
- Requires-Dist: toml>=0.10.2
25
25
  Requires-Dist: pyyaml>=6.0.2
26
- Requires-Dist: pyjson5>=1.6.9
27
- Requires-Dist: requests>=2.32.5
28
- Requires-Dist: tqdm>=4.67.1
29
- Requires-Dist: joblib>=1.5.2
30
26
  Requires-Dist: randomname>=0.2.1
31
- Requires-Dist: cryptography>=44.0.2
27
+ Requires-Dist: rclone-python>=0.1.23
28
+ Requires-Dist: requests>=2.32.5
29
+ Requires-Dist: rich>=14.0.0
32
30
  Requires-Dist: tenacity>=9.1.2
33
- Requires-Dist: markdown>=3.9
31
+ Requires-Dist: toml>=0.10.2
32
+ Requires-Dist: tomli>=2.2.1
33
+ Requires-Dist: tqdm>=4.67.1
34
34
  Provides-Extra: windows
35
35
  Requires-Dist: pywin32; extra == "windows"
36
36
  Provides-Extra: docs