idmtools 0.0.0.dev0__py3-none-any.whl → 0.0.3__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.3.dist-info/METADATA +120 -0
- idmtools-0.0.3.dist-info/RECORD +116 -0
- idmtools-0.0.3.dist-info/entry_points.txt +9 -0
- idmtools-0.0.3.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.3.dist-info}/WHEEL +0 -0
- {idmtools-0.0.0.dev0.dist-info → idmtools-0.0.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""
|
|
2
|
+
utilities for collections.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import typing
|
|
7
|
+
from itertools import tee
|
|
8
|
+
from typing import Tuple, List, Mapping, Union, Iterable, Generator
|
|
9
|
+
from more_itertools import take
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def cut_iterable_to(obj: Iterable, to: int) -> Tuple[Union[List, Mapping], int]:
|
|
13
|
+
"""
|
|
14
|
+
Cut an iterable to a certain length.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
obj: The iterable to cut.
|
|
18
|
+
to: The number of elements to return.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
A list or dictionary (depending on the type of object) of elements and
|
|
22
|
+
the remaining elements in the original list or dictionary.
|
|
23
|
+
"""
|
|
24
|
+
if isinstance(obj, dict):
|
|
25
|
+
slice = {k: v for (k, v) in take(to, obj.items())}
|
|
26
|
+
else:
|
|
27
|
+
slice = take(to, obj)
|
|
28
|
+
|
|
29
|
+
remaining = len(obj) - to
|
|
30
|
+
remaining = 0 if remaining < 0 else remaining
|
|
31
|
+
return slice, remaining
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ExperimentParentIterator(typing.Iterator['Simulation']): # noqa F821
|
|
35
|
+
"""
|
|
36
|
+
Wraps a list of simulations with iterator that always provides parent experiment.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, lst, parent: 'IEntity'): # noqa F821
|
|
40
|
+
"""
|
|
41
|
+
Initializes the ExperimentParentIterator.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
lst: List of items(simulations) to iterator over
|
|
45
|
+
parent: Parent of items(Experiment)
|
|
46
|
+
"""
|
|
47
|
+
self.items = lst
|
|
48
|
+
self.__iter = iter(self.items) if not isinstance(self.items, (typing.Iterator, Generator)) else self.items
|
|
49
|
+
self.parent = parent
|
|
50
|
+
|
|
51
|
+
def __iter__(self):
|
|
52
|
+
"""
|
|
53
|
+
Iterator method returns self.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Self
|
|
57
|
+
"""
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
def __next__(self):
|
|
61
|
+
"""
|
|
62
|
+
Fetch the next items from our list.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Next item from our list
|
|
66
|
+
"""
|
|
67
|
+
i = next(self.__iter)
|
|
68
|
+
i._parent = self.parent
|
|
69
|
+
if hasattr(i, 'parent_id') and self.parent.uid is not None:
|
|
70
|
+
i.parent_id = self.parent.uid
|
|
71
|
+
return i
|
|
72
|
+
|
|
73
|
+
def __getitem__(self, item):
|
|
74
|
+
"""
|
|
75
|
+
Get items wrapper.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
item: Item to fetch
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Item from self.items
|
|
82
|
+
"""
|
|
83
|
+
return self.items[item]
|
|
84
|
+
|
|
85
|
+
def __getattr__(self, item):
|
|
86
|
+
"""
|
|
87
|
+
Get attr wrapper.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
item: Item to get
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Attribute from our items
|
|
94
|
+
"""
|
|
95
|
+
return getattr(self.items, item)
|
|
96
|
+
|
|
97
|
+
def __len__(self):
|
|
98
|
+
"""
|
|
99
|
+
Returns the total simulations.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Total simulations
|
|
103
|
+
|
|
104
|
+
Raises:
|
|
105
|
+
ValueError - When the underlying object is a generator, we cannot get the length of the object
|
|
106
|
+
"""
|
|
107
|
+
from idmtools.entities.templated_simulation import TemplatedSimulations
|
|
108
|
+
if isinstance(self.items, typing.Sized):
|
|
109
|
+
return len(self.items)
|
|
110
|
+
elif isinstance(self.items, TemplatedSimulations):
|
|
111
|
+
return sum([len(b) for b in self.items.builders])
|
|
112
|
+
raise ValueError("Cannot get the length of a generator object")
|
|
113
|
+
|
|
114
|
+
def append(self, item: 'Simulation'): # noqa F821
|
|
115
|
+
"""
|
|
116
|
+
Adds a simulation to an object.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
item: Item to add
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
None
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
ValueError when we cannot append because the item is not a simulation or our underlying object doesn't support appending
|
|
126
|
+
"""
|
|
127
|
+
from idmtools.entities.templated_simulation import TemplatedSimulations
|
|
128
|
+
from idmtools.entities.simulation import Simulation
|
|
129
|
+
if not isinstance(item, Simulation):
|
|
130
|
+
raise ValueError("You can only append simulations")
|
|
131
|
+
|
|
132
|
+
# Check possible duplicate
|
|
133
|
+
if self.parent.check_duplicate(item.id):
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
# Set parent
|
|
137
|
+
item.parent = self.parent
|
|
138
|
+
|
|
139
|
+
# Add to collection
|
|
140
|
+
if isinstance(self.items, (list, set)):
|
|
141
|
+
self.items.append(item)
|
|
142
|
+
return
|
|
143
|
+
elif isinstance(self.items, TemplatedSimulations):
|
|
144
|
+
self.items.add_simulation(item)
|
|
145
|
+
return
|
|
146
|
+
raise ValueError("Items doesn't support appending")
|
|
147
|
+
|
|
148
|
+
def extend(self, item: Union[List['Simulation'], 'TemplatedSimulations']): # noqa F821
|
|
149
|
+
"""
|
|
150
|
+
Extends object.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
item: Item to extend
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
None
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
ValueError when the underlying data object doesn't support adding additional item
|
|
160
|
+
"""
|
|
161
|
+
from idmtools.entities.templated_simulation import TemplatedSimulations
|
|
162
|
+
if isinstance(self.items, (list, set)):
|
|
163
|
+
# if it is a template, try to preserve so we can use generators
|
|
164
|
+
if isinstance(item, TemplatedSimulations):
|
|
165
|
+
self.extend(list(item))
|
|
166
|
+
else:
|
|
167
|
+
for it in item:
|
|
168
|
+
self.append(it)
|
|
169
|
+
return
|
|
170
|
+
if isinstance(self.items, TemplatedSimulations):
|
|
171
|
+
if isinstance(item, TemplatedSimulations):
|
|
172
|
+
self.items.add_simulations(list(item))
|
|
173
|
+
else:
|
|
174
|
+
self.items.add_simulations(item)
|
|
175
|
+
return
|
|
176
|
+
raise ValueError("Items doesn't support extending")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class ResetGenerator(typing.Iterator):
|
|
180
|
+
"""Iterator that counts upward forever."""
|
|
181
|
+
|
|
182
|
+
def __init__(self, generator_init):
|
|
183
|
+
"""
|
|
184
|
+
Initialize the ResetGenerator from generator_init.
|
|
185
|
+
|
|
186
|
+
Creates a copy of the generator using tee.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
generator_init: Initialize iterator/generator to copy
|
|
190
|
+
"""
|
|
191
|
+
self.generator_init = generator_init
|
|
192
|
+
self.generator = generator_init()
|
|
193
|
+
self.generator, self.__next_gen = tee(self.generator)
|
|
194
|
+
|
|
195
|
+
def __iter__(self):
|
|
196
|
+
"""
|
|
197
|
+
Get iteror.
|
|
198
|
+
"""
|
|
199
|
+
return self
|
|
200
|
+
|
|
201
|
+
def next_gen(self):
|
|
202
|
+
"""
|
|
203
|
+
The original generator/iterator.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
original generator/iterator.
|
|
207
|
+
"""
|
|
208
|
+
return self.__next_gen
|
|
209
|
+
|
|
210
|
+
def __next__(self):
|
|
211
|
+
"""
|
|
212
|
+
Get next item.
|
|
213
|
+
|
|
214
|
+
For reset iteration, if we hit the end of our iterator/generator, we copy it and reset
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Next item
|
|
218
|
+
|
|
219
|
+
Raises:
|
|
220
|
+
StopIteration at the end of the iteration.
|
|
221
|
+
"""
|
|
222
|
+
try:
|
|
223
|
+
result = next(self.generator)
|
|
224
|
+
except StopIteration:
|
|
225
|
+
self.generator, self.__next_gen = tee(self.__next_gen)
|
|
226
|
+
raise StopIteration
|
|
227
|
+
return result
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def duplicate_list_of_generators(lst: List[Generator]):
|
|
231
|
+
"""
|
|
232
|
+
Copy a list of iterators using tee.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
lst: List of generators
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Tuple with duplicate of iterators
|
|
239
|
+
"""
|
|
240
|
+
new_sw = []
|
|
241
|
+
old_sw = []
|
|
242
|
+
for sw in lst:
|
|
243
|
+
o, n = tee(sw)
|
|
244
|
+
new_sw.append(n)
|
|
245
|
+
old_sw.append(o)
|
|
246
|
+
return old_sw, new_sw
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
utilities for command line.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import contextlib
|
|
7
|
+
import logging
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from io import StringIO
|
|
11
|
+
from itertools import cycle
|
|
12
|
+
|
|
13
|
+
animation = cycle(("|", "/", "-", "\\"))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@contextlib.contextmanager
|
|
17
|
+
def suppress_output(stdout=True, stderr=True):
|
|
18
|
+
"""
|
|
19
|
+
Suppress any print/logging from a block of code.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
stdout: If True, hide output from stdout; if False, show it.
|
|
23
|
+
stderr: If True, hide output from stderr; if False, show it.
|
|
24
|
+
"""
|
|
25
|
+
# Remember existing output streams for restoration
|
|
26
|
+
original_stdout = sys.stdout
|
|
27
|
+
original_stderr = sys.stderr
|
|
28
|
+
|
|
29
|
+
# Suppress requested channel output
|
|
30
|
+
if stdout:
|
|
31
|
+
sys.stdout = StringIO()
|
|
32
|
+
if stderr:
|
|
33
|
+
sys.stderr = StringIO()
|
|
34
|
+
|
|
35
|
+
# Deactivate logging
|
|
36
|
+
previous_level = logging.root.manager.disable
|
|
37
|
+
logging.disable(logging.ERROR)
|
|
38
|
+
|
|
39
|
+
yield
|
|
40
|
+
|
|
41
|
+
# Restore original output streams
|
|
42
|
+
sys.stdout = original_stdout
|
|
43
|
+
sys.stderr = original_stderr
|
|
44
|
+
|
|
45
|
+
logging.disable(previous_level)
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Decorators defined for idmtools.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import datetime
|
|
7
|
+
import functools
|
|
8
|
+
import importlib
|
|
9
|
+
import importlib.util
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import threading
|
|
13
|
+
from concurrent.futures import Executor, as_completed
|
|
14
|
+
from concurrent.futures.thread import ThreadPoolExecutor
|
|
15
|
+
from functools import wraps
|
|
16
|
+
from logging import getLogger, DEBUG
|
|
17
|
+
from typing import Callable, Union, Optional, Type
|
|
18
|
+
|
|
19
|
+
user_logger = getLogger('user')
|
|
20
|
+
logger = getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class abstractstatic(staticmethod):
|
|
24
|
+
"""
|
|
25
|
+
A decorator for defining a method both as static and abstract.
|
|
26
|
+
"""
|
|
27
|
+
__slots__ = ()
|
|
28
|
+
|
|
29
|
+
def __init__(self, function):
|
|
30
|
+
"""
|
|
31
|
+
Initialize abstractstatic.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
function: Function to wrap as abstract
|
|
35
|
+
"""
|
|
36
|
+
super(abstractstatic, self).__init__(function)
|
|
37
|
+
function.__isabstractmethod__ = True
|
|
38
|
+
|
|
39
|
+
__isabstractmethod__ = True
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def optional_decorator(decorator: Callable, condition: Union[bool, Callable[[], bool]]):
|
|
43
|
+
"""
|
|
44
|
+
A decorator that adds a decorator only if condition is true.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
decorator: Decorator to add
|
|
48
|
+
condition: Condition to determine. Condition can be a callable as well
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Optionally wrapped func.
|
|
52
|
+
"""
|
|
53
|
+
if callable(condition):
|
|
54
|
+
condition = condition()
|
|
55
|
+
|
|
56
|
+
def decorate_in(func):
|
|
57
|
+
if condition:
|
|
58
|
+
func = decorator(func)
|
|
59
|
+
|
|
60
|
+
@wraps
|
|
61
|
+
def wrapper(*args, **kwargs):
|
|
62
|
+
func(*args, **kwargs)
|
|
63
|
+
|
|
64
|
+
return wrapper
|
|
65
|
+
|
|
66
|
+
return decorate_in
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class SingletonMixin(object):
|
|
70
|
+
"""
|
|
71
|
+
SingletonMixin defines a singleton that can be added to any class.
|
|
72
|
+
|
|
73
|
+
As a singleton, on one instance will be made per process.
|
|
74
|
+
"""
|
|
75
|
+
__singleton_lock = threading.Lock()
|
|
76
|
+
__singleton_instance = None
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def instance(cls):
|
|
80
|
+
"""
|
|
81
|
+
Return the instance of the object.
|
|
82
|
+
|
|
83
|
+
If the instance has not been created, it will be initialized before returning.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
The singleton instance
|
|
87
|
+
"""
|
|
88
|
+
if not cls.__singleton_instance:
|
|
89
|
+
with cls.__singleton_lock:
|
|
90
|
+
if not cls.__singleton_instance:
|
|
91
|
+
cls.__singleton_instance = cls()
|
|
92
|
+
return cls.__singleton_instance
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def cache_for(ttl=None) -> Callable:
|
|
96
|
+
"""
|
|
97
|
+
Cache a value for a certain time period.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
ttl: Expiration of cache
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Wrapper Function
|
|
104
|
+
"""
|
|
105
|
+
if ttl is None:
|
|
106
|
+
ttl = datetime.timedelta(minutes=1)
|
|
107
|
+
|
|
108
|
+
def wrap(func):
|
|
109
|
+
time, value = None, None
|
|
110
|
+
|
|
111
|
+
@wraps(func)
|
|
112
|
+
def wrapped(*args, **kw):
|
|
113
|
+
# if we are testing, disable caching of functions as it complicates test-all setups
|
|
114
|
+
from idmtools.core import TRUTHY_VALUES
|
|
115
|
+
if os.getenv('TESTING', '0').lower() in TRUTHY_VALUES:
|
|
116
|
+
return func(*args, **kw)
|
|
117
|
+
|
|
118
|
+
nonlocal time
|
|
119
|
+
nonlocal value
|
|
120
|
+
now = datetime.datetime.now()
|
|
121
|
+
if not time or now - time > ttl:
|
|
122
|
+
value = func(*args, **kw)
|
|
123
|
+
time = now
|
|
124
|
+
return value
|
|
125
|
+
|
|
126
|
+
return wrapped
|
|
127
|
+
|
|
128
|
+
return wrap
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def optional_yaspin_load(*yargs, **ykwargs) -> Callable:
|
|
132
|
+
"""
|
|
133
|
+
Adds a CLI spinner to a function based on conditions.
|
|
134
|
+
|
|
135
|
+
The spinner will be present if
|
|
136
|
+
|
|
137
|
+
* yaspin package is present.
|
|
138
|
+
* NO_SPINNER environment variable is not defined.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
*yargs: Arguments to pass to yaspin constructor.
|
|
142
|
+
**ykwargs: Keyword arguments to pass to yaspin constructor.
|
|
143
|
+
|
|
144
|
+
Examples:
|
|
145
|
+
::
|
|
146
|
+
|
|
147
|
+
@optional_yaspin_load(text="Loading test", color="yellow")
|
|
148
|
+
def test():
|
|
149
|
+
time.sleep(100)
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
A callable wrapper function.
|
|
153
|
+
"""
|
|
154
|
+
has_yaspin = importlib.util.find_spec("yaspin")
|
|
155
|
+
spinner = None
|
|
156
|
+
if has_yaspin and not os.getenv('NO_SPINNER', False):
|
|
157
|
+
from yaspin import yaspin
|
|
158
|
+
spinner = yaspin(*yargs, **ykwargs)
|
|
159
|
+
|
|
160
|
+
def decorate(func):
|
|
161
|
+
@wraps(func)
|
|
162
|
+
def wrapper(*args, **kwargs):
|
|
163
|
+
if spinner and not os.getenv('NO_SPINNER', False):
|
|
164
|
+
spinner.start()
|
|
165
|
+
try:
|
|
166
|
+
kwargs['spinner'] = spinner
|
|
167
|
+
result = func(*args, **kwargs)
|
|
168
|
+
except Exception as e:
|
|
169
|
+
if spinner:
|
|
170
|
+
spinner.stop()
|
|
171
|
+
raise e
|
|
172
|
+
if spinner:
|
|
173
|
+
spinner.stop()
|
|
174
|
+
return result
|
|
175
|
+
|
|
176
|
+
return wrapper
|
|
177
|
+
|
|
178
|
+
return decorate
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class ParallelizeDecorator:
|
|
182
|
+
"""
|
|
183
|
+
ParallelizeDecorator allows you to easily parallelize a group of code.
|
|
184
|
+
|
|
185
|
+
A simple of example would be following
|
|
186
|
+
|
|
187
|
+
Examples:
|
|
188
|
+
::
|
|
189
|
+
|
|
190
|
+
op_queue = ParallelizeDecorator()
|
|
191
|
+
|
|
192
|
+
class Ops:
|
|
193
|
+
op_queue.parallelize
|
|
194
|
+
def heavy_op():
|
|
195
|
+
time.sleep(10)
|
|
196
|
+
|
|
197
|
+
def do_lots_of_heavy():
|
|
198
|
+
futures = [self.heavy_op() for i in range(100)]
|
|
199
|
+
results = op_queue.get_results(futures)
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
def __init__(self, queue=None, pool_type: Optional[Type[Executor]] = ThreadPoolExecutor):
|
|
203
|
+
"""
|
|
204
|
+
Initialize our ParallelizeDecorator.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
queue: Queue to use. If not provided, one will be created.
|
|
208
|
+
pool_type: Pool type to use. Defaults to ThreadPoolExecutor.
|
|
209
|
+
"""
|
|
210
|
+
if queue is None:
|
|
211
|
+
self.queue = pool_type()
|
|
212
|
+
else:
|
|
213
|
+
self.queue = queue
|
|
214
|
+
|
|
215
|
+
def parallelize(self, func):
|
|
216
|
+
"""
|
|
217
|
+
Wrap a function in parallelization.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
func: Function to wrap with parallelization
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Function wrapped with parallelization object
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
@wraps(func)
|
|
227
|
+
def wrapper(*args, **kwargs):
|
|
228
|
+
future = self.queue.submit(func, *args, **kwargs)
|
|
229
|
+
return future
|
|
230
|
+
|
|
231
|
+
return wrapper
|
|
232
|
+
|
|
233
|
+
def join(self):
|
|
234
|
+
"""
|
|
235
|
+
Join our queue.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Join operation from queue
|
|
239
|
+
"""
|
|
240
|
+
return self.queue.join()
|
|
241
|
+
|
|
242
|
+
def get_results(self, futures, ordered=False):
|
|
243
|
+
"""
|
|
244
|
+
Get Results from our decorator.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
futures: Futures to get results from
|
|
248
|
+
ordered: Do we want results in order provided or as they complete. Default is as they complete which is False.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
Results from all the futures.
|
|
252
|
+
"""
|
|
253
|
+
results = []
|
|
254
|
+
if ordered:
|
|
255
|
+
for f in futures:
|
|
256
|
+
results.append(f.result())
|
|
257
|
+
else:
|
|
258
|
+
for f in as_completed(futures):
|
|
259
|
+
results.append(f.result())
|
|
260
|
+
|
|
261
|
+
if logger.isEnabledFor(DEBUG):
|
|
262
|
+
logger.debug("Parallelize Total Results: " + str(results))
|
|
263
|
+
return results
|
|
264
|
+
|
|
265
|
+
def __del__(self):
|
|
266
|
+
"""
|
|
267
|
+
Delete our queue before deleting ourselves.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
None
|
|
271
|
+
"""
|
|
272
|
+
del self.queue
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def check_symlink_capabilities(func):
|
|
276
|
+
"""Decorator to check symlink creation capabilities."""
|
|
277
|
+
|
|
278
|
+
@functools.wraps(func)
|
|
279
|
+
def wrapper(*args, **kwargs):
|
|
280
|
+
if sys.platform == 'win32' and sys.version_info < (3, 8):
|
|
281
|
+
user_logger.debug('Need administrator privileges to create symbolic links on Windows.')
|
|
282
|
+
return func(*args, **kwargs)
|
|
283
|
+
|
|
284
|
+
return wrapper
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def cache_directory(func):
|
|
288
|
+
"""Decorator to cache Suite/Experiment/simulation directory."""
|
|
289
|
+
|
|
290
|
+
@functools.wraps(func)
|
|
291
|
+
def wrapper(self, item, *args, **kwargs):
|
|
292
|
+
_platform_directory = getattr(item, '_platform_directory', None)
|
|
293
|
+
if _platform_directory:
|
|
294
|
+
item_dir = _platform_directory
|
|
295
|
+
else:
|
|
296
|
+
item_dir = func(self, item, *args, **kwargs)
|
|
297
|
+
setattr(item, '_platform_directory', item_dir)
|
|
298
|
+
return item_dir
|
|
299
|
+
|
|
300
|
+
return wrapper
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
root of display utilities for idmtools.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
from idmtools.utils.display.settings import * # noqa: F401, F403
|
|
7
|
+
from idmtools.utils.display.displays import * # noqa: F401, F403
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def display(obj, settings):
|
|
11
|
+
"""
|
|
12
|
+
Display an object using our settings.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
obj: Obj to display
|
|
16
|
+
settings: Display settings
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
None
|
|
20
|
+
"""
|
|
21
|
+
for setting in settings:
|
|
22
|
+
print(setting.display(obj))
|