idmtools 0.0.0.dev0__py3-none-any.whl → 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. idmtools/__init__.py +27 -8
  2. idmtools/analysis/__init__.py +5 -0
  3. idmtools/analysis/add_analyzer.py +89 -0
  4. idmtools/analysis/analyze_manager.py +490 -0
  5. idmtools/analysis/csv_analyzer.py +103 -0
  6. idmtools/analysis/download_analyzer.py +96 -0
  7. idmtools/analysis/map_worker_entry.py +100 -0
  8. idmtools/analysis/platform_analysis_bootstrap.py +94 -0
  9. idmtools/analysis/platform_anaylsis.py +291 -0
  10. idmtools/analysis/tags_analyzer.py +93 -0
  11. idmtools/assets/__init__.py +9 -0
  12. idmtools/assets/asset.py +453 -0
  13. idmtools/assets/asset_collection.py +514 -0
  14. idmtools/assets/content_handlers.py +19 -0
  15. idmtools/assets/errors.py +23 -0
  16. idmtools/assets/file_list.py +191 -0
  17. idmtools/builders/__init__.py +11 -0
  18. idmtools/builders/arm_simulation_builder.py +152 -0
  19. idmtools/builders/csv_simulation_builder.py +76 -0
  20. idmtools/builders/simulation_builder.py +348 -0
  21. idmtools/builders/sweep_arm.py +109 -0
  22. idmtools/builders/yaml_simulation_builder.py +82 -0
  23. idmtools/config/__init__.py +7 -0
  24. idmtools/config/idm_config_parser.py +486 -0
  25. idmtools/core/__init__.py +10 -0
  26. idmtools/core/cache_enabled.py +114 -0
  27. idmtools/core/context.py +68 -0
  28. idmtools/core/docker_task.py +207 -0
  29. idmtools/core/enums.py +51 -0
  30. idmtools/core/exceptions.py +91 -0
  31. idmtools/core/experiment_factory.py +71 -0
  32. idmtools/core/id_file.py +70 -0
  33. idmtools/core/interfaces/__init__.py +5 -0
  34. idmtools/core/interfaces/entity_container.py +64 -0
  35. idmtools/core/interfaces/iassets_enabled.py +58 -0
  36. idmtools/core/interfaces/ientity.py +331 -0
  37. idmtools/core/interfaces/iitem.py +206 -0
  38. idmtools/core/interfaces/imetadata_operations.py +89 -0
  39. idmtools/core/interfaces/inamed_entity.py +17 -0
  40. idmtools/core/interfaces/irunnable_entity.py +159 -0
  41. idmtools/core/logging.py +387 -0
  42. idmtools/core/platform_factory.py +316 -0
  43. idmtools/core/system_information.py +104 -0
  44. idmtools/core/task_factory.py +145 -0
  45. idmtools/entities/__init__.py +10 -0
  46. idmtools/entities/command_line.py +229 -0
  47. idmtools/entities/command_task.py +155 -0
  48. idmtools/entities/experiment.py +787 -0
  49. idmtools/entities/generic_workitem.py +43 -0
  50. idmtools/entities/ianalyzer.py +163 -0
  51. idmtools/entities/iplatform.py +1106 -0
  52. idmtools/entities/iplatform_default.py +39 -0
  53. idmtools/entities/iplatform_ops/__init__.py +5 -0
  54. idmtools/entities/iplatform_ops/iplatform_asset_collection_operations.py +148 -0
  55. idmtools/entities/iplatform_ops/iplatform_experiment_operations.py +415 -0
  56. idmtools/entities/iplatform_ops/iplatform_simulation_operations.py +315 -0
  57. idmtools/entities/iplatform_ops/iplatform_suite_operations.py +322 -0
  58. idmtools/entities/iplatform_ops/iplatform_workflowitem_operations.py +301 -0
  59. idmtools/entities/iplatform_ops/utils.py +185 -0
  60. idmtools/entities/itask.py +316 -0
  61. idmtools/entities/iworkflow_item.py +167 -0
  62. idmtools/entities/platform_requirements.py +20 -0
  63. idmtools/entities/relation_type.py +14 -0
  64. idmtools/entities/simulation.py +255 -0
  65. idmtools/entities/suite.py +188 -0
  66. idmtools/entities/task_proxy.py +37 -0
  67. idmtools/entities/templated_simulation.py +325 -0
  68. idmtools/frozen/frozen_dict.py +71 -0
  69. idmtools/frozen/frozen_list.py +66 -0
  70. idmtools/frozen/frozen_set.py +86 -0
  71. idmtools/frozen/frozen_tuple.py +18 -0
  72. idmtools/frozen/frozen_utils.py +179 -0
  73. idmtools/frozen/ifrozen.py +66 -0
  74. idmtools/plugins/__init__.py +5 -0
  75. idmtools/plugins/git_commit.py +117 -0
  76. idmtools/registry/__init__.py +4 -0
  77. idmtools/registry/experiment_specification.py +105 -0
  78. idmtools/registry/functions.py +28 -0
  79. idmtools/registry/hook_specs.py +132 -0
  80. idmtools/registry/master_plugin_registry.py +51 -0
  81. idmtools/registry/platform_specification.py +138 -0
  82. idmtools/registry/plugin_specification.py +129 -0
  83. idmtools/registry/task_specification.py +104 -0
  84. idmtools/registry/utils.py +119 -0
  85. idmtools/services/__init__.py +5 -0
  86. idmtools/services/ipersistance_service.py +135 -0
  87. idmtools/services/platforms.py +13 -0
  88. idmtools/utils/__init__.py +5 -0
  89. idmtools/utils/caller.py +24 -0
  90. idmtools/utils/collections.py +246 -0
  91. idmtools/utils/command_line.py +45 -0
  92. idmtools/utils/decorators.py +300 -0
  93. idmtools/utils/display/__init__.py +22 -0
  94. idmtools/utils/display/displays.py +181 -0
  95. idmtools/utils/display/settings.py +25 -0
  96. idmtools/utils/dropbox_location.py +30 -0
  97. idmtools/utils/entities.py +127 -0
  98. idmtools/utils/file.py +72 -0
  99. idmtools/utils/file_parser.py +151 -0
  100. idmtools/utils/filter_simulations.py +182 -0
  101. idmtools/utils/filters/__init__.py +5 -0
  102. idmtools/utils/filters/asset_filters.py +88 -0
  103. idmtools/utils/general.py +286 -0
  104. idmtools/utils/gitrepo.py +336 -0
  105. idmtools/utils/hashing.py +239 -0
  106. idmtools/utils/info.py +124 -0
  107. idmtools/utils/json.py +82 -0
  108. idmtools/utils/language.py +107 -0
  109. idmtools/utils/local_os.py +40 -0
  110. idmtools/utils/time.py +22 -0
  111. idmtools-0.0.2.dist-info/METADATA +120 -0
  112. idmtools-0.0.2.dist-info/RECORD +116 -0
  113. idmtools-0.0.2.dist-info/entry_points.txt +9 -0
  114. idmtools-0.0.2.dist-info/licenses/LICENSE.TXT +3 -0
  115. idmtools-0.0.0.dev0.dist-info/METADATA +0 -41
  116. idmtools-0.0.0.dev0.dist-info/RECORD +0 -5
  117. {idmtools-0.0.0.dev0.dist-info → idmtools-0.0.2.dist-info}/WHEEL +0 -0
  118. {idmtools-0.0.0.dev0.dist-info → idmtools-0.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,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!")