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.
- {accli-2.0.7 → accli-2.0.12}/PKG-INFO +1 -1
- {accli-2.0.7 → accli-2.0.12}/accli/AcceleratorTaskDispatcher.py +74 -24
- {accli-2.0.7 → accli-2.0.12}/accli/_version.py +1 -1
- {accli-2.0.7 → accli-2.0.12}/accli/gui.py +42 -15
- {accli-2.0.7 → accli-2.0.12}/accli/token.py +1 -0
- {accli-2.0.7 → accli-2.0.12}/accli.egg-info/PKG-INFO +1 -1
- {accli-2.0.7 → accli-2.0.12}/LICENSE +0 -0
- {accli-2.0.7 → accli-2.0.12}/README.md +0 -0
- {accli-2.0.7 → accli-2.0.12}/accli/AcceleratorJobProjectService.py +0 -0
- {accli-2.0.7 → accli-2.0.12}/accli/AcceleratorTerminalCliProjectService.py +0 -0
- {accli-2.0.7 → accli-2.0.12}/accli/CsvRegionalTimeseriesValidator.py +0 -0
- {accli-2.0.7 → accli-2.0.12}/accli/__init__.py +0 -0
- {accli-2.0.7 → accli-2.0.12}/accli/__main__.py +0 -0
- {accli-2.0.7 → accli-2.0.12}/accli/cleanup-nfs.ps1 +0 -0
- {accli-2.0.7 → accli-2.0.12}/accli/cli.py +0 -0
- {accli-2.0.7 → accli-2.0.12}/accli/common.py +0 -0
- {accli-2.0.7 → accli-2.0.12}/accli/mount_downloader.py +0 -0
- {accli-2.0.7 → accli-2.0.12}/accli/nfs_watcher.py +0 -0
- {accli-2.0.7 → accli-2.0.12}/accli/refresh_explorer.ps1 +0 -0
- {accli-2.0.7 → accli-2.0.12}/accli/setup-nfs.ps1 +0 -0
- {accli-2.0.7 → accli-2.0.12}/accli.egg-info/SOURCES.txt +0 -0
- {accli-2.0.7 → accli-2.0.12}/accli.egg-info/dependency_links.txt +0 -0
- {accli-2.0.7 → accli-2.0.12}/accli.egg-info/entry_points.txt +0 -0
- {accli-2.0.7 → accli-2.0.12}/accli.egg-info/requires.txt +0 -0
- {accli-2.0.7 → accli-2.0.12}/accli.egg-info/top_level.txt +0 -0
- {accli-2.0.7 → accli-2.0.12}/pyproject.toml +0 -0
- {accli-2.0.7 → accli-2.0.12}/setup.cfg +0 -0
|
@@ -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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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,
|
|
158
|
+
project_slug, manifest_name
|
|
109
159
|
)
|
|
110
|
-
|
|
160
|
+
|
|
111
161
|
if presigned_push_url:
|
|
112
|
-
with open(
|
|
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://{
|
|
171
|
+
return f"s3accjobstore://{manifest_name}", manifest_hash
|
|
122
172
|
|
|
123
173
|
|
|
124
174
|
class JobDispatchModel(BaseModel):
|
|
@@ -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",
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
self.
|
|
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=
|
|
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
|
-
|
|
310
|
-
self.
|
|
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()
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|