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.
- idmtools/__init__.py +27 -8
- idmtools/analysis/__init__.py +5 -0
- idmtools/analysis/add_analyzer.py +89 -0
- idmtools/analysis/analyze_manager.py +490 -0
- idmtools/analysis/csv_analyzer.py +103 -0
- idmtools/analysis/download_analyzer.py +96 -0
- idmtools/analysis/map_worker_entry.py +100 -0
- idmtools/analysis/platform_analysis_bootstrap.py +94 -0
- idmtools/analysis/platform_anaylsis.py +291 -0
- idmtools/analysis/tags_analyzer.py +93 -0
- idmtools/assets/__init__.py +9 -0
- idmtools/assets/asset.py +453 -0
- idmtools/assets/asset_collection.py +514 -0
- idmtools/assets/content_handlers.py +19 -0
- idmtools/assets/errors.py +23 -0
- idmtools/assets/file_list.py +191 -0
- idmtools/builders/__init__.py +11 -0
- idmtools/builders/arm_simulation_builder.py +152 -0
- idmtools/builders/csv_simulation_builder.py +76 -0
- idmtools/builders/simulation_builder.py +348 -0
- idmtools/builders/sweep_arm.py +109 -0
- idmtools/builders/yaml_simulation_builder.py +82 -0
- idmtools/config/__init__.py +7 -0
- idmtools/config/idm_config_parser.py +486 -0
- idmtools/core/__init__.py +10 -0
- idmtools/core/cache_enabled.py +114 -0
- idmtools/core/context.py +68 -0
- idmtools/core/docker_task.py +207 -0
- idmtools/core/enums.py +51 -0
- idmtools/core/exceptions.py +91 -0
- idmtools/core/experiment_factory.py +71 -0
- idmtools/core/id_file.py +70 -0
- idmtools/core/interfaces/__init__.py +5 -0
- idmtools/core/interfaces/entity_container.py +64 -0
- idmtools/core/interfaces/iassets_enabled.py +58 -0
- idmtools/core/interfaces/ientity.py +331 -0
- idmtools/core/interfaces/iitem.py +206 -0
- idmtools/core/interfaces/imetadata_operations.py +89 -0
- idmtools/core/interfaces/inamed_entity.py +17 -0
- idmtools/core/interfaces/irunnable_entity.py +159 -0
- idmtools/core/logging.py +387 -0
- idmtools/core/platform_factory.py +316 -0
- idmtools/core/system_information.py +104 -0
- idmtools/core/task_factory.py +145 -0
- idmtools/entities/__init__.py +10 -0
- idmtools/entities/command_line.py +229 -0
- idmtools/entities/command_task.py +155 -0
- idmtools/entities/experiment.py +787 -0
- idmtools/entities/generic_workitem.py +43 -0
- idmtools/entities/ianalyzer.py +163 -0
- idmtools/entities/iplatform.py +1106 -0
- idmtools/entities/iplatform_default.py +39 -0
- idmtools/entities/iplatform_ops/__init__.py +5 -0
- idmtools/entities/iplatform_ops/iplatform_asset_collection_operations.py +148 -0
- idmtools/entities/iplatform_ops/iplatform_experiment_operations.py +415 -0
- idmtools/entities/iplatform_ops/iplatform_simulation_operations.py +315 -0
- idmtools/entities/iplatform_ops/iplatform_suite_operations.py +322 -0
- idmtools/entities/iplatform_ops/iplatform_workflowitem_operations.py +301 -0
- idmtools/entities/iplatform_ops/utils.py +185 -0
- idmtools/entities/itask.py +316 -0
- idmtools/entities/iworkflow_item.py +167 -0
- idmtools/entities/platform_requirements.py +20 -0
- idmtools/entities/relation_type.py +14 -0
- idmtools/entities/simulation.py +255 -0
- idmtools/entities/suite.py +188 -0
- idmtools/entities/task_proxy.py +37 -0
- idmtools/entities/templated_simulation.py +325 -0
- idmtools/frozen/frozen_dict.py +71 -0
- idmtools/frozen/frozen_list.py +66 -0
- idmtools/frozen/frozen_set.py +86 -0
- idmtools/frozen/frozen_tuple.py +18 -0
- idmtools/frozen/frozen_utils.py +179 -0
- idmtools/frozen/ifrozen.py +66 -0
- idmtools/plugins/__init__.py +5 -0
- idmtools/plugins/git_commit.py +117 -0
- idmtools/registry/__init__.py +4 -0
- idmtools/registry/experiment_specification.py +105 -0
- idmtools/registry/functions.py +28 -0
- idmtools/registry/hook_specs.py +132 -0
- idmtools/registry/master_plugin_registry.py +51 -0
- idmtools/registry/platform_specification.py +138 -0
- idmtools/registry/plugin_specification.py +129 -0
- idmtools/registry/task_specification.py +104 -0
- idmtools/registry/utils.py +119 -0
- idmtools/services/__init__.py +5 -0
- idmtools/services/ipersistance_service.py +135 -0
- idmtools/services/platforms.py +13 -0
- idmtools/utils/__init__.py +5 -0
- idmtools/utils/caller.py +24 -0
- idmtools/utils/collections.py +246 -0
- idmtools/utils/command_line.py +45 -0
- idmtools/utils/decorators.py +300 -0
- idmtools/utils/display/__init__.py +22 -0
- idmtools/utils/display/displays.py +181 -0
- idmtools/utils/display/settings.py +25 -0
- idmtools/utils/dropbox_location.py +30 -0
- idmtools/utils/entities.py +127 -0
- idmtools/utils/file.py +72 -0
- idmtools/utils/file_parser.py +151 -0
- idmtools/utils/filter_simulations.py +182 -0
- idmtools/utils/filters/__init__.py +5 -0
- idmtools/utils/filters/asset_filters.py +88 -0
- idmtools/utils/general.py +286 -0
- idmtools/utils/gitrepo.py +336 -0
- idmtools/utils/hashing.py +239 -0
- idmtools/utils/info.py +124 -0
- idmtools/utils/json.py +82 -0
- idmtools/utils/language.py +107 -0
- idmtools/utils/local_os.py +40 -0
- idmtools/utils/time.py +22 -0
- idmtools-0.0.2.dist-info/METADATA +120 -0
- idmtools-0.0.2.dist-info/RECORD +116 -0
- idmtools-0.0.2.dist-info/entry_points.txt +9 -0
- idmtools-0.0.2.dist-info/licenses/LICENSE.TXT +3 -0
- idmtools-0.0.0.dev0.dist-info/METADATA +0 -41
- idmtools-0.0.0.dev0.dist-info/RECORD +0 -5
- {idmtools-0.0.0.dev0.dist-info → idmtools-0.0.2.dist-info}/WHEEL +0 -0
- {idmtools-0.0.0.dev0.dist-info → idmtools-0.0.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
"""
|
|
2
|
+
idmtools assets collection package.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import copy
|
|
7
|
+
import os
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from logging import getLogger
|
|
10
|
+
from os import PathLike
|
|
11
|
+
from typing import List, NoReturn, TypeVar, Union, Any, Dict, TYPE_CHECKING
|
|
12
|
+
from idmtools.assets import Asset, TAssetList
|
|
13
|
+
from idmtools.assets import TAssetFilterList
|
|
14
|
+
from idmtools.assets.errors import DuplicatedAssetError
|
|
15
|
+
from idmtools.core import FilterMode, ItemType
|
|
16
|
+
from idmtools.core.interfaces.ientity import IEntity
|
|
17
|
+
from idmtools.core.interfaces.iitem import IItem
|
|
18
|
+
from idmtools.utils.entities import get_default_tags
|
|
19
|
+
from idmtools.utils.file import scan_directory
|
|
20
|
+
from idmtools.utils.filters.asset_filters import default_asset_file_filter
|
|
21
|
+
from idmtools.utils.info import get_doc_base_url
|
|
22
|
+
|
|
23
|
+
IGNORE_DIRECTORIES = ['.git', '.svn', '.venv', '.idea', '.Rproj.user', '$RECYCLE.BIN', '__pycache__']
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
26
|
+
from idmtools.entities.iplatform import IPlatform
|
|
27
|
+
|
|
28
|
+
user_logger = getLogger('user')
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(repr=False)
|
|
32
|
+
class AssetCollection(IEntity):
|
|
33
|
+
"""
|
|
34
|
+
A class that represents a collection of assets.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
#: Assets for collection
|
|
38
|
+
assets: List[Asset] = field(default=None)
|
|
39
|
+
#: ItemType so platform knows how to handle item properly
|
|
40
|
+
item_type: ItemType = field(default=ItemType.ASSETCOLLECTION, compare=False)
|
|
41
|
+
|
|
42
|
+
def __init__(self, assets: Union[List[str], TAssetList, 'AssetCollection'] = None, tags=None):
|
|
43
|
+
"""
|
|
44
|
+
A constructor.
|
|
45
|
+
|
|
46
|
+
Args: assets: An optional list of assets to create the collection with.
|
|
47
|
+
tags: dict: tags associated with asset collection
|
|
48
|
+
"""
|
|
49
|
+
super().__init__()
|
|
50
|
+
if tags is None:
|
|
51
|
+
tags = {}
|
|
52
|
+
self.item_type = ItemType.ASSETCOLLECTION
|
|
53
|
+
if isinstance(assets, AssetCollection):
|
|
54
|
+
self.assets = copy.deepcopy(assets.assets)
|
|
55
|
+
elif assets:
|
|
56
|
+
self.assets = []
|
|
57
|
+
for asset in assets:
|
|
58
|
+
self.add_or_replace_asset(asset)
|
|
59
|
+
else:
|
|
60
|
+
self.assets = []
|
|
61
|
+
|
|
62
|
+
self.tags = self.tags or tags
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def from_id(cls, item_id: str, platform: 'IPlatform' = None, as_copy: bool = False, # noqa E821
|
|
66
|
+
**kwargs) -> 'AssetCollection':
|
|
67
|
+
"""
|
|
68
|
+
Loads a AssetCollection from id.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
item_id: Asset Collection ID
|
|
72
|
+
platform: Platform Object
|
|
73
|
+
as_copy: Should you load the object as a copy. When True, the contents of AC are copied, but not the id. Useful when editing ACs
|
|
74
|
+
**kwargs:
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
AssetCollection
|
|
78
|
+
"""
|
|
79
|
+
if item_id is None:
|
|
80
|
+
raise ValueError("You must specify an id")
|
|
81
|
+
item = super(AssetCollection, cls).from_id(item_id, platform, **kwargs)
|
|
82
|
+
return AssetCollection(item) if as_copy else item
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def from_directory(cls, assets_directory: str, recursive: bool = True, flatten: bool = False,
|
|
86
|
+
filters: 'TAssetFilterList' = None, filters_mode: FilterMode = FilterMode.OR, # noqa: F821
|
|
87
|
+
relative_path: str = None) -> 'TAssetCollection':
|
|
88
|
+
"""
|
|
89
|
+
Fill up an :class:`AssetCollection` from the specified directory.
|
|
90
|
+
|
|
91
|
+
See :meth:`~AssetCollection.assets_from_directory` for arguments.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
A created :class:`AssetCollection` object.
|
|
95
|
+
"""
|
|
96
|
+
assets = cls.assets_from_directory(assets_directory, recursive, flatten, filters, filters_mode, relative_path)
|
|
97
|
+
return cls(assets=assets)
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def assets_from_directory(assets_directory: Union[str, PathLike], recursive: bool = True, flatten: bool = False,
|
|
101
|
+
filters: 'TAssetFilterList' = None, # noqa: F821
|
|
102
|
+
filters_mode: FilterMode = FilterMode.OR,
|
|
103
|
+
forced_relative_path: str = None, no_ignore: bool = False) -> List[Asset]:
|
|
104
|
+
"""
|
|
105
|
+
Create assets for files in a given directory.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
assets_directory: The root directory of the assets.
|
|
109
|
+
recursive: True to recursively traverse the subdirectory.
|
|
110
|
+
flatten: Put all the files in root regardless of whether they were in a subdirectory or not.
|
|
111
|
+
filters: A list of filters to apply to the assets. The filters are functions taking an
|
|
112
|
+
:class:`~idmtools.assets.asset.Asset` as argument and returning true or false. True adds the asset to
|
|
113
|
+
the collection; False filters it out. See :meth:`~idmtools.utils.filters.asset_filters`.
|
|
114
|
+
filters_mode: When given multiple filters, either OR or AND the results.
|
|
115
|
+
forced_relative_path: Prefix a relative path to the path created from the root directory.
|
|
116
|
+
no_ignore: Should we not ignore common directories(.git, .svn. etc) The full list is defined in IGNORE_DIRECTORIES
|
|
117
|
+
|
|
118
|
+
Examples:
|
|
119
|
+
For **relative_path**, given the following folder structure root/a/1,txt root/b.txt and
|
|
120
|
+
relative_path="test". Will return assets with relative path: test/a/1,txt and test/b.txt
|
|
121
|
+
|
|
122
|
+
Given the previous example, if flatten is also set to True, the following relative_path will be set:
|
|
123
|
+
/1.txt and /b.txt
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
A list of assets.
|
|
127
|
+
"""
|
|
128
|
+
if isinstance(assets_directory, PathLike):
|
|
129
|
+
assets_directory = str(assets_directory)
|
|
130
|
+
found_assets = []
|
|
131
|
+
for entry in scan_directory(assets_directory, recursive, IGNORE_DIRECTORIES if not no_ignore else None):
|
|
132
|
+
relative_path = os.path.relpath(os.path.dirname(entry.path), assets_directory)
|
|
133
|
+
found_assets.append(Asset(absolute_path=os.path.abspath(entry.path),
|
|
134
|
+
relative_path=None if relative_path == "." else relative_path,
|
|
135
|
+
filename=entry.name))
|
|
136
|
+
|
|
137
|
+
# Apply the default filter
|
|
138
|
+
found_assets = list(filter(default_asset_file_filter, found_assets))
|
|
139
|
+
|
|
140
|
+
# Operations on assets (filter, flatten, force relative_path)
|
|
141
|
+
assets = []
|
|
142
|
+
for asset in found_assets:
|
|
143
|
+
if filters:
|
|
144
|
+
results = [f(asset) for f in filters]
|
|
145
|
+
if filters_mode == FilterMode.OR and not any(results):
|
|
146
|
+
continue
|
|
147
|
+
if filters_mode == FilterMode.AND and not all(results):
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
if flatten:
|
|
151
|
+
asset.relative_path = None
|
|
152
|
+
|
|
153
|
+
if forced_relative_path:
|
|
154
|
+
asset.relative_path = os.path.join(forced_relative_path, asset.relative_path)
|
|
155
|
+
|
|
156
|
+
assets.append(asset)
|
|
157
|
+
|
|
158
|
+
return assets
|
|
159
|
+
|
|
160
|
+
def copy(self) -> 'AssetCollection':
|
|
161
|
+
"""
|
|
162
|
+
Copy our Asset Collection, removing ID and tags.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
New AssetCollection containing Assets from other AssetCollection
|
|
166
|
+
"""
|
|
167
|
+
return AssetCollection(self)
|
|
168
|
+
|
|
169
|
+
def add_directory(self, assets_directory: Union[str, PathLike], recursive: bool = True, flatten: bool = False,
|
|
170
|
+
filters: 'TAssetFilterList' = None, filters_mode: FilterMode = FilterMode.OR, # noqa: F821
|
|
171
|
+
relative_path: str = None, no_ignore: bool = False):
|
|
172
|
+
"""
|
|
173
|
+
Retrieve assets from the specified directory and add them to the collection.
|
|
174
|
+
|
|
175
|
+
See :meth:`~AssetCollection.assets_from_directory` for arguments.
|
|
176
|
+
"""
|
|
177
|
+
if isinstance(assets_directory, PathLike):
|
|
178
|
+
assets_directory = str(assets_directory)
|
|
179
|
+
assets = AssetCollection.assets_from_directory(assets_directory, recursive, flatten, filters, filters_mode, relative_path, no_ignore)
|
|
180
|
+
for asset in assets:
|
|
181
|
+
self.add_asset(asset)
|
|
182
|
+
|
|
183
|
+
def is_editable(self, error=False) -> bool:
|
|
184
|
+
"""
|
|
185
|
+
Checks whether Item is editable.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
error: Throw error is not
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
True if editable, False otherwise.
|
|
192
|
+
"""
|
|
193
|
+
if self.platform_id:
|
|
194
|
+
if error:
|
|
195
|
+
raise ValueError(
|
|
196
|
+
f"You cannot modify an already provisioned Asset Collection. If you want to modify an existing AssetCollection see the recipe {get_doc_base_url()}cookbook/assets.html#modifying-asset-collection")
|
|
197
|
+
return False
|
|
198
|
+
return True
|
|
199
|
+
|
|
200
|
+
def add_asset(self, asset: Union[Asset, str, PathLike], fail_on_duplicate: bool = True, fail_on_deep_comparison: bool = False, **kwargs): # noqa: F821
|
|
201
|
+
"""
|
|
202
|
+
Add an asset to the collection.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
asset: A string or an :class:`~idmtools.assets.asset.Asset` object to add. If a string, the string will be
|
|
206
|
+
used as the absolute_path and any kwargs will be passed to the Asset constructor
|
|
207
|
+
fail_on_duplicate: Raise a **DuplicateAssetError** if an asset is duplicated.
|
|
208
|
+
If not, simply replace it.
|
|
209
|
+
fail_on_deep_comparison: Fails only if deep comparison differs
|
|
210
|
+
**kwargs: Arguments to pass to Asset constructor when asset is a string
|
|
211
|
+
|
|
212
|
+
Raises:
|
|
213
|
+
DuplicatedAssetError - If fail_on_duplicate is true and the asset is already part of the collection
|
|
214
|
+
"""
|
|
215
|
+
self.is_editable(True)
|
|
216
|
+
if isinstance(asset, (str, PathLike)):
|
|
217
|
+
asset = Asset(absolute_path=str(asset), **kwargs)
|
|
218
|
+
# do a simple check first
|
|
219
|
+
if asset in self.assets:
|
|
220
|
+
if fail_on_duplicate:
|
|
221
|
+
if not fail_on_deep_comparison or self.find_index_of_asset(asset, deep_compare=True) is None:
|
|
222
|
+
raise DuplicatedAssetError(("File with same paths but different content provided", asset) if fail_on_deep_comparison else asset)
|
|
223
|
+
else:
|
|
224
|
+
# The equality not considering the content of the asset, even if it is already present
|
|
225
|
+
# nothing guarantees that the content is the same. So remove and add the fresh one.
|
|
226
|
+
self.assets.remove(asset)
|
|
227
|
+
self.assets.append(asset)
|
|
228
|
+
|
|
229
|
+
def __add__(self, other: Union[TAssetList, 'AssetCollection', Asset]) -> 'AssetCollection':
|
|
230
|
+
"""
|
|
231
|
+
Allows using the a + b syntax when adding AssetCollections.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
other: Either a list of assets, an assetcollection, or an asset
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Returns AssetCollection
|
|
238
|
+
"""
|
|
239
|
+
if not isinstance(other, (list, AssetCollection, Asset)):
|
|
240
|
+
raise ValueError('You can only items of type AssetCollections, List of Assets, or Assets to a '
|
|
241
|
+
'AssetCollection')
|
|
242
|
+
self.is_editable(True)
|
|
243
|
+
na = AssetCollection()
|
|
244
|
+
na.add_assets(self)
|
|
245
|
+
if isinstance(other, Asset):
|
|
246
|
+
na.add_asset(other, False)
|
|
247
|
+
else:
|
|
248
|
+
if len(self.assets) == 0:
|
|
249
|
+
na.assets = copy.deepcopy(other.assets)
|
|
250
|
+
return na
|
|
251
|
+
na.add_assets(other, False)
|
|
252
|
+
return na
|
|
253
|
+
|
|
254
|
+
def add_assets(self, assets: Union[TAssetList, 'AssetCollection'], fail_on_duplicate: bool = True, fail_on_deep_comparison: bool = False):
|
|
255
|
+
"""
|
|
256
|
+
Add assets to a collection.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
assets: An list of assets as either list or a collection
|
|
260
|
+
fail_on_duplicate: Raise a **DuplicateAssetError** if an asset is duplicated.
|
|
261
|
+
If not, simply replace it.
|
|
262
|
+
fail_on_deep_comparison: Fail if relative path/file is same but contents differ
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
None
|
|
266
|
+
"""
|
|
267
|
+
self.is_editable(True)
|
|
268
|
+
for asset in assets:
|
|
269
|
+
self.add_asset(asset, fail_on_duplicate, fail_on_deep_comparison)
|
|
270
|
+
|
|
271
|
+
def add_or_replace_asset(self, asset: Union[Asset, str, PathLike], fail_on_deep_comparison: bool = False):
|
|
272
|
+
"""
|
|
273
|
+
Add or replaces an asset in a collection.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
asset: Asset to add or replace
|
|
277
|
+
fail_on_deep_comparison: Fail replace if contents differ
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
None.
|
|
281
|
+
"""
|
|
282
|
+
self.is_editable(True)
|
|
283
|
+
tasset = Asset(asset) if isinstance(asset, (str, PathLike)) else asset
|
|
284
|
+
index = self.find_index_of_asset(tasset)
|
|
285
|
+
if index is not None:
|
|
286
|
+
if fail_on_deep_comparison and not tasset.deep_equals(self.assets[index]):
|
|
287
|
+
raise ValueError(f"Contents of file {asset.short_remote_path()} being replaced differs. To prevent unexpected behaviour, please review script or disable deep checks")
|
|
288
|
+
self.assets[index] = tasset
|
|
289
|
+
else:
|
|
290
|
+
self.assets.append(tasset)
|
|
291
|
+
|
|
292
|
+
def get_one(self, **kwargs):
|
|
293
|
+
"""
|
|
294
|
+
Get an asset out of the collection based on the filers passed.
|
|
295
|
+
|
|
296
|
+
Examples::
|
|
297
|
+
|
|
298
|
+
>>> a = AssetCollection()
|
|
299
|
+
>>> a.get_one(filename="filename.txt")
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
**kwargs: keyword argument representing the filters.
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
None or Asset if found.
|
|
306
|
+
|
|
307
|
+
"""
|
|
308
|
+
try:
|
|
309
|
+
return next(filter(lambda a: all(getattr(a, k) == kwargs.get(k) for k in kwargs), self.assets))
|
|
310
|
+
except StopIteration:
|
|
311
|
+
return None
|
|
312
|
+
|
|
313
|
+
def remove(self, **kwargs) -> NoReturn:
|
|
314
|
+
"""
|
|
315
|
+
Remove an asset from the AssetCollection based on keywords attributes.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
**kwargs: Filter for the asset to remove.
|
|
319
|
+
"""
|
|
320
|
+
self.is_editable(True)
|
|
321
|
+
if 'index' in kwargs:
|
|
322
|
+
return self.assets.remove(self.assets[kwargs.get('index')])
|
|
323
|
+
|
|
324
|
+
if 'asset' in kwargs:
|
|
325
|
+
return self.assets.remove(kwargs.get('asset'))
|
|
326
|
+
|
|
327
|
+
asset = self.get_one(**kwargs)
|
|
328
|
+
if asset:
|
|
329
|
+
self.assets.remove(asset)
|
|
330
|
+
|
|
331
|
+
def pop(self, **kwargs) -> Asset:
|
|
332
|
+
"""
|
|
333
|
+
Get and delete an asset based on keywords.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
**kwargs: Filter for the asset to pop.
|
|
337
|
+
|
|
338
|
+
"""
|
|
339
|
+
self.is_editable(True)
|
|
340
|
+
if not kwargs:
|
|
341
|
+
return self.assets.pop()
|
|
342
|
+
|
|
343
|
+
asset = self.get_one(**kwargs)
|
|
344
|
+
if asset:
|
|
345
|
+
self.assets.remove(asset)
|
|
346
|
+
return asset
|
|
347
|
+
|
|
348
|
+
def extend(self, assets: List[Asset], fail_on_duplicate: bool = True) -> NoReturn:
|
|
349
|
+
"""
|
|
350
|
+
Extend the collection with new assets.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
assets: Which assets to add
|
|
354
|
+
fail_on_duplicate: Fail if duplicated asset is included.
|
|
355
|
+
|
|
356
|
+
"""
|
|
357
|
+
self.is_editable(True)
|
|
358
|
+
for asset in assets:
|
|
359
|
+
self.add_asset(asset, fail_on_duplicate)
|
|
360
|
+
|
|
361
|
+
def clear(self):
|
|
362
|
+
"""
|
|
363
|
+
Clear the asset collection.
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
None
|
|
367
|
+
"""
|
|
368
|
+
self.is_editable(True)
|
|
369
|
+
self.assets.clear()
|
|
370
|
+
|
|
371
|
+
def set_all_persisted(self):
|
|
372
|
+
"""
|
|
373
|
+
Set all persisted.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
None
|
|
377
|
+
"""
|
|
378
|
+
for a in self:
|
|
379
|
+
a.persisted = True
|
|
380
|
+
|
|
381
|
+
@property
|
|
382
|
+
def count(self):
|
|
383
|
+
"""
|
|
384
|
+
Number of assets in collections.
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
Total assets
|
|
388
|
+
"""
|
|
389
|
+
return len(self.assets)
|
|
390
|
+
|
|
391
|
+
@IEntity.uid.getter
|
|
392
|
+
def uid(self):
|
|
393
|
+
"""
|
|
394
|
+
Uid of Asset Collection.
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
Asset Collection UID.
|
|
398
|
+
"""
|
|
399
|
+
if self.count == 0:
|
|
400
|
+
return None
|
|
401
|
+
return super().uid
|
|
402
|
+
|
|
403
|
+
def __len__(self):
|
|
404
|
+
"""
|
|
405
|
+
Length of the asset collection(number of assets).
|
|
406
|
+
|
|
407
|
+
Returns:
|
|
408
|
+
Total number of assets
|
|
409
|
+
"""
|
|
410
|
+
return len(self.assets)
|
|
411
|
+
|
|
412
|
+
def __getitem__(self, index):
|
|
413
|
+
"""
|
|
414
|
+
Get item from assets collection.
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
index: Index to load
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
Asset at the index.
|
|
421
|
+
"""
|
|
422
|
+
return self.assets[index]
|
|
423
|
+
|
|
424
|
+
def __iter__(self):
|
|
425
|
+
"""
|
|
426
|
+
Allow asset collection to be iterable.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
Iterator of assets
|
|
430
|
+
"""
|
|
431
|
+
yield from self.assets
|
|
432
|
+
|
|
433
|
+
def has_asset(self, absolute_path: str = None, filename: str = None, relative_path: str = None, checksum: str = None) -> bool:
|
|
434
|
+
"""
|
|
435
|
+
Search for asset by absolute_path or by filename.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
absolute_path: Absolute path of source file
|
|
439
|
+
filename: Destination filename
|
|
440
|
+
relative_path: Relative path of asset
|
|
441
|
+
checksum: Checksum of asset(optional)
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
True if asset exists, False otherwise
|
|
445
|
+
"""
|
|
446
|
+
# make a dummy asset
|
|
447
|
+
content = None if absolute_path or checksum else ""
|
|
448
|
+
tmp_asset = Asset(absolute_path=absolute_path, filename=filename, relative_path=relative_path, checksum=checksum, content=content)
|
|
449
|
+
return self.find_index_of_asset(tmp_asset) is not None
|
|
450
|
+
|
|
451
|
+
def find_index_of_asset(self, other: 'Asset', deep_compare: bool = False) -> Union[int, None]:
|
|
452
|
+
"""
|
|
453
|
+
Finds the index of asset by path or filename.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
other: Other asset
|
|
457
|
+
deep_compare: Should content as well as path be compared
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
Index number if found.
|
|
461
|
+
None if not found.
|
|
462
|
+
"""
|
|
463
|
+
for idx, asset in enumerate(self.assets):
|
|
464
|
+
if deep_compare and asset.deep_equals(other):
|
|
465
|
+
return idx
|
|
466
|
+
elif not deep_compare and asset == other:
|
|
467
|
+
return idx
|
|
468
|
+
return None
|
|
469
|
+
|
|
470
|
+
def pre_creation(self, platform: 'IPlatform') -> None:
|
|
471
|
+
"""
|
|
472
|
+
Pre-Creation hook for the asset collection.
|
|
473
|
+
|
|
474
|
+
Args:
|
|
475
|
+
platform: Platform object we are create asset collection on
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
None
|
|
479
|
+
|
|
480
|
+
Notes:
|
|
481
|
+
TODO - Make default tags optional
|
|
482
|
+
"""
|
|
483
|
+
if self.tags:
|
|
484
|
+
self.tags.update(get_default_tags())
|
|
485
|
+
else:
|
|
486
|
+
self.tags = get_default_tags()
|
|
487
|
+
IItem.pre_creation(self, platform)
|
|
488
|
+
|
|
489
|
+
def set_tags(self, tags: Dict[str, Any]):
|
|
490
|
+
"""
|
|
491
|
+
Set the tags on the asset collection.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
tags: Tags to set on the item
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
None
|
|
498
|
+
"""
|
|
499
|
+
self.tags = tags
|
|
500
|
+
|
|
501
|
+
def add_tags(self, tags: Dict[str, Any]):
|
|
502
|
+
"""
|
|
503
|
+
Add tags to the Asset collection.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
tags: Tags to add
|
|
507
|
+
|
|
508
|
+
Returns:
|
|
509
|
+
None
|
|
510
|
+
"""
|
|
511
|
+
self.tags.update(tags)
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
TAssetCollection = TypeVar("TAssetCollection", bound=AssetCollection)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
idmtools assets content handlers.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def json_handler(content):
|
|
10
|
+
"""
|
|
11
|
+
Dump a json to a string.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
content: Content to write
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
String on content
|
|
18
|
+
"""
|
|
19
|
+
return json.dumps(content)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
idmtools assets errors.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DuplicatedAssetError(Exception):
|
|
9
|
+
"""
|
|
10
|
+
DuplicatedAssetError is an error for when duplicates assets are added to collection.
|
|
11
|
+
|
|
12
|
+
Notes:
|
|
13
|
+
TODO: Add a doclink
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, asset: 'TAsset'): # noqa: F821
|
|
17
|
+
"""
|
|
18
|
+
Initialize our error.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
asset:Asset that caused the error
|
|
22
|
+
"""
|
|
23
|
+
super().__init__(f"{asset} is already present in the collection!")
|