cloudos-cli 2.17.0__py3-none-any.whl
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.
- cloudos_cli/__init__.py +11 -0
- cloudos_cli/__main__.py +1297 -0
- cloudos_cli/_version.py +1 -0
- cloudos_cli/clos.py +726 -0
- cloudos_cli/jobs/__init__.py +8 -0
- cloudos_cli/jobs/job.py +555 -0
- cloudos_cli/queue/__init__.py +8 -0
- cloudos_cli/queue/queue.py +139 -0
- cloudos_cli/utils/__init__.py +9 -0
- cloudos_cli/utils/errors.py +32 -0
- cloudos_cli/utils/requests.py +75 -0
- cloudos_cli-2.17.0.dist-info/LICENSE +674 -0
- cloudos_cli-2.17.0.dist-info/METADATA +1060 -0
- cloudos_cli-2.17.0.dist-info/RECORD +41 -0
- cloudos_cli-2.17.0.dist-info/WHEEL +5 -0
- cloudos_cli-2.17.0.dist-info/entry_points.txt +2 -0
- cloudos_cli-2.17.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/functions_for_pytest.py +7 -0
- tests/test_clos/__init__.py +0 -0
- tests/test_clos/test_create_cromwell_header.py +35 -0
- tests/test_clos/test_cromwell_switch.py +77 -0
- tests/test_clos/test_detect_workflow.py +47 -0
- tests/test_clos/test_get_cromwell_status.py +77 -0
- tests/test_clos/test_get_curated_workflow_list.py +72 -0
- tests/test_clos/test_get_job_list.py +79 -0
- tests/test_clos/test_get_job_status.py +75 -0
- tests/test_clos/test_get_project_list.py +74 -0
- tests/test_clos/test_get_user_info.py +68 -0
- tests/test_clos/test_get_workflow_list.py +87 -0
- tests/test_clos/test_is_module.py +48 -0
- tests/test_clos/test_process_job_list.py +74 -0
- tests/test_clos/test_process_project_list.py +36 -0
- tests/test_clos/test_process_workflow_list.py +36 -0
- tests/test_clos/test_wait_job_completion.py +40 -0
- tests/test_clos/test_workflow_import.py +77 -0
- tests/test_jobs/__init__.py +0 -0
- tests/test_jobs/test_convert_nextflow_to_json.py +104 -0
- tests/test_jobs/test_project_id.py +67 -0
- tests/test_jobs/test_send_job.py +84 -0
- tests/test_jobs/test_workflow_id.py +67 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This is the main class to create job queues.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
import json
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Union
|
|
10
|
+
from cloudos_cli.clos import Cloudos
|
|
11
|
+
from cloudos_cli.utils.errors import BadRequestException
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class Queue(Cloudos):
|
|
16
|
+
"""Class to store and operate job queues.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
cloudos_url : string
|
|
21
|
+
The CloudOS service url.
|
|
22
|
+
apikey : string
|
|
23
|
+
Your CloudOS API key.
|
|
24
|
+
cromwell_token : string
|
|
25
|
+
Cromwell server token.
|
|
26
|
+
workspace_id : string
|
|
27
|
+
The specific Cloudos workspace id.
|
|
28
|
+
verify: [bool|string]
|
|
29
|
+
Whether to use SSL verification or not. Alternatively, if
|
|
30
|
+
a string is passed, it will be interpreted as the path to
|
|
31
|
+
the SSL certificate file.
|
|
32
|
+
"""
|
|
33
|
+
workspace_id: str
|
|
34
|
+
verify: Union[bool, str] = True
|
|
35
|
+
|
|
36
|
+
def get_job_queues(self):
|
|
37
|
+
"""Get all the job queues from a CloudOS workspace.
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
r : list
|
|
42
|
+
A list of dicts, each corresponding to a job queue.
|
|
43
|
+
"""
|
|
44
|
+
headers = {"apikey": self.apikey}
|
|
45
|
+
r = requests.get("{}/api/v1/teams/aws/v2/job-queues?teamId={}".format(self.cloudos_url,
|
|
46
|
+
self.workspace_id),
|
|
47
|
+
headers=headers, verify=self.verify)
|
|
48
|
+
if r.status_code >= 400:
|
|
49
|
+
raise BadRequestException(r)
|
|
50
|
+
return json.loads(r.content)
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def process_queue_list(r, all_fields=False):
|
|
54
|
+
"""Process a queue list from a self.get_job_queues call.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
r : list
|
|
59
|
+
A list of dicts, each corresponding to a job queue.
|
|
60
|
+
all_fields : bool. Default=False
|
|
61
|
+
Whether to return a reduced version of the DataFrame containing
|
|
62
|
+
only the selected columns or the full DataFrame.
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
df : pandas.DataFrame
|
|
67
|
+
A DataFrame with the requested columns from the job queues.
|
|
68
|
+
"""
|
|
69
|
+
COLUMNS = ['id',
|
|
70
|
+
'name',
|
|
71
|
+
'label',
|
|
72
|
+
'description',
|
|
73
|
+
'isDefault',
|
|
74
|
+
'resourceType',
|
|
75
|
+
'executor',
|
|
76
|
+
'status'
|
|
77
|
+
]
|
|
78
|
+
df_full = pd.json_normalize(r)
|
|
79
|
+
if all_fields:
|
|
80
|
+
df = df_full
|
|
81
|
+
else:
|
|
82
|
+
df = df_full.loc[:, COLUMNS]
|
|
83
|
+
return df
|
|
84
|
+
|
|
85
|
+
def fetch_job_queue_id(self, workflow_type, batch=True, job_queue=None):
|
|
86
|
+
"""Fetches CloudOS ID for a given job queue.
|
|
87
|
+
|
|
88
|
+
This method will try to find the
|
|
89
|
+
corresponding CloudOS ID for the job_queue in a given workspace. If
|
|
90
|
+
job_queue=None, this method will select the available default queue in
|
|
91
|
+
the workspace, or the newest "ready" job queue if no default queues are
|
|
92
|
+
available.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
workflow_type : str ['wdl'|'cromwell'|'nextflow']
|
|
97
|
+
The type of workflow to run.
|
|
98
|
+
batch: bool
|
|
99
|
+
Whether to create a batch job or an ignite one.
|
|
100
|
+
job_queue : str or None
|
|
101
|
+
The name of the job queue to search. If None, a default one will be selected.
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
job_queue_id : str or None
|
|
106
|
+
The CloudOS ID for the selected job queue, or None if batch=False.
|
|
107
|
+
"""
|
|
108
|
+
if not batch:
|
|
109
|
+
return None
|
|
110
|
+
if workflow_type == 'wdl':
|
|
111
|
+
workflow_type = 'cromwell'
|
|
112
|
+
if workflow_type not in ['cromwell', 'nextflow']:
|
|
113
|
+
raise ValueError('[ERROR] Only nextflow or cromwell workflows are allowed when ' +
|
|
114
|
+
'running using AWS batch.')
|
|
115
|
+
job_queues = self.get_job_queues()
|
|
116
|
+
available_queues = [q for q in job_queues if q['status'] == 'Ready' and
|
|
117
|
+
q['executor'] == workflow_type]
|
|
118
|
+
if len(available_queues) == 0:
|
|
119
|
+
raise Exception(f'[ERROR] There are no available job queues for {workflow_type} ' +
|
|
120
|
+
'workflows. Consider creating one using CloudOS UI.')
|
|
121
|
+
default_queue = [q for q in available_queues if q['isDefault']]
|
|
122
|
+
if len(default_queue) > 0:
|
|
123
|
+
default_queue_id = default_queue[0]['id']
|
|
124
|
+
default_queue_name = default_queue[0]['label']
|
|
125
|
+
queue_as_default = 'CloudOS default'
|
|
126
|
+
else:
|
|
127
|
+
default_queue_id = available_queues[-1]['id']
|
|
128
|
+
default_queue_name = available_queues[-1]['label']
|
|
129
|
+
queue_as_default = 'most recent suitable'
|
|
130
|
+
if job_queue is None:
|
|
131
|
+
print(f'[Message] No job queue was specified, using the {queue_as_default} queue: ' +
|
|
132
|
+
f'{default_queue_name}.')
|
|
133
|
+
return default_queue_id
|
|
134
|
+
selected_queue = [q for q in available_queues if q['label'] == job_queue]
|
|
135
|
+
if len(selected_queue) == 0:
|
|
136
|
+
print(f'[Message] Queue \'{job_queue}\' you specified was not found, using the {queue_as_default} ' +
|
|
137
|
+
f'queue instead: {default_queue_name}.')
|
|
138
|
+
return default_queue_id
|
|
139
|
+
return selected_queue[0]['id']
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Specific classes to handle errors.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BadRequestException(Exception):
|
|
7
|
+
"""Handle bad request exceptions and shows improved messages.
|
|
8
|
+
|
|
9
|
+
Parameters
|
|
10
|
+
----------
|
|
11
|
+
rv : requests.Response
|
|
12
|
+
The request variable returned that caused the error.
|
|
13
|
+
"""
|
|
14
|
+
def __init__(self, rv):
|
|
15
|
+
msg = "Server returned status {}. Reason: {}".format(rv.status_code, rv.reason)
|
|
16
|
+
super(BadRequestException, self).__init__(msg)
|
|
17
|
+
self.rv = rv
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TimeOutException(Exception):
|
|
21
|
+
"""Handle TimeOut exceptions and shows improved messages.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
rv : requests.Response
|
|
26
|
+
The request variable returned that caused the error.
|
|
27
|
+
"""
|
|
28
|
+
def __init__(self, rv):
|
|
29
|
+
msg = ("Server exceeded the max time to process request. " +
|
|
30
|
+
"Status: {}; Reason: {}".format(rv.status_code, rv.reason))
|
|
31
|
+
super(TimeOutException, self).__init__(msg)
|
|
32
|
+
self.rv = rv
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Specific functions to wrapp error strategy for requests
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
from requests.adapters import HTTPAdapter
|
|
7
|
+
from urllib3.util import Retry
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def retry_requests_get(url, total=5, status_forcelist=[429, 500, 502, 503, 504], **kwargs):
|
|
11
|
+
"""Wrap normal requests get with an error strategy.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
url : string
|
|
16
|
+
The request URL
|
|
17
|
+
total : int
|
|
18
|
+
Total number of retries
|
|
19
|
+
status_forcelist : list
|
|
20
|
+
A list of ints with the status codes to trigger the retries
|
|
21
|
+
|
|
22
|
+
Return
|
|
23
|
+
------
|
|
24
|
+
response : requests.Response
|
|
25
|
+
The Response object returned by the API server
|
|
26
|
+
"""
|
|
27
|
+
retry_strategy = Retry(
|
|
28
|
+
total=total,
|
|
29
|
+
status_forcelist=status_forcelist
|
|
30
|
+
)
|
|
31
|
+
# Create an HTTP adapter with the retry strategy and mount it to session
|
|
32
|
+
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
33
|
+
|
|
34
|
+
# Create a new session object
|
|
35
|
+
session = requests.Session()
|
|
36
|
+
session.mount('http://', adapter)
|
|
37
|
+
session.mount('https://', adapter)
|
|
38
|
+
|
|
39
|
+
# Make a request using the session object
|
|
40
|
+
response = session.get(url, **kwargs)
|
|
41
|
+
return response
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def retry_requests_post(url, total=5, status_forcelist=[429, 500, 502, 503, 504], **kwargs):
|
|
45
|
+
"""Wrap normal requests post with an error strategy.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
url : string
|
|
50
|
+
The request URL
|
|
51
|
+
total : int
|
|
52
|
+
Total number of retries
|
|
53
|
+
status_forcelist : list
|
|
54
|
+
A list of ints with the status codes to trigger the retries
|
|
55
|
+
|
|
56
|
+
Return
|
|
57
|
+
------
|
|
58
|
+
response : requests.Response
|
|
59
|
+
The Response object returned by the API server
|
|
60
|
+
"""
|
|
61
|
+
retry_strategy = Retry(
|
|
62
|
+
total=total,
|
|
63
|
+
status_forcelist=status_forcelist
|
|
64
|
+
)
|
|
65
|
+
# Create an HTTP adapter with the retry strategy and mount it to session
|
|
66
|
+
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
67
|
+
|
|
68
|
+
# Create a new session object
|
|
69
|
+
session = requests.Session()
|
|
70
|
+
session.mount('http://', adapter)
|
|
71
|
+
session.mount('https://', adapter)
|
|
72
|
+
|
|
73
|
+
# Make a request using the session object
|
|
74
|
+
response = session.post(url, **kwargs)
|
|
75
|
+
return response
|