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,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))