accli 0.1.2__tar.gz → 0.1.3.1__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-0.1.2 → accli-0.1.3.1}/PKG-INFO +4 -1
- accli-0.1.2/accli/AcceleratorProjectService.py → accli-0.1.3.1/accli/AcceleratorJobProjectService.py +14 -5
- accli-0.1.3.1/accli/AcceleratorTerminalCliProjectService.py +272 -0
- accli-0.1.3.1/accli/__init__.py +6 -0
- accli-0.1.3.1/accli/__main__.py +4 -0
- accli-0.1.3.1/accli/cli.py +179 -0
- {accli-0.1.2 → accli-0.1.3.1}/accli.egg-info/PKG-INFO +4 -1
- accli-0.1.3.1/accli.egg-info/SOURCES.txt +14 -0
- accli-0.1.3.1/accli.egg-info/entry_points.txt +2 -0
- accli-0.1.3.1/accli.egg-info/requires.txt +4 -0
- {accli-0.1.2 → accli-0.1.3.1}/pyproject.toml +6 -3
- accli-0.1.2/accli/__init__.py +0 -3
- accli-0.1.2/accli.egg-info/SOURCES.txt +0 -10
- accli-0.1.2/accli.egg-info/requires.txt +0 -1
- {accli-0.1.2 → accli-0.1.3.1}/LICENSE +0 -0
- {accli-0.1.2 → accli-0.1.3.1}/README.md +0 -0
- {accli-0.1.2 → accli-0.1.3.1}/accli.egg-info/dependency_links.txt +0 -0
- {accli-0.1.2 → accli-0.1.3.1}/accli.egg-info/top_level.txt +0 -0
- {accli-0.1.2 → accli-0.1.3.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: accli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3.1
|
|
4
4
|
Summary: IIASA Accelerator Client
|
|
5
5
|
Author-email: Wrufesh S <wrufesh@gmail.com>
|
|
6
6
|
License: The MIT License (MIT)
|
|
@@ -33,6 +33,9 @@ Requires-Python: >=3.7
|
|
|
33
33
|
Description-Content-Type: text/markdown
|
|
34
34
|
License-File: LICENSE
|
|
35
35
|
Requires-Dist: urllib3
|
|
36
|
+
Requires-Dist: typer[all]
|
|
37
|
+
Requires-Dist: tinydb
|
|
38
|
+
Requires-Dist: requests
|
|
36
39
|
|
|
37
40
|
## Accelerator python client
|
|
38
41
|
|
accli-0.1.2/accli/AcceleratorProjectService.py → accli-0.1.3.1/accli/AcceleratorJobProjectService.py
RENAMED
|
@@ -11,11 +11,11 @@ class AccAPIError(Exception):
|
|
|
11
11
|
# self.response_data = kwargs.pop('response_data')
|
|
12
12
|
# super().__init__(*args, **kwargs)
|
|
13
13
|
|
|
14
|
-
class
|
|
14
|
+
class AcceleratorJobProjectService:
|
|
15
15
|
def __init__(
|
|
16
16
|
self,
|
|
17
17
|
user_token,
|
|
18
|
-
|
|
18
|
+
job_cli_base_url='http://accelerator-api/v1/ajob-cli',
|
|
19
19
|
verify_cert=True
|
|
20
20
|
):
|
|
21
21
|
|
|
@@ -26,7 +26,7 @@ class AcceleratorProjectService:
|
|
|
26
26
|
else:
|
|
27
27
|
self.http_client = urllib3.poolmanager.PoolManager(cert_reqs="CERT_NONE", num_pools=1)
|
|
28
28
|
|
|
29
|
-
self.cli_base_url =
|
|
29
|
+
self.cli_base_url = job_cli_base_url
|
|
30
30
|
self.common_request_headers = {
|
|
31
31
|
'x-authorization': user_token
|
|
32
32
|
}
|
|
@@ -63,6 +63,15 @@ class AcceleratorProjectService:
|
|
|
63
63
|
if res.data:
|
|
64
64
|
return res.json()
|
|
65
65
|
|
|
66
|
+
def get_bucket_object_validation_details(self, bucket_object_id):
|
|
67
|
+
res = self.http_client_request(
|
|
68
|
+
"GET",
|
|
69
|
+
f"{self.cli_base_url}/validation-detail/{bucket_object_id}",
|
|
70
|
+
headers=self.common_request_headers
|
|
71
|
+
)
|
|
72
|
+
if res.data:
|
|
73
|
+
return res.json()
|
|
74
|
+
|
|
66
75
|
|
|
67
76
|
def get_file_url(self, bucket_object_id):
|
|
68
77
|
res = self.http_client_request(
|
|
@@ -299,7 +308,7 @@ class AcceleratorProjectService:
|
|
|
299
308
|
headers = dict()
|
|
300
309
|
headers["Content-Type"] = "application/octet-stream"
|
|
301
310
|
|
|
302
|
-
part_size, part_count =
|
|
311
|
+
part_size, part_count = 50 * 1024**2, -1
|
|
303
312
|
|
|
304
313
|
upload_id = None
|
|
305
314
|
app_bucket_id = None
|
|
@@ -376,7 +385,7 @@ class AcceleratorProjectService:
|
|
|
376
385
|
headers = dict()
|
|
377
386
|
headers["Content-Type"] = "application/octet-stream"
|
|
378
387
|
|
|
379
|
-
part_size, part_count =
|
|
388
|
+
part_size, part_count = 50 * 1024**2, -1
|
|
380
389
|
|
|
381
390
|
upload_id = None
|
|
382
391
|
app_bucket_id = None
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import requests
|
|
3
|
+
import urllib3
|
|
4
|
+
import json
|
|
5
|
+
import base64
|
|
6
|
+
import concurrent.futures
|
|
7
|
+
from rich.progress import Progress
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AccAPIError(Exception):
|
|
11
|
+
|
|
12
|
+
def __init__(self, message, response, status_code):
|
|
13
|
+
|
|
14
|
+
super().__init__(message)
|
|
15
|
+
|
|
16
|
+
self.response = response
|
|
17
|
+
self.status_code = status_code
|
|
18
|
+
|
|
19
|
+
class AcceleratorTerminalCliProjectService:
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
user_token,
|
|
23
|
+
cli_base_url='http://accelerator-api/v1/aterm-cli',
|
|
24
|
+
verify_cert=True
|
|
25
|
+
):
|
|
26
|
+
|
|
27
|
+
self.user_token = user_token
|
|
28
|
+
|
|
29
|
+
if verify_cert:
|
|
30
|
+
self.http_client = urllib3.poolmanager.PoolManager(num_pools=1)
|
|
31
|
+
else:
|
|
32
|
+
self.http_client = urllib3.poolmanager.PoolManager(cert_reqs="CERT_NONE", num_pools=1)
|
|
33
|
+
|
|
34
|
+
self.cli_base_url = cli_base_url
|
|
35
|
+
self.common_request_headers = {
|
|
36
|
+
'x-authorization': user_token
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def http_client_request(self, *args, **kwargs):
|
|
40
|
+
res = self.http_client.request(*args, **kwargs)
|
|
41
|
+
|
|
42
|
+
if str(res.status)[0] in ['4', '5']:
|
|
43
|
+
raise AccAPIError(
|
|
44
|
+
f"Accelerator api error:: status_code={res.status} :: response_data={res.data}",
|
|
45
|
+
status_code=res.status,
|
|
46
|
+
response=res
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return res
|
|
50
|
+
|
|
51
|
+
def get_file_stat(self, project_slug, filename):
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
res = self.http_client_request(
|
|
55
|
+
"POST",
|
|
56
|
+
f"{self.cli_base_url}/{project_slug}/file-stat/",
|
|
57
|
+
json=dict(filename=filename),
|
|
58
|
+
headers=self.common_request_headers
|
|
59
|
+
)
|
|
60
|
+
except AccAPIError as err:
|
|
61
|
+
if err.status_code == 404:
|
|
62
|
+
return None
|
|
63
|
+
else:
|
|
64
|
+
raise err
|
|
65
|
+
|
|
66
|
+
return res.json()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_multipart_put_create_signed_url(
|
|
70
|
+
self,
|
|
71
|
+
project_slug,
|
|
72
|
+
app_bucket_id,
|
|
73
|
+
object_name,
|
|
74
|
+
upload_id,
|
|
75
|
+
part_number
|
|
76
|
+
):
|
|
77
|
+
res = self.http_client_request(
|
|
78
|
+
"GET",
|
|
79
|
+
f"{self.cli_base_url}/{project_slug}/put-multipart-signed-url",
|
|
80
|
+
fields=dict(
|
|
81
|
+
app_bucket_id=app_bucket_id,
|
|
82
|
+
object_name=object_name,
|
|
83
|
+
upload_id=upload_id,
|
|
84
|
+
part_number=part_number
|
|
85
|
+
),
|
|
86
|
+
headers=self.common_request_headers
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return res.json()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_put_create_multipart_upload_id(self, project_slug, filename):
|
|
94
|
+
|
|
95
|
+
b64_filename = base64.b64encode(filename.encode()).decode()
|
|
96
|
+
|
|
97
|
+
res = self.http_client_request(
|
|
98
|
+
"GET",
|
|
99
|
+
f"{self.cli_base_url}/{project_slug}/create-multipart-upload-id/{b64_filename}",
|
|
100
|
+
headers=self.common_request_headers
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
data = res.json()
|
|
104
|
+
|
|
105
|
+
return data['upload_id'], data['app_bucket_id'], data['uniqified_filename']
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def complete_create_multipart_upload(
|
|
109
|
+
self,
|
|
110
|
+
project_slug,
|
|
111
|
+
app_bucket_id,
|
|
112
|
+
filename,
|
|
113
|
+
upload_id,
|
|
114
|
+
parts: list[tuple[str, str]]
|
|
115
|
+
):
|
|
116
|
+
headers = {"Content-Type": "application/json"}
|
|
117
|
+
|
|
118
|
+
headers.update(self.common_request_headers)
|
|
119
|
+
|
|
120
|
+
res = self.http_client_request(
|
|
121
|
+
"PUT",
|
|
122
|
+
f"{self.cli_base_url}/{project_slug}/complete-create-multipart-upload",
|
|
123
|
+
json=dict(
|
|
124
|
+
app_bucket_id=app_bucket_id,
|
|
125
|
+
filename=filename,
|
|
126
|
+
upload_id=upload_id,
|
|
127
|
+
parts=base64.b64encode(json.dumps(parts).encode()).decode()
|
|
128
|
+
),
|
|
129
|
+
headers=headers
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return res.json()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def abort_create_multipart_upload(self, project_slug, app_bucket_id, filename, upload_id):
|
|
136
|
+
|
|
137
|
+
headers = {"Content-Type": "application/json"}
|
|
138
|
+
|
|
139
|
+
headers.update(self.common_request_headers)
|
|
140
|
+
|
|
141
|
+
res = self.http_client_request(
|
|
142
|
+
"PUT",
|
|
143
|
+
f"{self.cli_base_url}/{project_slug}/abort-create-multipart-upload",
|
|
144
|
+
json=dict(
|
|
145
|
+
app_bucket_id=app_bucket_id,
|
|
146
|
+
filename=filename,
|
|
147
|
+
upload_id=upload_id
|
|
148
|
+
),
|
|
149
|
+
headers=headers
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def read_part_data(self, stream, size, part_data=b"", progress=None):
|
|
155
|
+
"""Read part data of given size from stream."""
|
|
156
|
+
size -= len(part_data)
|
|
157
|
+
while size:
|
|
158
|
+
data = stream.read(size)
|
|
159
|
+
if not data:
|
|
160
|
+
break # EOF reached
|
|
161
|
+
if not isinstance(data, bytes):
|
|
162
|
+
raise ValueError("read() must return 'bytes' object")
|
|
163
|
+
part_data += data
|
|
164
|
+
size -= len(data)
|
|
165
|
+
if progress:
|
|
166
|
+
progress.update(len(data))
|
|
167
|
+
return part_data
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def put_part(self, project_slug, app_bucket_id, uniqified_filename, upload_id, part_number, part_data, progress, task):
|
|
171
|
+
|
|
172
|
+
put_presigned_url = self.get_multipart_put_create_signed_url(
|
|
173
|
+
project_slug, app_bucket_id, uniqified_filename, upload_id, part_number
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
part_upload_response = requests.put(
|
|
177
|
+
put_presigned_url,
|
|
178
|
+
data=part_data,
|
|
179
|
+
# headers=headers,
|
|
180
|
+
verify=False,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
progress.update(task, advance=len(part_data))
|
|
184
|
+
|
|
185
|
+
etag = part_upload_response.headers.get("etag").replace('"', "")
|
|
186
|
+
return part_number, etag
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def upload_filestream_to_accelerator(self, project_slug, filename, file_stream, progress, task, max_workers=os.cpu_count()):
|
|
190
|
+
headers = dict()
|
|
191
|
+
headers["Content-Type"] = "application/octet-stream"
|
|
192
|
+
|
|
193
|
+
part_size, part_count = 200 * 1024**2, -1
|
|
194
|
+
|
|
195
|
+
upload_id = None
|
|
196
|
+
app_bucket_id = None
|
|
197
|
+
uniqified_filename = None
|
|
198
|
+
|
|
199
|
+
one_byte = b""
|
|
200
|
+
stop = False
|
|
201
|
+
part_number = 0
|
|
202
|
+
parts = []
|
|
203
|
+
uploaded_size = 0
|
|
204
|
+
put_presigned_url = None
|
|
205
|
+
|
|
206
|
+
futures = []
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
|
|
210
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
211
|
+
|
|
212
|
+
while not stop:
|
|
213
|
+
part_number += 1
|
|
214
|
+
part_data = self.read_part_data(
|
|
215
|
+
file_stream,
|
|
216
|
+
part_size + 1,
|
|
217
|
+
one_byte,
|
|
218
|
+
progress=None,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# If part_data_size is less or equal to part_size,
|
|
222
|
+
# then we have reached last part.
|
|
223
|
+
if len(part_data) <= part_size:
|
|
224
|
+
part_count = part_number
|
|
225
|
+
stop = True
|
|
226
|
+
else:
|
|
227
|
+
one_byte = part_data[-1:]
|
|
228
|
+
part_data = part_data[:-1]
|
|
229
|
+
|
|
230
|
+
uploaded_size += len(part_data)
|
|
231
|
+
|
|
232
|
+
if not upload_id:
|
|
233
|
+
(
|
|
234
|
+
upload_id,
|
|
235
|
+
app_bucket_id,
|
|
236
|
+
uniqified_filename,
|
|
237
|
+
) = self.get_put_create_multipart_upload_id(
|
|
238
|
+
project_slug,
|
|
239
|
+
filename,
|
|
240
|
+
# headers=headers
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
future = executor.submit(self.put_part, project_slug, app_bucket_id, uniqified_filename, upload_id, part_number, part_data, progress, task)
|
|
245
|
+
|
|
246
|
+
futures.append(future)
|
|
247
|
+
|
|
248
|
+
futures_results = concurrent.futures.wait(futures, return_when=concurrent.futures.ALL_COMPLETED)
|
|
249
|
+
|
|
250
|
+
parts = [f.result() for f in futures_results.done]
|
|
251
|
+
|
|
252
|
+
parts.sort(key=lambda x: x[0])
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
created_bucket_object_id = self.complete_create_multipart_upload(
|
|
256
|
+
project_slug, app_bucket_id, uniqified_filename, upload_id, parts
|
|
257
|
+
)
|
|
258
|
+
return created_bucket_object_id
|
|
259
|
+
|
|
260
|
+
except Exception as err:
|
|
261
|
+
# Cancel if any error
|
|
262
|
+
if upload_id:
|
|
263
|
+
self.abort_create_multipart_upload(
|
|
264
|
+
project_slug,
|
|
265
|
+
app_bucket_id,
|
|
266
|
+
uniqified_filename,
|
|
267
|
+
upload_id,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
raise err
|
|
271
|
+
|
|
272
|
+
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import glob
|
|
3
|
+
import os
|
|
4
|
+
import typer
|
|
5
|
+
import requests
|
|
6
|
+
from rich import print
|
|
7
|
+
from typing_extensions import Annotated
|
|
8
|
+
from tinydb import TinyDB, Query
|
|
9
|
+
|
|
10
|
+
from .AcceleratorTerminalCliProjectService import AcceleratorTerminalCliProjectService
|
|
11
|
+
|
|
12
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, ProgressColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn, TimeRemainingColumn
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
app = typer.Typer()
|
|
16
|
+
|
|
17
|
+
def get_db_path():
|
|
18
|
+
|
|
19
|
+
home = os.path.expanduser("~")
|
|
20
|
+
token_directory = f"{home}/.accli"
|
|
21
|
+
|
|
22
|
+
if not os.path.exists(token_directory):
|
|
23
|
+
os.makedirs(token_directory)
|
|
24
|
+
|
|
25
|
+
return f"{token_directory}/data.json"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def save_token_details(token, server_url, webcli_url):
|
|
29
|
+
|
|
30
|
+
db_path = get_db_path()
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
os.remove(db_path)
|
|
34
|
+
except OSError:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
db = TinyDB(db_path)
|
|
38
|
+
db.insert({
|
|
39
|
+
'token': token,
|
|
40
|
+
'server_url': server_url,
|
|
41
|
+
'webcli_url': webcli_url
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
def get_token():
|
|
45
|
+
db_path = get_db_path()
|
|
46
|
+
|
|
47
|
+
db = TinyDB(db_path)
|
|
48
|
+
|
|
49
|
+
for item in db:
|
|
50
|
+
token = item.get('token')
|
|
51
|
+
if token:
|
|
52
|
+
break
|
|
53
|
+
|
|
54
|
+
if not token:
|
|
55
|
+
print("Token does not exists. Please login.")
|
|
56
|
+
return token
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_server_url():
|
|
60
|
+
db_path = get_db_path()
|
|
61
|
+
|
|
62
|
+
db = TinyDB(db_path)
|
|
63
|
+
|
|
64
|
+
for item in db:
|
|
65
|
+
server_url = item.get('server_url')
|
|
66
|
+
if server_url:
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
if not server_url:
|
|
70
|
+
print("Server url does not exists. Please login.")
|
|
71
|
+
return server_url
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_size(path):
|
|
75
|
+
size = 0
|
|
76
|
+
|
|
77
|
+
for file in glob.iglob(f"{path}/**/*.*", recursive=True):
|
|
78
|
+
|
|
79
|
+
size += os.path.getsize(file)
|
|
80
|
+
|
|
81
|
+
return size
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@app.command()
|
|
86
|
+
def login(
|
|
87
|
+
server: Annotated[str, typer.Option(help="Accelerator server url.")] = "https://acceleratoapi.iiasa.ac.at",
|
|
88
|
+
webcli: Annotated[str, typer.Option(help="Accelerator web client for authorization.")] = "https://accelerator.iiasa.ac.at"
|
|
89
|
+
):
|
|
90
|
+
print(
|
|
91
|
+
f"Welcome to Accelerator Terminal Client.\n"
|
|
92
|
+
f"Powered by IIASA\n"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
print(f"Get authorization code [link={webcli}/acli-auth-code]here[/link].\n")
|
|
96
|
+
|
|
97
|
+
device_authorization_code = typer.prompt("Enter the authorization code?")
|
|
98
|
+
|
|
99
|
+
token_response = requests.post(
|
|
100
|
+
f"{server}/v1/oauth/device/token/",
|
|
101
|
+
json={"device_authorization_code": device_authorization_code}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
print("")
|
|
105
|
+
|
|
106
|
+
if token_response.status_code == 400:
|
|
107
|
+
print(f"ERROR: {token_response.json().get('detail')}")
|
|
108
|
+
raise typer.Exit(1)
|
|
109
|
+
|
|
110
|
+
save_token_details(token_response.json(), server, webcli)
|
|
111
|
+
|
|
112
|
+
print("Successfully logged in.:rocket: :rocket:")
|
|
113
|
+
|
|
114
|
+
def upload_file(project_slug, accelerator_filename, local_filepath, progress, task, folder_name, max_workers=os.cpu_count()):
|
|
115
|
+
|
|
116
|
+
access_token = get_token()
|
|
117
|
+
|
|
118
|
+
server_url = get_server_url()
|
|
119
|
+
|
|
120
|
+
term_cli_project_service = AcceleratorTerminalCliProjectService(
|
|
121
|
+
user_token=access_token,
|
|
122
|
+
cli_base_url=f"{server_url}/v1/aterm-cli",
|
|
123
|
+
verify_cert=False
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
stat = term_cli_project_service.get_file_stat(project_slug, f"{folder_name}/{accelerator_filename}")
|
|
127
|
+
|
|
128
|
+
if stat:
|
|
129
|
+
progress.update(task, advance=stat.get('size'))
|
|
130
|
+
else:
|
|
131
|
+
|
|
132
|
+
with open(local_filepath, 'rb') as file_stream:
|
|
133
|
+
term_cli_project_service.upload_filestream_to_accelerator(project_slug, f"{folder_name}/{accelerator_filename}", file_stream, progress, task, max_workers=max_workers)
|
|
134
|
+
|
|
135
|
+
@app.command()
|
|
136
|
+
def upload(
|
|
137
|
+
project_slug: Annotated[str, typer.Option(help="Project slug")],
|
|
138
|
+
path: Annotated[str, typer.Option(help="File or folder path.")],
|
|
139
|
+
folder_name: Annotated[str, typer.Option(help="New folder name.")],
|
|
140
|
+
max_workers: Annotated[int, typer.Option(help="Maximum worker pool for multipart upload")] = os.cpu_count()
|
|
141
|
+
):
|
|
142
|
+
|
|
143
|
+
if not re.fullmatch(r'\w+', folder_name):
|
|
144
|
+
raise ValueError("Folder name is invalid.")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
with Progress(
|
|
148
|
+
TextColumn("[progress.description]"),
|
|
149
|
+
BarColumn(),
|
|
150
|
+
TaskProgressColumn(),
|
|
151
|
+
TimeElapsedColumn(),
|
|
152
|
+
TextColumn("{task.description}"),
|
|
153
|
+
transient=True
|
|
154
|
+
) as progress:
|
|
155
|
+
|
|
156
|
+
if (os.path.isdir(path)):
|
|
157
|
+
|
|
158
|
+
if not path.endswith("/"):
|
|
159
|
+
path = path + ("/")
|
|
160
|
+
|
|
161
|
+
folder_size = get_size(path)
|
|
162
|
+
print('Folder size', folder_size)
|
|
163
|
+
upload_task = progress.add_task("[cyan]Uploading.", total=folder_size)
|
|
164
|
+
|
|
165
|
+
for local_file_path in glob.iglob(f"{path}/**/*.*", recursive=True):
|
|
166
|
+
accelerator_filename = os.path.relpath(local_file_path, path)
|
|
167
|
+
|
|
168
|
+
progress.update(
|
|
169
|
+
upload_task,
|
|
170
|
+
description=f"[cyan]Uploading {local_file_path} \t"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
upload_file(project_slug, accelerator_filename, local_file_path, progress, upload_task, folder_name, max_workers=max_workers)
|
|
174
|
+
elif (os.path.isfile(path)):
|
|
175
|
+
raise NotImplementedError('Only folder can be uploaded. File upload is not implemented.')
|
|
176
|
+
else:
|
|
177
|
+
print("ERROR: No such file or directory.")
|
|
178
|
+
typer.Exit(1)
|
|
179
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: accli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3.1
|
|
4
4
|
Summary: IIASA Accelerator Client
|
|
5
5
|
Author-email: Wrufesh S <wrufesh@gmail.com>
|
|
6
6
|
License: The MIT License (MIT)
|
|
@@ -33,6 +33,9 @@ Requires-Python: >=3.7
|
|
|
33
33
|
Description-Content-Type: text/markdown
|
|
34
34
|
License-File: LICENSE
|
|
35
35
|
Requires-Dist: urllib3
|
|
36
|
+
Requires-Dist: typer[all]
|
|
37
|
+
Requires-Dist: tinydb
|
|
38
|
+
Requires-Dist: requests
|
|
36
39
|
|
|
37
40
|
## Accelerator python client
|
|
38
41
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
accli/AcceleratorJobProjectService.py
|
|
5
|
+
accli/AcceleratorTerminalCliProjectService.py
|
|
6
|
+
accli/__init__.py
|
|
7
|
+
accli/__main__.py
|
|
8
|
+
accli/cli.py
|
|
9
|
+
accli.egg-info/PKG-INFO
|
|
10
|
+
accli.egg-info/SOURCES.txt
|
|
11
|
+
accli.egg-info/dependency_links.txt
|
|
12
|
+
accli.egg-info/entry_points.txt
|
|
13
|
+
accli.egg-info/requires.txt
|
|
14
|
+
accli.egg-info/top_level.txt
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "accli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.3.1"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Wrufesh S", email="wrufesh@gmail.com" },
|
|
10
10
|
]
|
|
@@ -12,7 +12,7 @@ description = "IIASA Accelerator Client"
|
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
license = {file = "LICENSE"}
|
|
14
14
|
requires-python = ">=3.7"
|
|
15
|
-
dependencies = ['urllib3']
|
|
15
|
+
dependencies = ['urllib3', 'typer[all]', 'tinydb', 'requests']
|
|
16
16
|
classifiers = [
|
|
17
17
|
"Programming Language :: Python :: 3",
|
|
18
18
|
"License :: OSI Approved :: MIT License",
|
|
@@ -24,4 +24,7 @@ packages = ['accli']
|
|
|
24
24
|
|
|
25
25
|
[project.urls]
|
|
26
26
|
"Homepage" = "https://github.com/iiasa/accli"
|
|
27
|
-
"Bug Tracker" = "https://github.com/iiasa/accli/issues"
|
|
27
|
+
"Bug Tracker" = "https://github.com/iiasa/accli/issues"
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
accli = "accli.cli.app"
|
accli-0.1.2/accli/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
urllib3
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|