idmtools-platform-comps 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_comps/__init__.py +25 -8
- idmtools_platform_comps/cli/__init__.py +4 -0
- idmtools_platform_comps/cli/cli_functions.py +50 -0
- idmtools_platform_comps/cli/comps.py +492 -0
- idmtools_platform_comps/comps_cli.py +48 -0
- idmtools_platform_comps/comps_operations/__init__.py +6 -0
- idmtools_platform_comps/comps_operations/asset_collection_operations.py +263 -0
- idmtools_platform_comps/comps_operations/experiment_operations.py +569 -0
- idmtools_platform_comps/comps_operations/simulation_operations.py +678 -0
- idmtools_platform_comps/comps_operations/suite_operations.py +228 -0
- idmtools_platform_comps/comps_operations/workflow_item_operations.py +269 -0
- idmtools_platform_comps/comps_platform.py +309 -0
- idmtools_platform_comps/plugin_info.py +168 -0
- idmtools_platform_comps/ssmt_operations/__init__.py +6 -0
- idmtools_platform_comps/ssmt_operations/simulation_operations.py +77 -0
- idmtools_platform_comps/ssmt_operations/workflow_item_operations.py +73 -0
- idmtools_platform_comps/ssmt_platform.py +44 -0
- idmtools_platform_comps/ssmt_work_items/__init__.py +4 -0
- idmtools_platform_comps/ssmt_work_items/comps_work_order_task.py +29 -0
- idmtools_platform_comps/ssmt_work_items/comps_workitems.py +113 -0
- idmtools_platform_comps/ssmt_work_items/icomps_workflowitem.py +71 -0
- idmtools_platform_comps/ssmt_work_items/work_order.py +54 -0
- idmtools_platform_comps/utils/__init__.py +4 -0
- idmtools_platform_comps/utils/assetize_output/__init__.py +4 -0
- idmtools_platform_comps/utils/assetize_output/assetize_output.py +125 -0
- idmtools_platform_comps/utils/assetize_output/assetize_ssmt_script.py +144 -0
- idmtools_platform_comps/utils/base_singularity_work_order.json +6 -0
- idmtools_platform_comps/utils/download/__init__.py +4 -0
- idmtools_platform_comps/utils/download/download.py +178 -0
- idmtools_platform_comps/utils/download/download_ssmt.py +81 -0
- idmtools_platform_comps/utils/download_experiment.py +116 -0
- idmtools_platform_comps/utils/file_filter_workitem.py +519 -0
- idmtools_platform_comps/utils/general.py +358 -0
- idmtools_platform_comps/utils/linux_mounts.py +73 -0
- idmtools_platform_comps/utils/lookups.py +123 -0
- idmtools_platform_comps/utils/package_version.py +489 -0
- idmtools_platform_comps/utils/python_requirements_ac/__init__.py +4 -0
- idmtools_platform_comps/utils/python_requirements_ac/create_asset_collection.py +155 -0
- idmtools_platform_comps/utils/python_requirements_ac/install_requirements.py +109 -0
- idmtools_platform_comps/utils/python_requirements_ac/requirements_to_asset_collection.py +374 -0
- idmtools_platform_comps/utils/python_version.py +40 -0
- idmtools_platform_comps/utils/scheduling.py +154 -0
- idmtools_platform_comps/utils/singularity_build.py +491 -0
- idmtools_platform_comps/utils/spatial_output.py +76 -0
- idmtools_platform_comps/utils/ssmt_utils/__init__.py +6 -0
- idmtools_platform_comps/utils/ssmt_utils/common.py +70 -0
- idmtools_platform_comps/utils/ssmt_utils/file_filter.py +568 -0
- idmtools_platform_comps/utils/sweeping.py +162 -0
- idmtools_platform_comps-0.0.2.dist-info/METADATA +100 -0
- idmtools_platform_comps-0.0.2.dist-info/RECORD +62 -0
- idmtools_platform_comps-0.0.2.dist-info/entry_points.txt +9 -0
- idmtools_platform_comps-0.0.2.dist-info/licenses/LICENSE.TXT +3 -0
- {idmtools_platform_comps-0.0.0.dev0.dist-info → idmtools_platform_comps-0.0.2.dist-info}/top_level.txt +1 -0
- ssmt_image/Dockerfile +52 -0
- ssmt_image/Makefile +21 -0
- ssmt_image/__init__.py +6 -0
- ssmt_image/bootstrap.sh +30 -0
- ssmt_image/build_docker_image.py +161 -0
- ssmt_image/pip.conf +3 -0
- ssmt_image/push_docker_image.py +49 -0
- ssmt_image/requirements.txt +9 -0
- idmtools_platform_comps-0.0.0.dev0.dist-info/METADATA +0 -41
- idmtools_platform_comps-0.0.0.dev0.dist-info/RECORD +0 -5
- {idmtools_platform_comps-0.0.0.dev0.dist-info → idmtools_platform_comps-0.0.2.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
"""idmtools FileFilterWorkItem is a interface for SSMT command to act on files using filters in WorkItems.
|
|
2
|
+
|
|
3
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
4
|
+
"""
|
|
5
|
+
import copy
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from abc import ABC
|
|
9
|
+
from os import PathLike
|
|
10
|
+
from pathlib import PurePath
|
|
11
|
+
from uuid import UUID
|
|
12
|
+
import re
|
|
13
|
+
import inspect
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from logging import getLogger, DEBUG
|
|
16
|
+
from COMPS.Data.CommissionableEntity import CommissionableEntity
|
|
17
|
+
from typing import List, Union, Callable, Dict
|
|
18
|
+
from idmtools import IdmConfigParser
|
|
19
|
+
from idmtools.assets import Asset, AssetCollection
|
|
20
|
+
from idmtools.assets.file_list import FileList
|
|
21
|
+
from idmtools.core.interfaces.irunnable_entity import IRunnableEntity
|
|
22
|
+
from idmtools.entities import CommandLine
|
|
23
|
+
from idmtools.entities.command_task import CommandTask
|
|
24
|
+
from idmtools.entities.experiment import Experiment
|
|
25
|
+
from idmtools.entities.iplatform import IPlatform
|
|
26
|
+
from idmtools.entities.iworkflow_item import IWorkflowItem
|
|
27
|
+
from idmtools.entities.simulation import Simulation
|
|
28
|
+
from idmtools.utils.info import get_help_version_url
|
|
29
|
+
from idmtools_platform_comps.comps_platform import COMPSPlatform
|
|
30
|
+
from idmtools_platform_comps.ssmt_work_items.comps_workitems import SSMTWorkItem
|
|
31
|
+
from idmtools.core.enums import ItemType, EntityStatus
|
|
32
|
+
|
|
33
|
+
EntityFilterFunc = Callable[[CommissionableEntity], bool]
|
|
34
|
+
FilterableSSMTItem = Union[Experiment, Simulation, IWorkflowItem]
|
|
35
|
+
FilenameFormatFunction = Callable[[str], str]
|
|
36
|
+
logger = getLogger(__name__)
|
|
37
|
+
user_logger = getLogger("user")
|
|
38
|
+
|
|
39
|
+
WI_PROPERTY_MAP = dict(
|
|
40
|
+
related_experiments=ItemType.EXPERIMENT,
|
|
41
|
+
related_simulations=ItemType.SIMULATION,
|
|
42
|
+
related_work_items=ItemType.WORKFLOW_ITEM,
|
|
43
|
+
related_asset_collections=ItemType.ASSETCOLLECTION
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Default list of ignored files
|
|
47
|
+
DEFAULT_EXCLUDES = ["StdErr.txt", "StdOut.txt", "WorkOrder.json", "*.log"]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class CrossEnvironmentFilterNotSupport(Exception):
|
|
51
|
+
"""Defines cross environment error for when a user tried to filter across multiple comps environments."""
|
|
52
|
+
doc_link: str = "platforms/comps/errors.html#errors"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class AtLeastOneItemToWatch(Exception):
|
|
56
|
+
"""Defines error for when there are not items being watched by FileFilterWorkItem."""
|
|
57
|
+
doc_link: str = "platforms/comps/errors.html#errors"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(repr=False)
|
|
61
|
+
class FileFilterWorkItem(SSMTWorkItem, ABC):
|
|
62
|
+
"""
|
|
63
|
+
Defines our filtering workitem base that is used by assetize outputs and download work items.
|
|
64
|
+
"""
|
|
65
|
+
#: List of glob patterns. See https://docs.python.org/3.7/library/glob.html for details on the patterns
|
|
66
|
+
file_patterns: List[str] = field(default_factory=list)
|
|
67
|
+
# Exclude patterns.
|
|
68
|
+
exclude_patterns: List[str] = field(default_factory=lambda: copy.copy(DEFAULT_EXCLUDES))
|
|
69
|
+
#: Include Assets directories. This allows patterns to also include items from the assets directory
|
|
70
|
+
include_assets: bool = field(default=False)
|
|
71
|
+
#: Formatting pattern for directory names. Simulations tend to have similar outputs so the workitem puts those in directories using the simulation id by default as the directory name
|
|
72
|
+
simulation_prefix_format_str: str = field(default="{simulation.id}")
|
|
73
|
+
#: WorkFlowItem outputs will not have a folder prefix by default. If you are filtering multiple work items, you may want to set this to "{workflow_item.id}"
|
|
74
|
+
work_item_prefix_format_str: str = field(default=None)
|
|
75
|
+
#: Simulations outputs will not have a folder. Useful when you are filtering a single simulation
|
|
76
|
+
no_simulation_prefix: bool = field(default=False)
|
|
77
|
+
#: Enable verbose
|
|
78
|
+
verbose: bool = field(default=False)
|
|
79
|
+
#: Python Functions that will be ran before Filtering script. The function must be named
|
|
80
|
+
pre_run_functions: List[Callable] = field(default_factory=list)
|
|
81
|
+
#: Python Function to filter entities. This Function should receive a Comps CommissionableEntity. True means include item, false is don't
|
|
82
|
+
entity_filter_function: EntityFilterFunc = field(default=None)
|
|
83
|
+
#: Function to pass a custom function that is called on the name. This can be used to do advanced mapping or renaming of files
|
|
84
|
+
filename_format_function: FilenameFormatFunction = field(default=None)
|
|
85
|
+
#: Enables running jobs without creating executing. It instead produces a file list of what would be includes in the final filter
|
|
86
|
+
dry_run: bool = field(default=False)
|
|
87
|
+
|
|
88
|
+
_ssmt_script: str = field(default=None, repr=False)
|
|
89
|
+
# later change to load all functions in file_filter.py
|
|
90
|
+
_ssmt_depends: List[str] = field(default_factory=lambda: ["common.py", "file_filter.py"], repr=False)
|
|
91
|
+
|
|
92
|
+
def __post_init__(self, item_name: str, asset_collection_id: UUID, asset_files: FileList, user_files: FileList, command: str):
|
|
93
|
+
"""
|
|
94
|
+
Initialize the FileFilterWorkItem.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
item_name: ItemName(Workitem)
|
|
98
|
+
asset_collection_id: Asset collection to attach to workitem
|
|
99
|
+
asset_files: Asset collections files to add
|
|
100
|
+
user_files: User files to add
|
|
101
|
+
command: Command to run
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
None
|
|
105
|
+
|
|
106
|
+
Raises:
|
|
107
|
+
ValueError - If the ssmt script is not defined.
|
|
108
|
+
"""
|
|
109
|
+
if self._ssmt_script is None:
|
|
110
|
+
raise ValueError("When defining a FileFilterWorkItem, you need an _ssmt_script")
|
|
111
|
+
# Set command to nothing here for now. Eventually this will go away after 1.7.0
|
|
112
|
+
self.task = CommandTask(command=f'python3 Assets/{PurePath(self._ssmt_script).name}')
|
|
113
|
+
super().__post_init__(item_name, asset_collection_id, asset_files, user_files, "")
|
|
114
|
+
|
|
115
|
+
def create_command(self) -> str:
|
|
116
|
+
"""
|
|
117
|
+
Builds our command line for the SSMT Job.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Command string
|
|
121
|
+
"""
|
|
122
|
+
command = f"python3 Assets/{PurePath(self._ssmt_script).name} "
|
|
123
|
+
if self.file_patterns:
|
|
124
|
+
command += '--file-pattern'
|
|
125
|
+
for pattern in self.file_patterns:
|
|
126
|
+
command += f' "{pattern}"'
|
|
127
|
+
|
|
128
|
+
if self.exclude_patterns:
|
|
129
|
+
command += ' --exclude-pattern'
|
|
130
|
+
for pattern in self.exclude_patterns:
|
|
131
|
+
command += f' "{pattern}"'
|
|
132
|
+
|
|
133
|
+
if self.no_simulation_prefix:
|
|
134
|
+
command += ' --no-simulation-prefix'
|
|
135
|
+
else:
|
|
136
|
+
command += f' --simulation-prefix-format-str "{self.simulation_prefix_format_str}"'
|
|
137
|
+
|
|
138
|
+
if self.work_item_prefix_format_str:
|
|
139
|
+
command += f' --work-item-prefix-format-str "{self.work_item_prefix_format_str}"'
|
|
140
|
+
|
|
141
|
+
if self.include_assets:
|
|
142
|
+
command += ' --assets'
|
|
143
|
+
|
|
144
|
+
for pre_run_func in self.pre_run_functions:
|
|
145
|
+
command += f' --pre-run-func {pre_run_func.__name__}'
|
|
146
|
+
|
|
147
|
+
if self.entity_filter_function:
|
|
148
|
+
command += f' --entity-filter-func {self.entity_filter_function.__name__}'
|
|
149
|
+
|
|
150
|
+
if self.filename_format_function:
|
|
151
|
+
command += f' --filename-format-func {self.filename_format_function.__name__}'
|
|
152
|
+
|
|
153
|
+
if self.verbose:
|
|
154
|
+
command += ' --verbose'
|
|
155
|
+
|
|
156
|
+
if self.dry_run:
|
|
157
|
+
command += ' --dry-run'
|
|
158
|
+
|
|
159
|
+
command = self._extra_command_args(command)
|
|
160
|
+
|
|
161
|
+
if logger.isEnabledFor(DEBUG):
|
|
162
|
+
logger.debug(f'Command: {command}')
|
|
163
|
+
|
|
164
|
+
return command
|
|
165
|
+
|
|
166
|
+
def _extra_command_args(self, command: str) -> str:
|
|
167
|
+
"""Add extra command arguments."""
|
|
168
|
+
return command
|
|
169
|
+
|
|
170
|
+
def __pickle_pre_run(self):
|
|
171
|
+
"""
|
|
172
|
+
Pickles the pre run functions.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
None
|
|
176
|
+
"""
|
|
177
|
+
if self.pre_run_functions:
|
|
178
|
+
source = ""
|
|
179
|
+
for function in self.pre_run_functions:
|
|
180
|
+
new_source = self.__format_function_source(function)
|
|
181
|
+
source += "\n\n" + new_source
|
|
182
|
+
self.assets.add_or_replace_asset(Asset(filename='pre_run.py', content=source))
|
|
183
|
+
|
|
184
|
+
def __pickle_format_func(self):
|
|
185
|
+
"""
|
|
186
|
+
Pickle Format filename Function.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
None
|
|
190
|
+
"""
|
|
191
|
+
if self.filename_format_function:
|
|
192
|
+
new_source = self.__format_function_source(self.filename_format_function)
|
|
193
|
+
self.assets.add_or_replace_asset(Asset(filename='filename_format_func.py', content=new_source))
|
|
194
|
+
|
|
195
|
+
def __pickle_filter_func(self):
|
|
196
|
+
"""
|
|
197
|
+
Pickle Filter Function.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
None
|
|
201
|
+
"""
|
|
202
|
+
if self.entity_filter_function:
|
|
203
|
+
new_source = self.__format_function_source(self.entity_filter_function)
|
|
204
|
+
self.assets.add_or_replace_asset(Asset(filename='entity_filter_func.py', content=new_source))
|
|
205
|
+
|
|
206
|
+
@staticmethod
|
|
207
|
+
def __format_function_source(function: Callable) -> str:
|
|
208
|
+
"""
|
|
209
|
+
Formats the function source. Functions could be indented.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
function: Function to format
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Formatted function
|
|
216
|
+
"""
|
|
217
|
+
source = inspect.getsource(function).splitlines()
|
|
218
|
+
space_base = 0
|
|
219
|
+
while source[0][space_base] == " ":
|
|
220
|
+
space_base += 1
|
|
221
|
+
replace_expr = re.compile("^[ ]{" + str(space_base) + "}")
|
|
222
|
+
new_source = []
|
|
223
|
+
for line in source:
|
|
224
|
+
new_source.append(replace_expr.sub("", line))
|
|
225
|
+
return "\n".join(new_source)
|
|
226
|
+
|
|
227
|
+
def clear_exclude_patterns(self):
|
|
228
|
+
"""
|
|
229
|
+
Clear Exclude Patterns will remove all current rules.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
None
|
|
233
|
+
"""
|
|
234
|
+
self.exclude_patterns = []
|
|
235
|
+
|
|
236
|
+
def pre_creation(self, platform: IPlatform) -> None:
|
|
237
|
+
"""
|
|
238
|
+
Pre-Creation.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
platform: Platform
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
None
|
|
245
|
+
"""
|
|
246
|
+
self._filter_workitem_pre_creation(platform)
|
|
247
|
+
if self.name is None or self.name == "idmtools workflow item":
|
|
248
|
+
self.name = self.__generate_name()
|
|
249
|
+
|
|
250
|
+
current_dir = PurePath(__file__).parent
|
|
251
|
+
# Add our ssm script
|
|
252
|
+
self.assets.add_or_replace_asset(self._ssmt_script)
|
|
253
|
+
utils_dir = current_dir.joinpath("ssmt_utils")
|
|
254
|
+
# Add dependencies
|
|
255
|
+
if self._ssmt_depends:
|
|
256
|
+
for file in self._ssmt_depends:
|
|
257
|
+
self.assets.add_or_replace_asset(utils_dir.joinpath(file))
|
|
258
|
+
self.__pickle_pre_run()
|
|
259
|
+
self.__pickle_format_func()
|
|
260
|
+
self.__pickle_filter_func()
|
|
261
|
+
self.task.command = CommandLine(self.create_command(), is_windows=False)
|
|
262
|
+
if IdmConfigParser.is_output_enabled():
|
|
263
|
+
user_logger.info("Creating Watcher")
|
|
264
|
+
|
|
265
|
+
super().pre_creation(platform)
|
|
266
|
+
|
|
267
|
+
def _filter_workitem_pre_creation(self, platform):
|
|
268
|
+
"""
|
|
269
|
+
Filter the workitem before creation.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
platform: Platform
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
None
|
|
276
|
+
|
|
277
|
+
Raises:
|
|
278
|
+
AtLeastOneItemToWatch - If there are not items we are watching, we cannot run our workitem.
|
|
279
|
+
"""
|
|
280
|
+
if self.total_items_watched() == 0:
|
|
281
|
+
raise AtLeastOneItemToWatch("You must specify at least one item to watch")
|
|
282
|
+
if len(self.file_patterns) == 0:
|
|
283
|
+
logger.info("No file pattern specified. Setting to default pattern '**' to filter for all outputs")
|
|
284
|
+
self.file_patterns.append("**")
|
|
285
|
+
self.__convert_ids_to_items(platform)
|
|
286
|
+
self.__ensure_all_dependencies_created_and_in_proper_env(platform)
|
|
287
|
+
|
|
288
|
+
def __generate_name(self) -> str:
|
|
289
|
+
"""
|
|
290
|
+
Generate Automatic name for the WorkItem.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Return generated name
|
|
294
|
+
"""
|
|
295
|
+
total_items = 0
|
|
296
|
+
name = None
|
|
297
|
+
first_item_type = None
|
|
298
|
+
for prop, item_type in WI_PROPERTY_MAP.items():
|
|
299
|
+
item_type_count = len(getattr(self, prop))
|
|
300
|
+
total_items += len(getattr(self, prop))
|
|
301
|
+
# get first item as we iterate through
|
|
302
|
+
if name is None and item_type_count:
|
|
303
|
+
item = getattr(self, prop)
|
|
304
|
+
name = item[0].id if hasattr(item[0], 'id') else item[0]
|
|
305
|
+
first_item_type = item_type
|
|
306
|
+
|
|
307
|
+
if total_items > 1:
|
|
308
|
+
return "Filter outputs"
|
|
309
|
+
|
|
310
|
+
return f"Filter outputs for {str(first_item_type).replace('ItemType.', '').lower().capitalize()} {name}"
|
|
311
|
+
|
|
312
|
+
def __convert_ids_to_items(self, platform):
|
|
313
|
+
"""
|
|
314
|
+
Convert our ids to items.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
platform: Platform object
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
None
|
|
321
|
+
"""
|
|
322
|
+
for prop, item_type in WI_PROPERTY_MAP.items():
|
|
323
|
+
new_items = []
|
|
324
|
+
for item in getattr(self, prop):
|
|
325
|
+
if isinstance(item, (str, PathLike)):
|
|
326
|
+
# first test if the item is a file
|
|
327
|
+
if os.path.exists(item):
|
|
328
|
+
item = platform.get_item_from_id_file(item, item_type)
|
|
329
|
+
else:
|
|
330
|
+
if isinstance(item, PathLike):
|
|
331
|
+
user_logger.warning(f"Could not find a file with name {item}. Attempting to load as an ID")
|
|
332
|
+
item = platform.get_item(item, item_type, force=True)
|
|
333
|
+
new_items.append(item)
|
|
334
|
+
setattr(self, prop, new_items)
|
|
335
|
+
|
|
336
|
+
def __ensure_all_dependencies_created_and_in_proper_env(self, platform: COMPSPlatform):
|
|
337
|
+
"""
|
|
338
|
+
Ensures all items we are watching.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
platform:
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
None
|
|
345
|
+
|
|
346
|
+
Raises:
|
|
347
|
+
CrossEnvironmentFilterNotSupport - If items are from multiple environemnts.
|
|
348
|
+
"""
|
|
349
|
+
for work_prop, item_type in WI_PROPERTY_MAP.items():
|
|
350
|
+
items = getattr(self, work_prop)
|
|
351
|
+
for item in items:
|
|
352
|
+
if item.status is None:
|
|
353
|
+
if isinstance(item, IRunnableEntity):
|
|
354
|
+
item.run(platform=platform)
|
|
355
|
+
# this should only be sim in this branch
|
|
356
|
+
elif isinstance(item, Simulation):
|
|
357
|
+
item.parent.run(platform=platform)
|
|
358
|
+
elif isinstance(item, AssetCollection):
|
|
359
|
+
platform.create_items(item)
|
|
360
|
+
if item_type in [ItemType.SIMULATION, ItemType.WORKFLOW_ITEM, ItemType.EXPERIMENT]:
|
|
361
|
+
fail = False
|
|
362
|
+
po = item.get_platform_object(platform=platform)
|
|
363
|
+
if item_type in [ItemType.SIMULATION, ItemType.EXPERIMENT]:
|
|
364
|
+
if item_type == ItemType.SIMULATION and po.configuration is None or po.configuration.environment_name is None:
|
|
365
|
+
po = item.parent.get_platform_object(platform=platform)
|
|
366
|
+
if po.configuration is None:
|
|
367
|
+
user_logger.warning(f"Cannot determine environment of item of type {item_type} with id of {item.id}. Running filter against items in other environments will result in an error")
|
|
368
|
+
elif po.configuration.environment_name.lower() != platform.environment.lower():
|
|
369
|
+
fail = True
|
|
370
|
+
elif item_type == ItemType.WORKFLOW_ITEM and po.environment_name.lower() != platform.environment.lower():
|
|
371
|
+
fail = True
|
|
372
|
+
if fail:
|
|
373
|
+
raise CrossEnvironmentFilterNotSupport(f"You cannot filter files between environment. In this case, the {item_type.value} {item.id} is in {po.configuration.environment_name} but you are running your workitem in {platform.environment}")
|
|
374
|
+
|
|
375
|
+
def total_items_watched(self) -> int:
|
|
376
|
+
"""
|
|
377
|
+
Returns the number of items watched.
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Total number of items watched
|
|
381
|
+
"""
|
|
382
|
+
total = 0
|
|
383
|
+
for item_type in WI_PROPERTY_MAP.keys():
|
|
384
|
+
total += len(getattr(self, item_type))
|
|
385
|
+
return total
|
|
386
|
+
|
|
387
|
+
def run_after_by_id(self, item_id: str, item_type: ItemType, platform: COMPSPlatform = None):
|
|
388
|
+
"""
|
|
389
|
+
Runs the workitem after an existing item finishes.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
item_id: ItemId
|
|
393
|
+
item_type: ItemType
|
|
394
|
+
platform: Platform
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
None
|
|
398
|
+
|
|
399
|
+
Raises:
|
|
400
|
+
ValueError - If item_type is not an experiment, simulation, or workflow item
|
|
401
|
+
"""
|
|
402
|
+
if item_type not in [ItemType.EXPERIMENT, ItemType.SIMULATION, ItemType.WORKFLOW_ITEM]:
|
|
403
|
+
raise ValueError("Currently only Experiment, Simuation, and WorkFlowItems can be filtered")
|
|
404
|
+
p = super()._check_for_platform_from_context(platform)
|
|
405
|
+
extra = dict()
|
|
406
|
+
item = p.get_item(item_id=item_id, item_type=item_type, **extra)
|
|
407
|
+
if item:
|
|
408
|
+
self.from_items(item)
|
|
409
|
+
raise FileNotFoundError(f"Cannot find the item with {item_id} of type {item_type}")
|
|
410
|
+
|
|
411
|
+
def from_items(self, item: Union[FilterableSSMTItem, List[FilterableSSMTItem]]):
|
|
412
|
+
"""
|
|
413
|
+
Add items to load assets from.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
item: Item or list of items to watch.
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
None
|
|
420
|
+
|
|
421
|
+
Raises:
|
|
422
|
+
ValueError - If any items specified are not an Experiment, Simulation or WorkItem
|
|
423
|
+
|
|
424
|
+
Notes:
|
|
425
|
+
We should add suite support in the future if possible. This should be done in client side by converting suite to list of experiments.
|
|
426
|
+
"""
|
|
427
|
+
if not isinstance(item, list):
|
|
428
|
+
items_to_add = [item]
|
|
429
|
+
else:
|
|
430
|
+
items_to_add = item
|
|
431
|
+
for i in items_to_add:
|
|
432
|
+
if isinstance(i, Experiment):
|
|
433
|
+
self.related_experiments.append(i)
|
|
434
|
+
elif isinstance(i, Simulation):
|
|
435
|
+
self.related_simulations.append(i)
|
|
436
|
+
elif isinstance(i, IWorkflowItem):
|
|
437
|
+
self.related_work_items.append(i)
|
|
438
|
+
else:
|
|
439
|
+
raise ValueError("We can only filter the output of experiments, simulations, and workitems")
|
|
440
|
+
|
|
441
|
+
def wait(self, wait_on_done_progress: bool = True, timeout: int = None, refresh_interval=None, platform: 'COMPSPlatform' = None) -> Union[None]:
|
|
442
|
+
"""
|
|
443
|
+
Waits on Filter Workitem to finish. This first waits on any dependent items to finish(Experiment/Simulation/WorkItems).
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
wait_on_done_progress: When set to true, a progress bar will be shown from the item
|
|
447
|
+
timeout: Timeout for waiting on item. If none, wait will be forever
|
|
448
|
+
refresh_interval: How often to refresh progress
|
|
449
|
+
platform: Platform
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
AssetCollection created if item succeeds
|
|
453
|
+
"""
|
|
454
|
+
# wait on related items before we wait on our item
|
|
455
|
+
p = super()._check_for_platform_from_context(platform)
|
|
456
|
+
opts = dict(wait_on_done_progress=wait_on_done_progress, timeout=timeout, refresh_interval=refresh_interval, platform=p)
|
|
457
|
+
self._wait_on_children(**opts)
|
|
458
|
+
|
|
459
|
+
super().wait(**opts)
|
|
460
|
+
|
|
461
|
+
def _wait_on_children(self, **opts):
|
|
462
|
+
"""
|
|
463
|
+
Wait on children implementation.
|
|
464
|
+
|
|
465
|
+
Loops through the relations and ensure all are done before waiting on ourselve
|
|
466
|
+
Args:
|
|
467
|
+
**opts:
|
|
468
|
+
|
|
469
|
+
Returns:
|
|
470
|
+
None
|
|
471
|
+
"""
|
|
472
|
+
if logger.isEnabledFor(DEBUG):
|
|
473
|
+
logger.debug("Wait on items being watched to finish running")
|
|
474
|
+
for item_type in WI_PROPERTY_MAP.keys():
|
|
475
|
+
items = getattr(self, item_type)
|
|
476
|
+
for item in items:
|
|
477
|
+
# The only two done states in idmtools are SUCCEEDED and FAILED
|
|
478
|
+
if item.status not in [EntityStatus.SUCCEEDED, EntityStatus.FAILED]:
|
|
479
|
+
# We only can wait on Experiments and Workitems. For simulations, we use the parent
|
|
480
|
+
if isinstance(item, IRunnableEntity):
|
|
481
|
+
item.wait(**opts)
|
|
482
|
+
# user simulation's parent to wait
|
|
483
|
+
elif isinstance(item, Simulation):
|
|
484
|
+
if item.parent is None:
|
|
485
|
+
if not item.parent_id:
|
|
486
|
+
raise ValueError(f"Cannot determine simulation {item.id}'s parent and item still in progress. Please wait on it to complete before {self.__class__.name}")
|
|
487
|
+
else:
|
|
488
|
+
item.parent = Experiment.from_id(item.parent_id)
|
|
489
|
+
item.parent.wait(**opts)
|
|
490
|
+
if logger.isEnabledFor(DEBUG):
|
|
491
|
+
logger.debug(f"Done waiting on items watching. Now waiting on {self.__class__.name}: {self.id}")
|
|
492
|
+
super().wait(**opts)
|
|
493
|
+
|
|
494
|
+
def fetch_error(self, print_error: bool = True) -> Union[Dict]:
|
|
495
|
+
"""
|
|
496
|
+
Fetches the error from a WorkItem.
|
|
497
|
+
|
|
498
|
+
Args:
|
|
499
|
+
print_error: Should error be printed. If false, error will be returned
|
|
500
|
+
|
|
501
|
+
Returns:
|
|
502
|
+
Error info
|
|
503
|
+
"""
|
|
504
|
+
if self.status != EntityStatus.FAILED:
|
|
505
|
+
raise ValueError("You cannot fetch error if the items is not in a failed state")
|
|
506
|
+
|
|
507
|
+
try:
|
|
508
|
+
file = self.platform.get_files(self, ['error_reason.json'])
|
|
509
|
+
file = file['error_reason.json'].decode('utf-8')
|
|
510
|
+
file = json.loads(file)
|
|
511
|
+
if print_error:
|
|
512
|
+
user_logger.error(f'Error from server: {file["args"][0]}')
|
|
513
|
+
if 'doc_link' in file:
|
|
514
|
+
user_logger.error(get_help_version_url(file['doc_link']))
|
|
515
|
+
else:
|
|
516
|
+
user_logger.error(user_logger.error('\n'.join(file['tb'])))
|
|
517
|
+
return file
|
|
518
|
+
except Exception as e:
|
|
519
|
+
logger.exception(e)
|