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,461 @@
1
+ """
2
+ This is FilePlatform operations utils.
3
+
4
+ Copyright 2025, Gates Foundation. All rights reserved.
5
+ """
6
+ import os
7
+ from pathlib import Path
8
+ from logging import getLogger
9
+ from typing import Dict, Union, List, Optional
10
+ from idmtools.core.interfaces.ientity import IEntity
11
+ from idmtools.entities import Suite
12
+ from idmtools.core import EntityStatus, ItemType
13
+ from idmtools.entities.experiment import Experiment
14
+ from idmtools.entities.simulation import Simulation
15
+
16
+ logger = getLogger(__name__)
17
+ user_logger = getLogger("user")
18
+
19
+ FILE_MAPS = {
20
+ "0": EntityStatus.SUCCEEDED,
21
+ "-1": EntityStatus.FAILED,
22
+ "100": EntityStatus.RUNNING,
23
+ "None": EntityStatus.CREATED
24
+ }
25
+
26
+
27
+ class FileItem:
28
+ """
29
+ Represent File Object.
30
+ """
31
+
32
+ _metas: Dict
33
+ _platform_directory: Path
34
+
35
+ def __init__(self, metas: Dict):
36
+ """
37
+ Constructor.
38
+ Args:
39
+ metas: metadata
40
+ """
41
+ self._metas = metas
42
+ self._platform_directory = Path(metas["dir"])
43
+
44
+ def get_platform_object(self):
45
+ """
46
+ Get platform.
47
+
48
+ Returns:
49
+ Platform
50
+ """
51
+ return self
52
+
53
+
54
+ class FileSuite(FileItem, Suite):
55
+ """
56
+ Represents a Suite loaded from a file platform.
57
+ """
58
+
59
+ def __init__(self, metas: Dict):
60
+ """
61
+ Initialize a FileSuite object from metadata.
62
+
63
+ This constructor extracts common suite metadata such as ID, name, status, experiments,
64
+ and tags from a given metadata dictionary.
65
+ Args:
66
+ metas (Dict): A dictionary containing suite metadata. Expected keys:
67
+ - 'id': Unique identifier for the suite.
68
+ - 'name': Name of the suite.
69
+ - 'status': Execution status.
70
+ - 'experiments': List of experiment IDs or IEntity instances.
71
+ - 'tags': Dictionary of metadata tags.
72
+ """
73
+ FileItem.__init__(self, metas)
74
+ self.uid = metas['id']
75
+ self.name = metas['name']
76
+ self.status = metas['status']
77
+ self.__experiments: Optional[List[str, IEntity]] = metas['experiments']
78
+ self.tags = metas['tags']
79
+
80
+ @property
81
+ def experiments(self) -> List:
82
+ """
83
+ Retrieve the list of experiments associated with this suite.
84
+ Returns:
85
+ List: A list of resolved `Experiment` objects.
86
+ """
87
+ return self.get_experiments()
88
+
89
+ def get_experiments(self) -> List:
90
+ """
91
+ Resolve and return the list of experiments.
92
+
93
+ For any unresolved entries (e.g., experiment IDs), this method uses
94
+ the current platform to retrieve the full `Experiment` object. Already
95
+ resolved `Experiment` instances are returned as-is.
96
+
97
+ Returns:
98
+ List: A list of `Experiment` objects resolved from the internal metadata.
99
+ """
100
+ platform = self.get_current_platform_or_error()
101
+ exp_list = []
102
+ for exp in self.__experiments:
103
+ if isinstance(exp, Experiment):
104
+ exp_list.append(exp)
105
+ else:
106
+ exp_list.append(platform.get_item(exp, item_type=ItemType.EXPERIMENT, force=True, raw=True))
107
+ self.__experiments = exp_list
108
+ return exp_list
109
+
110
+ @experiments.setter
111
+ def experiments(self, experiments: List):
112
+ """
113
+ Set the list of experiments for this suite.
114
+
115
+ Accepts either resolved `Experiment` objects or a mix of IDs and objects.
116
+ The list will be resolved to full objects upon next access(get_experiments()).
117
+
118
+ Args:
119
+ experiments (List): A list of experiment IDs or `Experiment` instances.
120
+ """
121
+ self.__experiments = experiments
122
+
123
+ def __repr__(self):
124
+ """
125
+ String representation of suite.
126
+ """
127
+ return f"<{self.__class__.__name__} {self.uid} - {len(self.experiments)} experiments>"
128
+
129
+ def add_experiment(self, experiment: 'Experiment') -> 'NoReturn': # noqa: F821
130
+ """
131
+ Add an experiment to the suite.
132
+
133
+ Args:
134
+ experiment: the experiment to be linked to suite
135
+ """
136
+ self.__experiments.append(experiment)
137
+
138
+
139
+ class FileExperiment(FileItem, Experiment):
140
+ """
141
+ Represents an Experiment loaded from a file platform.
142
+
143
+ This subclass of `Experiment` maps metadata into a lightweight experiment representation,
144
+ where simulations may initially be stored as either IDs or resolved `Simulation` objects.
145
+ """
146
+
147
+ def __init__(self, metas: Dict):
148
+ """
149
+ Initialize a FileExperiment from a metadata dictionary.
150
+
151
+ Args:
152
+ metas (Dict): Metadata dictionary containing keys:
153
+ - 'id': Unique identifier for the experiment.
154
+ - 'name': Experiment name.
155
+ - 'status': Status of the experiment (raw value).
156
+ - 'suite_id': ID of the parent suite.
157
+ - 'simulations': List of simulation IDs or `Simulation` objects.
158
+ - 'tags': Dictionary of experiment tags.
159
+ - 'assets': Asset collection or related metadata.
160
+ """
161
+ FileItem.__init__(self, metas)
162
+ self.suite_id = self.parent_id = metas['suite_id']
163
+ self.__simulations: Optional[List[str, IEntity]] = metas['simulations']
164
+ self.uid = metas['id']
165
+ self.name = metas['name']
166
+ self._status = metas['status']
167
+ self.tags = metas['tags']
168
+ self.assets = metas['assets']
169
+
170
+ @property
171
+ def simulations(self) -> List:
172
+ """
173
+ Access the list of simulations associated with the experiment.
174
+
175
+ Returns:
176
+ List: A list containing either simulation IDs or resolved `Simulation` objects.
177
+ """
178
+ return self.get_simulations()
179
+
180
+ def get_simulations(self) -> List:
181
+ """
182
+ Resolve and return full `Simulation` objects from their IDs.
183
+
184
+ This method uses file platform to retrieve any simulations
185
+ that are not yet resolved, and replaces unresolved IDs in-place.
186
+
187
+ Returns:
188
+ List: Fully resolved list of `Simulation` objects.
189
+ """
190
+ platform = self.get_current_platform_or_error()
191
+ sim_list = []
192
+ for sim in self.__simulations:
193
+ if isinstance(sim, Simulation):
194
+ sim_list.append(sim)
195
+ else:
196
+ sim_list.append(platform.get_item(sim, item_type=ItemType.SIMULATION, force=True, raw=True))
197
+ self.__simulations = sim_list
198
+ return sim_list
199
+
200
+ @simulations.setter
201
+ def simulations(self, simulations: List):
202
+ """
203
+ Set the simulations list directly.
204
+
205
+ Args:
206
+ simulations (List): A list of simulation IDs or `Simulation` instances.
207
+ """
208
+ self.__simulations = simulations
209
+
210
+ def add_simulation(self, simulation: 'Simulation') -> 'NoReturn': # noqa: F821
211
+ """
212
+ Add a single simulation to the experiment.
213
+
214
+ Args:
215
+ simulation (Simulation): The simulation to add.
216
+ """
217
+ # Check possible duplicate
218
+ ids = [sim.id if isinstance(sim, Simulation) else sim for sim in self.__simulations]
219
+ if simulation.id in ids:
220
+ return
221
+
222
+ # Set parent
223
+ simulation.parent = self
224
+
225
+ self.__simulations.append(simulation)
226
+
227
+ @property
228
+ def status(self):
229
+ """
230
+ Get status.
231
+ Returns:
232
+ Status
233
+ """
234
+ return self._status
235
+
236
+ @status.setter
237
+ def status(self, status):
238
+ """
239
+ Set Status.
240
+ Args:
241
+ status: status
242
+
243
+ Returns:
244
+ None
245
+ """
246
+ self._status = status
247
+
248
+ def __repr__(self):
249
+ """
250
+ String representation of experiment.
251
+ """
252
+ return f"<{self.__class__.__name__} {self.uid} - {len(self.simulations)} simulations>"
253
+
254
+
255
+ class FileSimulation(FileItem, Simulation):
256
+ """
257
+ Represents a simulation loaded from file metadata.
258
+
259
+ This class is a lightweight wrapper around the standard `Simulation` object,
260
+ used by the FilePlatform. It initializes the simulation using values from
261
+ a metadata dictionary, typically derived from file contents.
262
+
263
+ Attributes:
264
+ uid (str): uid of simulation.
265
+ parent_id (str): ID of the parent experiment.
266
+ experiment_id (str): Alias of `parent_id` for clarity.
267
+ name (str): Name of the simulation.
268
+ status (EntityStatus or raw value): Execution status.
269
+ tags (Dict): Key-value metadata tags.
270
+ task (Any): Task definition or reference (platform-dependent).
271
+ assets (Any): Asset metadata or collection.
272
+ """
273
+
274
+ def __init__(self, metas: Dict):
275
+ """
276
+ Constructor.
277
+ Args:
278
+ metas: Metas dict
279
+ """
280
+ FileItem.__init__(self, metas)
281
+ self.uid = metas['id']
282
+ self.parent_id = metas['parent_id']
283
+ self.experiment_id = metas['parent_id']
284
+ self.name = metas['name']
285
+ self.status = metas['status']
286
+ self.tags = metas['tags']
287
+ self.task = metas['task']
288
+ self.assets = metas['assets']
289
+
290
+
291
+ def clean_item_name(experiment_name: str, maxlen: int = 30) -> str:
292
+ """
293
+ Handle some special characters in experiment names.
294
+ Args:
295
+ experiment_name: name of the experiment
296
+ maxlen: max length of the experiment name
297
+ Returns:
298
+ the experiment name allowed for use
299
+ """
300
+ import re
301
+ chars_to_replace = ['/', '\\', ':', "'", '"', '?', '<', '>', '*', '|', "\0", "(", ")", "[", "]", '`', ',', '!', '$',
302
+ '&', '"', ' ']
303
+ clean_names_expr = re.compile(f'[{re.escape("".join(chars_to_replace))}]')
304
+
305
+ experiment_name = clean_names_expr.sub("_", experiment_name)
306
+ s = experiment_name.encode("ascii", "ignore").decode('utf8').strip()
307
+ # Truncate to maxlen
308
+ return s[:maxlen]
309
+
310
+
311
+ def add_dummy_suite(experiment: Experiment, suite_name: str = None, tags: Dict = None) -> Suite:
312
+ """
313
+ Create Suite parent for given experiment.
314
+ Args:
315
+ experiment: idmtools Experiment
316
+ suite_name: new Suite name
317
+ tags: new Suite tags
318
+ Returns:
319
+ Suite
320
+ """
321
+ if suite_name is None:
322
+ suite_name = 'Suite'
323
+ suite = Suite(name=suite_name)
324
+
325
+ if tags is not None:
326
+ suite.tags = tags
327
+
328
+ # add experiment
329
+ suite.add_experiment(experiment)
330
+ return suite
331
+
332
+
333
+ def get_max_filepath(dir_path: Union[Path, str]) -> str:
334
+ """
335
+ Get the maximum file path in a directory.
336
+ Args:
337
+ dir_path: directory path
338
+
339
+ Returns:
340
+ maximum file relative path
341
+ """
342
+ max_length = 0 # Variable to store the maximum filename length
343
+ max_file_path = None # Variable to store the maximum relative filepath
344
+
345
+ # Walk through all files and subdirectories recursively
346
+ dir_path = os.path.abspath(dir_path)
347
+ for root, dirs, files in os.walk(dir_path):
348
+ for file in files:
349
+ full_path = os.path.join(root, file)
350
+ file_path = full_path.replace(dir_path, '').lstrip('\\')
351
+ if max_file_path is None:
352
+ max_file_path = file_path
353
+ max_length = len(max_file_path)
354
+ else:
355
+ max_length_new = max(max_length, len(file_path))
356
+ if max_length_new > max_length:
357
+ max_file_path = file_path
358
+ max_length = len(max_file_path)
359
+
360
+ return max_file_path
361
+
362
+
363
+ def validate_folder_files_path_length(common_asset_dir: Union[Path, str], link_dir: Union[Path, str],
364
+ limit: int = 256):
365
+ """
366
+ Validate common asset path length.
367
+ Args:
368
+ common_asset_dir: common asset directory
369
+ link_dir: link directory
370
+ limit: path length limit
371
+ Returns:
372
+ None
373
+ """
374
+ if not is_windows():
375
+ return
376
+ if is_long_paths_enabled():
377
+ return
378
+ asset_path = get_max_filepath(common_asset_dir)
379
+ if asset_path is None:
380
+ validate_file_path_length(link_dir, limit)
381
+ else:
382
+ sim_asset_path = os.path.join(str(link_dir), asset_path)
383
+ validate_file_path_length(sim_asset_path, limit)
384
+
385
+
386
+ def validate_file_copy_path_length(src: Union[Path, str], dest: Union[Path, str], limit: int = 256):
387
+ """
388
+ Validate file copy path length.
389
+ Args:
390
+ src: source path
391
+ dest: destination path
392
+ limit: path length limit
393
+ Returns:
394
+ None
395
+ """
396
+ if not is_windows():
397
+ return
398
+ if is_long_paths_enabled():
399
+ return
400
+ filename = Path(src).name
401
+ dest_file = Path(dest, filename)
402
+ validate_file_path_length(dest_file, limit)
403
+
404
+
405
+ def validate_file_path_length(file_path: Union[Path, str], limit: int = 256):
406
+ """
407
+ Validate file path length.
408
+ Args:
409
+ file_path: file path
410
+ limit: path length limit
411
+ Returns:
412
+ None
413
+ """
414
+ if not is_windows():
415
+ return
416
+ if is_long_paths_enabled():
417
+ return
418
+ total_length = len(str(file_path))
419
+ if total_length > limit:
420
+ user_logger.warning(
421
+ f"\nFile path length too long: {total_length} > {limit}. Refer to file: '{file_path}'")
422
+ user_logger.warning(
423
+ "You may want to adjust your job_directory location, short Experiment name or Suite name to reduce the file path length. Or you can enable long paths in Windows, refer to https://www.autodesk.com/support/technical/article/caas/sfdcarticles/sfdcarticles/The-Windows-10-default-path-length-limitation-MAX-PATH-is-256-characters.html.")
424
+ # raise FileNotFoundError(f"File path length too long: {total_length} > {limit}. Refer to file: '{file_path}'")
425
+ exit(-1)
426
+
427
+
428
+ def is_windows() -> bool:
429
+ """
430
+ Check if the platform is Windows.
431
+ Returns:
432
+ True if Windows, False otherwise
433
+ """
434
+ # return os.name == 'nt'
435
+ import platform
436
+ return platform.system() in ["Windows"]
437
+
438
+
439
+ def is_long_paths_enabled() -> bool:
440
+ """Check if long paths are enabled in Windows."""
441
+ import winreg
442
+ try:
443
+ # Open the registry key where LongPathsEnabled is stored
444
+ registry_key = winreg.OpenKey(
445
+ winreg.HKEY_LOCAL_MACHINE,
446
+ r"SYSTEM\CurrentControlSet\Control\FileSystem",
447
+ 0,
448
+ winreg.KEY_READ
449
+ )
450
+ # Query the value of LongPathsEnabled
451
+ long_paths_enabled, _ = winreg.QueryValueEx(registry_key, "LongPathsEnabled")
452
+ winreg.CloseKey(registry_key)
453
+
454
+ # Return True if it's enabled (i.e., value is 1), otherwise False
455
+ return long_paths_enabled == 1
456
+ except FileNotFoundError:
457
+ # The registry key or value does not exist
458
+ return False
459
+ except Exception as e:
460
+ print(f"Error checking long paths: {e}")
461
+ return
@@ -0,0 +1,82 @@
1
+ """
2
+ idmtools file platform plugin definition.
3
+
4
+ Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
5
+ """
6
+ from pathlib import Path
7
+ from typing import Type, Dict
8
+ from idmtools.entities.iplatform import IPlatform
9
+ from idmtools.registry.platform_specification import example_configuration_impl, get_platform_impl, \
10
+ get_platform_type_impl, PlatformSpecification
11
+ from idmtools.registry.plugin_specification import get_description_impl
12
+
13
+
14
+ FILE_EXAMPLE_CONFIG = """
15
+ [File]
16
+ job_directory = /data
17
+ """
18
+
19
+
20
+ class FilePlatformSpecification(PlatformSpecification):
21
+ """
22
+ File Platform Specification definition.
23
+ """
24
+
25
+ @get_description_impl
26
+ def get_description(self) -> str:
27
+ """
28
+ Retrieve description.
29
+ """
30
+ return "Provides access to the Local Platform to IDM Tools"
31
+
32
+ @get_platform_impl
33
+ def get(self, **configuration) -> IPlatform:
34
+ """
35
+ Build our file platform from the passed in configuration object.
36
+
37
+ We do our import of platform here to avoid any weirdness
38
+ Args:
39
+ configuration:
40
+
41
+ Returns:
42
+ IPlatform
43
+ """
44
+ from idmtools_platform_file.file_platform import FilePlatform
45
+ return FilePlatform(**configuration)
46
+
47
+ @example_configuration_impl
48
+ def example_configuration(self):
49
+ """
50
+ Retrieve example configuration.
51
+ """
52
+ return FILE_EXAMPLE_CONFIG
53
+
54
+ @get_platform_type_impl
55
+ def get_type(self) -> Type['FilePlatform']: # noqa: F821
56
+ """
57
+ Get type.
58
+
59
+ Returns:
60
+ Type
61
+ """
62
+ from idmtools_platform_file.file_platform import FilePlatform
63
+ return FilePlatform
64
+
65
+ def get_version(self) -> str:
66
+ """
67
+ Returns the version of the plugin.
68
+
69
+ Returns:
70
+ Plugin Version
71
+ """
72
+ from idmtools_platform_file import __version__
73
+ return __version__
74
+
75
+ def get_configuration_aliases(self) -> Dict[str, Dict]:
76
+ """Provides configuration aliases that exist in FILE."""
77
+ config_aliases = dict(
78
+ FILE=dict(
79
+ job_directory=str(Path.home())
80
+ )
81
+ )
82
+ return config_aliases
@@ -0,0 +1,4 @@
1
+ """idmtools comps utils.
2
+
3
+ Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
4
+ """