accli 0.4.1__tar.gz → 0.5.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: accli
3
- Version: 0.4.1
3
+ Version: 0.5.1
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
@@ -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
@@ -16,6 +17,19 @@ class AccAPIError(Exception):
16
17
  # self.response_data = kwargs.pop('response_data')
17
18
  # super().__init__(*args, **kwargs)
18
19
 
20
+ class UnhealthyControlServerError(Exception):
21
+ pass
22
+
23
+ retries = urllib3.util.Retry(total=10, backoff_factor=1)
24
+
25
+ http_client = urllib3.poolmanager.PoolManager(
26
+ num_pools=20, retries=retries
27
+ )
28
+
29
+ http_client_wo_cert_verification = urllib3.poolmanager.PoolManager(
30
+ cert_reqs="CERT_NONE", num_pools=20, retries=retries
31
+ )
32
+
19
33
  class AcceleratorJobProjectService:
20
34
  def __init__(
21
35
  self,
@@ -26,12 +40,10 @@ class AcceleratorJobProjectService:
26
40
 
27
41
  self.user_token = user_token
28
42
 
29
- retries = urllib3.util.Retry(total=10, backoff_factor=1)
30
-
31
43
  if verify_cert:
32
- self.http_client = urllib3.poolmanager.PoolManager(num_pools=1, retries=retries)
44
+ self.http_client = http_client
33
45
  else:
34
- self.http_client = urllib3.poolmanager.PoolManager(cert_reqs="CERT_NONE", num_pools=1, retries=retries)
46
+ self.http_client = http_client_wo_cert_verification
35
47
 
36
48
  self.cli_base_url = f"{server_url}/v1/ajob-cli"
37
49
  self.common_request_headers = {
@@ -39,15 +51,13 @@ class AcceleratorJobProjectService:
39
51
  }
40
52
 
41
53
  def http_client_request(self, *args, **kwargs):
54
+
42
55
  res = self.http_client.request(*args, **kwargs)
43
56
 
44
57
  if str(res.status)[0] in ['4', '5']:
45
58
  raise AccAPIError(
46
59
  f"Accelerator api error:: status_code={res.status} :: response_data={res.data}",
47
- # status_code=res.status,
48
- # response_data=res.data
49
60
  )
50
-
51
61
  return res
52
62
 
53
63
  def get_file_stat(self, bucket_object_id):
@@ -58,6 +68,16 @@ class AcceleratorJobProjectService:
58
68
  )
59
69
  return res.json()
60
70
 
71
+ def get_file_url_from_repo(self, filename):
72
+ project_slug = filename.split('/')[0]
73
+ res = self.http_client_request(
74
+ "GET",
75
+ f"{self.cli_base_url}/{project_slug}/get-file-download-url/?filename={filename}",
76
+ headers=self.common_request_headers
77
+ )
78
+ if res.data:
79
+ return res.json()
80
+
61
81
  def get_dataset_type(self, *args, **kwargs):
62
82
  return self.get_bucket_object_validation_type(*args, **kwargs)
63
83
 
@@ -96,17 +116,34 @@ class AcceleratorJobProjectService:
96
116
  if url:
97
117
  resp = self.http_client_request("GET", url, preload_content=False)
98
118
  return resp
119
+
120
+ def check_job_health(self):
121
+ try:
122
+ res = self.http_client_request(
123
+ "GET",
124
+ f"{self.cli_base_url}/is-healthy/",
125
+ headers=self.common_request_headers
126
+ ).json()
127
+ except Exception as err:
128
+ raise UnhealthyControlServerError(str(err))
129
+
130
+ is_healthy = res['is_healthy']
131
+ return is_healthy
99
132
 
100
133
  def add_log_file(self, data: bytes, filename):
101
- res = self.http_client_request(
102
- "GET",
103
- f"{self.cli_base_url}/presigned-upload-url/?filename={filename}",
104
- headers=self.common_request_headers
105
- ).json()
134
+ try:
135
+ res = self.http_client_request(
136
+ "GET",
137
+ f"{self.cli_base_url}/presigned-log-upload-url/?filename={filename}",
138
+ headers=self.common_request_headers
139
+ ).json()
140
+ except Exception as err:
141
+ raise UnhealthyControlServerError(str(err))
106
142
 
107
143
  upload_url = res['upload_url']
108
144
  app_bucket_id = res['app_bucket_id']
109
145
  res_filename = res['filename']
146
+ is_healthy = res['is_healthy']
110
147
 
111
148
  requests.put(
112
149
  upload_url,
@@ -115,15 +152,21 @@ class AcceleratorJobProjectService:
115
152
  verify=False,
116
153
  )
117
154
 
118
- self.http_client_request(
119
- "POST",
120
- f"{self.cli_base_url}/register-log-file/",
121
- json=dict(
122
- filename=res_filename,
123
- app_bucket_id=app_bucket_id
124
- ),
125
- headers=self.common_request_headers
126
- )
155
+ try:
156
+ self.http_client_request(
157
+ "POST",
158
+ f"{self.cli_base_url}/register-log-file/",
159
+ json=dict(
160
+ filename=res_filename,
161
+ app_bucket_id=app_bucket_id
162
+ ),
163
+ headers=self.common_request_headers
164
+ )
165
+ except Exception as err:
166
+ raise UnhealthyControlServerError(str(err))
167
+
168
+ if not is_healthy:
169
+ raise UnhealthyControlServerError('Unhealthy control server.')
127
170
 
128
171
 
129
172
 
@@ -531,9 +574,28 @@ class Fs:
531
574
  with open(dest_filepath, 'wb') as file:
532
575
  file.write(filestream.getvalue())
533
576
 
577
+ @staticmethod
578
+ def get_file_url(remote_filepath):
579
+ user_token = os.environ.get("ACC_JOB_TOKEN", None)
580
+ server_url = os.environ.get("ACC_JOB_GATEWAY_SERVER", None)
581
+
582
+ if not (user_token and server_url):
583
+ raise ValueError("Remote data repository credentials not found.")
584
+
585
+ accelerator_job_service = AcceleratorJobProjectService(
586
+ user_token,
587
+ server_url=server_url,
588
+ verify_cert=True
589
+ )
590
+
591
+ return accelerator_job_service.get_file_url_from_repo(
592
+ remote_filepath
593
+ )
594
+
595
+
534
596
  @staticmethod
535
597
  def write_file(source: typing.Union[str, io.BytesIO], dest_filepath):
536
- user_token = os.environ.get("ACC_JOB_JOB_TOKEN", None)
598
+ user_token = os.environ.get("ACC_JOB_TOKEN", None)
537
599
  server_url = os.environ.get("ACC_JOB_GATEWAY_SERVER", None)
538
600
 
539
601
  if isinstance(source, io.BytesIO):
@@ -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
- from pydantic import BaseModel, root_validator
8
-
9
- from accli.token import get_github_app_token
9
+ from pydantic.v1 import BaseModel, root_validator
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):
@@ -139,6 +163,8 @@ class WKubeTaskKwargs(BaseModel):
139
163
 
140
164
  conf: Dict[str,str] = {}
141
165
 
166
+ build_timeout: Optional[int]
167
+
142
168
  def dict(self, *args, **kwargs):
143
169
  result = super().dict(*args, **kwargs)
144
170
  if 'job_folder' in result:
@@ -202,7 +228,7 @@ class WKubeTask(GenericTask):
202
228
 
203
229
  WKubeTaskPydantic(*t_args, **t_kwargs)
204
230
  wkube_task_kwargs = WKubeTaskKwargs(*t_args, **t_kwargs)
205
- wkube_task_meta.update(WKubeTaskMeta(*t_args, **t_kwargs).dict())
231
+ wkube_task_meta.update(WKubeTaskMeta(*t_args, **t_kwargs).dict(exclude_unset=True))
206
232
 
207
233
 
208
234
  self.dispatch_model_task = JobDispatchModel(
@@ -210,7 +236,7 @@ class WKubeTask(GenericTask):
210
236
  execute_cluster='WKUBE',
211
237
  job_location='acc_native_jobs.dispatch_wkube_task',
212
238
  job_args=[],
213
- 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(),
214
240
  **wkube_task_meta
215
241
  )
216
242
 
@@ -18,6 +18,16 @@ class AccAPIError(Exception):
18
18
  self.response = response
19
19
  self.status_code = status_code
20
20
 
21
+ retries = urllib3.util.Retry(total=10, backoff_factor=1)
22
+
23
+ http_client = urllib3.poolmanager.PoolManager(
24
+ num_pools=20, retries=retries
25
+ )
26
+
27
+ http_client_wo_cert_verification = urllib3.poolmanager.PoolManager(
28
+ cert_reqs="CERT_NONE", num_pools=20, retries=retries
29
+ )
30
+
21
31
  class AcceleratorTerminalCliProjectService:
22
32
  def __init__(
23
33
  self,
@@ -28,12 +38,10 @@ class AcceleratorTerminalCliProjectService:
28
38
 
29
39
  self.user_token = user_token
30
40
 
31
- retries = urllib3.util.Retry(total=10, backoff_factor=1)
32
-
33
41
  if verify_cert:
34
- self.http_client = urllib3.poolmanager.PoolManager(num_pools=1, retries=retries)
42
+ self.http_client = http_client
35
43
  else:
36
- self.http_client = urllib3.poolmanager.PoolManager(cert_reqs="CERT_NONE", num_pools=1, retries=retries)
44
+ self.http_client = http_client_wo_cert_verification
37
45
 
38
46
  self.server_url = server_url
39
47
  self.cli_base_url = f"{self.server_url}/v1/aterm-cli"
@@ -70,6 +78,16 @@ class AcceleratorTerminalCliProjectService:
70
78
 
71
79
  return res.json()
72
80
 
81
+ def get_file_url_from_repo(self, filename):
82
+ project_slug = filename.split('/')[0]
83
+ res = self.http_client_request(
84
+ "GET",
85
+ f"{self.cli_base_url}/{project_slug}/get-file-download-url/?filename={filename}",
86
+ headers=self.common_request_headers
87
+ )
88
+ if res.data:
89
+ return res.json()
90
+
73
91
  def get_github_app_token(self, project_slug):
74
92
 
75
93
  res = self.http_client_request(
@@ -80,6 +98,19 @@ class AcceleratorTerminalCliProjectService:
80
98
 
81
99
  return res.json()
82
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 res.json()
113
+
83
114
  def dispatch(self, project_slug, job_description):
84
115
 
85
116
  try:
@@ -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.4.1"
3
+ VERSION = "v0.5.1"
@@ -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()
@@ -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.4.1
3
+ Version: 0.5.1
4
4
  Summary: IIASA Accelerator Client
5
5
  Author-email: Wrufesh S <wrufesh@gmail.com>
6
6
  License: The MIT License (MIT)
@@ -1,9 +1,6 @@
1
- .gitignore
2
- Dockerfile.dev
3
1
  LICENSE
4
2
  README.md
5
3
  pyproject.toml
6
- requirements.txt
7
4
  accli/AcceleratorJobProjectService.py
8
5
  accli/AcceleratorTaskDispatcher.py
9
6
  accli/AcceleratorTerminalCliProjectService.py
@@ -3,5 +3,5 @@ typer[all]
3
3
  tinydb
4
4
  requests
5
5
  jsonschema
6
- pydantic==1.10.13
6
+ pydantic
7
7
  gitpython
@@ -12,7 +12,7 @@ description = "IIASA Accelerator Client"
12
12
  readme = "README.md"
13
13
  license = {file = "LICENSE"}
14
14
  requires-python = ">=3.7.17"
15
- dependencies = ['urllib3', 'typer[all]', 'tinydb', 'requests', 'jsonschema', 'pydantic==1.10.13', 'gitpython']
15
+ dependencies = ['urllib3', 'typer[all]', 'tinydb', 'requests', 'jsonschema', 'pydantic', 'gitpython']
16
16
  classifiers = [
17
17
  "Programming Language :: Python :: 3",
18
18
  "License :: OSI Approved :: MIT License",
accli-0.4.1/.gitignore DELETED
@@ -1,6 +0,0 @@
1
- build
2
- dist
3
- .pypirc
4
- *.egg-info/
5
- __pycache__
6
- .venv
@@ -1,2 +0,0 @@
1
-
2
- FROM python:3.7.17
@@ -1,8 +0,0 @@
1
- urllib3
2
- requests
3
- typer[all]
4
- tinydb
5
- twine
6
- jsonschema
7
- pydantic==1.10.13
8
- gitpython==3.1.43
File without changes
File without changes
File without changes
File without changes