idmtools-platform-general 0.0.0.dev0__py3-none-any.whl → 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- idmtools_platform_file/__init__.py +18 -0
- idmtools_platform_file/assets/__init__.py +77 -0
- idmtools_platform_file/assets/_run.sh.jinja2 +47 -0
- idmtools_platform_file/assets/batch.sh.jinja2 +24 -0
- idmtools_platform_file/assets/run_simulation.sh +8 -0
- idmtools_platform_file/cli/__init__.py +5 -0
- idmtools_platform_file/cli/file.py +185 -0
- idmtools_platform_file/file_operations/__init__.py +4 -0
- idmtools_platform_file/file_operations/file_operations.py +298 -0
- idmtools_platform_file/file_operations/operations_interface.py +74 -0
- idmtools_platform_file/file_platform.py +288 -0
- idmtools_platform_file/platform_operations/__init__.py +5 -0
- idmtools_platform_file/platform_operations/asset_collection_operations.py +172 -0
- idmtools_platform_file/platform_operations/experiment_operations.py +314 -0
- idmtools_platform_file/platform_operations/json_metadata_operations.py +320 -0
- idmtools_platform_file/platform_operations/simulation_operations.py +212 -0
- idmtools_platform_file/platform_operations/suite_operations.py +243 -0
- idmtools_platform_file/platform_operations/utils.py +461 -0
- idmtools_platform_file/plugin_info.py +82 -0
- idmtools_platform_file/tools/__init__.py +4 -0
- idmtools_platform_file/tools/job_history.py +334 -0
- idmtools_platform_file/tools/status_report/__init__.py +4 -0
- idmtools_platform_file/tools/status_report/status_report.py +222 -0
- idmtools_platform_file/tools/status_report/utils.py +159 -0
- idmtools_platform_general-0.0.2.dist-info/METADATA +81 -0
- idmtools_platform_general-0.0.2.dist-info/RECORD +36 -0
- idmtools_platform_general-0.0.2.dist-info/entry_points.txt +6 -0
- idmtools_platform_general-0.0.2.dist-info/licenses/LICENSE.TXT +3 -0
- idmtools_platform_general-0.0.2.dist-info/top_level.txt +3 -0
- idmtools_platform_process/__init__.py +17 -0
- idmtools_platform_process/platform_operations/__init__.py +5 -0
- idmtools_platform_process/platform_operations/experiment_operations.py +53 -0
- idmtools_platform_process/plugin_info.py +80 -0
- idmtools_platform_process/process_platform.py +52 -0
- tests/input/hello.sh +2 -0
- idmtools_platform_general/__init__.py +0 -8
- idmtools_platform_general-0.0.0.dev0.dist-info/METADATA +0 -41
- idmtools_platform_general-0.0.0.dev0.dist-info/RECORD +0 -5
- idmtools_platform_general-0.0.0.dev0.dist-info/top_level.txt +0 -1
- {idmtools_platform_general-0.0.0.dev0.dist-info → idmtools_platform_general-0.0.2.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Here we implement the base operations interfaces for all file based platforms.
|
|
3
|
+
|
|
4
|
+
Copyright 2025, Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Type, Union, Any
|
|
10
|
+
from idmtools.core import ItemType
|
|
11
|
+
from idmtools.core.interfaces.ientity import IEntity
|
|
12
|
+
from idmtools.entities.simulation import Simulation
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class IOperations(ABC):
|
|
17
|
+
"""
|
|
18
|
+
Abstract base class defining platform-specific operations.
|
|
19
|
+
|
|
20
|
+
This interface should be implemented by platform FilePlatform or SlurmPlatform
|
|
21
|
+
to handle directory structure, job submission, and file linking.
|
|
22
|
+
"""
|
|
23
|
+
platform: 'FilePlatform' # noqa: F821
|
|
24
|
+
platform_type: Type = field(default=None)
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def get_directory(self, item: IEntity) -> Path:
|
|
28
|
+
"""Get the directory of the given entity."""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def get_directory_by_id(self, item_id: str, item_type: ItemType) -> Path:
|
|
33
|
+
"""Get the directory by item id."""
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def make_command_executable(self, simulation: Simulation) -> None:
|
|
38
|
+
"""Make command executable."""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def mk_directory(self, item: IEntity, exist_ok: bool = False) -> None:
|
|
43
|
+
"""Make a new directory."""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def link_file(self, target: Union[Path, str], link: Union[Path, str]) -> None:
|
|
48
|
+
"""Link files with symlink."""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def link_dir(self, target: Union[Path, str], link: Union[Path, str]) -> None:
|
|
53
|
+
"""Link directory with symlink."""
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def update_script_mode(self, script_path: Union[Path, str], mode: int) -> None:
|
|
58
|
+
"""Update script mode."""
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def create_batch_file(self, item: IEntity, **kwargs) -> None:
|
|
63
|
+
"""Create batch file."""
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def get_simulation_status(self, sim_id: str) -> Any:
|
|
68
|
+
"""Get simulation status."""
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
@abstractmethod
|
|
72
|
+
def create_file(self, file_path: str, content: str) -> None:
|
|
73
|
+
"""Create file."""
|
|
74
|
+
pass
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Here we implement the FilePlatform object.
|
|
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 Union, List
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
|
|
12
|
+
from idmtools import IdmConfigParser
|
|
13
|
+
from idmtools.core import ItemType, EntityStatus, TRUTHY_VALUES
|
|
14
|
+
from idmtools.entities import Suite
|
|
15
|
+
from idmtools.entities.experiment import Experiment
|
|
16
|
+
from idmtools.entities.simulation import Simulation
|
|
17
|
+
from idmtools.entities.iplatform import IPlatform
|
|
18
|
+
from idmtools_platform_file.file_operations.file_operations import FileOperations
|
|
19
|
+
from idmtools_platform_file.platform_operations.asset_collection_operations import FilePlatformAssetCollectionOperations
|
|
20
|
+
from idmtools_platform_file.platform_operations.experiment_operations import FilePlatformExperimentOperations
|
|
21
|
+
from idmtools_platform_file.platform_operations.json_metadata_operations import JSONMetadataOperations
|
|
22
|
+
from idmtools_platform_file.platform_operations.simulation_operations import FilePlatformSimulationOperations
|
|
23
|
+
from idmtools_platform_file.platform_operations.suite_operations import FilePlatformSuiteOperations
|
|
24
|
+
from idmtools_platform_file.platform_operations.utils import FileExperiment, FileSimulation, FileSuite
|
|
25
|
+
|
|
26
|
+
logger = getLogger(__name__)
|
|
27
|
+
user_logger = getLogger('user')
|
|
28
|
+
|
|
29
|
+
op_defaults = dict(default=None, compare=False, metadata={"pickle_ignore": True})
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(repr=False)
|
|
33
|
+
class FilePlatform(IPlatform):
|
|
34
|
+
"""
|
|
35
|
+
File Platform definition.
|
|
36
|
+
"""
|
|
37
|
+
job_directory: str = field(default=None, metadata=dict(help="Job Directory"))
|
|
38
|
+
max_job: int = field(default=4, metadata=dict(help="Maximum number of jobs to run concurrently"))
|
|
39
|
+
run_sequence: bool = field(default=True, metadata=dict(help="Run jobs in sequence"))
|
|
40
|
+
sym_link: bool = field(default=True, metadata=dict(help="Use symbolic links"))
|
|
41
|
+
# Default retries for jobs
|
|
42
|
+
retries: int = field(default=1, metadata=dict(help="Number of retries for failed jobs"))
|
|
43
|
+
# number of MPI processes
|
|
44
|
+
ntasks: int = field(default=1,
|
|
45
|
+
metadata=dict(help="Number of MPI processes. If greater than 1, it triggers mpirun."))
|
|
46
|
+
# modules to be load
|
|
47
|
+
modules: list = field(default_factory=list, metadata=dict(help="Modules to load"))
|
|
48
|
+
# extra packages to install
|
|
49
|
+
extra_packages: list = field(default_factory=list, metadata=dict(help="Extra packages to install"))
|
|
50
|
+
maxlen: int = field(default=30, metadata=dict(help="Maximum length of suite/experiment name"))
|
|
51
|
+
|
|
52
|
+
_suites: FilePlatformSuiteOperations = field(**op_defaults, repr=False, init=False)
|
|
53
|
+
_experiments: FilePlatformExperimentOperations = field(**op_defaults, repr=False, init=False)
|
|
54
|
+
_simulations: FilePlatformSimulationOperations = field(**op_defaults, repr=False, init=False)
|
|
55
|
+
_assets: FilePlatformAssetCollectionOperations = field(**op_defaults, repr=False, init=False)
|
|
56
|
+
_metas: JSONMetadataOperations = field(**op_defaults, repr=False, init=False)
|
|
57
|
+
_op_client: FileOperations = field(**op_defaults, repr=False, init=False)
|
|
58
|
+
|
|
59
|
+
def __post_init__(self):
|
|
60
|
+
self.__init_interfaces()
|
|
61
|
+
self.supported_types = {ItemType.SUITE, ItemType.EXPERIMENT, ItemType.SIMULATION}
|
|
62
|
+
if self.job_directory is None:
|
|
63
|
+
raise ValueError("Job Directory is required.")
|
|
64
|
+
self.job_directory = os.path.abspath(self.job_directory)
|
|
65
|
+
self.name_directory = IdmConfigParser.get_option(None, "name_directory", 'True').lower() in TRUTHY_VALUES
|
|
66
|
+
self.sim_name_directory = IdmConfigParser.get_option(None, "sim_name_directory",
|
|
67
|
+
'False').lower() in TRUTHY_VALUES
|
|
68
|
+
super().__post_init__()
|
|
69
|
+
self._object_cache_expiration = 600
|
|
70
|
+
|
|
71
|
+
def __init_interfaces(self):
|
|
72
|
+
self._suites = FilePlatformSuiteOperations(platform=self)
|
|
73
|
+
self._experiments = FilePlatformExperimentOperations(platform=self)
|
|
74
|
+
self._simulations = FilePlatformSimulationOperations(platform=self)
|
|
75
|
+
self._assets = FilePlatformAssetCollectionOperations(platform=self)
|
|
76
|
+
self._metas = JSONMetadataOperations(platform=self)
|
|
77
|
+
self._op_client = FileOperations(platform=self)
|
|
78
|
+
|
|
79
|
+
def post_setstate(self):
|
|
80
|
+
"""
|
|
81
|
+
Utility function.
|
|
82
|
+
Returns: None
|
|
83
|
+
"""
|
|
84
|
+
self.__init_interfaces()
|
|
85
|
+
|
|
86
|
+
def mk_directory(self, item: Union[Suite, Experiment, Simulation] = None, dest: Union[Path, str] = None,
|
|
87
|
+
exist_ok: bool = True) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Make a new directory.
|
|
90
|
+
Args:
|
|
91
|
+
item: Suite/Experiment/Simulation
|
|
92
|
+
dest: the folder path
|
|
93
|
+
exist_ok: True/False
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
None
|
|
97
|
+
"""
|
|
98
|
+
self._op_client.mk_directory(item, dest, exist_ok)
|
|
99
|
+
|
|
100
|
+
def get_directory(self, item: Union[Suite, Experiment, Simulation]) -> Path:
|
|
101
|
+
"""
|
|
102
|
+
Get item's path.
|
|
103
|
+
Args:
|
|
104
|
+
item: Suite, Experiment, Simulation
|
|
105
|
+
Returns:
|
|
106
|
+
item file directory
|
|
107
|
+
"""
|
|
108
|
+
return self._op_client.get_directory(item)
|
|
109
|
+
|
|
110
|
+
def get_directory_by_id(self, item_id: str, item_type: ItemType) -> Path:
|
|
111
|
+
"""
|
|
112
|
+
Get item's path.
|
|
113
|
+
Args:
|
|
114
|
+
item_id: entity id (Suite, Experiment, Simulation)
|
|
115
|
+
item_type: the type of items (Suite, Experiment, Simulation)
|
|
116
|
+
Returns:
|
|
117
|
+
item file directory
|
|
118
|
+
"""
|
|
119
|
+
return self._op_client.get_directory_by_id(item_id, item_type)
|
|
120
|
+
|
|
121
|
+
def create_batch_file(self, item: Union[Experiment, Simulation], **kwargs) -> None:
|
|
122
|
+
"""
|
|
123
|
+
Create batch file.
|
|
124
|
+
Args:
|
|
125
|
+
item: the item to build batch file for
|
|
126
|
+
kwargs: keyword arguments used to expand functionality.
|
|
127
|
+
Returns:
|
|
128
|
+
None
|
|
129
|
+
"""
|
|
130
|
+
self._op_client.create_batch_file(item, **kwargs)
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def update_script_mode(script_path: Union[Path, str], mode: int = 0o777) -> None:
|
|
134
|
+
"""
|
|
135
|
+
Change file mode.
|
|
136
|
+
Args:
|
|
137
|
+
script_path: script path
|
|
138
|
+
mode: permission mode
|
|
139
|
+
Returns:
|
|
140
|
+
None
|
|
141
|
+
"""
|
|
142
|
+
script_path = Path(script_path)
|
|
143
|
+
script_path.chmod(mode)
|
|
144
|
+
|
|
145
|
+
def flatten_item(self, item: object, raw: bool = False, **kwargs) -> List[object]:
|
|
146
|
+
"""
|
|
147
|
+
Flatten an item: resolve the children until getting to the leaves.
|
|
148
|
+
|
|
149
|
+
For example, for an experiment, will return all the simulations.
|
|
150
|
+
For a suite, will return all the simulations contained in the suites experiments.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
item: Which item to flatten
|
|
154
|
+
raw: If True, returns raw platform objects, False, return local objects
|
|
155
|
+
kwargs: Extra parameters for conversion
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
List of leaf items, which can be from either the local platform or a File platform:
|
|
159
|
+
- Simulations (either local Simulation or FileSimulation),
|
|
160
|
+
"""
|
|
161
|
+
# Return directly if item is already in leaf and raw = False
|
|
162
|
+
if not raw and (isinstance(item, Simulation) and not isinstance(item, FileSimulation)):
|
|
163
|
+
return [item]
|
|
164
|
+
# Handle platform object conversion if needed
|
|
165
|
+
if not isinstance(item, (FileSuite, FileExperiment, FileSimulation)):
|
|
166
|
+
return self.flatten_item(item.get_platform_object(), raw=raw, **kwargs)
|
|
167
|
+
|
|
168
|
+
# Process types (suites and experiments)
|
|
169
|
+
if isinstance(item, (FileSuite, FileExperiment)):
|
|
170
|
+
children = self._get_children_for_platform_item(item)
|
|
171
|
+
# Assign server experiment to child.experiment to avoid recreating child's parent
|
|
172
|
+
if isinstance(item, FileExperiment):
|
|
173
|
+
for child in children:
|
|
174
|
+
child.experiment = item
|
|
175
|
+
return [leaf
|
|
176
|
+
for child in children
|
|
177
|
+
for leaf in self.flatten_item(child, raw=raw, **kwargs)]
|
|
178
|
+
|
|
179
|
+
# Handle leaf types
|
|
180
|
+
if isinstance(item, FileSimulation):
|
|
181
|
+
self._ensure_simulation_experiment(item)
|
|
182
|
+
|
|
183
|
+
if not raw:
|
|
184
|
+
parent = item.experiment if isinstance(item, FileSimulation) else None
|
|
185
|
+
item = self._convert_platform_item_to_entity(item, parent=parent, **kwargs)
|
|
186
|
+
|
|
187
|
+
return [item]
|
|
188
|
+
|
|
189
|
+
def _ensure_simulation_experiment(self, simulation: FileSimulation) -> None:
|
|
190
|
+
"""
|
|
191
|
+
Ensure the given simulation has a valid experiment attached.
|
|
192
|
+
|
|
193
|
+
If the simulation's 'experiment' attribute is missing or uninitialized,
|
|
194
|
+
fetch the experiment from the server using its ID and normalize it.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
simulation (FileSimulation): The simulation object to validate.
|
|
198
|
+
Raises:
|
|
199
|
+
ValueError: If 'experiment_id' is missing or invalid.
|
|
200
|
+
"""
|
|
201
|
+
try:
|
|
202
|
+
experiment = hasattr(simulation, "experiment")
|
|
203
|
+
except Exception:
|
|
204
|
+
experiment = None
|
|
205
|
+
|
|
206
|
+
if experiment is None:
|
|
207
|
+
experiment = self.get_item(simulation.experiment_id, item_type=ItemType.EXPERIMENT, raw=True)
|
|
208
|
+
simulation.experiment = experiment
|
|
209
|
+
|
|
210
|
+
def validate_item_for_analysis(self, item: Union[Simulation, FileSimulation], analyze_failed_items=False):
|
|
211
|
+
"""
|
|
212
|
+
Check if item is valid for analysis.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
item: which item to verify status
|
|
216
|
+
analyze_failed_items: bool
|
|
217
|
+
|
|
218
|
+
Returns: bool
|
|
219
|
+
"""
|
|
220
|
+
result = False
|
|
221
|
+
|
|
222
|
+
# TODO: we may consolidate two cases into one
|
|
223
|
+
if isinstance(item, FileSimulation):
|
|
224
|
+
if item.status == EntityStatus.SUCCEEDED:
|
|
225
|
+
result = True
|
|
226
|
+
else:
|
|
227
|
+
if analyze_failed_items and item.status == EntityStatus.FAILED:
|
|
228
|
+
result = True
|
|
229
|
+
elif isinstance(item, Simulation):
|
|
230
|
+
if item.succeeded:
|
|
231
|
+
result = True
|
|
232
|
+
else:
|
|
233
|
+
if analyze_failed_items and item.status == EntityStatus.FAILED:
|
|
234
|
+
result = True
|
|
235
|
+
return result
|
|
236
|
+
|
|
237
|
+
def link_file(self, target: Union[Path, str], link: Union[Path, str]) -> None:
|
|
238
|
+
"""
|
|
239
|
+
Link files.
|
|
240
|
+
Args:
|
|
241
|
+
target: the source file path
|
|
242
|
+
link: the file path
|
|
243
|
+
Returns:
|
|
244
|
+
None
|
|
245
|
+
"""
|
|
246
|
+
self._op_client.link_file(target, link)
|
|
247
|
+
|
|
248
|
+
def link_dir(self, target: Union[Path, str], link: Union[Path, str]) -> None:
|
|
249
|
+
"""
|
|
250
|
+
Link directory/files.
|
|
251
|
+
Args:
|
|
252
|
+
target: the source folder path.
|
|
253
|
+
link: the folder path
|
|
254
|
+
Returns:
|
|
255
|
+
None
|
|
256
|
+
"""
|
|
257
|
+
self._op_client.link_dir(target, link)
|
|
258
|
+
|
|
259
|
+
def make_command_executable(self, simulation: Simulation) -> None:
|
|
260
|
+
"""
|
|
261
|
+
Make simulation command executable.
|
|
262
|
+
Args:
|
|
263
|
+
simulation: idmtools Simulation
|
|
264
|
+
Returns:
|
|
265
|
+
None
|
|
266
|
+
"""
|
|
267
|
+
self._op_client.make_command_executable(simulation)
|
|
268
|
+
|
|
269
|
+
def get_simulation_status(self, sim_id: str, **kwargs) -> EntityStatus:
|
|
270
|
+
"""
|
|
271
|
+
Retrieve simulation status.
|
|
272
|
+
Args:
|
|
273
|
+
sim_id: Simulation ID
|
|
274
|
+
kwargs: keyword arguments used to expand functionality
|
|
275
|
+
Returns:
|
|
276
|
+
EntityStatus
|
|
277
|
+
"""
|
|
278
|
+
return self._op_client.get_simulation_status(sim_id, **kwargs)
|
|
279
|
+
|
|
280
|
+
def entity_display_name(self, item: Union[Suite, Experiment, Simulation]) -> str:
|
|
281
|
+
"""
|
|
282
|
+
Get display name for entity.
|
|
283
|
+
Args:
|
|
284
|
+
item: Suite, Experiment or Simulation
|
|
285
|
+
Returns:
|
|
286
|
+
str
|
|
287
|
+
"""
|
|
288
|
+
return self._op_client.entity_display_name(item)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Here we implement the FilePlatform asset collection operations.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import shutil
|
|
7
|
+
from uuid import UUID
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from dataclasses import field, dataclass
|
|
10
|
+
from logging import getLogger
|
|
11
|
+
from typing import TYPE_CHECKING, Type, List, Dict, Union, Optional
|
|
12
|
+
from idmtools.core import ItemType
|
|
13
|
+
from idmtools.assets import AssetCollection, Asset
|
|
14
|
+
from idmtools.entities.experiment import Experiment
|
|
15
|
+
from idmtools.entities.simulation import Simulation
|
|
16
|
+
from idmtools.entities.iplatform_ops.iplatform_asset_collection_operations import IPlatformAssetCollectionOperations
|
|
17
|
+
from idmtools_platform_file.platform_operations.utils import FileSimulation, validate_file_copy_path_length, \
|
|
18
|
+
validate_file_path_length
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from idmtools_platform_file.file_platform import FilePlatform
|
|
22
|
+
|
|
23
|
+
logger = getLogger(__name__)
|
|
24
|
+
user_logger = getLogger("user")
|
|
25
|
+
|
|
26
|
+
EXCLUDE_FILES = ['_run.sh', 'metadata.json', 'stdout.txt', 'stderr.txt', 'status.txt', 'job_id.txt', 'job_status.txt']
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class FilePlatformAssetCollectionOperations(IPlatformAssetCollectionOperations):
|
|
31
|
+
"""
|
|
32
|
+
Provides AssetCollection Operations to FilePlatform.
|
|
33
|
+
"""
|
|
34
|
+
platform: 'FilePlatform' # noqa F821
|
|
35
|
+
platform_type: Type = field(default=None)
|
|
36
|
+
|
|
37
|
+
def get(self, asset_collection_id: Optional[UUID], **kwargs) -> AssetCollection:
|
|
38
|
+
"""
|
|
39
|
+
Get an asset collection by id.
|
|
40
|
+
Args:
|
|
41
|
+
asset_collection_id: id of asset collection
|
|
42
|
+
kwargs: keyword arguments used to expand functionality.
|
|
43
|
+
Returns:
|
|
44
|
+
AssetCollection
|
|
45
|
+
"""
|
|
46
|
+
raise NotImplementedError("Get asset collection is not supported on FilePlatform.")
|
|
47
|
+
|
|
48
|
+
def platform_create(self, asset_collection: AssetCollection, **kwargs) -> AssetCollection:
|
|
49
|
+
"""
|
|
50
|
+
Create AssetCollection.
|
|
51
|
+
Args:
|
|
52
|
+
asset_collection: AssetCollection to create
|
|
53
|
+
kwargs: keyword arguments used to expand functionality.
|
|
54
|
+
Returns:
|
|
55
|
+
AssetCollection
|
|
56
|
+
"""
|
|
57
|
+
raise NotImplementedError("platform_create is not supported on FilePlatform.")
|
|
58
|
+
|
|
59
|
+
def link_common_assets(self, simulation: Simulation, common_asset_dir: Union[Path, str] = None) -> None:
|
|
60
|
+
"""
|
|
61
|
+
Link directory/files.
|
|
62
|
+
Args:
|
|
63
|
+
simulation: Simulation
|
|
64
|
+
common_asset_dir: the common asset folder path
|
|
65
|
+
Returns:
|
|
66
|
+
None
|
|
67
|
+
"""
|
|
68
|
+
if common_asset_dir is None:
|
|
69
|
+
common_asset_dir = Path(self.platform.get_directory(simulation.parent), 'Assets')
|
|
70
|
+
link_dir = Path(self.platform.get_directory(simulation), 'Assets')
|
|
71
|
+
|
|
72
|
+
# Copy common assets to simulation directory
|
|
73
|
+
self.platform.link_dir(common_asset_dir, link_dir)
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def _get_assets_from_dir(sim_dir: Path, files: List[str]) -> Dict[str, bytearray]:
|
|
77
|
+
ret = {}
|
|
78
|
+
for file in files:
|
|
79
|
+
asset_file = sim_dir / file
|
|
80
|
+
if asset_file.exists():
|
|
81
|
+
asset = Asset(absolute_path=asset_file.absolute())
|
|
82
|
+
ret[file] = bytearray(asset.bytes)
|
|
83
|
+
else:
|
|
84
|
+
raise RuntimeError(f"Couldn't find asset for path '{file}'.")
|
|
85
|
+
return ret
|
|
86
|
+
|
|
87
|
+
def get_assets(self, simulation: Union[Simulation, FileSimulation], files: List[str], **kwargs) -> Dict[str, bytearray]:
|
|
88
|
+
"""
|
|
89
|
+
Get assets for simulation.
|
|
90
|
+
Args:
|
|
91
|
+
simulation: Simulation or FileSimulation
|
|
92
|
+
files: files to be retrieved
|
|
93
|
+
kwargs: keyword arguments used to expand functionality.
|
|
94
|
+
Returns:
|
|
95
|
+
Dict[str, bytearray]
|
|
96
|
+
"""
|
|
97
|
+
if isinstance(simulation, (Simulation, FileSimulation)):
|
|
98
|
+
sim_dir = self.platform.get_directory_by_id(simulation.id, ItemType.SIMULATION)
|
|
99
|
+
return self._get_assets_from_dir(sim_dir, files)
|
|
100
|
+
else:
|
|
101
|
+
raise NotImplementedError(
|
|
102
|
+
f"get_assets() for items of type {type(simulation)} is not supported on FilePlatform.")
|
|
103
|
+
|
|
104
|
+
def list_assets(self, item: Union[Experiment, Simulation], exclude: List[str] = None, **kwargs) -> List[Asset]:
|
|
105
|
+
"""
|
|
106
|
+
List assets for Experiment/Simulation.
|
|
107
|
+
Args:
|
|
108
|
+
item: Experiment/Simulation
|
|
109
|
+
exclude: list of file path
|
|
110
|
+
kwargs: keyword arguments used to expand functionality.
|
|
111
|
+
Returns:
|
|
112
|
+
list of Asset
|
|
113
|
+
"""
|
|
114
|
+
exclude = exclude if exclude is not None else EXCLUDE_FILES
|
|
115
|
+
if isinstance(item, Experiment):
|
|
116
|
+
assets_dir = Path(self.platform.get_directory(item), 'Assets')
|
|
117
|
+
return AssetCollection.assets_from_directory(assets_dir, recursive=True)
|
|
118
|
+
elif isinstance(item, Simulation):
|
|
119
|
+
assets_dir = self.platform.get_directory(item)
|
|
120
|
+
asset_list = AssetCollection.assets_from_directory(assets_dir, recursive=True)
|
|
121
|
+
assets = [asset for asset in asset_list if asset.filename not in exclude]
|
|
122
|
+
return assets
|
|
123
|
+
else:
|
|
124
|
+
raise NotImplementedError("List assets for this item is not supported on FilePlatform.")
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def copy_asset(src: Union[Asset, Path, str], dest: Union[Path, str]) -> None:
|
|
128
|
+
"""
|
|
129
|
+
Copy asset/file to destination.
|
|
130
|
+
Args:
|
|
131
|
+
src: the file content
|
|
132
|
+
dest: the file path
|
|
133
|
+
Returns:
|
|
134
|
+
None
|
|
135
|
+
"""
|
|
136
|
+
if isinstance(src, Asset):
|
|
137
|
+
if src.absolute_path:
|
|
138
|
+
validate_file_copy_path_length(src.absolute_path, dest)
|
|
139
|
+
shutil.copy(src.absolute_path, dest)
|
|
140
|
+
elif src.content:
|
|
141
|
+
dest_filepath = Path(dest, src.filename)
|
|
142
|
+
validate_file_path_length(dest_filepath)
|
|
143
|
+
dest_filepath.write_bytes(src.bytes)
|
|
144
|
+
else:
|
|
145
|
+
validate_file_copy_path_length(src, dest)
|
|
146
|
+
shutil.copy(src, dest)
|
|
147
|
+
|
|
148
|
+
def dump_assets(self, item: Union[Experiment, Simulation], **kwargs) -> None:
|
|
149
|
+
"""
|
|
150
|
+
Dump item's assets.
|
|
151
|
+
Args:
|
|
152
|
+
item: Experiment/Simulation
|
|
153
|
+
kwargs: keyword arguments used to expand functionality.
|
|
154
|
+
Returns:
|
|
155
|
+
None
|
|
156
|
+
"""
|
|
157
|
+
if isinstance(item, Experiment):
|
|
158
|
+
self.pre_create(item.assets)
|
|
159
|
+
exp_asset_dir = Path(self.platform.get_directory(item), 'Assets')
|
|
160
|
+
self.platform.mk_directory(dest=exp_asset_dir)
|
|
161
|
+
for asset in item.assets:
|
|
162
|
+
self.platform.mk_directory(dest=exp_asset_dir.joinpath(asset.relative_path))
|
|
163
|
+
self.copy_asset(asset, exp_asset_dir.joinpath(asset.relative_path))
|
|
164
|
+
self.post_create(item.assets)
|
|
165
|
+
elif isinstance(item, Simulation):
|
|
166
|
+
self.pre_create(item.assets)
|
|
167
|
+
sim_dir = self.platform.get_directory(item)
|
|
168
|
+
for asset in item.assets:
|
|
169
|
+
self.copy_asset(asset, sim_dir)
|
|
170
|
+
self.post_create(item.assets)
|
|
171
|
+
else:
|
|
172
|
+
raise NotImplementedError(f"dump_assets() for item of type {type(item)} is not supported on FilePlatform.")
|