idmtools-platform-general 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_file/__init__.py +18 -0
- idmtools_platform_file/assets/__init__.py +77 -0
- idmtools_platform_file/assets/_run.sh.jinja2 +47 -0
- idmtools_platform_file/assets/batch.sh.jinja2 +24 -0
- idmtools_platform_file/assets/run_simulation.sh +8 -0
- idmtools_platform_file/cli/__init__.py +5 -0
- idmtools_platform_file/cli/file.py +185 -0
- idmtools_platform_file/file_operations/__init__.py +4 -0
- idmtools_platform_file/file_operations/file_operations.py +298 -0
- idmtools_platform_file/file_operations/operations_interface.py +74 -0
- idmtools_platform_file/file_platform.py +288 -0
- idmtools_platform_file/platform_operations/__init__.py +5 -0
- idmtools_platform_file/platform_operations/asset_collection_operations.py +172 -0
- idmtools_platform_file/platform_operations/experiment_operations.py +314 -0
- idmtools_platform_file/platform_operations/json_metadata_operations.py +320 -0
- idmtools_platform_file/platform_operations/simulation_operations.py +212 -0
- idmtools_platform_file/platform_operations/suite_operations.py +243 -0
- idmtools_platform_file/platform_operations/utils.py +461 -0
- idmtools_platform_file/plugin_info.py +82 -0
- idmtools_platform_file/tools/__init__.py +4 -0
- idmtools_platform_file/tools/job_history.py +334 -0
- idmtools_platform_file/tools/status_report/__init__.py +4 -0
- idmtools_platform_file/tools/status_report/status_report.py +222 -0
- idmtools_platform_file/tools/status_report/utils.py +159 -0
- idmtools_platform_general-0.0.2.dist-info/METADATA +81 -0
- idmtools_platform_general-0.0.2.dist-info/RECORD +36 -0
- idmtools_platform_general-0.0.2.dist-info/entry_points.txt +6 -0
- idmtools_platform_general-0.0.2.dist-info/licenses/LICENSE.TXT +3 -0
- idmtools_platform_general-0.0.2.dist-info/top_level.txt +3 -0
- idmtools_platform_process/__init__.py +17 -0
- idmtools_platform_process/platform_operations/__init__.py +5 -0
- idmtools_platform_process/platform_operations/experiment_operations.py +53 -0
- idmtools_platform_process/plugin_info.py +80 -0
- idmtools_platform_process/process_platform.py +52 -0
- tests/input/hello.sh +2 -0
- idmtools_platform_general/__init__.py +0 -8
- idmtools_platform_general-0.0.0.dev0.dist-info/METADATA +0 -41
- idmtools_platform_general-0.0.0.dev0.dist-info/RECORD +0 -5
- idmtools_platform_general-0.0.0.dev0.dist-info/top_level.txt +0 -1
- {idmtools_platform_general-0.0.0.dev0.dist-info → idmtools_platform_general-0.0.2.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Here we implement the FilePlatform experiment operations.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import shutil
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import TYPE_CHECKING, List, Type, Dict, Optional, Any
|
|
10
|
+
from idmtools.assets import Asset, AssetCollection
|
|
11
|
+
from idmtools.core import ItemType
|
|
12
|
+
from idmtools.entities import Suite
|
|
13
|
+
from idmtools.entities.experiment import Experiment
|
|
14
|
+
from idmtools.entities.iplatform_ops.iplatform_experiment_operations import IPlatformExperimentOperations
|
|
15
|
+
from idmtools_platform_file.platform_operations.utils import FileExperiment, FileSimulation, FileSuite
|
|
16
|
+
from logging import getLogger
|
|
17
|
+
|
|
18
|
+
logger = getLogger(__name__)
|
|
19
|
+
user_logger = getLogger('user')
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from idmtools_platform_file.file_platform import FilePlatform
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class FilePlatformExperimentOperations(IPlatformExperimentOperations):
|
|
27
|
+
"""
|
|
28
|
+
Experiment Operations for File Platform.
|
|
29
|
+
"""
|
|
30
|
+
platform: 'FilePlatform' # noqa: F821
|
|
31
|
+
platform_type: Type = field(default=FileExperiment)
|
|
32
|
+
RUN_SIMULATION_SCRIPT_PATH = Path(__file__).parent.parent.joinpath('assets/run_simulation.sh')
|
|
33
|
+
|
|
34
|
+
def get(self, experiment_id: str, **kwargs) -> FileExperiment:
|
|
35
|
+
"""
|
|
36
|
+
Gets an experiment from the File platform.
|
|
37
|
+
Args:
|
|
38
|
+
experiment_id: experiment id
|
|
39
|
+
kwargs: keyword arguments used to expand functionality
|
|
40
|
+
Returns:
|
|
41
|
+
File Experiment object
|
|
42
|
+
"""
|
|
43
|
+
metas = self.platform._metas.filter(item_type=ItemType.EXPERIMENT, property_filter={'id': str(experiment_id)})
|
|
44
|
+
if len(metas) > 0:
|
|
45
|
+
return FileExperiment(metas[0])
|
|
46
|
+
else:
|
|
47
|
+
raise RuntimeError(f"Not found Experiment with id '{experiment_id}'")
|
|
48
|
+
|
|
49
|
+
def platform_create(self, experiment: Experiment, **kwargs) -> FileExperiment:
|
|
50
|
+
"""
|
|
51
|
+
Creates an experiment on File Platform.
|
|
52
|
+
Args:
|
|
53
|
+
experiment: idmtools experiment
|
|
54
|
+
kwargs: keyword arguments used to expand functionality
|
|
55
|
+
Returns:
|
|
56
|
+
File Experiment object created
|
|
57
|
+
"""
|
|
58
|
+
self.platform.mk_directory(experiment, exist_ok=True)
|
|
59
|
+
meta = self.platform._metas.dump(experiment)
|
|
60
|
+
self.platform._assets.dump_assets(experiment)
|
|
61
|
+
self.platform.create_batch_file(experiment, **kwargs)
|
|
62
|
+
|
|
63
|
+
# Copy file run_simulation.sh
|
|
64
|
+
dest_script = Path(self.platform.get_directory(experiment)).joinpath('run_simulation.sh')
|
|
65
|
+
shutil.copy(str(self.RUN_SIMULATION_SCRIPT_PATH), str(dest_script))
|
|
66
|
+
|
|
67
|
+
# Make executable
|
|
68
|
+
self.platform.update_script_mode(dest_script)
|
|
69
|
+
|
|
70
|
+
# Return File Experiment
|
|
71
|
+
return FileExperiment(meta)
|
|
72
|
+
|
|
73
|
+
def get_children(self, experiment: FileExperiment, parent: Experiment = None, raw=True, **kwargs) -> List[Any]:
|
|
74
|
+
"""
|
|
75
|
+
Fetch file experiment's children.
|
|
76
|
+
Args:
|
|
77
|
+
experiment: File experiment
|
|
78
|
+
raw: True/False
|
|
79
|
+
parent: the parent of the simulations
|
|
80
|
+
kwargs: keyword arguments used to expand functionality
|
|
81
|
+
Returns:
|
|
82
|
+
List of file simulations
|
|
83
|
+
"""
|
|
84
|
+
sim_list = []
|
|
85
|
+
sim_meta_list = self.platform._metas.get_children(experiment)
|
|
86
|
+
for meta in sim_meta_list:
|
|
87
|
+
file_sim = FileSimulation(meta)
|
|
88
|
+
file_sim.status = self.platform.get_simulation_status(file_sim.id)
|
|
89
|
+
if raw:
|
|
90
|
+
sim_list.append(file_sim)
|
|
91
|
+
else:
|
|
92
|
+
sim = self.platform._simulations.to_entity(file_sim, parent=parent)
|
|
93
|
+
sim_list.append(sim)
|
|
94
|
+
return sim_list
|
|
95
|
+
|
|
96
|
+
def get_parent(self, experiment: FileExperiment, **kwargs) -> FileSuite:
|
|
97
|
+
"""
|
|
98
|
+
Fetches the parent of an experiment.
|
|
99
|
+
Args:
|
|
100
|
+
experiment: File experiment
|
|
101
|
+
kwargs: keyword arguments used to expand functionality
|
|
102
|
+
Returns:
|
|
103
|
+
The Suite being the parent of this experiment.
|
|
104
|
+
"""
|
|
105
|
+
if experiment.parent_id is None:
|
|
106
|
+
return None
|
|
107
|
+
else:
|
|
108
|
+
return self.platform._suites.get(experiment.parent_id, raw=True, **kwargs)
|
|
109
|
+
|
|
110
|
+
def platform_run_item(self, experiment: Experiment, **kwargs):
|
|
111
|
+
"""
|
|
112
|
+
Run experiment.
|
|
113
|
+
Args:
|
|
114
|
+
experiment: idmtools Experiment
|
|
115
|
+
kwargs: keyword arguments used to expand functionality
|
|
116
|
+
Returns:
|
|
117
|
+
None
|
|
118
|
+
"""
|
|
119
|
+
# Ensure parent
|
|
120
|
+
self.platform._metas.dump(experiment)
|
|
121
|
+
if experiment.parent:
|
|
122
|
+
user_logger.info(f'suite: {str(experiment.parent.id)}')
|
|
123
|
+
|
|
124
|
+
user_logger.info(f'job_directory: {Path(self.platform.job_directory).resolve()}')
|
|
125
|
+
user_logger.info(f'experiment: {experiment.id}')
|
|
126
|
+
user_logger.info(f"\nExperiment Directory: \n{self.platform.get_directory(experiment)}")
|
|
127
|
+
|
|
128
|
+
def post_run_item(self, experiment: Experiment, **kwargs):
|
|
129
|
+
"""
|
|
130
|
+
Perform post-processing steps after an experiment run.
|
|
131
|
+
Args:
|
|
132
|
+
experiment: The experiment object that has just finished running
|
|
133
|
+
**kwargs: Additional keyword arguments
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
None
|
|
137
|
+
"""
|
|
138
|
+
super().post_run_item(experiment, **kwargs)
|
|
139
|
+
# Refresh platform object
|
|
140
|
+
experiment._platform_object = self.get(experiment.id, **kwargs)
|
|
141
|
+
|
|
142
|
+
def send_assets(self, experiment: Experiment, **kwargs):
|
|
143
|
+
"""
|
|
144
|
+
Copy our experiment assets.
|
|
145
|
+
Replaced by self.platform._assets.dump_assets(experiment)
|
|
146
|
+
Args:
|
|
147
|
+
experiment: idmtools Experiment
|
|
148
|
+
kwargs: keyword arguments used to expand functionality
|
|
149
|
+
Returns:
|
|
150
|
+
None
|
|
151
|
+
"""
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
def list_assets(self, experiment: Experiment, **kwargs) -> List[Asset]:
|
|
155
|
+
"""
|
|
156
|
+
List assets for an experiment.
|
|
157
|
+
Args:
|
|
158
|
+
experiment: Experiment to get assets for
|
|
159
|
+
kwargs:
|
|
160
|
+
Returns:
|
|
161
|
+
List[Asset]
|
|
162
|
+
"""
|
|
163
|
+
assets = self.platform._assets.list_assets(experiment, **kwargs)
|
|
164
|
+
return assets
|
|
165
|
+
|
|
166
|
+
def get_assets_from_file_experiment(self, experiment: FileExperiment) -> AssetCollection:
|
|
167
|
+
"""
|
|
168
|
+
Get assets for a comps experiment.
|
|
169
|
+
Args:
|
|
170
|
+
experiment: Experiment to get asset collection for.
|
|
171
|
+
Returns:
|
|
172
|
+
AssetCollection if configuration is set and configuration.asset_collection_id is set.
|
|
173
|
+
"""
|
|
174
|
+
assets = AssetCollection()
|
|
175
|
+
assets_dir = Path(self.platform.get_directory_by_id(experiment.id, ItemType.EXPERIMENT), 'Assets')
|
|
176
|
+
if assets_dir.exists():
|
|
177
|
+
assets_list = AssetCollection.assets_from_directory(assets_dir, recursive=True)
|
|
178
|
+
for a in assets_list:
|
|
179
|
+
assets.add_asset(a)
|
|
180
|
+
return assets
|
|
181
|
+
|
|
182
|
+
def to_entity(self, file_exp: FileExperiment, parent: Optional[Suite] = None, children: bool = True,
|
|
183
|
+
**kwargs) -> Experiment:
|
|
184
|
+
"""
|
|
185
|
+
Convert a FileExperiment to idmtools Experiment.
|
|
186
|
+
Args:
|
|
187
|
+
file_exp: simulation to convert
|
|
188
|
+
parent: optional experiment object
|
|
189
|
+
children: bool
|
|
190
|
+
kwargs:
|
|
191
|
+
Returns:
|
|
192
|
+
Experiment object
|
|
193
|
+
"""
|
|
194
|
+
exp = Experiment()
|
|
195
|
+
exp.platform = self.platform
|
|
196
|
+
exp.uid = file_exp.uid
|
|
197
|
+
exp.name = file_exp.name
|
|
198
|
+
if parent:
|
|
199
|
+
exp.parent = parent
|
|
200
|
+
elif file_exp.suite_id:
|
|
201
|
+
exp.parent = self.platform.get_item(file_exp.suite_id, ItemType.SUITE, force=True)
|
|
202
|
+
exp.tags = file_exp.tags
|
|
203
|
+
exp._platform_object = file_exp
|
|
204
|
+
exp.simulations = []
|
|
205
|
+
|
|
206
|
+
exp.assets = self.get_assets_from_file_experiment(file_exp)
|
|
207
|
+
if exp.assets is None:
|
|
208
|
+
exp.assets = AssetCollection()
|
|
209
|
+
|
|
210
|
+
if children:
|
|
211
|
+
exp.simulations = self.get_children(file_exp, parent=exp, raw=False)
|
|
212
|
+
|
|
213
|
+
return exp
|
|
214
|
+
|
|
215
|
+
def refresh_status(self, experiment: Experiment, **kwargs):
|
|
216
|
+
"""
|
|
217
|
+
Refresh status of experiment.
|
|
218
|
+
Args:
|
|
219
|
+
experiment: idmtools Experiment
|
|
220
|
+
kwargs: keyword arguments used to expand functionality
|
|
221
|
+
Returns:
|
|
222
|
+
Dict of simulation id as key and working dir as value
|
|
223
|
+
"""
|
|
224
|
+
# Refresh status for each simulation
|
|
225
|
+
for sim in experiment.simulations:
|
|
226
|
+
sim.status = self.platform.get_simulation_status(sim.id, **kwargs)
|
|
227
|
+
|
|
228
|
+
def create_sim_directory_map(self, experiment_id: str) -> Dict:
|
|
229
|
+
"""
|
|
230
|
+
Build simulation working directory mapping.
|
|
231
|
+
Args:
|
|
232
|
+
experiment_id: experiment id
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Dict of simulation id as key and working dir as value
|
|
236
|
+
"""
|
|
237
|
+
exp = self.platform.get_item(experiment_id, ItemType.EXPERIMENT, raw=False)
|
|
238
|
+
sims = exp.simulations
|
|
239
|
+
return {sim.id: str(self.platform.get_directory(sim)) for sim in sims}
|
|
240
|
+
|
|
241
|
+
def platform_delete(self, experiment_id: str) -> None:
|
|
242
|
+
"""
|
|
243
|
+
Delete platform experiment.
|
|
244
|
+
Args:
|
|
245
|
+
experiment_id: platform experiment id
|
|
246
|
+
Returns:
|
|
247
|
+
None
|
|
248
|
+
"""
|
|
249
|
+
exp = self.platform.get_item(experiment_id, ItemType.EXPERIMENT, raw=False)
|
|
250
|
+
try:
|
|
251
|
+
shutil.rmtree(self.platform.get_directory(exp))
|
|
252
|
+
except RuntimeError:
|
|
253
|
+
logger.info("Could not delete the associated experiment...")
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
def platform_cancel(self, experiment_id: str, force: bool = True) -> Any:
|
|
257
|
+
"""
|
|
258
|
+
Cancel platform experiment's file job.
|
|
259
|
+
Args:
|
|
260
|
+
experiment_id: experiment id
|
|
261
|
+
force: bool, True/False
|
|
262
|
+
Returns:
|
|
263
|
+
Any
|
|
264
|
+
"""
|
|
265
|
+
pass
|
|
266
|
+
|
|
267
|
+
def get_assets(self, experiment: Experiment, files: List[str], **kwargs) -> Dict[str, bytearray]:
|
|
268
|
+
"""
|
|
269
|
+
Fetch the files associated with an experiment.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
experiment: Experiment (idmools Experiment or COMPSExperiment)
|
|
273
|
+
files: List of files to download
|
|
274
|
+
**kwargs:
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
Dict[str, Dict[str, Dict[str, str]]]:
|
|
278
|
+
A nested dictionary structured as:
|
|
279
|
+
{
|
|
280
|
+
experiment.id: {
|
|
281
|
+
simulation.id {
|
|
282
|
+
filename: file content as string,
|
|
283
|
+
...
|
|
284
|
+
},
|
|
285
|
+
...
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
"""
|
|
289
|
+
ret = dict()
|
|
290
|
+
if isinstance(experiment, FileExperiment):
|
|
291
|
+
file_exp = experiment
|
|
292
|
+
else:
|
|
293
|
+
file_exp = experiment.get_platform_object()
|
|
294
|
+
simulations = self.platform.flatten_item(file_exp, raw=True)
|
|
295
|
+
for sim in simulations:
|
|
296
|
+
ret[sim.id] = self.platform._simulations.get_assets(sim, files, **kwargs)
|
|
297
|
+
return ret
|
|
298
|
+
|
|
299
|
+
def run_item(self, experiment: Experiment, **kwargs):
|
|
300
|
+
"""
|
|
301
|
+
Called during commissioning of an item. This should create the remote resource.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
experiment:Experiment
|
|
305
|
+
**kwargs: Keyword arguments to pass to pre_run_item, platform_run_item, post_run_item
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
None
|
|
309
|
+
"""
|
|
310
|
+
# Consider Suite
|
|
311
|
+
if experiment.parent:
|
|
312
|
+
experiment.parent.add_experiment(experiment)
|
|
313
|
+
self.platform._suites.platform_create(experiment.parent)
|
|
314
|
+
super().run_item(experiment, **kwargs)
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Here we implement the JSON Metadata operations.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING, Dict, List, Type, Union
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from idmtools.core import ItemType
|
|
12
|
+
from idmtools.core.interfaces import imetadata_operations
|
|
13
|
+
from idmtools.entities import Suite
|
|
14
|
+
from idmtools.entities.experiment import Experiment
|
|
15
|
+
from idmtools.entities.simulation import Simulation
|
|
16
|
+
from idmtools.utils.json import IDMJSONEncoder
|
|
17
|
+
from idmtools_platform_file.platform_operations.utils import FileSuite, FileExperiment
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from idmtools_platform_file.file_platform import FilePlatform
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class JSONMetadataOperations(imetadata_operations.IMetadataOperations):
|
|
25
|
+
"""
|
|
26
|
+
JSON operations used in File Platform.
|
|
27
|
+
"""
|
|
28
|
+
platform: 'FilePlatform' # noqa: F821
|
|
29
|
+
platform_type: Type = field(default=None)
|
|
30
|
+
metadata_filename: str = field(default='metadata.json')
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def _read_from_file(filepath: Union[Path, str]) -> Dict:
|
|
34
|
+
"""
|
|
35
|
+
Utility: read metadata from a file.
|
|
36
|
+
Args:
|
|
37
|
+
filepath: metadata file path
|
|
38
|
+
Returns:
|
|
39
|
+
JSON
|
|
40
|
+
"""
|
|
41
|
+
filepath = Path(filepath)
|
|
42
|
+
with filepath.open(mode='r') as f:
|
|
43
|
+
metadata = json.load(f)
|
|
44
|
+
return metadata
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def _write_to_file(filepath: Union[Path, str], data: Dict, indent: int = None) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Utility: save metadata to a file.
|
|
50
|
+
Args:
|
|
51
|
+
filepath: metadata file path
|
|
52
|
+
data: metadata as dictionary
|
|
53
|
+
indent: indent level for pretty printing the JSON file. None for compact JSON.
|
|
54
|
+
Returns:
|
|
55
|
+
None
|
|
56
|
+
"""
|
|
57
|
+
filepath = Path(filepath)
|
|
58
|
+
filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
with filepath.open(mode='w') as f:
|
|
60
|
+
json.dump(data, f, indent=indent, cls=IDMJSONEncoder)
|
|
61
|
+
|
|
62
|
+
def get_metadata_filepath(self, item: Union[Suite, Experiment, Simulation]) -> Path:
|
|
63
|
+
"""
|
|
64
|
+
Retrieve item's metadata file path.
|
|
65
|
+
Args:
|
|
66
|
+
item: idmtools entity (Suite, Experiment and Simulation)
|
|
67
|
+
Returns:
|
|
68
|
+
item's metadata file path
|
|
69
|
+
"""
|
|
70
|
+
if not isinstance(item, (Suite, Experiment, Simulation)):
|
|
71
|
+
raise RuntimeError("get_metadata_filepath method supports Suite/Experiment/Simulation only.")
|
|
72
|
+
item_dir = self.platform.get_directory(item)
|
|
73
|
+
filepath = Path(item_dir, self.metadata_filename)
|
|
74
|
+
return filepath
|
|
75
|
+
|
|
76
|
+
def get_metadata_filepath_by_id(self, item_id: str, item_type: ItemType) -> Path:
|
|
77
|
+
"""
|
|
78
|
+
Retrieve item's metadata file path.
|
|
79
|
+
Args:
|
|
80
|
+
item_id: item id
|
|
81
|
+
item_type: the type of metadata to search for matches (simulation, experiment, suite, etc.)
|
|
82
|
+
Returns:
|
|
83
|
+
item's metadata file path
|
|
84
|
+
"""
|
|
85
|
+
if item_type not in (ItemType.SUITE, ItemType.EXPERIMENT, ItemType.SIMULATION):
|
|
86
|
+
raise RuntimeError("get_metadata_filepath method supports Suite/Experiment/Simulation only.")
|
|
87
|
+
item_dir = self.platform.get_directory_by_id(item_id, item_type)
|
|
88
|
+
filepath = Path(item_dir, self.metadata_filename)
|
|
89
|
+
return filepath
|
|
90
|
+
|
|
91
|
+
def get(self, item: Union[Suite, Experiment, Simulation]) -> Dict:
|
|
92
|
+
"""
|
|
93
|
+
Obtain item's metadata.
|
|
94
|
+
Args:
|
|
95
|
+
item: idmtools entity (Suite, Experiment and Simulation)
|
|
96
|
+
Returns:
|
|
97
|
+
key/value dict of metadata from the given item
|
|
98
|
+
"""
|
|
99
|
+
if not isinstance(item, (Suite, Experiment, Simulation)):
|
|
100
|
+
raise RuntimeError("Get method supports Suite/Experiment/Simulation only.")
|
|
101
|
+
data = item.to_dict()
|
|
102
|
+
if isinstance(item, Suite):
|
|
103
|
+
data.pop('experiments', None)
|
|
104
|
+
meta = json.loads(json.dumps(data, cls=IDMJSONEncoder))
|
|
105
|
+
meta['id'] = meta['_uid']
|
|
106
|
+
meta['uid'] = meta['_uid']
|
|
107
|
+
meta['status'] = 'CREATED'
|
|
108
|
+
meta['dir'] = os.path.abspath(self.platform.get_directory(item))
|
|
109
|
+
|
|
110
|
+
if isinstance(item, Suite):
|
|
111
|
+
meta['experiments'] = [experiment.id for experiment in item.experiments]
|
|
112
|
+
elif isinstance(item, Experiment):
|
|
113
|
+
meta['suite_id'] = meta["parent_id"]
|
|
114
|
+
meta['simulations'] = [simulation.id for simulation in item.simulations]
|
|
115
|
+
elif isinstance(item, Simulation):
|
|
116
|
+
meta['experiment_id'] = meta["parent_id"]
|
|
117
|
+
return meta
|
|
118
|
+
|
|
119
|
+
def dump(self, item: Union[Suite, Experiment, Simulation]) -> Dict:
|
|
120
|
+
"""
|
|
121
|
+
Save item's metadata to a file and also save tags.json file.
|
|
122
|
+
Args:
|
|
123
|
+
item: idmtools entity (Suite, Experiment and Simulation)
|
|
124
|
+
Returns:
|
|
125
|
+
key/value dict of metadata from the given item
|
|
126
|
+
"""
|
|
127
|
+
if not isinstance(item, (Suite, Experiment, Simulation)):
|
|
128
|
+
raise RuntimeError("Dump method supports Suite/Experiment/Simulation only.")
|
|
129
|
+
dest = self.get_metadata_filepath(item)
|
|
130
|
+
meta = self.get(item)
|
|
131
|
+
self._write_to_file(dest, meta)
|
|
132
|
+
|
|
133
|
+
# Also write tags.json file
|
|
134
|
+
keys_to_extract = ["id", "item_type", "tags"]
|
|
135
|
+
extracted = {key: meta[key] for key in keys_to_extract}
|
|
136
|
+
|
|
137
|
+
tags_path = dest.parent / "tags.json"
|
|
138
|
+
self._write_to_file(tags_path, extracted, indent=2)
|
|
139
|
+
return meta
|
|
140
|
+
|
|
141
|
+
def load(self, item: Union[Suite, Experiment, Simulation]) -> Dict:
|
|
142
|
+
"""
|
|
143
|
+
Obtain item's metadata file.
|
|
144
|
+
Args:
|
|
145
|
+
item: idmtools entity (Suite, Experiment and Simulation)
|
|
146
|
+
Returns:
|
|
147
|
+
key/value dict of metadata from the given item
|
|
148
|
+
"""
|
|
149
|
+
if not isinstance(item, (Suite, Experiment, Simulation)):
|
|
150
|
+
raise RuntimeError("Load method supports Suite/Experiment/Simulation only.")
|
|
151
|
+
meta_file = self.get_metadata_filepath(item)
|
|
152
|
+
meta = self._read_from_file(meta_file)
|
|
153
|
+
return meta
|
|
154
|
+
|
|
155
|
+
def load_from_file(self, metadata_filepath: Union[Path, str]) -> Dict:
|
|
156
|
+
"""
|
|
157
|
+
Obtain the metadata for the given filepath.
|
|
158
|
+
Args:
|
|
159
|
+
metadata_filepath: str
|
|
160
|
+
Returns:
|
|
161
|
+
key/value dict of metadata from the given filepath
|
|
162
|
+
"""
|
|
163
|
+
if not Path(metadata_filepath).exists():
|
|
164
|
+
raise RuntimeError(f"File not found: '{metadata_filepath}'.")
|
|
165
|
+
meta = self._read_from_file(metadata_filepath)
|
|
166
|
+
return meta
|
|
167
|
+
|
|
168
|
+
def update(self, item: Union[Suite, Experiment, Simulation], metadata: Dict = None, replace=True) -> None:
|
|
169
|
+
"""
|
|
170
|
+
Update or replace item's metadata file.
|
|
171
|
+
Args:
|
|
172
|
+
item: idmtools entity (Suite, Experiment and Simulation.)
|
|
173
|
+
metadata: dict to be updated or replaced
|
|
174
|
+
replace: True/False
|
|
175
|
+
Returns:
|
|
176
|
+
None
|
|
177
|
+
"""
|
|
178
|
+
if metadata is None:
|
|
179
|
+
metadata = {}
|
|
180
|
+
if not isinstance(item, (Suite, Experiment, Simulation)):
|
|
181
|
+
raise RuntimeError("Set method supports Suite/Experiment/Simulation only.")
|
|
182
|
+
meta = metadata
|
|
183
|
+
if not replace:
|
|
184
|
+
meta = self.load(item)
|
|
185
|
+
meta.update(metadata)
|
|
186
|
+
meta_file = self.get_metadata_filepath(item)
|
|
187
|
+
self._write_to_file(meta_file, meta)
|
|
188
|
+
|
|
189
|
+
def clear(self, item: Union[Suite, Experiment, Simulation]) -> None:
|
|
190
|
+
"""
|
|
191
|
+
Clear the item's metadata file.
|
|
192
|
+
Args:
|
|
193
|
+
item: clear the item's metadata file
|
|
194
|
+
Returns:
|
|
195
|
+
None
|
|
196
|
+
"""
|
|
197
|
+
if not isinstance(item, (Suite, Experiment, Simulation)):
|
|
198
|
+
raise RuntimeError("Clear method supports Suite/Experiment/Simulation only.")
|
|
199
|
+
self.update(item=item, metadata={}, replace=True)
|
|
200
|
+
|
|
201
|
+
def get_children(self, item: Union[Suite, Experiment, FileSuite, FileExperiment]) -> List[Dict]:
|
|
202
|
+
"""
|
|
203
|
+
Fetch item's children.
|
|
204
|
+
Args:
|
|
205
|
+
item: idmtools entity (Suite, FileSuite, Experiment, FileExperiment)
|
|
206
|
+
Returns:
|
|
207
|
+
Lis of metadata
|
|
208
|
+
"""
|
|
209
|
+
if not isinstance(item, (Suite, FileSuite, Experiment, FileExperiment)):
|
|
210
|
+
raise RuntimeError("Get children method supports [File]Suite and [File]Experiment only.")
|
|
211
|
+
item_list = []
|
|
212
|
+
if isinstance(item, (Suite, FileSuite)):
|
|
213
|
+
meta = self.load(item)
|
|
214
|
+
for exp_id in meta['experiments']:
|
|
215
|
+
meta_file = self.get_metadata_filepath_by_id(exp_id, ItemType.EXPERIMENT)
|
|
216
|
+
exp_meta = self._read_from_file(meta_file)
|
|
217
|
+
item_list.append(exp_meta)
|
|
218
|
+
else:
|
|
219
|
+
item_dir = self.platform.get_directory(item)
|
|
220
|
+
pattern = f'*/{self.metadata_filename}'
|
|
221
|
+
for meta_file in item_dir.glob(pattern=pattern):
|
|
222
|
+
meta = self.load_from_file(meta_file)
|
|
223
|
+
item_list.append(meta)
|
|
224
|
+
return item_list
|
|
225
|
+
|
|
226
|
+
def get_all(self, item_type: ItemType, item_id: str = '') -> List[Dict]:
|
|
227
|
+
"""
|
|
228
|
+
Obtain all the metadata for a given item type.
|
|
229
|
+
Args:
|
|
230
|
+
item_type: the type of metadata to search for matches (simulation, experiment, suite, etc.)
|
|
231
|
+
item_id: item id
|
|
232
|
+
Returns:
|
|
233
|
+
list of metadata with given item type
|
|
234
|
+
"""
|
|
235
|
+
root = Path(self.platform.job_directory)
|
|
236
|
+
item_list = []
|
|
237
|
+
|
|
238
|
+
if item_type is ItemType.SIMULATION:
|
|
239
|
+
# Match sim under experiment, under optional suite
|
|
240
|
+
patterns = [
|
|
241
|
+
f"s_*/e_*/*{item_id}/{self.metadata_filename}", # suite/experiment/simulation
|
|
242
|
+
f"e_*/*{item_id}/{self.metadata_filename}", # experiment/simulation (no suite)
|
|
243
|
+
]
|
|
244
|
+
elif item_type is ItemType.EXPERIMENT:
|
|
245
|
+
patterns = [
|
|
246
|
+
f"s_*/e_*{item_id}/{self.metadata_filename}", # suite/experiment
|
|
247
|
+
f"e_*{item_id}/{self.metadata_filename}", # standalone experiment
|
|
248
|
+
]
|
|
249
|
+
elif item_type is ItemType.SUITE:
|
|
250
|
+
patterns = [
|
|
251
|
+
f"s_*{item_id}/{self.metadata_filename}", # suite only
|
|
252
|
+
]
|
|
253
|
+
else:
|
|
254
|
+
raise RuntimeError(f"Unknown item type: {item_type}")
|
|
255
|
+
|
|
256
|
+
# Search each pattern
|
|
257
|
+
for pattern in patterns:
|
|
258
|
+
for meta_file in root.glob(pattern):
|
|
259
|
+
try:
|
|
260
|
+
meta = self.load_from_file(meta_file)
|
|
261
|
+
item_list.append(meta)
|
|
262
|
+
except Exception as e:
|
|
263
|
+
print(f"Warning: Failed to load metadata from {meta_file}: {e}")
|
|
264
|
+
|
|
265
|
+
return item_list
|
|
266
|
+
|
|
267
|
+
@staticmethod
|
|
268
|
+
def _match_filter(item: Dict, metadata: Dict, ignore_none=True):
|
|
269
|
+
"""
|
|
270
|
+
Utility: verify if item match metadata.
|
|
271
|
+
Note: compare key/value if value is not None else just check key exists
|
|
272
|
+
Args:
|
|
273
|
+
item: dict represents metadata of Suite/Experiment/Simulation
|
|
274
|
+
metadata: dict as a filter
|
|
275
|
+
ignore_none: True/False (ignore None value or not)
|
|
276
|
+
Returns:
|
|
277
|
+
list of Dict items
|
|
278
|
+
"""
|
|
279
|
+
for k, v in metadata.items():
|
|
280
|
+
if ignore_none:
|
|
281
|
+
if v is None:
|
|
282
|
+
is_match = k in item
|
|
283
|
+
else:
|
|
284
|
+
is_match = k in item and item[k] == v
|
|
285
|
+
else:
|
|
286
|
+
if v is None:
|
|
287
|
+
is_match = k in item and item[k] is None
|
|
288
|
+
else:
|
|
289
|
+
is_match = k in item and item[k] == v
|
|
290
|
+
if not is_match:
|
|
291
|
+
return False
|
|
292
|
+
return True
|
|
293
|
+
|
|
294
|
+
def filter(self, item_type: ItemType, property_filter: Dict = None, tag_filter: Dict = None,
|
|
295
|
+
meta_items: List[Dict] = None, ignore_none=True) -> List[Dict]:
|
|
296
|
+
"""
|
|
297
|
+
Obtain all items that match the given properties key/value pairs passed.
|
|
298
|
+
The two filters are applied on item with 'AND' logical checking.
|
|
299
|
+
Args:
|
|
300
|
+
item_type: the type of items to search for matches (simulation, experiment, suite, etc.)
|
|
301
|
+
property_filter: a dict of metadata key/value pairs for exact match searching
|
|
302
|
+
tag_filter: a dict of metadata key/value pairs for exact match searching
|
|
303
|
+
meta_items: list of metadata
|
|
304
|
+
ignore_none: True/False (ignore None value or not)
|
|
305
|
+
Returns:
|
|
306
|
+
a list of metadata matching the properties key/value with given item type
|
|
307
|
+
"""
|
|
308
|
+
if meta_items is None:
|
|
309
|
+
item_id = property_filter["id"] if property_filter and "id" in property_filter else ''
|
|
310
|
+
meta_items = self.get_all(item_type, item_id=item_id)
|
|
311
|
+
item_list = []
|
|
312
|
+
for meta in meta_items:
|
|
313
|
+
is_match = True
|
|
314
|
+
if property_filter:
|
|
315
|
+
is_match = self._match_filter(meta, property_filter, ignore_none=ignore_none)
|
|
316
|
+
if tag_filter:
|
|
317
|
+
is_match = is_match and self._match_filter(meta['tags'], tag_filter, ignore_none=ignore_none)
|
|
318
|
+
if is_match:
|
|
319
|
+
item_list.append(meta)
|
|
320
|
+
return item_list
|