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.
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.3.dist-info/METADATA +120 -0
  112. idmtools-0.0.3.dist-info/RECORD +116 -0
  113. idmtools-0.0.3.dist-info/entry_points.txt +9 -0
  114. idmtools-0.0.3.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.3.dist-info}/WHEEL +0 -0
  118. {idmtools-0.0.0.dev0.dist-info → idmtools-0.0.3.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
@@ -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