idmtools 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 (118) hide show
  1. idmtools/__init__.py +27 -8
  2. idmtools/analysis/__init__.py +5 -0
  3. idmtools/analysis/add_analyzer.py +89 -0
  4. idmtools/analysis/analyze_manager.py +490 -0
  5. idmtools/analysis/csv_analyzer.py +103 -0
  6. idmtools/analysis/download_analyzer.py +96 -0
  7. idmtools/analysis/map_worker_entry.py +100 -0
  8. idmtools/analysis/platform_analysis_bootstrap.py +94 -0
  9. idmtools/analysis/platform_anaylsis.py +291 -0
  10. idmtools/analysis/tags_analyzer.py +93 -0
  11. idmtools/assets/__init__.py +9 -0
  12. idmtools/assets/asset.py +453 -0
  13. idmtools/assets/asset_collection.py +514 -0
  14. idmtools/assets/content_handlers.py +19 -0
  15. idmtools/assets/errors.py +23 -0
  16. idmtools/assets/file_list.py +191 -0
  17. idmtools/builders/__init__.py +11 -0
  18. idmtools/builders/arm_simulation_builder.py +152 -0
  19. idmtools/builders/csv_simulation_builder.py +76 -0
  20. idmtools/builders/simulation_builder.py +348 -0
  21. idmtools/builders/sweep_arm.py +109 -0
  22. idmtools/builders/yaml_simulation_builder.py +82 -0
  23. idmtools/config/__init__.py +7 -0
  24. idmtools/config/idm_config_parser.py +486 -0
  25. idmtools/core/__init__.py +10 -0
  26. idmtools/core/cache_enabled.py +114 -0
  27. idmtools/core/context.py +68 -0
  28. idmtools/core/docker_task.py +207 -0
  29. idmtools/core/enums.py +51 -0
  30. idmtools/core/exceptions.py +91 -0
  31. idmtools/core/experiment_factory.py +71 -0
  32. idmtools/core/id_file.py +70 -0
  33. idmtools/core/interfaces/__init__.py +5 -0
  34. idmtools/core/interfaces/entity_container.py +64 -0
  35. idmtools/core/interfaces/iassets_enabled.py +58 -0
  36. idmtools/core/interfaces/ientity.py +331 -0
  37. idmtools/core/interfaces/iitem.py +206 -0
  38. idmtools/core/interfaces/imetadata_operations.py +89 -0
  39. idmtools/core/interfaces/inamed_entity.py +17 -0
  40. idmtools/core/interfaces/irunnable_entity.py +159 -0
  41. idmtools/core/logging.py +387 -0
  42. idmtools/core/platform_factory.py +316 -0
  43. idmtools/core/system_information.py +104 -0
  44. idmtools/core/task_factory.py +145 -0
  45. idmtools/entities/__init__.py +10 -0
  46. idmtools/entities/command_line.py +229 -0
  47. idmtools/entities/command_task.py +155 -0
  48. idmtools/entities/experiment.py +787 -0
  49. idmtools/entities/generic_workitem.py +43 -0
  50. idmtools/entities/ianalyzer.py +163 -0
  51. idmtools/entities/iplatform.py +1106 -0
  52. idmtools/entities/iplatform_default.py +39 -0
  53. idmtools/entities/iplatform_ops/__init__.py +5 -0
  54. idmtools/entities/iplatform_ops/iplatform_asset_collection_operations.py +148 -0
  55. idmtools/entities/iplatform_ops/iplatform_experiment_operations.py +415 -0
  56. idmtools/entities/iplatform_ops/iplatform_simulation_operations.py +315 -0
  57. idmtools/entities/iplatform_ops/iplatform_suite_operations.py +322 -0
  58. idmtools/entities/iplatform_ops/iplatform_workflowitem_operations.py +301 -0
  59. idmtools/entities/iplatform_ops/utils.py +185 -0
  60. idmtools/entities/itask.py +316 -0
  61. idmtools/entities/iworkflow_item.py +167 -0
  62. idmtools/entities/platform_requirements.py +20 -0
  63. idmtools/entities/relation_type.py +14 -0
  64. idmtools/entities/simulation.py +255 -0
  65. idmtools/entities/suite.py +188 -0
  66. idmtools/entities/task_proxy.py +37 -0
  67. idmtools/entities/templated_simulation.py +325 -0
  68. idmtools/frozen/frozen_dict.py +71 -0
  69. idmtools/frozen/frozen_list.py +66 -0
  70. idmtools/frozen/frozen_set.py +86 -0
  71. idmtools/frozen/frozen_tuple.py +18 -0
  72. idmtools/frozen/frozen_utils.py +179 -0
  73. idmtools/frozen/ifrozen.py +66 -0
  74. idmtools/plugins/__init__.py +5 -0
  75. idmtools/plugins/git_commit.py +117 -0
  76. idmtools/registry/__init__.py +4 -0
  77. idmtools/registry/experiment_specification.py +105 -0
  78. idmtools/registry/functions.py +28 -0
  79. idmtools/registry/hook_specs.py +132 -0
  80. idmtools/registry/master_plugin_registry.py +51 -0
  81. idmtools/registry/platform_specification.py +138 -0
  82. idmtools/registry/plugin_specification.py +129 -0
  83. idmtools/registry/task_specification.py +104 -0
  84. idmtools/registry/utils.py +119 -0
  85. idmtools/services/__init__.py +5 -0
  86. idmtools/services/ipersistance_service.py +135 -0
  87. idmtools/services/platforms.py +13 -0
  88. idmtools/utils/__init__.py +5 -0
  89. idmtools/utils/caller.py +24 -0
  90. idmtools/utils/collections.py +246 -0
  91. idmtools/utils/command_line.py +45 -0
  92. idmtools/utils/decorators.py +300 -0
  93. idmtools/utils/display/__init__.py +22 -0
  94. idmtools/utils/display/displays.py +181 -0
  95. idmtools/utils/display/settings.py +25 -0
  96. idmtools/utils/dropbox_location.py +30 -0
  97. idmtools/utils/entities.py +127 -0
  98. idmtools/utils/file.py +72 -0
  99. idmtools/utils/file_parser.py +151 -0
  100. idmtools/utils/filter_simulations.py +182 -0
  101. idmtools/utils/filters/__init__.py +5 -0
  102. idmtools/utils/filters/asset_filters.py +88 -0
  103. idmtools/utils/general.py +286 -0
  104. idmtools/utils/gitrepo.py +336 -0
  105. idmtools/utils/hashing.py +239 -0
  106. idmtools/utils/info.py +124 -0
  107. idmtools/utils/json.py +82 -0
  108. idmtools/utils/language.py +107 -0
  109. idmtools/utils/local_os.py +40 -0
  110. idmtools/utils/time.py +22 -0
  111. idmtools-0.0.2.dist-info/METADATA +120 -0
  112. idmtools-0.0.2.dist-info/RECORD +116 -0
  113. idmtools-0.0.2.dist-info/entry_points.txt +9 -0
  114. idmtools-0.0.2.dist-info/licenses/LICENSE.TXT +3 -0
  115. idmtools-0.0.0.dev0.dist-info/METADATA +0 -41
  116. idmtools-0.0.0.dev0.dist-info/RECORD +0 -5
  117. {idmtools-0.0.0.dev0.dist-info → idmtools-0.0.2.dist-info}/WHEEL +0 -0
  118. {idmtools-0.0.0.dev0.dist-info → idmtools-0.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1106 @@
1
+ """
2
+ Here we define the Platform interface.
3
+
4
+ IPlatform is responsible for all the communication to our platform and translation from idmtools objects to platform specific objects and vice versa.
5
+
6
+ Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
7
+ """
8
+ import os
9
+ import copy
10
+ from abc import ABCMeta
11
+ from dataclasses import dataclass
12
+ from dataclasses import fields, field
13
+ from functools import partial
14
+ from os import PathLike
15
+ import pandas as pd
16
+ from pathlib import PureWindowsPath, PurePath
17
+ from itertools import groupby
18
+ from logging import getLogger, DEBUG
19
+ from typing import Dict, List, NoReturn, Type, TypeVar, Any, Union, Tuple, Set, Iterator, Callable, Optional
20
+ from idmtools.core.context import set_current_platform
21
+ from idmtools import IdmConfigParser
22
+ from idmtools.core import CacheEnabled, UnknownItemException, EntityContainer, UnsupportedPlatformType
23
+ from idmtools.core.enums import ItemType, EntityStatus
24
+ from idmtools.core.interfaces.ientity import IEntity
25
+ from idmtools.core.interfaces.iitem import IItem
26
+ from idmtools.core.interfaces.irunnable_entity import IRunnableEntity
27
+ from idmtools.entities.experiment import Experiment
28
+ from idmtools.core.id_file import read_id_file
29
+ from idmtools.entities.iplatform_default import IPlatformDefault
30
+ from idmtools.entities.iplatform_ops.iplatform_asset_collection_operations import IPlatformAssetCollectionOperations
31
+ from idmtools.entities.iplatform_ops.iplatform_experiment_operations import IPlatformExperimentOperations
32
+ from idmtools.entities.iplatform_ops.iplatform_simulation_operations import IPlatformSimulationOperations
33
+ from idmtools.entities.iplatform_ops.iplatform_suite_operations import IPlatformSuiteOperations
34
+ from idmtools.entities.iplatform_ops.iplatform_workflowitem_operations import IPlatformWorkflowItemOperations
35
+ from idmtools.entities.itask import ITask
36
+ from idmtools.entities.iworkflow_item import IWorkflowItem
37
+ from idmtools.entities.platform_requirements import PlatformRequirements
38
+ from idmtools.entities.relation_type import RelationType
39
+ from idmtools.entities.simulation import Simulation
40
+ from idmtools.entities.suite import Suite
41
+ from idmtools.assets.asset_collection import AssetCollection
42
+ from idmtools.services.platforms import PlatformPersistService
43
+ from idmtools.utils.caller import get_caller
44
+ from idmtools.utils.entities import validate_user_inputs_against_dataclass
45
+
46
+ logger = getLogger(__name__)
47
+ user_logger = getLogger('user')
48
+
49
+ CALLER_LIST = ['_create_platform_from_block', # create platform through Platform Factory
50
+ 'fetch', # create platform through un-pickle
51
+ 'get', # create platform through platform spec' get method
52
+ '__newobj__', # create platform through copy.deepcopy
53
+ '_main', # create platform through analyzer manager
54
+ '<module>'] # create platform through specific module
55
+
56
+ # Maps an object type to a platform interface object. We use strings to use getattr. This also let's us also reduce
57
+ # all the if else crud
58
+ ITEM_TYPE_TO_OBJECT_INTERFACE = {
59
+ ItemType.EXPERIMENT: '_experiments',
60
+ ItemType.SIMULATION: '_simulations',
61
+ ItemType.SUITE: '_suites',
62
+ ItemType.WORKFLOW_ITEM: '_workflow_items',
63
+ ItemType.ASSETCOLLECTION: '_assets'
64
+ }
65
+ STANDARD_TYPE_TO_INTERFACE = {
66
+ Experiment: ItemType.EXPERIMENT,
67
+ Simulation: ItemType.SIMULATION,
68
+ IWorkflowItem: ItemType.WORKFLOW_ITEM,
69
+ Suite: ItemType.SUITE
70
+ }
71
+
72
+
73
+ @dataclass(repr=False)
74
+ class IPlatform(IItem, CacheEnabled, metaclass=ABCMeta):
75
+ """
76
+ Interface defining a platform.
77
+
78
+ A platform needs to implement basic operation such as:
79
+
80
+ - Creating experiment
81
+ - Creating simulation
82
+ - Commissioning
83
+ - File handling
84
+ """
85
+ #: Maps the platform types to idmtools types
86
+ platform_type_map: Dict[Type, ItemType] = field(default=None, repr=False, init=False)
87
+ _object_cache_expiration: 'int' = field(default=60, repr=False, init=False)
88
+
89
+ supported_types: Set[ItemType] = field(default_factory=lambda: set(), repr=False, init=False)
90
+ _platform_supports: List[PlatformRequirements] = field(default_factory=list, repr=False, init=False)
91
+ _platform_defaults: List[IPlatformDefault] = field(default_factory=list)
92
+
93
+ _experiments: IPlatformExperimentOperations = field(default=None, repr=False, init=False, compare=False)
94
+ _simulations: IPlatformSimulationOperations = field(default=None, repr=False, init=False, compare=False)
95
+ _suites: IPlatformSuiteOperations = field(default=None, repr=False, init=False, compare=False)
96
+ _workflow_items: IPlatformWorkflowItemOperations = field(default=None, repr=False, init=False, compare=False)
97
+ _assets: IPlatformAssetCollectionOperations = field(default=None, repr=False, init=False, compare=False)
98
+ #: Controls what platform should do we re-running experiments by default
99
+ _regather_assets_on_modify: bool = field(default=False, repr=False, init=False, compare=False)
100
+ # store the config block used to create platform
101
+ _config_block: str = field(default=None)
102
+ #: Defines the path to common assets
103
+ _common_asset_path: str = field(default="Assets", repr=True, init=False, compare=False)
104
+
105
+ refresh_interval: int = field(default=5, repr=False, init=True, compare=False, metadata=dict(help="Refresh Interval during wait."))
106
+
107
+ def __new__(cls, *args, **kwargs):
108
+ """
109
+ Create a new object.
110
+
111
+ Args:
112
+ args: User inputs.
113
+ kwargs: User inputs.
114
+
115
+ Returns:
116
+ The object created.
117
+
118
+ Raises:
119
+ ValueError - If the platform was not created as expected.
120
+ """
121
+ # Check the caller
122
+ caller = get_caller()
123
+
124
+ # Action based on the caller
125
+ if caller not in CALLER_LIST:
126
+ logger.warning(
127
+ "Please use Factory to create Platform! For example: \n platform = Platform('COMPS', **kwargs)")
128
+ return super().__new__(cls)
129
+
130
+ def __post_init__(self) -> NoReturn:
131
+ """
132
+ Work to be done after object creation.
133
+
134
+ Returns:
135
+ None
136
+ """
137
+ # build item type map and determined supported features
138
+ self.platform_type_map = dict()
139
+ for item_type, interface in ITEM_TYPE_TO_OBJECT_INTERFACE.items():
140
+ if getattr(self, interface) is not None and getattr(self, interface).platform_type is not None:
141
+ self.platform_type_map[getattr(self, interface).platform_type] = item_type
142
+
143
+ self.validate_inputs_types()
144
+ set_current_platform(self)
145
+ # Initialize the cache
146
+ self.initialize_cache()
147
+
148
+ # Save itself
149
+ PlatformPersistService.save(self)
150
+
151
+ def validate_inputs_types(self) -> NoReturn:
152
+ """
153
+ Validate user inputs and case attributes with the correct data types.
154
+
155
+ Returns:
156
+ None
157
+ """
158
+ # retrieve field values, default values and types
159
+ fds = fields(self)
160
+ field_value = {f.name: getattr(self, f.name) for f in fds}
161
+ field_type = {f.name: f.type for f in fds}
162
+
163
+ # Make sure the user values have the requested type
164
+ fs_kwargs = validate_user_inputs_against_dataclass(field_type, field_value)
165
+
166
+ # Update attr with validated data types
167
+ for fn in fs_kwargs:
168
+ setattr(self, fn, field_value[fn])
169
+
170
+ def _get_platform_item(self, item_id: str, item_type: ItemType, **kwargs) -> Any:
171
+ """
172
+ Get an item by its ID.
173
+
174
+ The implementing classes must know how to distinguish items of different levels (e.g. simulation, experiment, suite).
175
+
176
+ Args:
177
+ item_id: The ID of the item to retrieve.
178
+ item_type: The type of object to retrieve.
179
+
180
+ Returns:
181
+ The specified item found on the platform or None.
182
+
183
+ Raises:
184
+ ValueError: If the item type is not supported
185
+ """
186
+ if item_type not in self.platform_type_map.values():
187
+ raise ValueError(f"Unsupported Item Type. {self.__class__.__name__} only supports "
188
+ f"{self.platform_type_map.values()}")
189
+ interface = ITEM_TYPE_TO_OBJECT_INTERFACE[item_type]
190
+ return getattr(self, interface).get(item_id, **kwargs)
191
+
192
+ def get_item(self, item_id: str, item_type: ItemType = None, force: bool = False, raw: bool = False,
193
+ **kwargs) -> Union[Experiment, Suite, Simulation, IWorkflowItem, AssetCollection, None]:
194
+ """
195
+ Retrieve an object from the platform.
196
+
197
+ This function is cached; force allows you to force the refresh of the cache.
198
+ If no **object_type** is passed, the function will try all the types (experiment, suite, simulation).
199
+
200
+ Args:
201
+ item_id: The ID of the object to retrieve.
202
+ item_type: The type of the object to be retrieved.
203
+ force: If True, force the object fetching from the platform.
204
+ raw: Return either an |IT_s| object or a platform object.
205
+
206
+ Returns:
207
+ The object found on the platform or None.
208
+
209
+ Raises:
210
+ ValueError: If the item type is not supported
211
+ UnknownItemException: If the item type is not found on platform
212
+ """
213
+ self.validate_type(item_type)
214
+
215
+ cache_key = self.get_cache_key(force, item_id, item_type, kwargs, raw, 'r' if raw else 'o')
216
+
217
+ # If force -> delete in the cache
218
+ if force:
219
+ if logger.isEnabledFor(DEBUG):
220
+ logger.debug(f"Removing {cache_key} from cache")
221
+ self.cache.delete(cache_key)
222
+
223
+ # If we cannot find the object in the cache -> retrieve depending on the type
224
+ if cache_key not in self.cache:
225
+ if logger.isEnabledFor(DEBUG):
226
+ logger.debug(f"Retrieve item {item_id} of type {item_type}")
227
+ ce = self._get_platform_item(item_id, item_type, **kwargs)
228
+
229
+ # Nothing was found on the platform
230
+ if not ce:
231
+ raise UnknownItemException(f"Object {item_type} {item_id} not found on the platform...")
232
+
233
+ # Create the object if we do not want it raw
234
+ if raw:
235
+ return_object = ce
236
+ else:
237
+ return_object = self._convert_platform_item_to_entity(ce, **kwargs)
238
+ if return_object._platform_object is None:
239
+ return_object._platform_object = ce
240
+ return_object.platform = self
241
+
242
+ # Persist
243
+ self.cache.set(cache_key, return_object, expire=self._object_cache_expiration)
244
+
245
+ else:
246
+ return_object = self.cache.get(cache_key)
247
+ return_object.platform = self
248
+
249
+ return return_object
250
+
251
+ def _get_children_for_platform_item(self, item: Any, raw: bool = True, **kwargs) -> List[Any]:
252
+ """
253
+ Returns the children for a platform object.
254
+
255
+ For example, A COMPS Experiment or Simulation.
256
+
257
+ The children can either be returns as native platform objects or as idmtools objects.
258
+
259
+ Args:
260
+ item: Item to fetch children for
261
+ raw: When true, return the native platform representation of the children, otherwise return the idmtools
262
+ implementation
263
+ **kwargs:
264
+
265
+ Returns:
266
+ Children of platform object
267
+ """
268
+ item_type, interface = self._get_operation_interface(item)
269
+ if item_type in [ItemType.EXPERIMENT, ItemType.SUITE]:
270
+ children = getattr(self, ITEM_TYPE_TO_OBJECT_INTERFACE[item_type]).get_children(item, **kwargs)
271
+ else:
272
+ return []
273
+ if not raw:
274
+ ret = []
275
+ for e in children:
276
+ n = self._convert_platform_item_to_entity(e, **kwargs)
277
+ if n._platform_object is None:
278
+ n._platform_object = e
279
+ ret.append(n)
280
+ return EntityContainer(ret)
281
+ else:
282
+ return children
283
+
284
+ def _get_operation_interface(self, item: Any) -> Tuple[ItemType, str]:
285
+ """
286
+ Get the base item type and the interface string for said item.
287
+
288
+ For example, on COMPSPlatform, if you passed a COMPSExperiment object, the function would return ItemType.Experiment, _experiments
289
+
290
+
291
+ Args:
292
+ item: Item to look up
293
+
294
+ Returns:
295
+ Tuple with the Base item type and the string path to the interface
296
+
297
+ Raises:
298
+ ValueError: If the item 's interface cannot be found
299
+ """
300
+ # check both base types and platform specs
301
+ for interface_type_mapping in [STANDARD_TYPE_TO_INTERFACE, self.platform_type_map]:
302
+ for interface, item_type in interface_type_mapping.items():
303
+ if isinstance(item, interface):
304
+ return item_type, ITEM_TYPE_TO_OBJECT_INTERFACE[item_type]
305
+ raise ValueError(f"{self.__class__.__name__} has no mapping for {item.__class__.__name__}")
306
+
307
+ def get_children(self, item_id: str, item_type: ItemType,
308
+ force: bool = False, raw: bool = False, item: Any = None, **kwargs) -> Any:
309
+ """
310
+ Retrieve the children of a given object.
311
+
312
+ Args:
313
+ item_id: The ID of the object for which we want the children.
314
+ force: If True, force the object fetching from the platform.
315
+ raw: Return either an |IT_s| object or a platform object.
316
+ item_type: Pass the type of the object for quicker retrieval.
317
+ item: optional platform or idm item to use instead of loading
318
+
319
+ Returns:
320
+ The children of the object or None.
321
+ """
322
+ cache_key = self.get_cache_key(force, item_id, item_type, kwargs, raw, 'c')
323
+
324
+ if force:
325
+ self.cache.delete(cache_key)
326
+
327
+ if cache_key not in self.cache:
328
+ ce = item or self.get_item(item_id, raw=raw, item_type=item_type)
329
+ ce.platform = self
330
+ kwargs['parent'] = ce
331
+ if raw:
332
+ children = self._get_children_for_platform_item(ce, raw=raw, **kwargs)
333
+ else:
334
+ children = self._get_children_for_platform_item(ce.get_platform_object(), raw=raw, **kwargs)
335
+ self.cache.set(cache_key, children, expire=self._object_cache_expiration)
336
+ return children
337
+
338
+ return self.cache.get(cache_key)
339
+
340
+ def get_children_by_object(self, parent: IEntity) -> List[IEntity]:
341
+ """
342
+ Returns a list of children for an entity.
343
+
344
+ Args:
345
+ parent: Parent object
346
+
347
+ Returns:
348
+ List of children
349
+ """
350
+ return self._get_children_for_platform_item(parent.get_platform_object(), raw=False)
351
+
352
+ def get_parent_by_object(self, child: IEntity) -> IEntity:
353
+ """
354
+ Parent of object.
355
+
356
+ Args:
357
+ child: Child object to find parent for
358
+
359
+ Returns:
360
+ Returns parent object
361
+ """
362
+ return self._get_parent_for_platform_item(child.get_platform_object(), raw=False)
363
+
364
+ def _get_parent_for_platform_item(self, platform_item: Any, raw: bool = True, **kwargs) -> Any:
365
+ """
366
+ Return the parent item for a given platform_item.
367
+
368
+ Args:
369
+ platform_item: Child item
370
+ raw: Return a platform item if True, an idmtools entity if false
371
+ **kwargs: Additional platform specific parameters
372
+
373
+ Returns:
374
+ Parent or None
375
+ """
376
+ item_type, interface = self._get_operation_interface(platform_item)
377
+ if item_type not in [ItemType.EXPERIMENT, ItemType.SIMULATION, ItemType.WORKFLOW_ITEM]:
378
+ raise ValueError("Currently only Experiments, Simulations and Work Items support parents")
379
+ obj = getattr(self, interface).get_parent(platform_item, **kwargs)
380
+ if obj is not None:
381
+ parent_item_type, parent_interface = self._get_operation_interface(obj)
382
+ if not raw:
383
+ return getattr(self, parent_interface).to_entity(obj)
384
+ return obj
385
+
386
+ def get_parent(self, item_id: str, item_type: ItemType = None, force: bool = False,
387
+ raw: bool = False, **kwargs):
388
+ """
389
+ Retrieve the parent of a given object.
390
+
391
+ Args:
392
+ item_id: The ID of the object for which we want the parent.
393
+ force: If True, force the object fetching from the platform.
394
+ raw: Return either an |IT_s| object or a platform object.
395
+ item_type: Pass the type of the object for quicker retrieval.
396
+
397
+ Returns:
398
+ The parent of the object or None.
399
+
400
+ """
401
+ self.validate_type(item_type)
402
+
403
+ # Create the cache key based on everything we pass to the function
404
+ cache_key = f'p_{item_id}' + ('r' if raw else 'o') + '_'.join(f"{k}_{v}" for k, v in kwargs.items())
405
+
406
+ if force:
407
+ self.cache.delete(cache_key)
408
+
409
+ if cache_key not in self.cache:
410
+ ce = self.get_item(item_id, raw=True, item_type=item_type)
411
+ parent = self._get_parent_for_platform_item(ce, raw=raw, **kwargs)
412
+ self.cache.set(cache_key, parent, expire=self._object_cache_expiration)
413
+ return parent
414
+
415
+ return self.cache.get(cache_key)
416
+
417
+ def get_cache_key(self, force, item_id, item_type, kwargs, raw, prefix='p'):
418
+ """
419
+ Get cache key for an item.
420
+
421
+ Args:
422
+ force: Should we force the load
423
+ item_id: Item id
424
+ item_type: Item type
425
+ kwargs: Kwargs
426
+ raw: Should we use raw storage?
427
+ prefix: Prefix for the item
428
+
429
+ Returns:
430
+ Cache Key
431
+ """
432
+ if not item_type or item_type not in self.supported_types:
433
+ raise Exception("The provided type is invalid or not supported by this platform...")
434
+ # Create the cache key based on everything we pass to the function
435
+ cache_key = f'{prefix}_{item_id}' + ('r' if raw else 'o') + '_'.join(f"{k}_{v}" for k, v in kwargs.items())
436
+ if force:
437
+ self.cache.delete(cache_key)
438
+ return cache_key
439
+
440
+ def create_items(self, items: Union[List[IEntity], IEntity], **kwargs) -> List[IEntity]:
441
+ """
442
+ Create items (simulations, experiments, or suites) on the platform.
443
+
444
+ The function will batch the items based on type and call the self._create_batch for creation.
445
+
446
+ Args:
447
+ items: The list of items to create.
448
+ kwargs: Extra arguments
449
+ Returns:
450
+ List of item IDs created.
451
+ """
452
+ if isinstance(items, IEntity):
453
+ items = [items]
454
+ if not isinstance(items, Iterator):
455
+ self._is_item_list_supported(items)
456
+
457
+ result = []
458
+ for key, group in groupby(items, lambda x: x.item_type):
459
+ result.extend(self._create_items_of_type(group, key, **kwargs))
460
+ return result
461
+
462
+ def _create_items_of_type(self, items: Iterator[IEntity], item_type: ItemType, **kwargs):
463
+ """
464
+ Creates items of specific type using batches.
465
+
466
+ Args:
467
+ items: Items to create
468
+ item_type: Item type to create
469
+
470
+ Returns:
471
+ Items created
472
+ """
473
+ interface = ITEM_TYPE_TO_OBJECT_INTERFACE[item_type]
474
+ ni = getattr(self, interface).batch_create(items, **kwargs)
475
+ return ni
476
+
477
+ def _is_item_list_supported(self, items: List[IEntity]):
478
+ """
479
+ Checks if all items in a list are supported by the platform.
480
+
481
+ Args:
482
+ items: Items to verify
483
+
484
+ Returns:
485
+ True if items supported, false otherwise
486
+
487
+ Raises:
488
+ ValueError: If the item type is not supported
489
+ """
490
+ for item in items:
491
+ if item.item_type not in self.platform_type_map.values():
492
+ raise ValueError(
493
+ f'Unable to create items of type: {item.item_type} for platform: {self.__class__.__name__}')
494
+
495
+ def run_items(self, items: Union[IEntity, List[IEntity]], **kwargs):
496
+ """
497
+ Run items on the platform.
498
+
499
+ Args:
500
+ items: Items to run
501
+
502
+ Returns:
503
+ None
504
+ """
505
+ if isinstance(items, IEntity):
506
+ items = [items]
507
+ self._is_item_list_supported(items)
508
+
509
+ for item in items:
510
+ item.platform = self
511
+ item._platform_directory = None
512
+ interface = ITEM_TYPE_TO_OBJECT_INTERFACE[item.item_type]
513
+ getattr(self, interface).run_item(item, **kwargs)
514
+
515
+ def __repr__(self):
516
+ """Platform as string."""
517
+ return f"<Platform {self.__class__.__name__} - id: {self.uid}>"
518
+
519
+ def _convert_platform_item_to_entity(self, platform_item: Any, **kwargs) -> IEntity:
520
+ """
521
+ Convert a Native Platform Object to an idmtools object.
522
+
523
+ Args:
524
+ platform_item: Item to convert
525
+ **kwargs: Optional items to be used in to_entity calls
526
+
527
+ Returns:
528
+ IDMTools representation of object
529
+ """
530
+ for src_type, dest_type in self.platform_type_map.items():
531
+ if isinstance(platform_item, src_type):
532
+ interface = ITEM_TYPE_TO_OBJECT_INTERFACE[dest_type]
533
+ return getattr(self, interface).to_entity(platform_item, **kwargs)
534
+ return platform_item
535
+
536
+ def validate_item_for_analysis(self, item: object, analyze_failed_items=False):
537
+ """
538
+ Check if item is valid for analysis.
539
+
540
+ Args:
541
+ item: Which item to flatten
542
+ analyze_failed_items: bool
543
+
544
+ Returns: bool
545
+
546
+ """
547
+ result = False
548
+ if item.succeeded:
549
+ result = True
550
+ else:
551
+ if analyze_failed_items and item.status == EntityStatus.FAILED:
552
+ result = True
553
+
554
+ return result
555
+
556
+ def flatten_item(self, item: object, **kwargs) -> List[object]:
557
+ """
558
+ Flatten an item: resolve the children until getting to the leaves.
559
+
560
+ For example, for an experiment, will return all the simulations.
561
+ For a suite, will return all the simulations contained in the suites experiments.
562
+
563
+ Args:
564
+ item: Which item to flatten
565
+ kwargs: extra parameters
566
+
567
+ Returns:
568
+ List of leaves
569
+
570
+ """
571
+ children = self.get_children(item.uid, item.item_type, force=True)
572
+ if children is None or (isinstance(children, list) and len(children) == 0):
573
+ items = [item]
574
+ else:
575
+ items = list()
576
+ for child in children:
577
+ items += self.flatten_item(item=child)
578
+ return items
579
+
580
+ def refresh_status(self, item: IEntity) -> NoReturn:
581
+ """
582
+ Populate the platform item and specified item with its status.
583
+
584
+ Args:
585
+ item: The item to check status for.
586
+ """
587
+ self.validate_type(item)
588
+ interface = ITEM_TYPE_TO_OBJECT_INTERFACE[item.item_type]
589
+ if item.platform is None:
590
+ item.platform = self
591
+ getattr(self, interface).refresh_status(item)
592
+
593
+ def get_files(self, item: IEntity, files: Union[Set[str], List[str]], output: str = None, **kwargs) -> \
594
+ Union[Dict[str, Dict[str, bytearray]], Dict[str, bytearray]]:
595
+ """
596
+ Get files for a platform entity.
597
+
598
+ Args:
599
+ item: Item to fetch files for
600
+ files: List of file names to get
601
+ output: save files to
602
+ kwargs: Platform arguments
603
+
604
+ Returns:
605
+ For simulations, this returns a dictionary with filename as key and values being binary data from file or a
606
+ dict.
607
+
608
+ For experiments, this returns a dictionary with key as sim id and then the values as a dict of the
609
+ simulations described above
610
+ """
611
+ self.validate_type(item)
612
+ interface = ITEM_TYPE_TO_OBJECT_INTERFACE[item.item_type]
613
+ ret = getattr(self, interface).get_assets(item, files, **kwargs)
614
+
615
+ if output:
616
+ if item.item_type not in (ItemType.SIMULATION, ItemType.WORKFLOW_ITEM, ItemType.ASSETCOLLECTION):
617
+ user_logger.info("Currently 'output' only supports Simulation, WorkItem and AssetCollection!")
618
+ else:
619
+ for ofi, ofc in ret.items():
620
+ file_path = os.path.join(output, str(item.uid), ofi)
621
+ parent_path = os.path.dirname(file_path)
622
+ if not os.path.exists(parent_path):
623
+ os.makedirs(parent_path)
624
+
625
+ with open(file_path, 'wb') as outfile:
626
+ outfile.write(ofc)
627
+
628
+ return ret
629
+
630
+ def get_files_by_id(self, item_id: str, item_type: ItemType, files: Union[Set[str], List[str]],
631
+ output: str = None) -> \
632
+ Union[Dict[str, Dict[str, bytearray]], Dict[str, bytearray]]:
633
+ """
634
+ Get files by item id (str).
635
+
636
+ Args:
637
+ item_id: COMPS Item, say, Simulation Id or WorkItem Id
638
+ item_type: Item Type
639
+ files: List of files to retrieve
640
+ output: save files to
641
+
642
+ Returns: dict with key/value: file_name/file_content
643
+ """
644
+ if item_id is None or item_id == "":
645
+ raise ValueError("item_id cannot be None or empty")
646
+ idm_item = self.get_item(item_id, item_type, raw=True)
647
+ return self.get_files(idm_item, files, output)
648
+
649
+ def are_requirements_met(self, requirements: Union[PlatformRequirements, Set[PlatformRequirements]]) -> bool:
650
+ """
651
+ Does the platform support the list of requirements.
652
+
653
+ Args:
654
+ requirements: Requirements should be a list of PlatformRequirements or a single PlatformRequirements
655
+
656
+ Returns:
657
+ True if all the requirements are supported
658
+ """
659
+ if isinstance(requirements, PlatformRequirements):
660
+ requirements = [requirements]
661
+ return all([x in self._platform_supports for x in requirements])
662
+
663
+ def is_task_supported(self, task: ITask) -> bool:
664
+ """
665
+ Is a task supported on this platform.
666
+
667
+ This depends on the task properly setting its requirements. See :py:attr:`idmtools.entities.itask.ITask.platform_requirements` and
668
+ :py:class:`idmtools.entities.platform_requirements.PlatformRequirements`
669
+
670
+ Args:
671
+ task: Task to check support of
672
+
673
+ Returns:
674
+ True if the task is supported, False otherwise.
675
+ """
676
+ return self.are_requirements_met(task.platform_requirements)
677
+
678
+ def __wait_till_callback(
679
+ self, item: Union[Experiment, IWorkflowItem, Suite],
680
+ callback: Union[partial, Callable[[Union[Experiment, IWorkflowItem, Suite]], bool]],
681
+ timeout: int = 60 * 60 * 24,
682
+ refresh_interval: int = 5
683
+ ):
684
+ """
685
+ Runs a loop until a timeout is met where the item's status is refreshed. A callback is then called with the items as the arguments and if the returns is true, we stop waiting.
686
+
687
+ Args:
688
+ item: Item to monitor
689
+ callback: Callback to determine if item is done. It should return true is item is complete
690
+ timeout: Timeout for waiting. Defaults to 24 hours
691
+ refresh_interval: Refresh the status how often
692
+
693
+ Returns:
694
+ None
695
+
696
+ Raises:
697
+ TimeoutError: If a timeout occurs
698
+
699
+ See Also:
700
+ :meth:`idmtools.entities.iplatform.IPlatform.wait_till_done_progress`
701
+ :meth:`idmtools.entities.iplatform.IPlatform.__wait_until_done_progress_callback`
702
+ :meth:`idmtools.entities.iplatform.IPlatform.wait_till_done`
703
+ """
704
+ import time
705
+ start_time = time.time()
706
+ while time.time() - start_time < timeout:
707
+ if logger.isEnabledFor(DEBUG):
708
+ logger.debug("Refreshing simulation status")
709
+ self.refresh_status(item)
710
+ if callback(item):
711
+ return
712
+ time.sleep(refresh_interval)
713
+ raise TimeoutError(f"Timeout of {timeout} seconds exceeded")
714
+
715
+ def wait_till_done(self, item: IRunnableEntity, timeout: int = 60 * 60 * 24,
716
+ refresh_interval: int = 5, progress: bool = True):
717
+ """
718
+ Wait for the experiment to be done.
719
+
720
+ Args:
721
+ item: Experiment/Workitem to wait on
722
+ refresh_interval: How long to wait between polling.
723
+ timeout: How long to wait before failing.
724
+ progress: Should we display progress
725
+
726
+ See Also:
727
+ :meth:`idmtools.entities.iplatform.IPlatform.wait_till_done_progress`
728
+ :meth:`idmtools.entities.iplatform.IPlatform.__wait_until_done_progress_callback`
729
+ :meth:`idmtools.entities.iplatform.IPlatform.__wait_till_callback`
730
+ """
731
+ if progress:
732
+ self.wait_till_done_progress(item, timeout, refresh_interval)
733
+ else:
734
+ self.__wait_till_callback(item, lambda e: e.done, timeout, refresh_interval)
735
+
736
+ @staticmethod
737
+ def __wait_until_done_progress_callback(item: Union[Experiment, IWorkflowItem], progress_bar: 'tqdm', # noqa: F821
738
+ child_attribute: str = 'simulations',
739
+ done_states: List[EntityStatus] = None,
740
+ failed_warning: Dict[str, bool] = False) -> bool:
741
+ """
742
+ A callback for progress bar(when an item has children) and checking if an item has completed execution.
743
+
744
+ This is mainly meant for aggregate types where the status is from the children.
745
+
746
+ Args:
747
+ item: Item to monitor
748
+ progress_bar:
749
+ child_attribute: What is the name of the child attribute. For examples, if item was an Experiment, the
750
+ child_attribute would be 'simulations'
751
+ done_states: What states are considered done
752
+ failed_warning: Used to track if we have warned user of failure. We use dict to pass by refernce since we cannot do that with a bool
753
+
754
+ Returns:
755
+ True is item has completed execution
756
+
757
+ See Also:
758
+ :meth:`idmtools.entities.iplatform.IPlatform.wait_till_done_progress`
759
+ :meth:`idmtools.entities.iplatform.IPlatform.wait_till_done`
760
+ :meth:`idmtools.entities.iplatform.IPlatform.__wait_till_callback`
761
+ """
762
+ # ensure we have done states. Default to failed or SUCCEEDED
763
+ if done_states is None:
764
+ done_states = [EntityStatus.FAILED, EntityStatus.SUCCEEDED]
765
+ # if we do not have a progress bar, return items state
766
+ if child_attribute is None:
767
+ if isinstance(item, IWorkflowItem):
768
+ if item.status in done_states:
769
+ if progress_bar:
770
+ progress_bar.update(1)
771
+ progress_bar.close()
772
+ return True
773
+ return False
774
+ else:
775
+ return item.done
776
+
777
+ # if we do have a progress bar, update it
778
+ done = 0
779
+ # iterate over the children
780
+ for child in getattr(item, child_attribute):
781
+ # if the item is an experiment, use the status
782
+ if isinstance(item, Experiment) and child.status in done_states:
783
+ done += 1
784
+ # otherwise use the done attribute
785
+ elif isinstance(item, Suite) and child.done:
786
+ done += 1
787
+ # check if we need to update the progress bar
788
+ if hasattr(progress_bar, 'last_print_n') and done > progress_bar.last_print_n:
789
+ progress_bar.update(done - progress_bar.last_print_n)
790
+ # Alert user to failing simulations so they can stop execution if wanted
791
+ if isinstance(item, Experiment) and item.any_failed and not failed_warning['failed_warning']:
792
+ user_logger.warning(f"The Experiment {item.uid} has failed simulations. Check Experiment in platform")
793
+ failed_warning['failed_warning'] = True
794
+ return item.done
795
+
796
+ def wait_till_done_progress(self, item: IRunnableEntity, timeout: int = 60 * 60 * 24, refresh_interval: int = 5,
797
+ wait_progress_desc: str = None):
798
+ """
799
+ Wait on an item to complete with progress bar.
800
+
801
+ Args:
802
+ item: Item to monitor
803
+ timeout: Timeout on waiting
804
+ refresh_interval: How often to refresh
805
+ wait_progress_desc: Wait Progress Description
806
+
807
+ Returns:
808
+ None
809
+
810
+ See Also:
811
+ :meth:`idmtools.entities.iplatform.IPlatform.__wait_until_done_progress_callback`
812
+ :meth:`idmtools.entities.iplatform.IPlatform.wait_till_done`
813
+ :meth:`idmtools.entities.iplatform.IPlatform.__wait_till_callback`
814
+ """
815
+ # set prog to list
816
+ prog = []
817
+ # check that the user has not disable progress bars
818
+ child_attribute = None
819
+ if not IdmConfigParser.is_progress_bar_disabled():
820
+ from tqdm import tqdm
821
+ if isinstance(item, Experiment):
822
+ prog = tqdm([], total=len(item.simulations),
823
+ desc=wait_progress_desc if wait_progress_desc else f"Waiting on Experiment {item.name} to Finish running",
824
+ unit="simulation")
825
+ child_attribute = 'simulations'
826
+ elif isinstance(item, Suite):
827
+ prog = tqdm([], total=len(item.experiments),
828
+ desc=wait_progress_desc if wait_progress_desc else f"Waiting on Suite {item.name} to Finish running",
829
+ unit="experiment")
830
+ child_attribute = 'experiments'
831
+ elif isinstance(item, IWorkflowItem):
832
+ prog = tqdm([], total=1,
833
+ desc=wait_progress_desc if wait_progress_desc else f"Waiting on WorkItem {item.name}",
834
+ unit="workitem")
835
+ else:
836
+ child_attribute = None
837
+
838
+ failed_warning = dict(failed_warning=False)
839
+ self.__wait_till_callback(
840
+ item,
841
+ partial(self.__wait_until_done_progress_callback, progress_bar=prog, child_attribute=child_attribute,
842
+ failed_warning=failed_warning),
843
+ timeout,
844
+ refresh_interval
845
+ )
846
+
847
+ def get_related_items(self, item: IWorkflowItem, relation_type: RelationType) -> Dict[str, Dict[str, str]]:
848
+ """
849
+ Retrieve all related objects.
850
+
851
+ Args:
852
+ item: SSMTWorkItem
853
+ relation_type: Depends or Create
854
+
855
+ Returns: dict with key the object type
856
+ """
857
+ self.validate_type(item, ItemType.WORKFLOW_ITEM)
858
+ interface = ITEM_TYPE_TO_OBJECT_INTERFACE[item.item_type]
859
+ return getattr(self, interface).get_related_items(item, relation_type)
860
+
861
+ def validate_type(self, item: Union[IEntity, ItemType], target: ItemType = None) -> NoReturn:
862
+ """
863
+ Validate if the item is supported by the platform.
864
+
865
+ Args:
866
+ item: Item to validate
867
+ target: Target type to validate against
868
+
869
+ Returns:
870
+ No return
871
+ """
872
+ valid = True
873
+ _type = item if isinstance(item, (str, ItemType)) else item.item_type
874
+ if target is not None and _type != target:
875
+ valid = False
876
+ elif _type not in self.platform_type_map.values():
877
+ valid = False
878
+
879
+ if not valid:
880
+ raise UnsupportedPlatformType(
881
+ f"The provided type {_type} is invalid or not supported by platform {self.__class__.__name__}. It only supports ItemType: {', '.join([value.name for value in self.platform_type_map.values()])}")
882
+
883
+ def __enter__(self):
884
+ """
885
+ Enable our platform to work on contexts.
886
+
887
+ Returns:
888
+ Platform
889
+ """
890
+ set_current_platform(self)
891
+ return self
892
+
893
+ def __exit__(self, exc_type, exc_val, exc_tb):
894
+ """
895
+ Enable our platform to work on contexts.
896
+
897
+ Args:
898
+ exc_type: Type of exception type
899
+ exc_val:Value
900
+ exc_tb: Traceback
901
+
902
+ Returns:
903
+ None
904
+ """
905
+ from idmtools.core.context import remove_current_platform
906
+ remove_current_platform()
907
+
908
+ def is_regather_assets_on_modify(self) -> bool:
909
+ """
910
+ Return default behaviour for platform when rerunning experiment and gathering assets.
911
+
912
+ Returns:
913
+ True or false
914
+ """
915
+ return self._regather_assets_on_modify
916
+
917
+ def is_windows_platform(self, item: IEntity = None) -> bool:
918
+ """
919
+ Returns is the target platform is a windows system.
920
+ """
921
+ return self.are_requirements_met(PlatformRequirements.WINDOWS)
922
+
923
+ @property
924
+ def common_asset_path(self):
925
+ """
926
+ Return the path to common assets stored on the platform.
927
+
928
+ Returns:
929
+ Common Asset Path
930
+ """
931
+ return self._common_asset_path
932
+
933
+ @common_asset_path.setter
934
+ def common_asset_path(self, value):
935
+ """
936
+ Set path to common assets stored on the platform. This path should be in defined in relation to individual items(simulations, workflow items).
937
+
938
+ For example, on COMPS, we would set "Assets"
939
+
940
+ Args:
941
+ value: Path to use for common path
942
+
943
+ Returns:
944
+ None
945
+ """
946
+ if not isinstance(value, property):
947
+ logger.warning("Cannot set common asset path")
948
+
949
+ def join_path(self, *args) -> str:
950
+ """
951
+ Join path using platform rules.
952
+
953
+ Args:
954
+ *args:List of paths to join
955
+
956
+ Returns:
957
+ Joined path as string
958
+ """
959
+ if len(args) < 2:
960
+ raise ValueError("at least two items required to join")
961
+ if self.is_windows_platform():
962
+ return str(PureWindowsPath(*args))
963
+ else:
964
+ return str(PurePath(*args))
965
+
966
+ def id_from_file(self, filename: str):
967
+ """
968
+ Load just the id portion of an id file.
969
+
970
+ Args:
971
+ filename: Filename
972
+
973
+ Returns:
974
+ Item id laoded from file
975
+ """
976
+ item_id, item_type, platform_block, extra_args = read_id_file(filename)
977
+ return item_id
978
+
979
+ def get_item_from_id_file(self, id_filename: Union[PathLike, str], item_type: Optional[ItemType] = None) -> IEntity:
980
+ """
981
+ Load an item from an id file. This ignores the platform in the file.
982
+
983
+ Args:
984
+ id_filename: Filename to load
985
+ item_type: Optional item type
986
+
987
+ Returns:
988
+ Item from id file.
989
+ """
990
+ item_id, file_item_type, platform_block, extra_args = read_id_file(id_filename)
991
+ return self.get_item(item_id, item_type if item_type else ItemType[file_item_type.upper()])
992
+
993
+ def get_defaults_by_type(self, default_type: Type) -> List[IPlatformDefault]:
994
+ """
995
+ Returns any platform defaults for specific types.
996
+ Args:
997
+ default_type: Default type
998
+
999
+ Returns:
1000
+ List of default of that type
1001
+ """
1002
+ return [x for x in self._platform_defaults if isinstance(x, default_type)]
1003
+
1004
+ def create_sim_directory_map(self, item_id: str, item_type: ItemType) -> Dict:
1005
+ """
1006
+ Build simulation working directory mapping.
1007
+ Args:
1008
+ item_id: Entity id
1009
+ item_type: ItemType
1010
+ Returns:
1011
+ Dict of simulation id as key and working dir as value
1012
+ """
1013
+ interface = ITEM_TYPE_TO_OBJECT_INTERFACE[item_type]
1014
+ return getattr(self, interface).create_sim_directory_map(item_id)
1015
+
1016
+ def create_sim_directory_df(self, exp_id: str, include_tags: bool = True) -> pd.DataFrame:
1017
+ """
1018
+ Build simulation working directory mapping.
1019
+ Args:
1020
+ exp_id: experiment id
1021
+ include_tags: True/False
1022
+ Returns:
1023
+ DataFrame
1024
+ """
1025
+ tag_df = None
1026
+ if include_tags:
1027
+ tags_list = []
1028
+ sims = self.get_children(exp_id, ItemType.EXPERIMENT)
1029
+ for sim in sims:
1030
+ tags = copy.deepcopy(sim.tags)
1031
+ tags["simid"] = sim.id
1032
+ tags_list.append(tags)
1033
+ tag_df = pd.DataFrame(tags_list)
1034
+
1035
+ dir_map = self.create_sim_directory_map(exp_id, ItemType.EXPERIMENT)
1036
+
1037
+ dir_list = [dict(simid=sim_id, outpath=str(path)) for sim_id, path in dir_map.items()]
1038
+ dir_df = pd.DataFrame(dir_list)
1039
+
1040
+ if tag_df is not None and len(tag_df) > 0:
1041
+ result_df = pd.merge(left=tag_df, right=dir_df, on='simid')
1042
+ else:
1043
+ result_df = dir_df
1044
+
1045
+ return result_df
1046
+
1047
+ def save_sim_directory_df_to_csv(self, exp_id: str, include_tags: bool = True,
1048
+ output: str = os.getcwd(), save_header=False, file_name: str = None) -> None:
1049
+ """
1050
+ Save simulation directory df to csv file.
1051
+ Args:
1052
+ exp_id: experiment id
1053
+ include_tags: True/False
1054
+ output: output directory
1055
+ save_header: True/False
1056
+ file_name: user csv file name
1057
+ Returns:
1058
+ None
1059
+ """
1060
+ df = self.create_sim_directory_df(exp_id, include_tags=include_tags)
1061
+ try:
1062
+ os.mkdir(output)
1063
+ except OSError:
1064
+ pass
1065
+
1066
+ if file_name is None:
1067
+ file_name = f'{exp_id}.csv'
1068
+ df.to_csv(os.path.join(output, file_name), header=save_header, index=False)
1069
+
1070
+ def filter_simulations_by_tags(self, item_id: str, item_type: ItemType, tags: Dict = None, status=None,
1071
+ entity_type=False, skip_sims=None, max_simulations=None, **kwargs):
1072
+ """
1073
+ Filter simulations associated with a given Experiment or Suite using tag-based conditions.
1074
+
1075
+ This method is a platform-level convenience wrapper that delegates filtering logic to the
1076
+ `get_simulations_by_tags` method on the retrieved item. It supports:
1077
+ - Exact tag value matching
1078
+ - Callable filters for flexible conditions (e.g., lambda expressions)
1079
+ - Optionally limiting the number of returned simulations
1080
+ - Returning either simulation entities or just their IDs
1081
+
1082
+ Args:
1083
+ item_id (str): The unique ID of the Experiment or Suite.
1084
+ item_type (ItemType): The type of the item (ItemType.EXPERIMENT or ItemType.SUITE).
1085
+ tags (Dict, optional): Dictionary of tag filters to apply. Values can be:
1086
+ - Exact values (e.g., {"Coverage": 0.8})
1087
+ - Callable functions (e.g., {"Run_Number": lambda v: 0 <= v <= 10})
1088
+ - Ellipsis (...) or None to match presence of key only.
1089
+ status (EntityStatus, optional): Filter by status. If provided, only simulations with the specified status will be returned.
1090
+ entity_type (bool, optional): If True, return full simulation entities; otherwise, return simulation IDs.
1091
+ skip_sims (list, optional): A list of simulation IDs to exclude from the results.
1092
+ max_simulations (int, optional): Maximum number of simulations to return.
1093
+ **kwargs: Additional keyword arguments passed to the item's `get_simulations_by_tags` method.
1094
+
1095
+ Returns:
1096
+ Union[List[str], List[Simulation], Dict[str, List[Simulation]]]:
1097
+ - A list of simulation IDs (default),
1098
+ - Or a list/dictionary of Simulation objects if `entity_type=True`.
1099
+ """
1100
+ item = self.get_item(item_id, item_type, force=True)
1101
+ return item.get_simulations_by_tags(tags=tags, status=status, entity_type=entity_type, skip_sims=skip_sims,
1102
+ max_simulations=max_simulations, **kwargs)
1103
+
1104
+
1105
+ TPlatform = TypeVar("TPlatform", bound=IPlatform)
1106
+ TPlatformClass = Type[TPlatform]