idmtools-platform-comps 0.0.0.dev0__py3-none-any.whl → 0.0.2__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.
- idmtools_platform_comps/__init__.py +25 -8
- idmtools_platform_comps/cli/__init__.py +4 -0
- idmtools_platform_comps/cli/cli_functions.py +50 -0
- idmtools_platform_comps/cli/comps.py +492 -0
- idmtools_platform_comps/comps_cli.py +48 -0
- idmtools_platform_comps/comps_operations/__init__.py +6 -0
- idmtools_platform_comps/comps_operations/asset_collection_operations.py +263 -0
- idmtools_platform_comps/comps_operations/experiment_operations.py +569 -0
- idmtools_platform_comps/comps_operations/simulation_operations.py +678 -0
- idmtools_platform_comps/comps_operations/suite_operations.py +228 -0
- idmtools_platform_comps/comps_operations/workflow_item_operations.py +269 -0
- idmtools_platform_comps/comps_platform.py +309 -0
- idmtools_platform_comps/plugin_info.py +168 -0
- idmtools_platform_comps/ssmt_operations/__init__.py +6 -0
- idmtools_platform_comps/ssmt_operations/simulation_operations.py +77 -0
- idmtools_platform_comps/ssmt_operations/workflow_item_operations.py +73 -0
- idmtools_platform_comps/ssmt_platform.py +44 -0
- idmtools_platform_comps/ssmt_work_items/__init__.py +4 -0
- idmtools_platform_comps/ssmt_work_items/comps_work_order_task.py +29 -0
- idmtools_platform_comps/ssmt_work_items/comps_workitems.py +113 -0
- idmtools_platform_comps/ssmt_work_items/icomps_workflowitem.py +71 -0
- idmtools_platform_comps/ssmt_work_items/work_order.py +54 -0
- idmtools_platform_comps/utils/__init__.py +4 -0
- idmtools_platform_comps/utils/assetize_output/__init__.py +4 -0
- idmtools_platform_comps/utils/assetize_output/assetize_output.py +125 -0
- idmtools_platform_comps/utils/assetize_output/assetize_ssmt_script.py +144 -0
- idmtools_platform_comps/utils/base_singularity_work_order.json +6 -0
- idmtools_platform_comps/utils/download/__init__.py +4 -0
- idmtools_platform_comps/utils/download/download.py +178 -0
- idmtools_platform_comps/utils/download/download_ssmt.py +81 -0
- idmtools_platform_comps/utils/download_experiment.py +116 -0
- idmtools_platform_comps/utils/file_filter_workitem.py +519 -0
- idmtools_platform_comps/utils/general.py +358 -0
- idmtools_platform_comps/utils/linux_mounts.py +73 -0
- idmtools_platform_comps/utils/lookups.py +123 -0
- idmtools_platform_comps/utils/package_version.py +489 -0
- idmtools_platform_comps/utils/python_requirements_ac/__init__.py +4 -0
- idmtools_platform_comps/utils/python_requirements_ac/create_asset_collection.py +155 -0
- idmtools_platform_comps/utils/python_requirements_ac/install_requirements.py +109 -0
- idmtools_platform_comps/utils/python_requirements_ac/requirements_to_asset_collection.py +374 -0
- idmtools_platform_comps/utils/python_version.py +40 -0
- idmtools_platform_comps/utils/scheduling.py +154 -0
- idmtools_platform_comps/utils/singularity_build.py +491 -0
- idmtools_platform_comps/utils/spatial_output.py +76 -0
- idmtools_platform_comps/utils/ssmt_utils/__init__.py +6 -0
- idmtools_platform_comps/utils/ssmt_utils/common.py +70 -0
- idmtools_platform_comps/utils/ssmt_utils/file_filter.py +568 -0
- idmtools_platform_comps/utils/sweeping.py +162 -0
- idmtools_platform_comps-0.0.2.dist-info/METADATA +100 -0
- idmtools_platform_comps-0.0.2.dist-info/RECORD +62 -0
- idmtools_platform_comps-0.0.2.dist-info/entry_points.txt +9 -0
- idmtools_platform_comps-0.0.2.dist-info/licenses/LICENSE.TXT +3 -0
- {idmtools_platform_comps-0.0.0.dev0.dist-info → idmtools_platform_comps-0.0.2.dist-info}/top_level.txt +1 -0
- ssmt_image/Dockerfile +52 -0
- ssmt_image/Makefile +21 -0
- ssmt_image/__init__.py +6 -0
- ssmt_image/bootstrap.sh +30 -0
- ssmt_image/build_docker_image.py +161 -0
- ssmt_image/pip.conf +3 -0
- ssmt_image/push_docker_image.py +49 -0
- ssmt_image/requirements.txt +9 -0
- idmtools_platform_comps-0.0.0.dev0.dist-info/METADATA +0 -41
- idmtools_platform_comps-0.0.0.dev0.dist-info/RECORD +0 -5
- {idmtools_platform_comps-0.0.0.dev0.dist-info → idmtools_platform_comps-0.0.2.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
"""idmtools Tools to filter versions of packages for requriements for asset collections.
|
|
2
|
+
|
|
3
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
4
|
+
"""
|
|
5
|
+
import functools
|
|
6
|
+
import operator
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
from abc import ABC
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from logging import getLogger, DEBUG
|
|
13
|
+
from typing import Optional, List, Type
|
|
14
|
+
from urllib import request
|
|
15
|
+
import requests
|
|
16
|
+
from packaging.requirements import Requirement
|
|
17
|
+
from packaging.version import Version, parse
|
|
18
|
+
from html.parser import HTMLParser
|
|
19
|
+
|
|
20
|
+
user_logger = getLogger('user')
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
PKG_PYPI = 'https://pypi.python.org/pypi/{}/json'
|
|
24
|
+
PYPI_PRODUCTION_SIMPLE = 'https://packages.idmod.org/artifactory/api/pypi/pypi-production/simple'
|
|
25
|
+
|
|
26
|
+
IDM_DOCKER_PROD = 'https://packages.idmod.org/artifactory/list/docker-production'
|
|
27
|
+
IDMTOOLS_DOCKER_PROD = f'{IDM_DOCKER_PROD}/idmtools/'
|
|
28
|
+
MANIFEST_URL = "https://hub.docker.com/v2/repositories/library/{repository}/tags/?page_size=25&page=1&name={tag}"
|
|
29
|
+
|
|
30
|
+
logger = getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PackageHTMLParser(HTMLParser, ABC):
|
|
34
|
+
"""Base Parser for our other parsers."""
|
|
35
|
+
previous_tag = None
|
|
36
|
+
pkg_version = None
|
|
37
|
+
|
|
38
|
+
def __init__(self):
|
|
39
|
+
"""Constructor."""
|
|
40
|
+
super().__init__()
|
|
41
|
+
self.pkg_version = set()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class LinkHTMLParser(PackageHTMLParser):
|
|
45
|
+
"""Parse hrefs from links."""
|
|
46
|
+
|
|
47
|
+
def handle_starttag(self, tag, attrs):
|
|
48
|
+
"""Parse links and extra hrefs."""
|
|
49
|
+
self.previous_tag = tag
|
|
50
|
+
if tag != 'a':
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
attr = dict(attrs)
|
|
54
|
+
v = attr['href']
|
|
55
|
+
v = v.rstrip('/')
|
|
56
|
+
self.pkg_version.add(v)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class LinkNameParser(PackageHTMLParser):
|
|
60
|
+
"""
|
|
61
|
+
Provides parsing of packages from pypi/arfifactory.
|
|
62
|
+
|
|
63
|
+
We parse links that match versions patterns
|
|
64
|
+
"""
|
|
65
|
+
in_link = False
|
|
66
|
+
ver_pattern = re.compile(r'^[\d\.brcdev\+nightly]+$')
|
|
67
|
+
|
|
68
|
+
def handle_starttag(self, tag, attrs):
|
|
69
|
+
"""Handle begin of links."""
|
|
70
|
+
self.previous_tag = tag
|
|
71
|
+
self.in_link = tag == "a"
|
|
72
|
+
|
|
73
|
+
def handle_endtag(self, tag):
|
|
74
|
+
"""End link tags."""
|
|
75
|
+
if tag == "a":
|
|
76
|
+
self.in_link = False
|
|
77
|
+
|
|
78
|
+
def handle_data(self, data):
|
|
79
|
+
"""Process links."""
|
|
80
|
+
if self.in_link:
|
|
81
|
+
parts = data.split("-")
|
|
82
|
+
if len(parts) >= 2:
|
|
83
|
+
if self.ver_pattern.match(parts[1]):
|
|
84
|
+
self.pkg_version.add(parts[1])
|
|
85
|
+
elif parts[1].endswith(".zip"):
|
|
86
|
+
self.pkg_version.add(parts[1][:-4])
|
|
87
|
+
elif parts[1].endswith(".tar.gz"):
|
|
88
|
+
self.pkg_version.add(parts[1][:-7])
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def get_latest_package_version_from_pypi(pkg_name, display_all=False):
|
|
92
|
+
"""
|
|
93
|
+
Utility to get the latest version for a given package name.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
pkg_name: package name given
|
|
97
|
+
display_all: determine if output all package releases
|
|
98
|
+
|
|
99
|
+
Returns: the latest version of ven package
|
|
100
|
+
"""
|
|
101
|
+
url = f'https://pypi.python.org/pypi/{pkg_name}/json'
|
|
102
|
+
try:
|
|
103
|
+
releases = json.loads(request.urlopen(url).read())['releases']
|
|
104
|
+
except Exception:
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
all_releases = sorted(releases, key=parse, reverse=True)
|
|
108
|
+
|
|
109
|
+
if display_all:
|
|
110
|
+
print(all_releases)
|
|
111
|
+
|
|
112
|
+
release_versions = [ver for ver in all_releases if not parse(ver).is_prerelease]
|
|
113
|
+
latest_version = release_versions[0]
|
|
114
|
+
|
|
115
|
+
return latest_version
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def get_latest_pypi_package_version_from_artifactory(pkg_name, display_all=False, base_version: str = None):
|
|
119
|
+
"""
|
|
120
|
+
Utility to get the latest version for a given package name.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
pkg_name: package name given
|
|
124
|
+
display_all: determine if output all package releases
|
|
125
|
+
base_version: Base version
|
|
126
|
+
|
|
127
|
+
Returns: the latest version of ven package
|
|
128
|
+
"""
|
|
129
|
+
pkg_url = "/".join([PYPI_PRODUCTION_SIMPLE, pkg_name])
|
|
130
|
+
return get_latest_version_from_site(pkg_url, display_all=display_all, base_version=base_version)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_pypi_package_versions_from_artifactory(pkg_name, display_all=False, base_version: str = None,
|
|
134
|
+
exclude_pre_release: bool = True):
|
|
135
|
+
"""
|
|
136
|
+
Utility to get versions of a package in artifactory.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
pkg_name: package name given
|
|
140
|
+
display_all: determine if output all package releases
|
|
141
|
+
base_version: Base version
|
|
142
|
+
exclude_pre_release: Exclude any prerelease versions
|
|
143
|
+
|
|
144
|
+
Returns: the latest version of ven package
|
|
145
|
+
"""
|
|
146
|
+
pkg_url = "/".join([PYPI_PRODUCTION_SIMPLE, pkg_name])
|
|
147
|
+
return get_versions_from_site(pkg_url, base_version, display_all=display_all, parser=LinkNameParser,
|
|
148
|
+
exclude_pre_release=exclude_pre_release)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def get_latest_ssmt_image_version_from_artifactory(pkg_name='comps_ssmt_worker', base_version: Optional[str] = None,
|
|
152
|
+
display_all=False):
|
|
153
|
+
"""
|
|
154
|
+
Utility to get the latest version for a given package name.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
pkg_name: package name given
|
|
158
|
+
base_version: Optional base version. Versions above this will not be added.
|
|
159
|
+
display_all: determine if output all package releases
|
|
160
|
+
|
|
161
|
+
Returns: the latest version of ven package
|
|
162
|
+
"""
|
|
163
|
+
pkg_path = IDMTOOLS_DOCKER_PROD
|
|
164
|
+
pkg_url = "/".join([pkg_path, pkg_name])
|
|
165
|
+
base_version = ".".join(base_version.replace("+nightly", "").split(".")[:2])
|
|
166
|
+
return get_latest_version_from_site(pkg_url, base_version=base_version, display_all=display_all,
|
|
167
|
+
parser=LinkHTMLParser)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def get_docker_manifest(image_path="idmtools/comps_ssmt_worker", repo_base=IDM_DOCKER_PROD):
|
|
171
|
+
"""
|
|
172
|
+
Get docker manifest from IDM Artifactory. It mimics latest even when user has no latest tag defined.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
image_path:Path of docker image we want
|
|
176
|
+
repo_base:Base of the repo
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
None
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
ValueError - When the manifest cannot be found
|
|
183
|
+
"""
|
|
184
|
+
if ":" not in image_path:
|
|
185
|
+
image_path += ":latest"
|
|
186
|
+
|
|
187
|
+
path, tag = image_path.split(":")
|
|
188
|
+
if tag == "latest":
|
|
189
|
+
url = "/".join([IDM_DOCKER_PROD, path])
|
|
190
|
+
response = requests.get(url)
|
|
191
|
+
content = response.text
|
|
192
|
+
lines = [link.split(">") for link in content.split("\n") if "<a href" in link and "pre" not in link]
|
|
193
|
+
lines = {item_date[1].replace("/</a", ''): datetime.strptime(item_date[2].strip(" -"), '%d-%b-%Y %H:%M') for
|
|
194
|
+
item_date in lines}
|
|
195
|
+
tag = list(sorted(lines.items(), key=operator.itemgetter(1), reverse=True))[0][0]
|
|
196
|
+
image_path = ":".join([path, tag])
|
|
197
|
+
final_path = "/".join([path, tag, "manifest.json"])
|
|
198
|
+
pkg_path = f'{repo_base}/{final_path}'
|
|
199
|
+
response = requests.get(pkg_path)
|
|
200
|
+
if response.status_code != 200:
|
|
201
|
+
raise ValueError("Could not find manifest for file")
|
|
202
|
+
return response.json(), image_path
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def get_digest_from_docker_hub(repo, tag='latest'):
|
|
206
|
+
"""
|
|
207
|
+
Get the digest for image from docker.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
repo: string, repository (e.g. 'library/fedora')
|
|
211
|
+
tag: string, tag of the repository (e.g. 'latest')
|
|
212
|
+
"""
|
|
213
|
+
response = requests.get(
|
|
214
|
+
MANIFEST_URL.format(repository=repo, tag=tag)
|
|
215
|
+
)
|
|
216
|
+
manifest = response.json()
|
|
217
|
+
if response.ok and manifest['count']:
|
|
218
|
+
images = list(filter(lambda x: x['architecture'] == "amd64", manifest['results'][0]['images']))
|
|
219
|
+
if len(images):
|
|
220
|
+
return images[0]['digest']
|
|
221
|
+
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@functools.lru_cache(8)
|
|
226
|
+
def fetch_versions_from_server(pkg_url: str, parser: Type[PackageHTMLParser] = LinkHTMLParser) -> List[str]:
|
|
227
|
+
"""
|
|
228
|
+
Fetch all versions from server.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
pkg_url: Url to fetch
|
|
232
|
+
parser: Parser tp use
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
All the releases for a package
|
|
236
|
+
"""
|
|
237
|
+
resp = requests.get(pkg_url)
|
|
238
|
+
pkg_name = pkg_url.split('/')[-1]
|
|
239
|
+
if resp.status_code != 200:
|
|
240
|
+
logger.warning(f"Error fetching package {pkg_name} information. Status code: {resp.status_code}")
|
|
241
|
+
return []
|
|
242
|
+
|
|
243
|
+
html_str = resp.text
|
|
244
|
+
|
|
245
|
+
parser = parser()
|
|
246
|
+
parser.feed(html_str)
|
|
247
|
+
releases = parser.pkg_version
|
|
248
|
+
releases = [v for v in releases if not v.startswith('.')]
|
|
249
|
+
|
|
250
|
+
all_releases = sorted(releases, key=parse, reverse=True)
|
|
251
|
+
return all_releases
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def fetch_versions_from_artifactory(pkg_name: str, parser: Type[PackageHTMLParser] = LinkHTMLParser) -> List[str]:
|
|
255
|
+
"""
|
|
256
|
+
Fetch all versions from server.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
pkg_name: Url to fetch
|
|
260
|
+
parser: Parser tp use
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
Available releases
|
|
264
|
+
"""
|
|
265
|
+
pkg_path = IDM_DOCKER_PROD
|
|
266
|
+
pkg_url = os.path.join(pkg_path, pkg_name)
|
|
267
|
+
|
|
268
|
+
resp = requests.get(pkg_url)
|
|
269
|
+
if resp.status_code != 200:
|
|
270
|
+
logger.warning('Could not fetch URL')
|
|
271
|
+
return None
|
|
272
|
+
|
|
273
|
+
html_str = resp.text
|
|
274
|
+
|
|
275
|
+
parser = parser()
|
|
276
|
+
parser.feed(html_str)
|
|
277
|
+
releases = parser.pkg_version
|
|
278
|
+
releases = [v for v in releases if not v.startswith('.')]
|
|
279
|
+
|
|
280
|
+
all_releases = sorted(releases, key=parse, reverse=True)
|
|
281
|
+
return all_releases
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@functools.lru_cache(3)
|
|
285
|
+
def get_versions_from_site(pkg_url, base_version: Optional[str] = None, display_all=False,
|
|
286
|
+
parser: Type[PackageHTMLParser] = LinkNameParser, exclude_pre_release: bool = True):
|
|
287
|
+
"""
|
|
288
|
+
Utility to get the the available versions for a package.
|
|
289
|
+
|
|
290
|
+
The default properties filter out pre releases. You can also include a base version to only list items starting with a particular version
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
pkg_url: package name given
|
|
294
|
+
base_version: Optional base version. Versions above this will not be added. For example, to get versions 1.18.5, 1.18.4, 1.18.3, 1.18.2 pass 1.18
|
|
295
|
+
display_all: determine if output all package releases
|
|
296
|
+
parser: Parser needs to be a HTMLParser that returns a pkg_versions
|
|
297
|
+
exclude_pre_release: Exclude prerelease versions
|
|
298
|
+
|
|
299
|
+
Returns: the latest version of ven package
|
|
300
|
+
|
|
301
|
+
Raises:
|
|
302
|
+
ValueError - If a latest versions cannot be determined
|
|
303
|
+
"""
|
|
304
|
+
all_releases = fetch_versions_from_server(pkg_url, parser=parser)
|
|
305
|
+
if all_releases is None:
|
|
306
|
+
raise ValueError(
|
|
307
|
+
f"Could not determine latest version for package {pkg_url}. You can manually specify a version to avoid this error")
|
|
308
|
+
|
|
309
|
+
if display_all:
|
|
310
|
+
print(all_releases)
|
|
311
|
+
if exclude_pre_release:
|
|
312
|
+
ver_pattern = re.compile(r'^[\d\.]+$')
|
|
313
|
+
release_versions = [ver for ver in all_releases if ver_pattern.match(ver)]
|
|
314
|
+
else:
|
|
315
|
+
release_versions = all_releases
|
|
316
|
+
|
|
317
|
+
if base_version:
|
|
318
|
+
release_versions = [ver for ver in release_versions if ver.startswith(base_version)]
|
|
319
|
+
|
|
320
|
+
# comps_ssmt_worker will store only x.x.x.x
|
|
321
|
+
if 'comps_ssmt_worker' in pkg_url.lower():
|
|
322
|
+
release_versions = [ver for ver in release_versions if len(ver.split('.')) == 4]
|
|
323
|
+
return release_versions
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
@functools.lru_cache(3)
|
|
327
|
+
def get_latest_version_from_site(pkg_url, base_version: Optional[str] = None, display_all=False,
|
|
328
|
+
parser: Type[PackageHTMLParser] = LinkNameParser, exclude_pre_release: bool = True):
|
|
329
|
+
"""
|
|
330
|
+
Utility to get the latest version for a given package name.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
pkg_url: package name given
|
|
334
|
+
base_version: Optional base version. Versions above this will not be added.
|
|
335
|
+
display_all: determine if output all package releases
|
|
336
|
+
parser: Parser needs to be a HTMLParser that returns a pkg_versions
|
|
337
|
+
exclude_pre_release: Exclude pre-release versions
|
|
338
|
+
|
|
339
|
+
Returns: the latest version of ven package
|
|
340
|
+
"""
|
|
341
|
+
if logger.isEnabledFor(DEBUG):
|
|
342
|
+
logger.debug(f"Fetching version from {pkg_url} with base {base_version}")
|
|
343
|
+
release_versions = get_versions_from_site(pkg_url, base_version, display_all=display_all, parser=parser,
|
|
344
|
+
exclude_pre_release=exclude_pre_release)
|
|
345
|
+
if base_version:
|
|
346
|
+
# only use the longest match latest
|
|
347
|
+
version_compatible_portion = ".".join(base_version.split(".")[:2])
|
|
348
|
+
if logger.isEnabledFor(DEBUG):
|
|
349
|
+
logger.debug(
|
|
350
|
+
f"Finding latest of matches for version {base_version} from {release_versions} using {version_compatible_portion}")
|
|
351
|
+
|
|
352
|
+
for ver in release_versions:
|
|
353
|
+
if ".".join(ver.split('.')[:2]) == version_compatible_portion:
|
|
354
|
+
return ver
|
|
355
|
+
return None
|
|
356
|
+
return release_versions[0] if release_versions else None
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def fetch_package_versions_from_pypi(pkg_name):
|
|
360
|
+
"""
|
|
361
|
+
Utility to get the latest version for a given package name.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
pkg_name: package name given
|
|
365
|
+
|
|
366
|
+
Returns: the latest version of ven package
|
|
367
|
+
"""
|
|
368
|
+
url = PKG_PYPI.format(pkg_name)
|
|
369
|
+
try:
|
|
370
|
+
releases = json.loads(request.urlopen(url).read())['releases']
|
|
371
|
+
except Exception:
|
|
372
|
+
return None
|
|
373
|
+
|
|
374
|
+
return releases
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def fetch_package_versions(pkg_name, is_released=True, sort=True, display_all=False):
|
|
378
|
+
"""
|
|
379
|
+
Utility to get the latest version for a given package name.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
pkg_name: package name given
|
|
383
|
+
is_released: get released version only
|
|
384
|
+
sort: make version sorted or not
|
|
385
|
+
display_all: determine if output all package releases
|
|
386
|
+
|
|
387
|
+
Returns: the latest version of ven package
|
|
388
|
+
"""
|
|
389
|
+
# First fetch versions from Artifactory
|
|
390
|
+
pkg_url = "/".join([PYPI_PRODUCTION_SIMPLE, pkg_name])
|
|
391
|
+
versions = fetch_versions_from_server(pkg_url, parser=LinkNameParser)
|
|
392
|
+
|
|
393
|
+
if versions is None:
|
|
394
|
+
versions = fetch_package_versions_from_pypi(pkg_name)
|
|
395
|
+
|
|
396
|
+
if sort:
|
|
397
|
+
versions = sorted(versions, key=parse, reverse=True)
|
|
398
|
+
|
|
399
|
+
if is_released:
|
|
400
|
+
versions = [ver for ver in versions if not parse(ver).is_prerelease]
|
|
401
|
+
|
|
402
|
+
if display_all:
|
|
403
|
+
print(display_all)
|
|
404
|
+
|
|
405
|
+
return versions
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def get_highest_version(pkg_requirement: str):
|
|
409
|
+
"""
|
|
410
|
+
Utility to get the latest version for a given package name.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
pkg_requirement: package requirement given
|
|
414
|
+
Returns: the highest valid version of the package
|
|
415
|
+
"""
|
|
416
|
+
req = Requirement(pkg_requirement)
|
|
417
|
+
available_versions = fetch_package_versions(req.name)
|
|
418
|
+
|
|
419
|
+
# Filter versions that satisfy the specifier
|
|
420
|
+
valid_versions = [Version(version) for version in available_versions if parse(version) in req.specifier]
|
|
421
|
+
|
|
422
|
+
if not valid_versions:
|
|
423
|
+
return None # No valid versions found
|
|
424
|
+
|
|
425
|
+
# Return the highest valid version
|
|
426
|
+
return str(max(valid_versions))
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def get_latest_version(pkg_name):
|
|
430
|
+
"""
|
|
431
|
+
Utility to get the latest version for a given package name.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
pkg_name: package name given
|
|
435
|
+
|
|
436
|
+
Returns: the latest version of package
|
|
437
|
+
"""
|
|
438
|
+
version = get_highest_version(pkg_name)
|
|
439
|
+
if version is None:
|
|
440
|
+
user_logger.info(f"No valid versions found for '{pkg_name}'")
|
|
441
|
+
exit(1)
|
|
442
|
+
return version
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def get_latest_compatible_version(pkg_name, base_version=None, versions=None, validate=True):
|
|
446
|
+
"""
|
|
447
|
+
Utility to get the latest compatible version from a given version list.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
base_version: Optional base version. Versions above this will not be added.
|
|
451
|
+
pkg_name: package name given
|
|
452
|
+
versions: user input of version list
|
|
453
|
+
validate: bool, if True, will validate base_version
|
|
454
|
+
|
|
455
|
+
Returns: the latest compatible version from versions
|
|
456
|
+
|
|
457
|
+
Raises:
|
|
458
|
+
Exception - If we cannot find version
|
|
459
|
+
Notes:
|
|
460
|
+
- TODO - Make custom exception or use ValueError
|
|
461
|
+
"""
|
|
462
|
+
if versions is None:
|
|
463
|
+
versions = fetch_package_versions(pkg_name)
|
|
464
|
+
|
|
465
|
+
# Return None if given version list is None or empty
|
|
466
|
+
if not versions:
|
|
467
|
+
return None
|
|
468
|
+
|
|
469
|
+
# Return the latest version if no base_version is given
|
|
470
|
+
if base_version is None:
|
|
471
|
+
return versions[0]
|
|
472
|
+
|
|
473
|
+
# Cleanup
|
|
474
|
+
base_version = base_version.replace('+nightly', '')
|
|
475
|
+
|
|
476
|
+
# Make sure the input is valid
|
|
477
|
+
parsed_version_to_check = parse(base_version)
|
|
478
|
+
|
|
479
|
+
# Check if the version is in the list
|
|
480
|
+
is_in_list = any(parsed_version_to_check == parse(version) for version in versions)
|
|
481
|
+
if not is_in_list and validate:
|
|
482
|
+
user_logger.info(f"Could not find the version of '{base_version}' for '{pkg_name}'.")
|
|
483
|
+
return None
|
|
484
|
+
|
|
485
|
+
# Find all possible candidates
|
|
486
|
+
candidates = [version for version in versions if version.startswith(base_version)]
|
|
487
|
+
|
|
488
|
+
# Pick the latest
|
|
489
|
+
return candidates[0]
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""idmtools create asset collection script.
|
|
2
|
+
|
|
3
|
+
This is part of the RequirementsToAssetCollection tool. This is ran on the SSMT to convert installed files to a AssetCollection.
|
|
4
|
+
|
|
5
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from COMPS import Client
|
|
10
|
+
from COMPS.Data import AssetCollectionFile, QueryCriteria
|
|
11
|
+
from COMPS.Data import Experiment
|
|
12
|
+
from COMPS.Data.AssetCollection import AssetCollection
|
|
13
|
+
from idmtools.utils.hashing import calculate_md5
|
|
14
|
+
|
|
15
|
+
MD5_KEY = 'idmtools-requirements-md5-{}'
|
|
16
|
+
AC_FILE = 'ac_info.txt'
|
|
17
|
+
LIBRARY_ROOT_PREFIX = 'L'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def build_asset_file_list(prefix=LIBRARY_ROOT_PREFIX):
|
|
21
|
+
"""
|
|
22
|
+
Utility function to build all library files.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
prefix: used to identify library files
|
|
26
|
+
|
|
27
|
+
Returns: file paths as a list
|
|
28
|
+
"""
|
|
29
|
+
output = []
|
|
30
|
+
for root, _, filenames in os.walk(prefix):
|
|
31
|
+
for filename in filenames:
|
|
32
|
+
asset = AssetCollectionFile(file_name=os.path.basename(filename),
|
|
33
|
+
relative_path=os.path.join("site-packages",
|
|
34
|
+
root.replace(prefix, "").strip("/")).strip("/"),
|
|
35
|
+
md5_checksum=calculate_md5(os.path.join(root, filename))
|
|
36
|
+
)
|
|
37
|
+
output.append(asset)
|
|
38
|
+
|
|
39
|
+
return output
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_first_simulation_of_experiment(exp_id):
|
|
43
|
+
"""
|
|
44
|
+
Retrieve the first simulation from an experiment.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
exp_id: use input (experiment id)
|
|
48
|
+
|
|
49
|
+
Returns: list of files paths
|
|
50
|
+
"""
|
|
51
|
+
comps_exp = Experiment.get(exp_id)
|
|
52
|
+
comps_sims = comps_exp.get_simulations(QueryCriteria().select_children('hpc_jobs'))
|
|
53
|
+
comps_sim = comps_sims[0]
|
|
54
|
+
|
|
55
|
+
return comps_sim
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def main(): # pragma: no cover
|
|
59
|
+
"""Main entry point for our create asset collection script."""
|
|
60
|
+
print(sys.argv)
|
|
61
|
+
|
|
62
|
+
if len(sys.argv) < 3:
|
|
63
|
+
raise Exception(
|
|
64
|
+
"The script needs to be called with `python <model.py> <experiment_id> <endpoint> <os_str>'.\n{}".format(
|
|
65
|
+
" ".join(sys.argv)))
|
|
66
|
+
|
|
67
|
+
# Get the experiments
|
|
68
|
+
exp_id = sys.argv[1]
|
|
69
|
+
print('exp_id: ', exp_id)
|
|
70
|
+
|
|
71
|
+
# Get endpoint
|
|
72
|
+
endpoint = sys.argv[2]
|
|
73
|
+
print('endpoint: ', endpoint)
|
|
74
|
+
|
|
75
|
+
# Platform key
|
|
76
|
+
os_target = sys.argv[3]
|
|
77
|
+
print('os: ', os_target)
|
|
78
|
+
|
|
79
|
+
client = Client()
|
|
80
|
+
client.login(endpoint)
|
|
81
|
+
|
|
82
|
+
# Retrieve the first simulation of the experiment
|
|
83
|
+
comps_sim = get_first_simulation_of_experiment(exp_id)
|
|
84
|
+
print('sim_id: ', comps_sim.id)
|
|
85
|
+
|
|
86
|
+
# Build files metadata
|
|
87
|
+
base_path = os.path.join(comps_sim.hpc_jobs[-1].working_directory, LIBRARY_ROOT_PREFIX)
|
|
88
|
+
asset_files = build_asset_file_list(prefix=base_path)
|
|
89
|
+
print('asset files count: ', len(asset_files))
|
|
90
|
+
|
|
91
|
+
# Output files
|
|
92
|
+
max_files = 10
|
|
93
|
+
print('Display the first 10 files:\n',
|
|
94
|
+
"\n".join([f"{a.relative_path}/{a.file_name}" for a in asset_files[0:max_files]]))
|
|
95
|
+
|
|
96
|
+
# Retrieve experiment's tags
|
|
97
|
+
comps_exp = Experiment.get(exp_id, QueryCriteria().select_children('tags'))
|
|
98
|
+
exp_tags = comps_exp.tags
|
|
99
|
+
|
|
100
|
+
# Retrieve experiment's tags
|
|
101
|
+
_reserved_tag = ['idmtools', 'task_type', MD5_KEY.format(os_target)]
|
|
102
|
+
comps_exp = Experiment.get(exp_id, QueryCriteria().select_children('tags'))
|
|
103
|
+
user_tags = {key: value for key, value in comps_exp.tags.items() if key not in _reserved_tag}
|
|
104
|
+
|
|
105
|
+
# Get md5_str
|
|
106
|
+
md5_str = exp_tags.get(MD5_KEY.format(os_target), None)
|
|
107
|
+
|
|
108
|
+
# Collect ac's tags
|
|
109
|
+
ac = AssetCollection()
|
|
110
|
+
tags = {MD5_KEY.format(os_target): md5_str}
|
|
111
|
+
tags.update(user_tags)
|
|
112
|
+
ac.set_tags(tags)
|
|
113
|
+
|
|
114
|
+
# Create asset collection
|
|
115
|
+
for af in asset_files:
|
|
116
|
+
ac.add_asset(af)
|
|
117
|
+
|
|
118
|
+
sys.stdout.flush()
|
|
119
|
+
missing_files = ac.save(return_missing_files=True)
|
|
120
|
+
|
|
121
|
+
# If COMPS responds that we're missing some files, then try creating it again,
|
|
122
|
+
# uploading only the files that COMPS doesn't already have.
|
|
123
|
+
if missing_files:
|
|
124
|
+
print(f"Total of {len(ac.assets) - len(missing_files)} files currently in comps. Resolving missing files")
|
|
125
|
+
ac2 = AssetCollection()
|
|
126
|
+
ac2.set_tags(tags)
|
|
127
|
+
|
|
128
|
+
for acf in ac.assets:
|
|
129
|
+
if acf.md5_checksum in missing_files:
|
|
130
|
+
rp = acf.relative_path
|
|
131
|
+
fn = acf.file_name
|
|
132
|
+
acf2 = AssetCollectionFile(fn, rp, tags=acf.tags)
|
|
133
|
+
rfp = os.path.join(base_path, rp.replace("site-packages", "").strip(os.path.sep), fn)
|
|
134
|
+
ac2.add_asset(acf2, rfp)
|
|
135
|
+
else:
|
|
136
|
+
ac2.add_asset(acf)
|
|
137
|
+
|
|
138
|
+
print("\n\n\n=====================\nUploading files not in comps: " + "\n".join(
|
|
139
|
+
[f"{a.relative_path}/{a.file_name}" for a in ac2.assets if
|
|
140
|
+
a.md5_checksum in missing_files or a.md5_checksum is None]))
|
|
141
|
+
|
|
142
|
+
sys.stdout.flush()
|
|
143
|
+
ac2.save()
|
|
144
|
+
ac = ac2
|
|
145
|
+
# Output ac
|
|
146
|
+
print('ac_id: ', ac.id)
|
|
147
|
+
|
|
148
|
+
# write ac_id to file ac_info.txt
|
|
149
|
+
with open(AC_FILE, 'w') as outfile:
|
|
150
|
+
outfile.write(str(ac.id))
|
|
151
|
+
sys.stdout.flush()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
if __name__ == "__main__": # pragma: no cover
|
|
155
|
+
main()
|