accli 0.5__tar.gz → 0.5.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: accli
3
- Version: 0.5
3
+ Version: 0.5.2
4
4
  Summary: IIASA Accelerator Client
5
5
  Author-email: Wrufesh S <wrufesh@gmail.com>
6
6
  License: The MIT License (MIT)
@@ -83,5 +83,3 @@ Please follow [this link.](https://packaging.python.org/en/latest/tutorials/pack
83
83
  2. Run 'python scripts/tag.py'
84
84
  3. `python -m build`
85
85
  4. `twine upload -r pypi -u __token__ -p <password-or-token> ./dist/*`
86
-
87
-
@@ -48,5 +48,3 @@ Please follow [this link.](https://packaging.python.org/en/latest/tutorials/pack
48
48
  2. Run 'python scripts/tag.py'
49
49
  3. `python -m build`
50
50
  4. `twine upload -r pypi -u __token__ -p <password-or-token> ./dist/*`
51
-
52
-
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import io
3
+ import sys
3
4
  import typing
4
5
  import requests
5
6
  import json
@@ -8,6 +9,8 @@ import urllib3
8
9
  from pathlib import Path
9
10
  from typing import List, Tuple
10
11
 
12
+ from accli.common import todict
13
+
11
14
  class AccAPIError(Exception):
12
15
  pass
13
16
 
@@ -16,6 +19,9 @@ class AccAPIError(Exception):
16
19
  # self.response_data = kwargs.pop('response_data')
17
20
  # super().__init__(*args, **kwargs)
18
21
 
22
+ class UnhealthyControlServerError(Exception):
23
+ pass
24
+
19
25
  retries = urllib3.util.Retry(total=10, backoff_factor=1)
20
26
 
21
27
  http_client = urllib3.poolmanager.PoolManager(
@@ -47,15 +53,13 @@ class AcceleratorJobProjectService:
47
53
  }
48
54
 
49
55
  def http_client_request(self, *args, **kwargs):
56
+
50
57
  res = self.http_client.request(*args, **kwargs)
51
58
 
52
59
  if str(res.status)[0] in ['4', '5']:
53
60
  raise AccAPIError(
54
61
  f"Accelerator api error:: status_code={res.status} :: response_data={res.data}",
55
- # status_code=res.status,
56
- # response_data=res.data
57
62
  )
58
-
59
63
  return res
60
64
 
61
65
  def get_file_stat(self, bucket_object_id):
@@ -64,7 +68,7 @@ class AcceleratorJobProjectService:
64
68
  f"{self.cli_base_url}/file-stat/{bucket_object_id}",
65
69
  headers=self.common_request_headers
66
70
  )
67
- return res.json()
71
+ return todict(res.data)
68
72
 
69
73
  def get_file_url_from_repo(self, filename):
70
74
  project_slug = filename.split('/')[0]
@@ -74,7 +78,7 @@ class AcceleratorJobProjectService:
74
78
  headers=self.common_request_headers
75
79
  )
76
80
  if res.data:
77
- return res.json()
81
+ return todict(res.data)
78
82
 
79
83
  def get_dataset_type(self, *args, **kwargs):
80
84
  return self.get_bucket_object_validation_type(*args, **kwargs)
@@ -86,7 +90,7 @@ class AcceleratorJobProjectService:
86
90
  headers=self.common_request_headers
87
91
  )
88
92
  if res.data:
89
- return res.json()
93
+ return todict(res.data)
90
94
 
91
95
  def get_bucket_object_validation_details(self, bucket_object_id):
92
96
  res = self.http_client_request(
@@ -95,7 +99,7 @@ class AcceleratorJobProjectService:
95
99
  headers=self.common_request_headers
96
100
  )
97
101
  if res.data:
98
- return res.json()
102
+ return todict(res.data)
99
103
 
100
104
 
101
105
  def get_file_url(self, bucket_object_id):
@@ -106,7 +110,7 @@ class AcceleratorJobProjectService:
106
110
  )
107
111
 
108
112
  if res.data:
109
- return res.json()
113
+ return todict(res.data)
110
114
 
111
115
 
112
116
  def get_file_stream(self, bucket_object_id):
@@ -114,13 +118,33 @@ class AcceleratorJobProjectService:
114
118
  if url:
115
119
  resp = self.http_client_request("GET", url, preload_content=False)
116
120
  return resp
121
+
122
+ def check_job_health(self):
123
+ try:
124
+ res = self.http_client_request(
125
+ "GET",
126
+ f"{self.cli_base_url}/is-healthy/",
127
+ headers=self.common_request_headers
128
+ )
129
+ if res.data:
130
+ res = todict(res.data)
131
+ except Exception as err:
132
+ raise UnhealthyControlServerError(str(err))
133
+
134
+ is_healthy = res['is_healthy']
135
+ return is_healthy
117
136
 
118
137
  def add_log_file(self, data: bytes, filename):
119
- res = self.http_client_request(
120
- "GET",
121
- f"{self.cli_base_url}/presigned-log-upload-url/?filename={filename}",
122
- headers=self.common_request_headers
123
- ).json()
138
+ try:
139
+ res = self.http_client_request(
140
+ "GET",
141
+ f"{self.cli_base_url}/presigned-log-upload-url/?filename={filename}",
142
+ headers=self.common_request_headers
143
+ )
144
+ if res.data:
145
+ res = todict(res.data)
146
+ except Exception as err:
147
+ raise UnhealthyControlServerError(str(err))
124
148
 
125
149
  upload_url = res['upload_url']
126
150
  app_bucket_id = res['app_bucket_id']
@@ -134,18 +158,21 @@ class AcceleratorJobProjectService:
134
158
  verify=False,
135
159
  )
136
160
 
137
- self.http_client_request(
138
- "POST",
139
- f"{self.cli_base_url}/register-log-file/",
140
- json=dict(
141
- filename=res_filename,
142
- app_bucket_id=app_bucket_id
143
- ),
144
- headers=self.common_request_headers
145
- )
161
+ try:
162
+ self.http_client_request(
163
+ "POST",
164
+ f"{self.cli_base_url}/register-log-file/",
165
+ json=dict(
166
+ filename=res_filename,
167
+ app_bucket_id=app_bucket_id
168
+ ),
169
+ headers=self.common_request_headers
170
+ )
171
+ except Exception as err:
172
+ raise UnhealthyControlServerError(str(err))
146
173
 
147
174
  if not is_healthy:
148
- os.Exit(1)
175
+ raise UnhealthyControlServerError('Unhealthy control server.')
149
176
 
150
177
 
151
178
 
@@ -168,7 +195,7 @@ class AcceleratorJobProjectService:
168
195
  headers=self.common_request_headers
169
196
  )
170
197
 
171
- return res.json()
198
+ return todict(res.data)
172
199
 
173
200
  def get_multipart_put_update_signed_url(
174
201
  self,
@@ -187,7 +214,7 @@ class AcceleratorJobProjectService:
187
214
  headers=self.common_request_headers
188
215
  )
189
216
 
190
- return res.json()
217
+ return todict(res.data)
191
218
 
192
219
  def get_put_create_multipart_upload_id(self, filename):
193
220
  res = self.http_client_request(
@@ -196,7 +223,7 @@ class AcceleratorJobProjectService:
196
223
  headers=self.common_request_headers
197
224
  )
198
225
 
199
- data = res.json()
226
+ data = todict(res.data)
200
227
 
201
228
  return data['upload_id'], data['app_bucket_id'], data['uniqified_filename']
202
229
 
@@ -207,7 +234,7 @@ class AcceleratorJobProjectService:
207
234
  headers=self.common_request_headers
208
235
  )
209
236
 
210
- return res.json()
237
+ return todict(res.data)
211
238
 
212
239
  def complete_job_multipart_upload(
213
240
  self,
@@ -234,7 +261,7 @@ class AcceleratorJobProjectService:
234
261
  headers=headers
235
262
  )
236
263
 
237
- return res.json()
264
+ return todict(res.data)
238
265
 
239
266
  def complete_update_multipart_upload(
240
267
  self, bucket_object_id, upload_id, parts: List[Tuple[str, str]]
@@ -344,7 +371,7 @@ class AcceleratorJobProjectService:
344
371
  headers=headers
345
372
  )
346
373
 
347
- return res.json()
374
+ return todict(res.data)
348
375
 
349
376
 
350
377
 
@@ -555,7 +582,7 @@ class Fs:
555
582
 
556
583
  @staticmethod
557
584
  def get_file_url(remote_filepath):
558
- user_token = os.environ.get("ACC_JOB_JOB_TOKEN", None)
585
+ user_token = os.environ.get("ACC_JOB_TOKEN", None)
559
586
  server_url = os.environ.get("ACC_JOB_GATEWAY_SERVER", None)
560
587
 
561
588
  if not (user_token and server_url):
@@ -1,37 +1,46 @@
1
1
  import git
2
2
  import os
3
+ import zipfile
3
4
  import hashlib
4
5
  import shutil
5
6
  import tempfile
7
+ import requests
6
8
  from typing import Optional, List, Dict
7
9
  from pydantic.v1 import BaseModel, root_validator
8
-
9
- from accli.token import get_github_app_token
10
+ from accli.AcceleratorTerminalCliProjectService import AcceleratorTerminalCliProjectService
11
+ from accli.token import get_github_app_token, get_token, get_server_url, get_project_slug
10
12
 
11
13
  FOLDER_JOB_REPO_URL = 'https://github.com/IIASA-Accelerator/wkube-job.git'
12
14
 
13
- def hash_folder(folder_path):
14
- md5_hash = hashlib.md5()
15
- sha256_hash = hashlib.sha256()
16
15
 
17
- for root, _, files in os.walk(folder_path):
18
- for file_name in files:
19
- file_path = os.path.join(root, file_name)
20
- try:
16
+ def compress_folder(folder_path, output_path):
17
+ # Fixed timestamp for normalization
18
+ fixed_time = (1980, 1, 1, 0, 0, 0)
19
+
20
+ with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
21
+ for root, dirs, files in os.walk(folder_path):
22
+ dirs.sort()
23
+ files.sort()
24
+ for file in files:
25
+ file_path = os.path.join(root, file)
26
+ arcname = os.path.relpath(file_path, start=folder_path)
27
+ # Add file to zip with fixed timestamp
28
+ info = zipfile.ZipInfo(arcname)
29
+ info.date_time = fixed_time
30
+ info.external_attr = 0o600 << 16 # Set file permissions
21
31
  with open(file_path, 'rb') as f:
22
- # Read and update MD5 and SHA256 hash object with file content
23
- for chunk in iter(lambda: f.read(4096), b""):
24
- md5_hash.update(chunk)
25
- sha256_hash.update(chunk)
26
- except IOError:
27
- # Handle read error (if any)
28
- print(f"Error reading file: {file_path}")
32
+
33
+ zipf.writestr(info, f.read(), zipfile.ZIP_DEFLATED)
29
34
 
30
- # Get hexadecimal digest of hashes
31
- md5_digest = md5_hash.hexdigest()
32
- # sha256_digest = sha256_hash.hexdigest()
35
+ def get_file_sha1(file_path):
36
+ sha1_hash = hashlib.sha1()
37
+ with open(file_path, "rb") as f:
38
+ chunk = f.read(8192)
39
+ while chunk:
40
+ sha1_hash.update(chunk)
41
+ chunk = f.read(8192)
42
+ return sha1_hash.hexdigest()
33
43
 
34
- return md5_digest
35
44
 
36
45
  def copy_tree(src, dst):
37
46
  """Recursively copy from src to dst, excluding .git folders."""
@@ -49,35 +58,50 @@ def copy_tree(src, dst):
49
58
 
50
59
  def push_folder_job(dir):
51
60
 
61
+ access_token = get_token()
62
+
63
+ server_url = get_server_url()
64
+
65
+ project_slug = get_project_slug()
66
+
67
+ term_cli_project_service = AcceleratorTerminalCliProjectService(
68
+ user_token=access_token,
69
+ server_url=server_url,
70
+ verify_cert=False
71
+ )
72
+
52
73
  repo_dir = tempfile.mkdtemp()
53
74
 
54
75
  copy_tree(dir, repo_dir)
55
-
56
- branch_name = hash_folder(repo_dir)
57
-
58
- repo = git.Repo.init(repo_dir)
59
76
 
60
- git_token = get_github_app_token()
77
+ temp_zip_path = f"{repo_dir}/temp.zip"
61
78
 
62
- remote_url = f'https://x-access-token:{git_token}@{FOLDER_JOB_REPO_URL.split("https://")[1]}'
63
- remote = repo.create_remote("accelerator_job_repo", remote_url)
79
+ compress_folder(repo_dir, temp_zip_path)
80
+
81
+ sha256_hash = get_file_sha1(temp_zip_path)
64
82
 
65
- remote.pull('master')
83
+ final_zip_path = f"{repo_dir}/{sha256_hash}.zip"
66
84
 
67
- try:
68
- repo.git.checkout(branch_name)
69
- except git.exc.GitCommandError:
70
- repo.git.checkout('-b', branch_name)
85
+
86
+ os.rename(temp_zip_path, final_zip_path)
71
87
 
72
- repo.git.add('.')
88
+ presigned_push_url = term_cli_project_service.get_jobstore_push_url(
89
+ project_slug, f"{sha256_hash}.zip"
90
+ )
73
91
 
74
- repo.index.commit(branch_name)
75
92
 
76
- # TODO see what happens with the same checksum
77
- remote.push(branch_name)
93
+ if presigned_push_url:
94
+ with open(final_zip_path, 'rb') as f:
95
+ requests.put(
96
+ presigned_push_url,
97
+ data=f,
98
+ verify=False,
99
+ )
100
+
101
+
78
102
  shutil.rmtree(repo_dir)
79
103
 
80
- return FOLDER_JOB_REPO_URL, branch_name
104
+ return f"s3accjobstore://{sha256_hash}.zip", sha256_hash
81
105
 
82
106
 
83
107
  class JobDispatchModel(BaseModel):
@@ -204,7 +228,7 @@ class WKubeTask(GenericTask):
204
228
 
205
229
  WKubeTaskPydantic(*t_args, **t_kwargs)
206
230
  wkube_task_kwargs = WKubeTaskKwargs(*t_args, **t_kwargs)
207
- wkube_task_meta.update(WKubeTaskMeta(*t_args, **t_kwargs).dict())
231
+ wkube_task_meta.update(WKubeTaskMeta(*t_args, **t_kwargs).dict(exclude_unset=True))
208
232
 
209
233
 
210
234
  self.dispatch_model_task = JobDispatchModel(
@@ -212,7 +236,7 @@ class WKubeTask(GenericTask):
212
236
  execute_cluster='WKUBE',
213
237
  job_location='acc_native_jobs.dispatch_wkube_task',
214
238
  job_args=[],
215
- job_kwargs= wkube_task_kwargs.dict() if wkube_task_kwargs else dict(),
239
+ job_kwargs= wkube_task_kwargs.dict(exclude_unset=True) if wkube_task_kwargs else dict(),
216
240
  **wkube_task_meta
217
241
  )
218
242
 
@@ -7,7 +7,7 @@ import concurrent.futures
7
7
  from typing import List, Tuple
8
8
  from rich.progress import Progress
9
9
 
10
-
10
+ from accli.common import todict
11
11
 
12
12
  class AccAPIError(Exception):
13
13
 
@@ -76,7 +76,7 @@ class AcceleratorTerminalCliProjectService:
76
76
  else:
77
77
  raise err
78
78
 
79
- return res.json()
79
+ return todict(res.data)
80
80
 
81
81
  def get_file_url_from_repo(self, filename):
82
82
  project_slug = filename.split('/')[0]
@@ -86,7 +86,7 @@ class AcceleratorTerminalCliProjectService:
86
86
  headers=self.common_request_headers
87
87
  )
88
88
  if res.data:
89
- return res.json()
89
+ return todict(res.data)
90
90
 
91
91
  def get_github_app_token(self, project_slug):
92
92
 
@@ -96,7 +96,20 @@ class AcceleratorTerminalCliProjectService:
96
96
  headers=self.common_request_headers
97
97
  )
98
98
 
99
- return res.json()
99
+ return todict(res.data)
100
+
101
+ def get_jobstore_push_url(self, project_slug, filename):
102
+
103
+ res = self.http_client_request(
104
+ "GET",
105
+ f"{self.cli_base_url}/{project_slug}/jobstore-push-url/?filename={filename}",
106
+ headers=self.common_request_headers
107
+ )
108
+
109
+ if res.status == 409 or res.status == '409':
110
+ return None
111
+
112
+ return todict(res.data)
100
113
 
101
114
  def dispatch(self, project_slug, job_description):
102
115
 
@@ -110,7 +123,7 @@ class AcceleratorTerminalCliProjectService:
110
123
  except AccAPIError as err:
111
124
  raise err
112
125
 
113
- return res.json()['job_id']
126
+ return todict(res.data)['job_id']
114
127
 
115
128
  def get_dataset_template_details(self, project_slug, template_slug):
116
129
 
@@ -125,7 +138,7 @@ class AcceleratorTerminalCliProjectService:
125
138
  else:
126
139
  raise err
127
140
 
128
- return res.json()
141
+ return todict(res.data)
129
142
 
130
143
 
131
144
  def get_multipart_put_create_signed_url(
@@ -148,7 +161,7 @@ class AcceleratorTerminalCliProjectService:
148
161
  headers=self.common_request_headers
149
162
  )
150
163
 
151
- return res.json()
164
+ return todict(res.data)
152
165
 
153
166
 
154
167
 
@@ -162,7 +175,7 @@ class AcceleratorTerminalCliProjectService:
162
175
  headers=self.common_request_headers
163
176
  )
164
177
 
165
- data = res.json()
178
+ data = todict(res.data)
166
179
 
167
180
  return data['upload_id'], data['app_bucket_id'], data['uniqified_filename']
168
181
 
@@ -191,7 +204,7 @@ class AcceleratorTerminalCliProjectService:
191
204
  headers=headers
192
205
  )
193
206
 
194
- return res.json()
207
+ return todict(res.data)
195
208
 
196
209
 
197
210
  def abort_create_multipart_upload(self, project_slug, app_bucket_id, filename, upload_id):
@@ -1,3 +1,3 @@
1
1
  # Please strictly put double quote to use this info for git tag
2
2
  # Read developer guide for git tag command with regex
3
- VERSION = "v0.5"
3
+ VERSION = "v0.5.2"
@@ -9,7 +9,7 @@ import importlib.util
9
9
  from rich import print
10
10
  from typing_extensions import Annotated
11
11
 
12
- from accli.token import save_token_details, get_token, get_server_url, set_github_app_token
12
+ from accli.token import save_token_details, get_token, get_server_url, set_github_app_token, set_project_slug
13
13
 
14
14
  from accli.CsvRegionalTimeseriesValidator import CsvRegionalTimeseriesValidator
15
15
  from ._version import VERSION
@@ -181,7 +181,7 @@ def dispatch(
181
181
  root_task_variable: Annotated[str, typer.Argument(help="Root task variable in workflow_file.")],
182
182
  server: Annotated[str, typer.Option(help="Accelerator server url.")] = "https://accelerator-api.iiasa.ac.at",
183
183
  ):
184
-
184
+ set_project_slug(project_slug)
185
185
  access_token = get_token()
186
186
 
187
187
  server_url = get_server_url()
@@ -0,0 +1,6 @@
1
+ import json
2
+
3
+ def todict(bytestring: bytes):
4
+ return json.loads(
5
+ bytestring.decode("utf-8")
6
+ )
@@ -61,6 +61,25 @@ def set_github_app_token(github_app_token):
61
61
  db = TinyDB(db_path)
62
62
  db.update({'github_app_token': github_app_token}, doc_ids=[1])
63
63
 
64
+ def set_project_slug(project_slug):
65
+ db_path = get_db_path()
66
+ db = TinyDB(db_path)
67
+ db.update({'project_slug': project_slug}, doc_ids=[1])
68
+
69
+ def get_project_slug():
70
+ db_path = get_db_path()
71
+
72
+ db = TinyDB(db_path)
73
+
74
+ for item in db:
75
+ project_slug = item.get('project_slug')
76
+ if project_slug:
77
+ break
78
+
79
+ if not project_slug:
80
+ print("project slug was not set.")
81
+ return project_slug
82
+
64
83
 
65
84
  def get_server_url():
66
85
  db_path = get_db_path()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: accli
3
- Version: 0.5
3
+ Version: 0.5.2
4
4
  Summary: IIASA Accelerator Client
5
5
  Author-email: Wrufesh S <wrufesh@gmail.com>
6
6
  License: The MIT License (MIT)
@@ -83,5 +83,3 @@ Please follow [this link.](https://packaging.python.org/en/latest/tutorials/pack
83
83
  2. Run 'python scripts/tag.py'
84
84
  3. `python -m build`
85
85
  4. `twine upload -r pypi -u __token__ -p <password-or-token> ./dist/*`
86
-
87
-
@@ -9,6 +9,7 @@ accli/__init__.py
9
9
  accli/__main__.py
10
10
  accli/_version.py
11
11
  accli/cli.py
12
+ accli/common.py
12
13
  accli/token.py
13
14
  accli.egg-info/PKG-INFO
14
15
  accli.egg-info/SOURCES.txt
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes