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,138 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PlatformSpecification provided definition for the platform plugin specification, hooks, and plugin manager.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
# Define our platform specific specifications
|
|
7
|
+
import typing
|
|
8
|
+
from abc import ABC
|
|
9
|
+
from logging import getLogger, DEBUG
|
|
10
|
+
import pluggy
|
|
11
|
+
from idmtools.registry import PluginSpecification
|
|
12
|
+
from idmtools.registry.plugin_specification import PLUGIN_REFERENCE_NAME
|
|
13
|
+
from idmtools.registry.utils import load_plugin_map
|
|
14
|
+
from idmtools.utils.decorators import SingletonMixin
|
|
15
|
+
|
|
16
|
+
if typing.TYPE_CHECKING:
|
|
17
|
+
from idmtools.entities.iplatform import IPlatform
|
|
18
|
+
example_configuration_spec = pluggy.HookspecMarker(PLUGIN_REFERENCE_NAME)
|
|
19
|
+
get_platform_spec = pluggy.HookspecMarker(PLUGIN_REFERENCE_NAME)
|
|
20
|
+
get_platform_type_spec = pluggy.HookspecMarker(PLUGIN_REFERENCE_NAME)
|
|
21
|
+
example_configuration_impl = pluggy.HookimplMarker(PLUGIN_REFERENCE_NAME)
|
|
22
|
+
get_platform_impl = pluggy.HookimplMarker(PLUGIN_REFERENCE_NAME)
|
|
23
|
+
get_platform_type_impl = pluggy.HookimplMarker(PLUGIN_REFERENCE_NAME)
|
|
24
|
+
logger = getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class PlatformSpecification(PluginSpecification, ABC):
|
|
28
|
+
"""
|
|
29
|
+
PlatformSpecification for Platform Plugins.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def get_name(cls, strip_all: bool = True) -> str:
|
|
34
|
+
"""
|
|
35
|
+
Get name of plugin. By default we remove the PlatformSpecification portion.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
strip_all: When true, PlatformSpecification is stripped from name. When false only Specification is Stripped
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Name of plugin
|
|
42
|
+
"""
|
|
43
|
+
if strip_all:
|
|
44
|
+
ret = cls.__name__.replace("PlatformSpecification", '')
|
|
45
|
+
else:
|
|
46
|
+
ret = cls.__name__.replace('Specification', '')
|
|
47
|
+
return ret
|
|
48
|
+
|
|
49
|
+
@example_configuration_spec
|
|
50
|
+
def example_configuration(self):
|
|
51
|
+
"""
|
|
52
|
+
Example configuration for the platform. This is useful in help or error messages.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Example configuration
|
|
56
|
+
"""
|
|
57
|
+
raise NotImplementedError("Plugin did not implement example_configuration")
|
|
58
|
+
|
|
59
|
+
@get_platform_spec
|
|
60
|
+
def get(self, configuration: dict) -> 'IPlatform':
|
|
61
|
+
"""
|
|
62
|
+
Return a new platform using the passed in configuration.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
configuration: The INI configuration file to use.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
The new platform.
|
|
69
|
+
"""
|
|
70
|
+
raise NotImplementedError("Plugin did not implement get")
|
|
71
|
+
|
|
72
|
+
@get_platform_type_spec
|
|
73
|
+
def get_type(self) -> typing.Type['IPlatform']:
|
|
74
|
+
"""
|
|
75
|
+
Get type of the Platform type.
|
|
76
|
+
"""
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
def get_configuration_aliases(self) -> typing.Dict[str, typing.Dict]:
|
|
80
|
+
"""
|
|
81
|
+
Get a list of configuration aliases for the platform.
|
|
82
|
+
|
|
83
|
+
A configuration alias should be in the form of "name" -> "Spec, Config Options Dict" where name is the alias the user will use, and the config options is a dictionary of config options to be passed to the item
|
|
84
|
+
Returns:
|
|
85
|
+
|
|
86
|
+
"""
|
|
87
|
+
return {}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class PlatformPlugins(SingletonMixin):
|
|
91
|
+
"""
|
|
92
|
+
PlatformPlugin registry.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def __init__(self, strip_all: bool = True) -> None:
|
|
96
|
+
"""
|
|
97
|
+
Initialize the Platform Registry. When strip all is false, the full plugin name will be used for names in map.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
strip_all: Whether to strip common parts of name from plugins in plugin map
|
|
101
|
+
"""
|
|
102
|
+
self._plugins = typing.cast(typing.Dict[str, PlatformSpecification],
|
|
103
|
+
load_plugin_map('idmtools_platform', PlatformSpecification, strip_all))
|
|
104
|
+
self._aliases: typing.Dict[str, typing.Tuple[PlatformSpecification, typing.Dict]] = dict()
|
|
105
|
+
for _name, spec in self._plugins.items():
|
|
106
|
+
for alias, details in spec.get_configuration_aliases().items():
|
|
107
|
+
if alias.upper() in self._aliases or alias.upper() in self._plugins:
|
|
108
|
+
logger.debug(f"Conflicting alias found: {alias.upper()} from {spec.get_name()}")
|
|
109
|
+
if logger.isEnabledFor(DEBUG):
|
|
110
|
+
logger.debug(f"Found Platform Configuration Alias: {alias}")
|
|
111
|
+
self._aliases[alias.upper()] = (spec, details)
|
|
112
|
+
|
|
113
|
+
def get_plugins(self) -> typing.Set[PlatformSpecification]:
|
|
114
|
+
"""
|
|
115
|
+
Get platform plugins.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Platform plugins
|
|
119
|
+
"""
|
|
120
|
+
return set(self._plugins.values())
|
|
121
|
+
|
|
122
|
+
def get_aliases(self) -> typing.Dict[str, typing.Tuple[PlatformSpecification, typing.Dict]]:
|
|
123
|
+
"""
|
|
124
|
+
Get Platform Configuration Aliases for Platform Plugin.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Platform CConfiguration Aliases
|
|
128
|
+
"""
|
|
129
|
+
return self._aliases
|
|
130
|
+
|
|
131
|
+
def get_plugin_map(self) -> typing.Dict[str, PlatformSpecification]:
|
|
132
|
+
"""
|
|
133
|
+
Get plugin map for Platform Plugins.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Plugin map
|
|
137
|
+
"""
|
|
138
|
+
return self._plugins
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Defines our base plugin definition and specifications.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from logging import getLogger
|
|
8
|
+
import pluggy
|
|
9
|
+
from typing import List, Union, Dict
|
|
10
|
+
|
|
11
|
+
PLUGIN_REFERENCE_NAME = 'idmtools_plugins'
|
|
12
|
+
get_description_spec = pluggy.HookspecMarker(PLUGIN_REFERENCE_NAME)
|
|
13
|
+
get_description_impl = pluggy.HookimplMarker(PLUGIN_REFERENCE_NAME)
|
|
14
|
+
logger = getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class ProjectTemplate:
|
|
19
|
+
"""
|
|
20
|
+
Defines a ProjectTemplate that plugins can define.
|
|
21
|
+
"""
|
|
22
|
+
name: str
|
|
23
|
+
url: Union[str, List[str]]
|
|
24
|
+
description: str = None
|
|
25
|
+
info: str = None
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def read_templates_from_json_stream(s) -> List['ProjectTemplate']:
|
|
29
|
+
"""
|
|
30
|
+
Read Project Template from stream.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
s: Stream where json data resides
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Templates loaded from json
|
|
37
|
+
"""
|
|
38
|
+
import json
|
|
39
|
+
data = json.loads(s.read().decode())
|
|
40
|
+
ret = list()
|
|
41
|
+
if isinstance(data, list):
|
|
42
|
+
for item in data:
|
|
43
|
+
ret.append(ProjectTemplate(**item))
|
|
44
|
+
else:
|
|
45
|
+
ret.append(ProjectTemplate(**data))
|
|
46
|
+
return ret
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class PluginSpecification:
|
|
50
|
+
"""
|
|
51
|
+
Base class for all plugins.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def get_name(cls, strip_all: bool = True) -> str:
|
|
56
|
+
"""
|
|
57
|
+
Get the name of the plugin. Although it can be overridden, the best practice is to use the class name as the plugin name.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
The name of the plugin as a string.
|
|
61
|
+
"""
|
|
62
|
+
if strip_all:
|
|
63
|
+
return cls.__name__.replace("Specification", "")
|
|
64
|
+
else:
|
|
65
|
+
return cls.__name__
|
|
66
|
+
|
|
67
|
+
@get_description_spec
|
|
68
|
+
def get_description(self) -> str:
|
|
69
|
+
"""
|
|
70
|
+
Get a brief description of the plugin and its functionality.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
The plugin description.
|
|
74
|
+
"""
|
|
75
|
+
raise NotImplementedError("The plugin did not implement a description!")
|
|
76
|
+
|
|
77
|
+
def get_project_templates(self) -> List[ProjectTemplate]:
|
|
78
|
+
"""
|
|
79
|
+
Returns a list of project templates related to the a plugin.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
List of project templates
|
|
83
|
+
"""
|
|
84
|
+
return list()
|
|
85
|
+
|
|
86
|
+
def get_example_urls(self) -> List[str]:
|
|
87
|
+
"""
|
|
88
|
+
Returns a list of URLs that a series of Examples for plugin can be downloaded from.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
List of urls
|
|
92
|
+
"""
|
|
93
|
+
return list()
|
|
94
|
+
|
|
95
|
+
def get_help_urls(self) -> Dict[str, str]:
|
|
96
|
+
"""
|
|
97
|
+
Returns a dictionary of topics and links to help.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Dict of help urls
|
|
101
|
+
"""
|
|
102
|
+
return dict()
|
|
103
|
+
|
|
104
|
+
@staticmethod
|
|
105
|
+
def get_version_url(version: str, extra: str = None,
|
|
106
|
+
repo_base_url: str = 'https://github.com/InstituteforDiseaseModeling/idmtools/tree/',
|
|
107
|
+
nightly_branch: str = 'dev'):
|
|
108
|
+
"""
|
|
109
|
+
Build a url using version.
|
|
110
|
+
|
|
111
|
+
Here we assume the tag will exist for that specific version
|
|
112
|
+
Args:
|
|
113
|
+
version: Version to look up. If it contains nightly, we default to nightly_branch
|
|
114
|
+
extra: Extra parts of url pass base
|
|
115
|
+
repo_base_url: Optional url
|
|
116
|
+
nightly_branch: default to dev
|
|
117
|
+
Returns:
|
|
118
|
+
URL for item
|
|
119
|
+
"""
|
|
120
|
+
return f'{repo_base_url}{nightly_branch if "nightly" in version else version[0:6]}/{extra}'
|
|
121
|
+
|
|
122
|
+
def get_version(self) -> str:
|
|
123
|
+
"""
|
|
124
|
+
Returns the version of the plugin.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Version for the plugin
|
|
128
|
+
"""
|
|
129
|
+
return None
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TaskSpecification provided definition for the experiment plugin specification, hooks, and plugin manager.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
# Define our model specific specifications
|
|
7
|
+
import typing
|
|
8
|
+
from abc import ABC
|
|
9
|
+
from logging import getLogger
|
|
10
|
+
import pluggy
|
|
11
|
+
from idmtools.entities.itask import ITask
|
|
12
|
+
from idmtools.registry import PluginSpecification
|
|
13
|
+
from idmtools.registry.plugin_specification import PLUGIN_REFERENCE_NAME
|
|
14
|
+
from idmtools.registry.utils import load_plugin_map
|
|
15
|
+
from idmtools.utils.decorators import SingletonMixin
|
|
16
|
+
|
|
17
|
+
example_configuration_spec = pluggy.HookspecMarker(PLUGIN_REFERENCE_NAME)
|
|
18
|
+
get_task_spec = pluggy.HookspecMarker(PLUGIN_REFERENCE_NAME)
|
|
19
|
+
get_task_type_spec = pluggy.HookspecMarker(PLUGIN_REFERENCE_NAME)
|
|
20
|
+
example_configuration_impl = pluggy.HookimplMarker(PLUGIN_REFERENCE_NAME)
|
|
21
|
+
get_task_impl = pluggy.HookimplMarker(PLUGIN_REFERENCE_NAME)
|
|
22
|
+
get_task_type_impl = pluggy.HookimplMarker(PLUGIN_REFERENCE_NAME)
|
|
23
|
+
logger = getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TaskSpecification(PluginSpecification, ABC):
|
|
27
|
+
"""
|
|
28
|
+
TaskSpecification is spec for Task plugins.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def get_name(cls, strip_all: bool = True) -> str:
|
|
33
|
+
"""
|
|
34
|
+
Get name of plugin. By default we remove the PlatformSpecification portion.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
strip_all: When true, TaskSpecification and TaskSpec is stripped from name. When false only
|
|
38
|
+
Specification and Spec is Stripped
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Name of plugin
|
|
42
|
+
"""
|
|
43
|
+
if strip_all:
|
|
44
|
+
ret = cls.__name__.replace('TaskSpecification', '').replace("TaskSpec", '').replace('Spec', '')
|
|
45
|
+
else:
|
|
46
|
+
ret = cls.__name__.replace('Specification', '').replace('Spec', '')
|
|
47
|
+
return ret
|
|
48
|
+
|
|
49
|
+
@get_task_spec
|
|
50
|
+
def get(self, configuration: dict) -> 'ITask': # noqa: F821
|
|
51
|
+
"""
|
|
52
|
+
Return a new model using the passed in configuration.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
configuration: The INI configuration file to use.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
The new model.
|
|
59
|
+
"""
|
|
60
|
+
raise NotImplementedError("Plugin did not implement get")
|
|
61
|
+
|
|
62
|
+
@get_task_type_spec
|
|
63
|
+
def get_type(self) -> typing.Type['ITask']: # noqa: F821
|
|
64
|
+
"""
|
|
65
|
+
Get task type.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Task type
|
|
69
|
+
"""
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class TaskPlugins(SingletonMixin):
|
|
74
|
+
"""
|
|
75
|
+
TaskPlugins acts as a registry for Task Plugins.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, strip_all: bool = True) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Initialize the Task Registry. When strip all is false, the full plugin name will be used for names in map.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
strip_all: Whether to strip common parts of name from plugins in plugin map
|
|
84
|
+
"""
|
|
85
|
+
self._plugins = typing.cast(typing.Dict[str, TaskSpecification],
|
|
86
|
+
load_plugin_map('idmtools_task', TaskSpecification, strip_all))
|
|
87
|
+
|
|
88
|
+
def get_plugins(self) -> typing.Set[TaskSpecification]:
|
|
89
|
+
"""
|
|
90
|
+
Get plugins for Tasks.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Plugins
|
|
94
|
+
"""
|
|
95
|
+
return set(self._plugins.values())
|
|
96
|
+
|
|
97
|
+
def get_plugin_map(self) -> typing.Dict[str, TaskSpecification]:
|
|
98
|
+
"""
|
|
99
|
+
Get a map of task plugins.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Task plugin map
|
|
103
|
+
"""
|
|
104
|
+
return self._plugins
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provides utilities for plugins.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import functools
|
|
7
|
+
import inspect
|
|
8
|
+
import logging
|
|
9
|
+
from logging import DEBUG, getLogger
|
|
10
|
+
from typing import Type, List, Any, Set, Dict
|
|
11
|
+
import pluggy
|
|
12
|
+
from idmtools.registry import PluginSpecification
|
|
13
|
+
from idmtools.registry.plugin_specification import PLUGIN_REFERENCE_NAME
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
logger = getLogger(__name__)
|
|
17
|
+
user_logger = getLogger('user')
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def is_a_plugin_of_type(value, plugin_specification: Type[PluginSpecification]) -> bool:
|
|
21
|
+
"""
|
|
22
|
+
Determine if a value of a plugin specification is of type :class:`~idmtools.registry.plugin_specification.PluginSpecification`.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
value: The value to inspect.
|
|
26
|
+
plugin_specification: Plugin specification to check against.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
A Boolean indicating True if the plugin is of a subclass of :class:`~idmtools.registry.plugin_specification.PluginSpecification`,
|
|
30
|
+
else False.
|
|
31
|
+
"""
|
|
32
|
+
return inspect.isclass(value) and issubclass(value, plugin_specification) \
|
|
33
|
+
and not inspect.isabstract(value) and value is not plugin_specification
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def load_plugin_map(entrypoint: str, spec_type: Type[PluginSpecification], strip_all: bool = True) -> Dict[str, Type[PluginSpecification]]:
|
|
37
|
+
"""
|
|
38
|
+
Load plugins from entry point with the indicated type of specification into a map.
|
|
39
|
+
|
|
40
|
+
.. warning::
|
|
41
|
+
|
|
42
|
+
This could cause name collisions if plugins of the same name are installed.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
entrypoint: The name of the entry point.
|
|
46
|
+
spec_type: The type of plugin specification.
|
|
47
|
+
strip_all: Pass through for get_name from Plugins. Changes names in plugin registries
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
(Dict[str, Type[PluginSpecification]]): Returns a dictionary of name and :class:`~idmtools.registry.plugin_specification.PluginSpecification`.
|
|
51
|
+
"""
|
|
52
|
+
plugins = plugins_loader(entrypoint, spec_type)
|
|
53
|
+
# create instances of the plugins
|
|
54
|
+
_plugin_map = dict()
|
|
55
|
+
for plugin in plugins:
|
|
56
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
57
|
+
logger.debug(f"Loading {str(plugin)} as {plugin.get_name()}")
|
|
58
|
+
try:
|
|
59
|
+
_plugin_map[plugin.get_name(strip_all)] = plugin()
|
|
60
|
+
except Exception as e:
|
|
61
|
+
logger.exception(e)
|
|
62
|
+
user_logger.error(f'Problem loading plugin: {plugin.get_name()}')
|
|
63
|
+
return _plugin_map
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def plugins_loader(entry_points_name: str, plugin_specification: Type[PluginSpecification]) -> Set[PluginSpecification]:
|
|
67
|
+
"""
|
|
68
|
+
Loads all the plugins of type :class:`~idmtools.registry.plugin_specification.PluginSpecification` from entry point name.
|
|
69
|
+
|
|
70
|
+
|IT_s| also supports loading plugins through a list of strings representing the paths to modules containing plugins.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
entry_points_name: Entry point name for plugins.
|
|
74
|
+
plugin_specification: Plugin specification to load.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
(Set[PluginSpecification]): All the plugins of the type indicated.
|
|
78
|
+
"""
|
|
79
|
+
manager = pluggy.PluginManager(PLUGIN_REFERENCE_NAME)
|
|
80
|
+
manager.add_hookspecs(plugin_specification)
|
|
81
|
+
manager.load_setuptools_entrypoints(entry_points_name)
|
|
82
|
+
|
|
83
|
+
manager.check_pending()
|
|
84
|
+
return manager.get_plugins()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@functools.lru_cache(maxsize=32)
|
|
88
|
+
def discover_plugins_from(library: Any, plugin_specification: Type[PluginSpecification]) -> \
|
|
89
|
+
List[Type[PluginSpecification]]:
|
|
90
|
+
"""
|
|
91
|
+
Search a library object for plugins of type :class:`~idmtools.registry.plugin_specification.PluginSpecification`.
|
|
92
|
+
|
|
93
|
+
Currently it detects module and classes. In the future support for strings will be added.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
library: Library object to discover plugins from.
|
|
97
|
+
plugin_specification: Specification to search for.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
List[Type[PluginSpecification]]: List of plugins.
|
|
101
|
+
"""
|
|
102
|
+
plugins = []
|
|
103
|
+
# check if the item is a module
|
|
104
|
+
if inspect.ismodule(library):
|
|
105
|
+
if logger.isEnabledFor(DEBUG):
|
|
106
|
+
logger.debug('Attempting to load library as a module: %s', library.__name__)
|
|
107
|
+
for k, v in library.__dict__.items():
|
|
108
|
+
if k[:2] != '__' and is_a_plugin_of_type(v, plugin_specification):
|
|
109
|
+
if logger.isEnabledFor(DEBUG):
|
|
110
|
+
logger.debug('Adding class %s from %s as a plugin', v.__name__, library.__name__)
|
|
111
|
+
plugins.append(v)
|
|
112
|
+
# or maybe a plugin object
|
|
113
|
+
elif is_a_plugin_of_type(library, plugin_specification):
|
|
114
|
+
if logger.isEnabledFor(DEBUG):
|
|
115
|
+
logger.debug('Adding class %s as a plugin', library.__name__)
|
|
116
|
+
plugins.append(library)
|
|
117
|
+
else:
|
|
118
|
+
logger.warn('Could not determine the the type of library specified by %s', str(library))
|
|
119
|
+
return plugins
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IPersistenceService allows caching of items locally into a diskcache db that does not expire upon deletion.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
import logging
|
|
8
|
+
import time
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from multiprocessing import cpu_count
|
|
11
|
+
|
|
12
|
+
import diskcache
|
|
13
|
+
from abc import ABCMeta
|
|
14
|
+
|
|
15
|
+
from idmtools.core import IDMTOOLS_USER_HOME
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class IPersistenceService(metaclass=ABCMeta):
|
|
21
|
+
"""
|
|
22
|
+
IPersistenceService provides a persistent cache. This is useful for network heavy operations.
|
|
23
|
+
"""
|
|
24
|
+
cache_directory = None
|
|
25
|
+
cache_name = None
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def _open_cache(cls):
|
|
29
|
+
"""
|
|
30
|
+
Open cache.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
None
|
|
34
|
+
"""
|
|
35
|
+
import sqlite3
|
|
36
|
+
from idmtools import IdmConfigParser
|
|
37
|
+
cls.cache_directory = Path(
|
|
38
|
+
IdmConfigParser.get_option(option="cache_directory", fallback=IDMTOOLS_USER_HOME.joinpath("cache")))
|
|
39
|
+
|
|
40
|
+
# the more the cpus, the more likely we are to encounter a scaling issue. Let's try to scale with that up to
|
|
41
|
+
# one second. above one second, we are introducing to much lag in processes
|
|
42
|
+
default_timeout = min(max(0.25, cpu_count() * 0.025 * 2), 2)
|
|
43
|
+
retries = 0
|
|
44
|
+
while retries < 5:
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
os.makedirs(cls.cache_directory, exist_ok=True)
|
|
48
|
+
cache = diskcache.FanoutCache(os.path.join(str(cls.cache_directory), 'disk_cache', cls.cache_name),
|
|
49
|
+
timeout=default_timeout, shards=cpu_count() * 2)
|
|
50
|
+
return cache
|
|
51
|
+
except (sqlite3.OperationalError, FileNotFoundError):
|
|
52
|
+
retries += 1
|
|
53
|
+
time.sleep(0.1)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def retrieve(cls, uid):
|
|
57
|
+
"""
|
|
58
|
+
Retrieve item with id <uid> from cache.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
uid: Id to fetch
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Item loaded from cache
|
|
65
|
+
"""
|
|
66
|
+
with cls._open_cache() as cache:
|
|
67
|
+
obj = cache.get(uid, retry=True)
|
|
68
|
+
return obj
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def save(cls, obj):
|
|
72
|
+
"""
|
|
73
|
+
Save an item to our cache.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
obj: Object to save.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Object uid
|
|
80
|
+
"""
|
|
81
|
+
with cls._open_cache() as cache:
|
|
82
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
83
|
+
logging.debug('Saving %s to %s', obj.uid, cls.cache_name)
|
|
84
|
+
cache.set(obj.uid, obj, retry=True)
|
|
85
|
+
|
|
86
|
+
return obj.uid
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def delete(cls, uid):
|
|
90
|
+
"""
|
|
91
|
+
Delete at item from our cache with id <uid>.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
uid: Id to delete
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
None
|
|
98
|
+
"""
|
|
99
|
+
with cls._open_cache() as cache:
|
|
100
|
+
cache.delete(uid, retry=True)
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def clear(cls):
|
|
104
|
+
"""
|
|
105
|
+
Clear our cache.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
None
|
|
109
|
+
"""
|
|
110
|
+
with cls._open_cache() as cache:
|
|
111
|
+
cache.clear(retry=True)
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def list(cls):
|
|
115
|
+
"""
|
|
116
|
+
List items in our cache.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
List of items in our cache
|
|
120
|
+
"""
|
|
121
|
+
with cls._open_cache() as cache:
|
|
122
|
+
_list = list(cache)
|
|
123
|
+
return _list
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def length(cls):
|
|
127
|
+
"""
|
|
128
|
+
Total length of our persistence cache.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Count of our cache
|
|
132
|
+
"""
|
|
133
|
+
with cls._open_cache() as cache:
|
|
134
|
+
_len = len(cache)
|
|
135
|
+
return _len
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PlatformPersistService provides cache for platforms.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
from idmtools.services.ipersistance_service import IPersistenceService
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PlatformPersistService(IPersistenceService):
|
|
10
|
+
"""
|
|
11
|
+
Provide a cache for our platforms.
|
|
12
|
+
"""
|
|
13
|
+
cache_name = "platforms"
|
idmtools/utils/caller.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Defines our IAnalyzer interface used as base of all other analyzers.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_caller():
|
|
9
|
+
"""
|
|
10
|
+
Trace the stack and find the caller.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
The direct caller.
|
|
14
|
+
"""
|
|
15
|
+
import inspect
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
s = inspect.stack()
|
|
19
|
+
except (IndexError, RuntimeError):
|
|
20
|
+
# in some high thread environments and under heavy load, we can get environment changes before retrieving
|
|
21
|
+
# stack in those case assume we are good
|
|
22
|
+
# We can also encounter IndexError in dynamic environments like Snakemake, jinja, etc
|
|
23
|
+
return "__newobj__"
|
|
24
|
+
return s[2][3]
|