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.
Files changed (40) hide show
  1. idmtools_platform_file/__init__.py +18 -0
  2. idmtools_platform_file/assets/__init__.py +77 -0
  3. idmtools_platform_file/assets/_run.sh.jinja2 +47 -0
  4. idmtools_platform_file/assets/batch.sh.jinja2 +24 -0
  5. idmtools_platform_file/assets/run_simulation.sh +8 -0
  6. idmtools_platform_file/cli/__init__.py +5 -0
  7. idmtools_platform_file/cli/file.py +185 -0
  8. idmtools_platform_file/file_operations/__init__.py +4 -0
  9. idmtools_platform_file/file_operations/file_operations.py +298 -0
  10. idmtools_platform_file/file_operations/operations_interface.py +74 -0
  11. idmtools_platform_file/file_platform.py +288 -0
  12. idmtools_platform_file/platform_operations/__init__.py +5 -0
  13. idmtools_platform_file/platform_operations/asset_collection_operations.py +172 -0
  14. idmtools_platform_file/platform_operations/experiment_operations.py +314 -0
  15. idmtools_platform_file/platform_operations/json_metadata_operations.py +320 -0
  16. idmtools_platform_file/platform_operations/simulation_operations.py +212 -0
  17. idmtools_platform_file/platform_operations/suite_operations.py +243 -0
  18. idmtools_platform_file/platform_operations/utils.py +461 -0
  19. idmtools_platform_file/plugin_info.py +82 -0
  20. idmtools_platform_file/tools/__init__.py +4 -0
  21. idmtools_platform_file/tools/job_history.py +334 -0
  22. idmtools_platform_file/tools/status_report/__init__.py +4 -0
  23. idmtools_platform_file/tools/status_report/status_report.py +222 -0
  24. idmtools_platform_file/tools/status_report/utils.py +159 -0
  25. idmtools_platform_general-0.0.2.dist-info/METADATA +81 -0
  26. idmtools_platform_general-0.0.2.dist-info/RECORD +36 -0
  27. idmtools_platform_general-0.0.2.dist-info/entry_points.txt +6 -0
  28. idmtools_platform_general-0.0.2.dist-info/licenses/LICENSE.TXT +3 -0
  29. idmtools_platform_general-0.0.2.dist-info/top_level.txt +3 -0
  30. idmtools_platform_process/__init__.py +17 -0
  31. idmtools_platform_process/platform_operations/__init__.py +5 -0
  32. idmtools_platform_process/platform_operations/experiment_operations.py +53 -0
  33. idmtools_platform_process/plugin_info.py +80 -0
  34. idmtools_platform_process/process_platform.py +52 -0
  35. tests/input/hello.sh +2 -0
  36. idmtools_platform_general/__init__.py +0 -8
  37. idmtools_platform_general-0.0.0.dev0.dist-info/METADATA +0 -41
  38. idmtools_platform_general-0.0.0.dev0.dist-info/RECORD +0 -5
  39. idmtools_platform_general-0.0.0.dev0.dist-info/top_level.txt +0 -1
  40. {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