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,486 @@
|
|
|
1
|
+
"""
|
|
2
|
+
idmtools IdmConfig paraer, the main configuration engine for idmtools.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import copy
|
|
7
|
+
import platform
|
|
8
|
+
from dataclasses import fields
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
from configparser import ConfigParser
|
|
13
|
+
from logging import getLogger, DEBUG
|
|
14
|
+
from typing import Any, Dict
|
|
15
|
+
from idmtools.core import TRUTHY_VALUES
|
|
16
|
+
from idmtools.utils.info import get_help_version_url
|
|
17
|
+
|
|
18
|
+
default_config = 'idmtools.ini'
|
|
19
|
+
|
|
20
|
+
# this is the only logger that should not be defined using init_logger
|
|
21
|
+
logger = getLogger(__name__)
|
|
22
|
+
user_logger = getLogger('user')
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def initialization(force=False):
|
|
26
|
+
"""
|
|
27
|
+
Initialization decorator for configuration methods.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
force: Force initialization
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Wrapper function
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def wrap(func):
|
|
37
|
+
def wrapped_f(*args, **kwargs):
|
|
38
|
+
IdmConfigParser.ensure_init(force=force)
|
|
39
|
+
value = func(*args, **kwargs)
|
|
40
|
+
return value
|
|
41
|
+
|
|
42
|
+
return wrapped_f
|
|
43
|
+
|
|
44
|
+
return wrap
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class IdmConfigParser:
|
|
48
|
+
"""
|
|
49
|
+
Class that parses an INI configuration file.
|
|
50
|
+
"""
|
|
51
|
+
_config = None
|
|
52
|
+
_instance = None
|
|
53
|
+
_config_path = None
|
|
54
|
+
_block = None
|
|
55
|
+
|
|
56
|
+
def __new__(cls, dir_path: str = '.', file_name: str = default_config) -> 'IdmConfigParser':
|
|
57
|
+
"""
|
|
58
|
+
Make :class:`IdmConfigParser` creation a singleton.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
dir_path: The INI configuration file directory.
|
|
62
|
+
file_name: The INI file name.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
An :class:`IdmConfigParser` instance.
|
|
66
|
+
"""
|
|
67
|
+
if not cls._instance:
|
|
68
|
+
cls._instance = super(IdmConfigParser, cls).__new__(cls)
|
|
69
|
+
cls._instance._load_config_file(dir_path, file_name)
|
|
70
|
+
# Only error when a user overrides the filename for idmtools.ini
|
|
71
|
+
if (dir_path != "." or file_name != default_config) and not cls.found_ini():
|
|
72
|
+
raise FileNotFoundError(f"The configuration file {os.path.join(dir_path, file_name)} was not found!")
|
|
73
|
+
# Call our startup plugins
|
|
74
|
+
from idmtools.registry.functions import FunctionPluginManager
|
|
75
|
+
FunctionPluginManager.instance().hook.idmtools_on_start()
|
|
76
|
+
return cls._instance
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def retrieve_dict_config_block(cls, field_type, section) -> Dict[str, Any]:
|
|
80
|
+
"""
|
|
81
|
+
Retrieve dictionary config block.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
field_type: Field type
|
|
85
|
+
section: Section to load
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Dictionary of the config block
|
|
89
|
+
"""
|
|
90
|
+
import ast
|
|
91
|
+
|
|
92
|
+
inputs = copy.deepcopy(section)
|
|
93
|
+
fs = set(field_type.keys()).intersection(set(section.keys()))
|
|
94
|
+
for fn in fs:
|
|
95
|
+
ft = field_type[fn]
|
|
96
|
+
try:
|
|
97
|
+
if ft in (int, float, str):
|
|
98
|
+
inputs[fn] = ft(section[fn])
|
|
99
|
+
elif ft is bool:
|
|
100
|
+
if isinstance(section[fn], str):
|
|
101
|
+
inputs[fn] = ast.literal_eval(section[fn])
|
|
102
|
+
else:
|
|
103
|
+
inputs[fn] = section[fn]
|
|
104
|
+
except ValueError as e:
|
|
105
|
+
user_logger.error(
|
|
106
|
+
f"The field {fn} requires a value of type {ft.__name__}. You provided <{section[fn]}>")
|
|
107
|
+
raise e
|
|
108
|
+
return inputs
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
@initialization
|
|
112
|
+
def retrieve_settings(cls, section: str = None, field_type=None) -> Dict[str, str]:
|
|
113
|
+
"""
|
|
114
|
+
Retrieve INI configuration values (to be used when updating platform fields). Call from each platform.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
section: The INI section from which to retrieve configuration values.
|
|
118
|
+
field_type: The requested data types.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
The configuration values as a dictionary.
|
|
122
|
+
"""
|
|
123
|
+
# retrieve THIS platform config settings
|
|
124
|
+
if field_type is None:
|
|
125
|
+
field_type = {}
|
|
126
|
+
field_config = cls.get_section(section)
|
|
127
|
+
|
|
128
|
+
# update field types
|
|
129
|
+
field_config_updated = cls.retrieve_dict_config_block(field_config, section)
|
|
130
|
+
return field_config_updated
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def _find_config(cls, dir_path: str = None, file_name: str = default_config) -> None:
|
|
134
|
+
"""
|
|
135
|
+
Recursively search for the INI configuration file starting from the **dir_path** provided up to the root, stopping once one is found.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
dir_path: The directory to start looking for the INI configuration file.
|
|
139
|
+
file_name: The name of the configuration file to look for.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
None
|
|
143
|
+
"""
|
|
144
|
+
full_dir_path = os.path.abspath(dir_path)
|
|
145
|
+
if os.path.exists(os.path.join(full_dir_path, file_name)):
|
|
146
|
+
cls._config_path = os.path.join(full_dir_path, file_name)
|
|
147
|
+
return cls._config_path
|
|
148
|
+
else:
|
|
149
|
+
dir_parent = os.path.dirname(full_dir_path)
|
|
150
|
+
if dir_parent == full_dir_path:
|
|
151
|
+
return None
|
|
152
|
+
else:
|
|
153
|
+
cls._config_path = cls._find_config(dir_parent, file_name)
|
|
154
|
+
return cls._config_path
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def get_global_configuration_name() -> str:
|
|
158
|
+
r"""
|
|
159
|
+
Get Global Configuration Name.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
On Windows, this returns %LOCALDATA%\\idmtools\\idmtools.ini
|
|
163
|
+
On Mac and Linux, it returns "/home/username/.idmtools.ini'
|
|
164
|
+
|
|
165
|
+
Raises:
|
|
166
|
+
Value Error on OSs not supported
|
|
167
|
+
"""
|
|
168
|
+
if platform.system() in ["Linux", "Darwin"]:
|
|
169
|
+
ini_file = os.path.join(str(Path.home()), ".idmtools.ini")
|
|
170
|
+
# On Windows, c:\users\user\AppData\Local\idmtools\idmtools.ini
|
|
171
|
+
elif platform.system() in ["Windows"]:
|
|
172
|
+
ini_file = os.path.join(os.path.expandvars(r'%LOCALAPPDATA%'), "idmtools", "idmtools.ini")
|
|
173
|
+
else:
|
|
174
|
+
raise ValueError("OS global configuration cannot be detected")
|
|
175
|
+
return ini_file
|
|
176
|
+
|
|
177
|
+
@classmethod
|
|
178
|
+
def _load_config_file(cls, dir_path: str = None, file_name: str = default_config):
|
|
179
|
+
"""
|
|
180
|
+
Find and then load the INI configuration file and parse it with :class:`IdmConfigParser`.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
dir_path: The directory to start looking for the INI configuration file.
|
|
184
|
+
file_name: The name of the configuration file to look for.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
None
|
|
188
|
+
"""
|
|
189
|
+
if dir_path is None:
|
|
190
|
+
dir_path = os.getcwd()
|
|
191
|
+
|
|
192
|
+
logger.debug(
|
|
193
|
+
f"Looking for config file in {dir_path}") # This log will generally only happen on recreation of config after clearing config
|
|
194
|
+
|
|
195
|
+
# Look for the config file. First check environment vars
|
|
196
|
+
if "IDMTOOLS_CONFIG_FILE" in os.environ:
|
|
197
|
+
if not os.path.exists(os.environ["IDMTOOLS_CONFIG_FILE"]):
|
|
198
|
+
raise FileNotFoundError(f'Cannot for idmtools config at {os.environ["IDMTOOLS_CONFIG_FILE"]}')
|
|
199
|
+
ini_file = os.environ["IDMTOOLS_CONFIG_FILE"]
|
|
200
|
+
# Try find file
|
|
201
|
+
else:
|
|
202
|
+
ini_file = cls._find_config(dir_path, file_name)
|
|
203
|
+
# Fallback to user home directories
|
|
204
|
+
if ini_file is None:
|
|
205
|
+
global_config = cls.get_global_configuration_name()
|
|
206
|
+
if os.path.exists(global_config):
|
|
207
|
+
ini_file = global_config
|
|
208
|
+
|
|
209
|
+
# If we didn't find a file, warn the user and init logging
|
|
210
|
+
if ini_file is None:
|
|
211
|
+
if os.getenv("NO_LOGGING_INIT", "f").lower() not in TRUTHY_VALUES:
|
|
212
|
+
cls._init_logging()
|
|
213
|
+
logger.warning(
|
|
214
|
+
f"/!\\ WARNING: File '{file_name}' Not Found! For details on how to configure idmtools, see {get_help_version_url('configuration.html')} for details on how to configure idmtools.")
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
# Load file
|
|
218
|
+
cls._config_path = ini_file
|
|
219
|
+
cls._config = ConfigParser()
|
|
220
|
+
cls._config.read(ini_file)
|
|
221
|
+
|
|
222
|
+
# in order to have case-insensitive section names, we add the lowercase version of all sections if not present
|
|
223
|
+
sections = cls._config.sections()
|
|
224
|
+
for section in sections:
|
|
225
|
+
lowercase_version = section.lower()
|
|
226
|
+
if not cls._config.has_section(section=lowercase_version):
|
|
227
|
+
cls._config._sections[lowercase_version] = cls._config._sections[section]
|
|
228
|
+
|
|
229
|
+
if os.getenv("NO_LOGGING_INIT", "f").lower() not in TRUTHY_VALUES:
|
|
230
|
+
# init logging here as this is our most likely entry-point into an idmtools "application"
|
|
231
|
+
cls._init_logging()
|
|
232
|
+
from idmtools.core.logging import VERBOSE
|
|
233
|
+
|
|
234
|
+
if IdmConfigParser.get_option("NO_PRINT_CONFIG_USED",
|
|
235
|
+
fallback="F").lower() not in TRUTHY_VALUES and IdmConfigParser.get_option(
|
|
236
|
+
"logging", "USER_OUTPUT", fallback="t").lower() in TRUTHY_VALUES:
|
|
237
|
+
# let users know when they are using environment variable to local config
|
|
238
|
+
if "IDMTOOLS_CONFIG_FILE" in os.environ:
|
|
239
|
+
user_logger.warning("idmtools config defined through 'IDMTOOLS_CONFIG_FILE' environment variable")
|
|
240
|
+
|
|
241
|
+
if IdmConfigParser.found_ini():
|
|
242
|
+
user_logger.log(VERBOSE, "INI File Found: {}".format(ini_file))
|
|
243
|
+
|
|
244
|
+
@classmethod
|
|
245
|
+
def _init_logging(cls):
|
|
246
|
+
from idmtools.core.logging import setup_logging, IdmToolsLoggingConfig
|
|
247
|
+
# set up default log values
|
|
248
|
+
log_config = dict()
|
|
249
|
+
# try to fetch logging options from config file and from environment vars
|
|
250
|
+
for field in fields(IdmToolsLoggingConfig):
|
|
251
|
+
value = cls.get_option("logging", field.name, fallback=None)
|
|
252
|
+
if value is not None:
|
|
253
|
+
log_config[field.name] = value
|
|
254
|
+
|
|
255
|
+
# handle special case
|
|
256
|
+
if log_config.get('console', None) is None:
|
|
257
|
+
log_config['console'] = None
|
|
258
|
+
|
|
259
|
+
setup_logging(IdmToolsLoggingConfig(**log_config))
|
|
260
|
+
|
|
261
|
+
if platform.system() == "Darwin":
|
|
262
|
+
# see https://bugs.python.org/issue27126
|
|
263
|
+
os.environ['NO_PROXY'] = "*"
|
|
264
|
+
|
|
265
|
+
# Do import locally to prevent load error
|
|
266
|
+
from idmtools import __version__
|
|
267
|
+
if "+nightly" in __version__ and os.getenv('IDMTOOLS_HIDE_DEV_WARNING', None) is None and os.getenv(
|
|
268
|
+
"_IDMTOOLS_COMPLETE", None) is None:
|
|
269
|
+
if logger.isEnabledFor(DEBUG):
|
|
270
|
+
logger.debug(f"You are using a development version of idmtools, version {__version__}!")
|
|
271
|
+
|
|
272
|
+
@classmethod
|
|
273
|
+
@initialization()
|
|
274
|
+
def get_section(cls, section: str = None, error: bool = True) -> Dict[str, str]:
|
|
275
|
+
"""
|
|
276
|
+
Retrieve INI section values (call directly from platform creation).
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
section: The INI section name where we retrieve all fields.
|
|
280
|
+
error: Should we throw error is we cannot find block
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
All fields as a dictionary.
|
|
284
|
+
|
|
285
|
+
Raises:
|
|
286
|
+
ValueError: If the block doesn't exist
|
|
287
|
+
"""
|
|
288
|
+
original_case_section = section
|
|
289
|
+
if section is None:
|
|
290
|
+
return None
|
|
291
|
+
lower_case_section = section.lower()
|
|
292
|
+
if (not cls.found_ini() or not cls.has_section(section=lower_case_section)) and error:
|
|
293
|
+
raise ValueError(f"Block '{original_case_section}' doesn't exist!")
|
|
294
|
+
|
|
295
|
+
section_item = cls._config.items(lower_case_section)
|
|
296
|
+
cls._block = lower_case_section
|
|
297
|
+
return dict(section_item)
|
|
298
|
+
|
|
299
|
+
@classmethod
|
|
300
|
+
@initialization()
|
|
301
|
+
def get_option(cls, section: str = None, option: str = None, fallback=None, environment_first: bool = True) -> str:
|
|
302
|
+
"""
|
|
303
|
+
Get configuration value based on the INI section and option.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
section: The INI section name.
|
|
307
|
+
option: The INI field name.
|
|
308
|
+
fallback: Fallback value
|
|
309
|
+
environment_first: Try to load from environment var first. Default to True. Environment variable names are in form IDMTOOLS_SECTION_OPTION
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
A configuration value as a string.
|
|
313
|
+
"""
|
|
314
|
+
if environment_first:
|
|
315
|
+
evn_name = "_".join(filter(None, ["IDMTOOLS", section, option])).upper()
|
|
316
|
+
value = os.environ.get(evn_name, None)
|
|
317
|
+
if value:
|
|
318
|
+
if logger.isEnabledFor(DEBUG):
|
|
319
|
+
logger.debug(f"Loaded option from environment var {evn_name}")
|
|
320
|
+
return value
|
|
321
|
+
if not cls.found_ini():
|
|
322
|
+
return fallback
|
|
323
|
+
|
|
324
|
+
if cls._config is None:
|
|
325
|
+
if fallback is None:
|
|
326
|
+
user_logger.warning("No Configuration file defined. Please define a fallback value")
|
|
327
|
+
return fallback
|
|
328
|
+
|
|
329
|
+
if section:
|
|
330
|
+
return cls._config.get(section.lower(), option, fallback=fallback)
|
|
331
|
+
else:
|
|
332
|
+
return cls._config.get("COMMON", option, fallback=fallback)
|
|
333
|
+
|
|
334
|
+
@classmethod
|
|
335
|
+
def is_progress_bar_disabled(cls) -> bool:
|
|
336
|
+
"""
|
|
337
|
+
Are progress bars disabled.
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
Return is progress bars should be enabled
|
|
341
|
+
"""
|
|
342
|
+
return all(
|
|
343
|
+
[x.lower() in TRUTHY_VALUES for x in [IdmConfigParser.get_option(None, "DISABLE_PROGRESS_BAR", 'f')]])
|
|
344
|
+
|
|
345
|
+
@classmethod
|
|
346
|
+
def is_output_enabled(cls) -> bool:
|
|
347
|
+
"""
|
|
348
|
+
Is output enabled.
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
Return if output should be disabled
|
|
352
|
+
"""
|
|
353
|
+
return any([x.lower() in TRUTHY_VALUES for x in [IdmConfigParser.get_option('logging', "USER_OUTPUT", 'on')]])
|
|
354
|
+
|
|
355
|
+
@classmethod
|
|
356
|
+
def ensure_init(cls, dir_path: str = '.', file_name: str = default_config, force: bool = False) -> None:
|
|
357
|
+
"""
|
|
358
|
+
Verify that the INI file loaded and a configparser instance is available.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
dir_path: The directory to search for the INI configuration file.
|
|
362
|
+
file_name: The configuration file name to search for.
|
|
363
|
+
force: Force reload of everything
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
None
|
|
367
|
+
|
|
368
|
+
Raises:
|
|
369
|
+
ValueError: If the config file is found but cannot be parsed
|
|
370
|
+
"""
|
|
371
|
+
if force:
|
|
372
|
+
cls.clear_instance()
|
|
373
|
+
|
|
374
|
+
if cls._instance is None:
|
|
375
|
+
cls(dir_path, file_name)
|
|
376
|
+
|
|
377
|
+
@classmethod
|
|
378
|
+
@initialization()
|
|
379
|
+
def get_config_path(cls) -> str:
|
|
380
|
+
"""
|
|
381
|
+
Check which INI configuration file is being used.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
The INI file full path that is loaded.
|
|
385
|
+
"""
|
|
386
|
+
return cls._config_path
|
|
387
|
+
|
|
388
|
+
@classmethod
|
|
389
|
+
@initialization()
|
|
390
|
+
def display_config_path(cls) -> None:
|
|
391
|
+
"""
|
|
392
|
+
Display the INI file path being used.
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
None
|
|
396
|
+
"""
|
|
397
|
+
user_logger.info(cls.get_config_path())
|
|
398
|
+
|
|
399
|
+
@classmethod
|
|
400
|
+
@initialization()
|
|
401
|
+
def view_config_file(cls) -> None:
|
|
402
|
+
"""
|
|
403
|
+
Display the INI file being used.
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
None
|
|
407
|
+
"""
|
|
408
|
+
if cls._config_path is None:
|
|
409
|
+
user_logger.warning("No configuration fouind")
|
|
410
|
+
else:
|
|
411
|
+
user_logger.info("View Config INI: \n{}".format(cls._config_path))
|
|
412
|
+
user_logger.info('-' * len(cls._config_path), '\n')
|
|
413
|
+
with open(cls._config_path) as f:
|
|
414
|
+
read_data = f.read()
|
|
415
|
+
user_logger.info(read_data)
|
|
416
|
+
|
|
417
|
+
@classmethod
|
|
418
|
+
def display_config_block_details(cls, block):
|
|
419
|
+
"""
|
|
420
|
+
Display the values of a config block.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
block: Block to print
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
None
|
|
427
|
+
"""
|
|
428
|
+
if cls.found_ini():
|
|
429
|
+
from idmtools.core.logging import VERBOSE
|
|
430
|
+
block_details = cls.get_section(block)
|
|
431
|
+
user_logger.log(VERBOSE, f"\n[{block}]")
|
|
432
|
+
user_logger.log(VERBOSE, json.dumps(block_details, indent=3))
|
|
433
|
+
|
|
434
|
+
@classmethod
|
|
435
|
+
@initialization()
|
|
436
|
+
def has_section(cls, section: str) -> bool:
|
|
437
|
+
"""
|
|
438
|
+
Does the config contain a section.
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
section: Section to check for
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
True if the section exists, False otherwise
|
|
445
|
+
"""
|
|
446
|
+
return cls._config.has_section(section.lower()) if cls._config else False
|
|
447
|
+
|
|
448
|
+
@classmethod
|
|
449
|
+
@initialization
|
|
450
|
+
def has_option(cls, section: str, option: str):
|
|
451
|
+
"""
|
|
452
|
+
Does the config have an option in specified section?
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
section: Section
|
|
456
|
+
option: Option
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
True if config has option
|
|
460
|
+
"""
|
|
461
|
+
return cls._config.has_option(section, option, fallback=None)
|
|
462
|
+
|
|
463
|
+
@classmethod
|
|
464
|
+
def found_ini(cls) -> bool:
|
|
465
|
+
"""
|
|
466
|
+
Did we find the config?
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
True if did, False Otherwise
|
|
470
|
+
"""
|
|
471
|
+
return cls._config is not None
|
|
472
|
+
|
|
473
|
+
@classmethod
|
|
474
|
+
def clear_instance(cls) -> None:
|
|
475
|
+
"""
|
|
476
|
+
Uninitialize and clean the :class:`IdmConfigParser` instance.
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
None
|
|
480
|
+
"""
|
|
481
|
+
# log as verbose
|
|
482
|
+
logger.log(15, "Clearing idm config")
|
|
483
|
+
cls._config = None
|
|
484
|
+
cls._instance = None
|
|
485
|
+
cls._config_path = None
|
|
486
|
+
cls._block = None
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core area classes are defined in this packaged.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
# flake8: noqa F821
|
|
7
|
+
from idmtools.core.cache_enabled import CacheEnabled
|
|
8
|
+
from idmtools.core.interfaces.entity_container import EntityContainer
|
|
9
|
+
from idmtools.core.enums import *
|
|
10
|
+
from idmtools.core.exceptions import *
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CacheEnabled definition. CacheEnabled enables diskcache wrapping on an item.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import tempfile
|
|
9
|
+
import time
|
|
10
|
+
from contextlib import suppress
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from multiprocessing import current_process, cpu_count
|
|
13
|
+
from logging import getLogger, DEBUG
|
|
14
|
+
from diskcache import Cache, DEFAULT_SETTINGS, FanoutCache
|
|
15
|
+
from typing import Union, Optional
|
|
16
|
+
|
|
17
|
+
MAX_CACHE_SIZE = int(2 ** 33) # 8GB
|
|
18
|
+
DEFAULT_SETTINGS["size_limit"] = MAX_CACHE_SIZE
|
|
19
|
+
DEFAULT_SETTINGS["sqlite_mmap_size"] = 2 ** 28
|
|
20
|
+
DEFAULT_SETTINGS["sqlite_cache_size"] = 2 ** 15
|
|
21
|
+
logger = getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(init=False, repr=False)
|
|
25
|
+
class CacheEnabled:
|
|
26
|
+
"""
|
|
27
|
+
Allows a class to leverage Diskcache and expose a cache property.
|
|
28
|
+
"""
|
|
29
|
+
_cache: Union[Cache, FanoutCache] = field(default=None, init=False, compare=False,
|
|
30
|
+
metadata={"pickle_ignore": True})
|
|
31
|
+
_cache_directory: str = field(default=None, init=False, compare=False)
|
|
32
|
+
|
|
33
|
+
def __del__(self):
|
|
34
|
+
"""
|
|
35
|
+
Deletes object. On Deletion, we destroy the cache.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
None
|
|
39
|
+
"""
|
|
40
|
+
self.cleanup_cache()
|
|
41
|
+
|
|
42
|
+
def initialize_cache(self, shards: Optional[int] = None, eviction_policy=None):
|
|
43
|
+
"""
|
|
44
|
+
Initialize cache.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
shards (Optional[int], optional): How many shards. It is best to set this when multi-procressing Defaults to None.
|
|
48
|
+
eviction_policy ([type], optional): See Diskcache docs. Defaults to None.
|
|
49
|
+
"""
|
|
50
|
+
logger.debug(f"Initializing the cache with {shards or 0} shards and {eviction_policy or 'none'} policy.")
|
|
51
|
+
if self._cache:
|
|
52
|
+
logger.warning("CacheEnabled class is calling `initialize_cache()` with a cache already initialized: "
|
|
53
|
+
"deleting the cache and recreating a new one.")
|
|
54
|
+
self.cleanup_cache()
|
|
55
|
+
|
|
56
|
+
# Create the cache directory if does not exist
|
|
57
|
+
if not self._cache_directory or not os.path.exists(self._cache_directory):
|
|
58
|
+
self._cache_directory = tempfile.mkdtemp()
|
|
59
|
+
logger.debug(f"Cache created in {self._cache_directory}")
|
|
60
|
+
else:
|
|
61
|
+
logger.debug(f"Cache retrieved in {self._cache_directory}")
|
|
62
|
+
|
|
63
|
+
# Create different cache depending on the options
|
|
64
|
+
if shards:
|
|
65
|
+
# set default timeout to grow with cpu count. In high thread environments, user hit timeouts
|
|
66
|
+
default_timeout = max(0.1, cpu_count() * 0.0125)
|
|
67
|
+
if logger.isEnabledFor(DEBUG):
|
|
68
|
+
logger.debug(f"Setting cache timeout to {default_timeout}")
|
|
69
|
+
self._cache = FanoutCache(self._cache_directory, shards=shards, timeout=default_timeout,
|
|
70
|
+
eviction_policy=eviction_policy)
|
|
71
|
+
else:
|
|
72
|
+
self._cache = Cache(self._cache_directory)
|
|
73
|
+
|
|
74
|
+
def cleanup_cache(self):
|
|
75
|
+
"""
|
|
76
|
+
Cleanup our cache.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
None
|
|
80
|
+
"""
|
|
81
|
+
# Only delete and close the cache if the owner thread ends
|
|
82
|
+
# Avoid deleting and closing when a child thread ends
|
|
83
|
+
with suppress(AttributeError):
|
|
84
|
+
if current_process().name != 'MainProcess':
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
if self._cache is not None:
|
|
88
|
+
self._cache.close()
|
|
89
|
+
del self._cache
|
|
90
|
+
|
|
91
|
+
if self._cache_directory and os.path.exists(self._cache_directory):
|
|
92
|
+
retries = 0
|
|
93
|
+
while retries < 3:
|
|
94
|
+
try:
|
|
95
|
+
shutil.rmtree(self._cache_directory)
|
|
96
|
+
time.sleep(0.15)
|
|
97
|
+
break
|
|
98
|
+
except IOError:
|
|
99
|
+
# we don't use logger here because it could be destroyed
|
|
100
|
+
print(f"Could not delete cache {self._cache_directory}")
|
|
101
|
+
retries += 1
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def cache(self) -> Union[Cache, FanoutCache]:
|
|
105
|
+
"""
|
|
106
|
+
Allows fetches of cache and ensures it is initialized.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Cache
|
|
110
|
+
"""
|
|
111
|
+
if self._cache is None:
|
|
112
|
+
self.initialize_cache()
|
|
113
|
+
|
|
114
|
+
return self._cache
|
idmtools/core/context.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manages the idmtools context, mostly around the platform object.
|
|
3
|
+
|
|
4
|
+
This context allows us to easily fetch what platforms we are executing on and also supported nested, multi-platform operations.
|
|
5
|
+
|
|
6
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
7
|
+
"""
|
|
8
|
+
from logging import getLogger
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
logger = getLogger(__name__)
|
|
12
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
13
|
+
# The current platform
|
|
14
|
+
from idmtools.entities.iplatform import IPlatform
|
|
15
|
+
|
|
16
|
+
current_platform_stack = []
|
|
17
|
+
CURRENT_PLATFORM: 'IPlatform' = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def set_current_platform(platform: 'IPlatform'):
|
|
21
|
+
"""
|
|
22
|
+
Set the current platform that is being used to execute scripts.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
platform: Platform to set
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
None
|
|
29
|
+
"""
|
|
30
|
+
global CURRENT_PLATFORM, current_platform_stack # noqa: F824
|
|
31
|
+
if CURRENT_PLATFORM and CURRENT_PLATFORM != platform and platform not in current_platform_stack:
|
|
32
|
+
current_platform_stack.append(CURRENT_PLATFORM)
|
|
33
|
+
CURRENT_PLATFORM = platform
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def remove_current_platform():
|
|
37
|
+
"""
|
|
38
|
+
Set CURRENT_PLATFORM to None and delete old platform object.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
None
|
|
42
|
+
"""
|
|
43
|
+
global CURRENT_PLATFORM, current_platform_stack # noqa: F824
|
|
44
|
+
old_current_platform = CURRENT_PLATFORM
|
|
45
|
+
if len(current_platform_stack):
|
|
46
|
+
new_platform = current_platform_stack.pop()
|
|
47
|
+
else:
|
|
48
|
+
new_platform = None
|
|
49
|
+
del old_current_platform
|
|
50
|
+
CURRENT_PLATFORM = new_platform
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def clear_context():
|
|
54
|
+
"""
|
|
55
|
+
Clear all platforms from context.
|
|
56
|
+
"""
|
|
57
|
+
global CURRENT_PLATFORM, current_platform_stack # noqa: F824
|
|
58
|
+
old_current_platform = CURRENT_PLATFORM
|
|
59
|
+
CURRENT_PLATFORM = None
|
|
60
|
+
current_platform_stack.clear()
|
|
61
|
+
del old_current_platform
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_current_platform() -> 'IPlatform':
|
|
65
|
+
"""
|
|
66
|
+
Get current platform.
|
|
67
|
+
"""
|
|
68
|
+
return CURRENT_PLATFORM
|