accli 2.0.7__tar.gz → 2.0.12__tar.gz

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.
Files changed (27) hide show
  1. {accli-2.0.7 → accli-2.0.12}/PKG-INFO +1 -1
  2. {accli-2.0.7 → accli-2.0.12}/accli/AcceleratorTaskDispatcher.py +74 -24
  3. {accli-2.0.7 → accli-2.0.12}/accli/_version.py +1 -1
  4. {accli-2.0.7 → accli-2.0.12}/accli/gui.py +42 -15
  5. {accli-2.0.7 → accli-2.0.12}/accli/token.py +1 -0
  6. {accli-2.0.7 → accli-2.0.12}/accli.egg-info/PKG-INFO +1 -1
  7. {accli-2.0.7 → accli-2.0.12}/LICENSE +0 -0
  8. {accli-2.0.7 → accli-2.0.12}/README.md +0 -0
  9. {accli-2.0.7 → accli-2.0.12}/accli/AcceleratorJobProjectService.py +0 -0
  10. {accli-2.0.7 → accli-2.0.12}/accli/AcceleratorTerminalCliProjectService.py +0 -0
  11. {accli-2.0.7 → accli-2.0.12}/accli/CsvRegionalTimeseriesValidator.py +0 -0
  12. {accli-2.0.7 → accli-2.0.12}/accli/__init__.py +0 -0
  13. {accli-2.0.7 → accli-2.0.12}/accli/__main__.py +0 -0
  14. {accli-2.0.7 → accli-2.0.12}/accli/cleanup-nfs.ps1 +0 -0
  15. {accli-2.0.7 → accli-2.0.12}/accli/cli.py +0 -0
  16. {accli-2.0.7 → accli-2.0.12}/accli/common.py +0 -0
  17. {accli-2.0.7 → accli-2.0.12}/accli/mount_downloader.py +0 -0
  18. {accli-2.0.7 → accli-2.0.12}/accli/nfs_watcher.py +0 -0
  19. {accli-2.0.7 → accli-2.0.12}/accli/refresh_explorer.ps1 +0 -0
  20. {accli-2.0.7 → accli-2.0.12}/accli/setup-nfs.ps1 +0 -0
  21. {accli-2.0.7 → accli-2.0.12}/accli.egg-info/SOURCES.txt +0 -0
  22. {accli-2.0.7 → accli-2.0.12}/accli.egg-info/dependency_links.txt +0 -0
  23. {accli-2.0.7 → accli-2.0.12}/accli.egg-info/entry_points.txt +0 -0
  24. {accli-2.0.7 → accli-2.0.12}/accli.egg-info/requires.txt +0 -0
  25. {accli-2.0.7 → accli-2.0.12}/accli.egg-info/top_level.txt +0 -0
  26. {accli-2.0.7 → accli-2.0.12}/pyproject.toml +0 -0
  27. {accli-2.0.7 → accli-2.0.12}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: accli
3
- Version: 2.0.7
3
+ Version: 2.0.12
4
4
  Summary: IIASA Accelerator Client
5
5
  Author-email: Wrufesh S <wrufesh@gmail.com>
6
6
  License: The MIT License (MIT)
@@ -4,6 +4,9 @@ import hashlib
4
4
  import shutil
5
5
  import tempfile
6
6
  import fnmatch
7
+ import concurrent.futures
8
+ import json
9
+ from pathlib import Path
7
10
  from typing import Optional, Dict, List
8
11
 
9
12
  import requests
@@ -50,9 +53,13 @@ def get_file_sha1(file_path):
50
53
 
51
54
 
52
55
  def copy_tree(src, dst, include=None, exclude=None, root_src=None):
53
- """Recursively copy from src to dst, excluding .git folders."""
56
+ """Recursively copy from src to dst, excluding .git folders. Includes override excludes."""
54
57
  if root_src is None:
55
58
  root_src = src
59
+
60
+ excludes = [e.strip() for e in exclude.split(',') if e.strip()] if exclude else []
61
+ includes = [i.strip() for i in include.split(',') if i.strip()] if include else []
62
+
56
63
  for item in os.listdir(src):
57
64
  src_path = os.path.join(src, item)
58
65
  dst_path = os.path.join(dst, item)
@@ -61,24 +68,54 @@ def copy_tree(src, dst, include=None, exclude=None, root_src=None):
61
68
  if os.path.isdir(src_path):
62
69
  if item == '.git':
63
70
  continue
64
- if exclude:
65
- excludes = [e.strip() for e in exclude.split(',') if e.strip()]
66
- if any(fnmatch.fnmatch(rel_path, p) or fnmatch.fnmatch(item, p) for p in excludes):
67
- continue
71
+
72
+ is_excluded = any(fnmatch.fnmatch(rel_path, p) or fnmatch.fnmatch(item, p) for p in excludes)
73
+ # Traverse even if excluded as long as includes exist, to find exceptions
74
+ if is_excluded and not includes:
75
+ continue
76
+
68
77
  os.makedirs(dst_path, exist_ok=True)
69
78
  copy_tree(src_path, dst_path, include, exclude, root_src)
70
79
  else:
71
- if exclude:
72
- excludes = [e.strip() for e in exclude.split(',') if e.strip()]
73
- if any(fnmatch.fnmatch(rel_path, p) or fnmatch.fnmatch(item, p) for p in excludes):
74
- continue
75
- if include:
76
- includes = [i.strip() for i in include.split(',') if i.strip()]
77
- if not any(fnmatch.fnmatch(rel_path, p) or fnmatch.fnmatch(item, p) for p in includes):
78
- continue
80
+ is_excluded = any(fnmatch.fnmatch(rel_path, p) or fnmatch.fnmatch(item, p) for p in excludes)
81
+ is_included = any(fnmatch.fnmatch(rel_path, p) or fnmatch.fnmatch(item, p) for p in includes)
82
+
83
+ if is_included:
84
+ pass # Included files override exclude
85
+ elif is_excluded:
86
+ continue # Skipped by exclude
87
+ elif includes and not excludes:
88
+ continue # If ONLY includes list is provided, act as whitelist
89
+
79
90
  shutil.copy2(src_path, dst_path)
80
91
 
81
92
 
93
+ def _upload_cas_file_worker(args):
94
+ rel_path, file_path, term_cli_project_service, project_slug = args
95
+ file_hash = get_file_sha1(file_path)
96
+ blob_name = f"cas_{file_hash}.blob"
97
+
98
+ print(f"Checking {rel_path} (hash: {file_hash[:8]})...")
99
+ presigned_push_url = term_cli_project_service.get_jobstore_push_url(
100
+ project_slug, blob_name
101
+ )
102
+ if presigned_push_url:
103
+ print(f"Uploading {rel_path}...")
104
+ with open(file_path, 'rb') as f:
105
+ res = requests.put(
106
+ presigned_push_url,
107
+ data=f,
108
+ verify=False,
109
+ )
110
+ res.raise_for_status()
111
+ print(f"Uploaded {rel_path}.")
112
+ else:
113
+ print(f"Skipped {rel_path} (already in jobstore).")
114
+
115
+ mode = os.stat(file_path).st_mode
116
+ return {"path": rel_path, "hash": file_hash, "mode": mode}
117
+
118
+
82
119
  @lru_cache(maxsize=None)
83
120
  def push_folder_job(directory, include=None, exclude=None):
84
121
  server_url = get_server_url()
@@ -97,19 +134,32 @@ def push_folder_job(directory, include=None, exclude=None):
97
134
  if os.path.isfile(f'{repo_dir}/wkube.py'):
98
135
  os.remove(f'{repo_dir}/wkube.py')
99
136
 
100
- temp_zip_path = f"{repo_dir}/temp.zip"
101
- compress_folder(repo_dir, temp_zip_path)
102
-
103
- sha256_hash = get_file_sha1(temp_zip_path)
104
- final_zip_path = f"{repo_dir}/{sha256_hash}.zip"
105
- os.rename(temp_zip_path, final_zip_path)
106
-
137
+ manifest = {"type": "cas_manifest", "files": []}
138
+
139
+ upload_args = []
140
+ for root, dirs, files in os.walk(repo_dir):
141
+ for file in files:
142
+ file_path = os.path.join(root, file)
143
+ rel_path = Path(file_path).relative_to(repo_dir).as_posix()
144
+ upload_args.append((rel_path, file_path, term_cli_project_service, project_slug))
145
+
146
+ with concurrent.futures.ThreadPoolExecutor(max_workers=30) as executor:
147
+ for result in executor.map(_upload_cas_file_worker, upload_args):
148
+ manifest["files"].append(result)
149
+
150
+ manifest_path = os.path.join(repo_dir, "manifest.json")
151
+ with open(manifest_path, "w") as f:
152
+ json.dump(manifest, f)
153
+
154
+ manifest_hash = get_file_sha1(manifest_path)
155
+ manifest_name = f"manifest_{manifest_hash}.json"
156
+
107
157
  presigned_push_url = term_cli_project_service.get_jobstore_push_url(
108
- project_slug, f"{sha256_hash}.zip"
158
+ project_slug, manifest_name
109
159
  )
110
-
160
+
111
161
  if presigned_push_url:
112
- with open(final_zip_path, 'rb') as f:
162
+ with open(manifest_path, 'rb') as f:
113
163
  res = requests.put(
114
164
  presigned_push_url,
115
165
  data=f,
@@ -118,7 +168,7 @@ def push_folder_job(directory, include=None, exclude=None):
118
168
  res.raise_for_status()
119
169
 
120
170
  shutil.rmtree(repo_dir)
121
- return f"s3accjobstore://{sha256_hash}.zip", sha256_hash
171
+ return f"s3accjobstore://{manifest_name}", manifest_hash
122
172
 
123
173
 
124
174
  class JobDispatchModel(BaseModel):
@@ -1,3 +1,3 @@
1
1
  # Please strictly put double quote to use this info for git tag
2
2
  # Read a developer guide for git tag command with regex
3
- VERSION = "v2.0.7"
3
+ VERSION = "v2.0.12"
@@ -88,7 +88,11 @@ class AccliGuiApp(tk.Tk):
88
88
 
89
89
  # Checkboxes
90
90
  self.style.configure("TCheckbutton", background="#1e1e1e", foreground="#ffffff")
91
- self.style.map("TCheckbutton", background=[("active", "#1e1e1e")])
91
+ self.style.map("TCheckbutton",
92
+ background=[("active", "#1e1e1e")],
93
+ indicatorbackground=[("selected", "#007acc"), ("!selected", "#2d2d2d")],
94
+ indicatorforeground=[("selected", "#ffffff"), ("!selected", "#2d2d2d")]
95
+ )
92
96
 
93
97
  def create_widgets(self):
94
98
  # Top banner
@@ -122,7 +126,7 @@ class AccliGuiApp(tk.Tk):
122
126
  cmd = [sys.executable] + args
123
127
  else:
124
128
  # Script mode
125
- cmd = [sys.executable, "-m", "accli.cli"] + args
129
+ cmd = [sys.executable, "-m", "accli"] + args
126
130
 
127
131
  if elevate and platform.system() != "Windows":
128
132
  import shutil
@@ -162,11 +166,14 @@ class AccliGuiApp(tk.Tk):
162
166
  process.wait()
163
167
 
164
168
  if on_done:
165
- self.after(0, lambda: on_done(process.returncode, "".join(output_lines)))
169
+ ret_code = process.returncode
170
+ out_text = "".join(output_lines)
171
+ self.after(0, lambda: on_done(ret_code, out_text))
166
172
  except Exception as e:
167
- self.append_log(f"ERROR executing command: {e}\n")
173
+ err_text = str(e)
174
+ self.append_log(f"ERROR executing command: {err_text}\n")
168
175
  if on_done:
169
- self.after(0, lambda: on_done(-1, str(e)))
176
+ self.after(0, lambda: on_done(-1, err_text))
170
177
 
171
178
  threading.Thread(target=worker, daemon=True).start()
172
179
 
@@ -196,10 +203,26 @@ class AccliGuiApp(tk.Tk):
196
203
  self.login_btn = ttk.Button(card, text="Authenticate / Login", command=self.action_login)
197
204
  self.login_btn.grid(row=0, column=2, padx=10, pady=5)
198
205
 
206
+ self.debug_mode_val = tk.BooleanVar(value=bool(os.environ.get("ACCLI_DEBUG")))
207
+ self.debug_check = ttk.Checkbutton(
208
+ card,
209
+ text="Debug mode",
210
+ variable=self.debug_mode_val,
211
+ command=self.on_debug_toggle
212
+ )
213
+ self.debug_check.grid(row=1, column=0, columnspan=3, sticky=tk.W, pady=(5, 0))
214
+
199
215
  # Connection status details
200
216
  self.conn_details_txt = tk.Text(auth_tab, height=12, bg="#2d2d2d", fg="#ffffff", insertbackground="white", font=("Courier New", 10), state=tk.DISABLED)
201
217
  self.conn_details_txt.pack(fill=tk.BOTH, expand=True, pady=10)
202
218
 
219
+ def on_debug_toggle(self):
220
+ if self.debug_mode_val.get():
221
+ os.environ["ACCLI_DEBUG"] = "1"
222
+ else:
223
+ os.environ.pop("ACCLI_DEBUG", None)
224
+ self.refresh_login_status()
225
+
203
226
  def refresh_login_status(self):
204
227
  from accli.token import get_db_path
205
228
  from tinydb import TinyDB
@@ -229,13 +252,16 @@ class AccliGuiApp(tk.Tk):
229
252
  self.server_entry.configure(state=tk.NORMAL)
230
253
  self.status_bar_val.set("Session Status: Disconnected")
231
254
 
232
- def on_check_done(code, output):
233
- self.conn_details_txt.configure(state=tk.NORMAL)
234
- self.conn_details_txt.delete("1.0", tk.END)
235
- self.conn_details_txt.insert(tk.END, output)
236
- self.conn_details_txt.configure(state=tk.DISABLED)
237
-
238
- self.run_cli_async(["status"], on_done=on_check_done, log_to_viewer=False)
255
+ details = [
256
+ f"Server URL: {server_url}",
257
+ f"Session Status: {'Connected / Logged In' if logged_in else 'Not Logged In'}",
258
+ f"Token DB Path: {db_path}",
259
+ f"Debug Mode: {'Enabled ($env:ACCLI_DEBUG=\"1\")' if os.environ.get('ACCLI_DEBUG') else 'Disabled'}"
260
+ ]
261
+ self.conn_details_txt.configure(state=tk.NORMAL)
262
+ self.conn_details_txt.delete("1.0", tk.END)
263
+ self.conn_details_txt.insert(tk.END, "\n".join(details) + "\n")
264
+ self.conn_details_txt.configure(state=tk.DISABLED)
239
265
 
240
266
  def action_login(self):
241
267
  if hasattr(self, "is_logged_in_state") and self.is_logged_in_state:
@@ -288,7 +314,7 @@ class AccliGuiApp(tk.Tk):
288
314
  response = requests.post(
289
315
  token_endpoint,
290
316
  json={"device_authorization_code": auth_code.strip()},
291
- verify=True
317
+ verify=(not bool(os.environ.get("ACCLI_DEBUG")))
292
318
  )
293
319
 
294
320
  if response.status_code == 400:
@@ -306,8 +332,9 @@ class AccliGuiApp(tk.Tk):
306
332
  self.after(0, lambda: messagebox.showinfo("Success", "Successfully logged in!"))
307
333
  self.after(0, self.refresh_login_status)
308
334
  except Exception as e:
309
- self.append_log(f"Authentication Failed: {e}\n")
310
- self.after(0, lambda: messagebox.showerror("Error", f"Authentication failed: {e}"))
335
+ err_text = str(e)
336
+ self.append_log(f"Authentication Failed: {err_text}\n")
337
+ self.after(0, lambda msg=err_text: messagebox.showerror("Error", f"Authentication failed: {msg}"))
311
338
  self.after(0, self.refresh_login_status)
312
339
 
313
340
  threading.Thread(target=perform_auth, daemon=True).start()
@@ -3,6 +3,7 @@ import threading
3
3
  import time
4
4
  import requests
5
5
  import typer
6
+ from rich import print
6
7
  from tinydb import TinyDB
7
8
 
8
9
  ACCLI_DEBUG = os.environ.get('ACCLI_DEBUG', False)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: accli
3
- Version: 2.0.7
3
+ Version: 2.0.12
4
4
  Summary: IIASA Accelerator Client
5
5
  Author-email: Wrufesh S <wrufesh@gmail.com>
6
6
  License: The MIT License (MIT)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes