idmtools 0.0.0.dev0__py3-none-any.whl → 0.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- idmtools/__init__.py +27 -8
- idmtools/analysis/__init__.py +5 -0
- idmtools/analysis/add_analyzer.py +89 -0
- idmtools/analysis/analyze_manager.py +490 -0
- idmtools/analysis/csv_analyzer.py +103 -0
- idmtools/analysis/download_analyzer.py +96 -0
- idmtools/analysis/map_worker_entry.py +100 -0
- idmtools/analysis/platform_analysis_bootstrap.py +94 -0
- idmtools/analysis/platform_anaylsis.py +291 -0
- idmtools/analysis/tags_analyzer.py +93 -0
- idmtools/assets/__init__.py +9 -0
- idmtools/assets/asset.py +453 -0
- idmtools/assets/asset_collection.py +514 -0
- idmtools/assets/content_handlers.py +19 -0
- idmtools/assets/errors.py +23 -0
- idmtools/assets/file_list.py +191 -0
- idmtools/builders/__init__.py +11 -0
- idmtools/builders/arm_simulation_builder.py +152 -0
- idmtools/builders/csv_simulation_builder.py +76 -0
- idmtools/builders/simulation_builder.py +348 -0
- idmtools/builders/sweep_arm.py +109 -0
- idmtools/builders/yaml_simulation_builder.py +82 -0
- idmtools/config/__init__.py +7 -0
- idmtools/config/idm_config_parser.py +486 -0
- idmtools/core/__init__.py +10 -0
- idmtools/core/cache_enabled.py +114 -0
- idmtools/core/context.py +68 -0
- idmtools/core/docker_task.py +207 -0
- idmtools/core/enums.py +51 -0
- idmtools/core/exceptions.py +91 -0
- idmtools/core/experiment_factory.py +71 -0
- idmtools/core/id_file.py +70 -0
- idmtools/core/interfaces/__init__.py +5 -0
- idmtools/core/interfaces/entity_container.py +64 -0
- idmtools/core/interfaces/iassets_enabled.py +58 -0
- idmtools/core/interfaces/ientity.py +331 -0
- idmtools/core/interfaces/iitem.py +206 -0
- idmtools/core/interfaces/imetadata_operations.py +89 -0
- idmtools/core/interfaces/inamed_entity.py +17 -0
- idmtools/core/interfaces/irunnable_entity.py +159 -0
- idmtools/core/logging.py +387 -0
- idmtools/core/platform_factory.py +316 -0
- idmtools/core/system_information.py +104 -0
- idmtools/core/task_factory.py +145 -0
- idmtools/entities/__init__.py +10 -0
- idmtools/entities/command_line.py +229 -0
- idmtools/entities/command_task.py +155 -0
- idmtools/entities/experiment.py +787 -0
- idmtools/entities/generic_workitem.py +43 -0
- idmtools/entities/ianalyzer.py +163 -0
- idmtools/entities/iplatform.py +1106 -0
- idmtools/entities/iplatform_default.py +39 -0
- idmtools/entities/iplatform_ops/__init__.py +5 -0
- idmtools/entities/iplatform_ops/iplatform_asset_collection_operations.py +148 -0
- idmtools/entities/iplatform_ops/iplatform_experiment_operations.py +415 -0
- idmtools/entities/iplatform_ops/iplatform_simulation_operations.py +315 -0
- idmtools/entities/iplatform_ops/iplatform_suite_operations.py +322 -0
- idmtools/entities/iplatform_ops/iplatform_workflowitem_operations.py +301 -0
- idmtools/entities/iplatform_ops/utils.py +185 -0
- idmtools/entities/itask.py +316 -0
- idmtools/entities/iworkflow_item.py +167 -0
- idmtools/entities/platform_requirements.py +20 -0
- idmtools/entities/relation_type.py +14 -0
- idmtools/entities/simulation.py +255 -0
- idmtools/entities/suite.py +188 -0
- idmtools/entities/task_proxy.py +37 -0
- idmtools/entities/templated_simulation.py +325 -0
- idmtools/frozen/frozen_dict.py +71 -0
- idmtools/frozen/frozen_list.py +66 -0
- idmtools/frozen/frozen_set.py +86 -0
- idmtools/frozen/frozen_tuple.py +18 -0
- idmtools/frozen/frozen_utils.py +179 -0
- idmtools/frozen/ifrozen.py +66 -0
- idmtools/plugins/__init__.py +5 -0
- idmtools/plugins/git_commit.py +117 -0
- idmtools/registry/__init__.py +4 -0
- idmtools/registry/experiment_specification.py +105 -0
- idmtools/registry/functions.py +28 -0
- idmtools/registry/hook_specs.py +132 -0
- idmtools/registry/master_plugin_registry.py +51 -0
- idmtools/registry/platform_specification.py +138 -0
- idmtools/registry/plugin_specification.py +129 -0
- idmtools/registry/task_specification.py +104 -0
- idmtools/registry/utils.py +119 -0
- idmtools/services/__init__.py +5 -0
- idmtools/services/ipersistance_service.py +135 -0
- idmtools/services/platforms.py +13 -0
- idmtools/utils/__init__.py +5 -0
- idmtools/utils/caller.py +24 -0
- idmtools/utils/collections.py +246 -0
- idmtools/utils/command_line.py +45 -0
- idmtools/utils/decorators.py +300 -0
- idmtools/utils/display/__init__.py +22 -0
- idmtools/utils/display/displays.py +181 -0
- idmtools/utils/display/settings.py +25 -0
- idmtools/utils/dropbox_location.py +30 -0
- idmtools/utils/entities.py +127 -0
- idmtools/utils/file.py +72 -0
- idmtools/utils/file_parser.py +151 -0
- idmtools/utils/filter_simulations.py +182 -0
- idmtools/utils/filters/__init__.py +5 -0
- idmtools/utils/filters/asset_filters.py +88 -0
- idmtools/utils/general.py +286 -0
- idmtools/utils/gitrepo.py +336 -0
- idmtools/utils/hashing.py +239 -0
- idmtools/utils/info.py +124 -0
- idmtools/utils/json.py +82 -0
- idmtools/utils/language.py +107 -0
- idmtools/utils/local_os.py +40 -0
- idmtools/utils/time.py +22 -0
- idmtools-0.0.3.dist-info/METADATA +120 -0
- idmtools-0.0.3.dist-info/RECORD +116 -0
- idmtools-0.0.3.dist-info/entry_points.txt +9 -0
- idmtools-0.0.3.dist-info/licenses/LICENSE.TXT +3 -0
- idmtools-0.0.0.dev0.dist-info/METADATA +0 -41
- idmtools-0.0.0.dev0.dist-info/RECORD +0 -5
- {idmtools-0.0.0.dev0.dist-info → idmtools-0.0.3.dist-info}/WHEEL +0 -0
- {idmtools-0.0.0.dev0.dist-info → idmtools-0.0.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tools around displays and formatting.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
from abc import ABCMeta, abstractmethod
|
|
7
|
+
from typing import Any
|
|
8
|
+
from tabulate import tabulate
|
|
9
|
+
|
|
10
|
+
from idmtools.utils.collections import cut_iterable_to
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class IDisplaySetting(metaclass=ABCMeta):
|
|
14
|
+
"""
|
|
15
|
+
Base class for a display setting.
|
|
16
|
+
|
|
17
|
+
The child class needs to implement the :meth:`display` method.
|
|
18
|
+
|
|
19
|
+
Includes:
|
|
20
|
+
|
|
21
|
+
- header: Optional header for the display.
|
|
22
|
+
- field: If specified, the :meth:`get_object` will call :attr:`getattr` for this field on the object.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, header: str = None, field: str = None):
|
|
26
|
+
"""
|
|
27
|
+
Initialize our IDisplaySetting.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
header: Header for display
|
|
31
|
+
field: Optional field to display instead of object
|
|
32
|
+
"""
|
|
33
|
+
self.header = header
|
|
34
|
+
self.field = field
|
|
35
|
+
|
|
36
|
+
def get_object(self, obj: Any) -> Any:
|
|
37
|
+
"""
|
|
38
|
+
Get object or field depending if field is set.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
obj: Object to get
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Either obj.field or obj depending if self.field is set
|
|
45
|
+
"""
|
|
46
|
+
return getattr(obj, self.field) if self.field else obj
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def display(self, obj: Any) -> str:
|
|
50
|
+
"""
|
|
51
|
+
Display the object.
|
|
52
|
+
|
|
53
|
+
Note that the attribute (identified by self.field) should be handled with :meth:`get_object`.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
obj: The object to consider for display.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
A string representing what to show.
|
|
60
|
+
"""
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class StringDisplaySetting(IDisplaySetting):
|
|
65
|
+
"""
|
|
66
|
+
Class that displays the object as string.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def display(self, obj):
|
|
70
|
+
"""
|
|
71
|
+
Display object.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
obj: Object to display
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
String of object
|
|
78
|
+
"""
|
|
79
|
+
obj = self.get_object(obj)
|
|
80
|
+
return str(obj)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class DictDisplaySetting(IDisplaySetting):
|
|
84
|
+
"""
|
|
85
|
+
Class that displays a dictionary.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def __init__(self, header: str = None, field: str = None, max_items: int = 10, flat: bool = False):
|
|
89
|
+
"""
|
|
90
|
+
DictDisplay.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
header: Optional field header.
|
|
94
|
+
field: The field in the object to consider.
|
|
95
|
+
max_items: The maximum number of items to display.
|
|
96
|
+
flat: If False, display as a list; if True, display as a comma-separated list.
|
|
97
|
+
"""
|
|
98
|
+
super().__init__(header=header, field=field)
|
|
99
|
+
self.max_items = max_items
|
|
100
|
+
self.flat = flat
|
|
101
|
+
|
|
102
|
+
def display(self, obj: Any) -> str:
|
|
103
|
+
"""
|
|
104
|
+
Display a dictionary.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
obj: Object to display
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
String display of object
|
|
111
|
+
"""
|
|
112
|
+
# Retrieve our object
|
|
113
|
+
obj = self.get_object(obj)
|
|
114
|
+
|
|
115
|
+
# Slice the dictionary depending on the `max_items`
|
|
116
|
+
slice, remaining = cut_iterable_to(obj, self.max_items)
|
|
117
|
+
printout = ""
|
|
118
|
+
|
|
119
|
+
# Different display depending on `flat`
|
|
120
|
+
if self.flat:
|
|
121
|
+
printout = ", ".join(f"{k}:{v}" for k, v in slice.items())
|
|
122
|
+
else:
|
|
123
|
+
for k, v in slice.items():
|
|
124
|
+
printout += f"- {k}:{v}\n"
|
|
125
|
+
printout = printout.strip()
|
|
126
|
+
|
|
127
|
+
# If there are items remaining, display
|
|
128
|
+
if remaining > 0:
|
|
129
|
+
printout = str(printout)
|
|
130
|
+
printout += f"\n... and {remaining} more"
|
|
131
|
+
|
|
132
|
+
return printout
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class TableDisplay(IDisplaySetting):
|
|
136
|
+
"""
|
|
137
|
+
Class that displays the object as a table.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(self, columns, max_rows=5, field=None):
|
|
141
|
+
"""
|
|
142
|
+
Initialize our TableDisplay.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
columns: A list of display settings.
|
|
146
|
+
max_rows: The maximum number of rows to display.
|
|
147
|
+
field: The field of the object to consider.
|
|
148
|
+
"""
|
|
149
|
+
super().__init__(field=field)
|
|
150
|
+
self.columns = columns
|
|
151
|
+
self.max_rows = max_rows
|
|
152
|
+
|
|
153
|
+
def display(self, obj) -> str:
|
|
154
|
+
"""
|
|
155
|
+
Display our object as a table.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
obj: Object to display
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Table represented as a string of the object
|
|
162
|
+
"""
|
|
163
|
+
# Retrieve our object
|
|
164
|
+
obj = super().get_object(obj)
|
|
165
|
+
|
|
166
|
+
# Cut the rows
|
|
167
|
+
slice, remaining = cut_iterable_to(obj, self.max_rows)
|
|
168
|
+
|
|
169
|
+
# Create the table
|
|
170
|
+
rows = []
|
|
171
|
+
for child in slice:
|
|
172
|
+
rows.append([s.display(child) for s in self.columns])
|
|
173
|
+
|
|
174
|
+
printout = tabulate(rows, headers=[s.header for s in self.columns], tablefmt='psql', showindex=False)
|
|
175
|
+
|
|
176
|
+
# If there are items remaining, display
|
|
177
|
+
if remaining > 0:
|
|
178
|
+
printout = str(printout)
|
|
179
|
+
printout += f"\n... and {remaining} more"
|
|
180
|
+
|
|
181
|
+
return printout
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
defines views for different types if items.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
from idmtools.utils.display.displays import DictDisplaySetting, StringDisplaySetting, TableDisplay
|
|
7
|
+
|
|
8
|
+
simulation_display_settings = [
|
|
9
|
+
StringDisplaySetting(header="ID", field="uid"),
|
|
10
|
+
DictDisplaySetting(header="Tags", field="tags", flat=False)]
|
|
11
|
+
|
|
12
|
+
experiment_display_settings = [
|
|
13
|
+
StringDisplaySetting(header="ID", field="uid"),
|
|
14
|
+
StringDisplaySetting(header="Simulation count", field="simulation_count")
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
experiment_table_display = [
|
|
18
|
+
StringDisplaySetting(),
|
|
19
|
+
TableDisplay(field="simulations", columns=simulation_display_settings)
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
suite_table_display = [
|
|
23
|
+
StringDisplaySetting(),
|
|
24
|
+
TableDisplay(field="experiments", columns=experiment_display_settings)
|
|
25
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
utilities for pathing of dropbox folders.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_current_user():
|
|
10
|
+
"""
|
|
11
|
+
Get current user name.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
Current username
|
|
15
|
+
"""
|
|
16
|
+
# Returns current logged in user for Windows filesystem
|
|
17
|
+
import getpass
|
|
18
|
+
return getpass.getuser()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_dropbox_location():
|
|
22
|
+
"""
|
|
23
|
+
Get user dropbox location.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
User dropbox location
|
|
27
|
+
"""
|
|
28
|
+
user = get_current_user()
|
|
29
|
+
dropbox_filepath = os.path.join("C:/Users/", user, "Dropbox (IDM)")
|
|
30
|
+
return dropbox_filepath
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
utilities for dataclasses.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import ast
|
|
7
|
+
import os
|
|
8
|
+
import dataclasses
|
|
9
|
+
import typing
|
|
10
|
+
from logging import getLogger
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
if typing.TYPE_CHECKING:
|
|
14
|
+
from idmtools.entities.experiment import Experiment
|
|
15
|
+
from idmtools.entities.iworkflow_item import IWorkflowItem
|
|
16
|
+
from idmtools.entities.iplatform import IPlatform
|
|
17
|
+
|
|
18
|
+
user_logger = getLogger('user')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_dataclass_common_fields(src, dest, exclude_none: bool = True) -> typing.Dict:
|
|
22
|
+
"""
|
|
23
|
+
Extracts fields from a dataclass source object who are also defined on destination object.
|
|
24
|
+
|
|
25
|
+
Useful for situations like nested configurations of data class options.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
src: Source dataclass object
|
|
29
|
+
dest: Dest dataclass object
|
|
30
|
+
exclude_none: When true, values of None will be excluded
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Dictionary of common fields between source and destination object
|
|
34
|
+
"""
|
|
35
|
+
dest_fields = [f.name for f in dataclasses.fields(dest)]
|
|
36
|
+
src_fields = dataclasses.fields(src)
|
|
37
|
+
result = dict()
|
|
38
|
+
for field in src_fields:
|
|
39
|
+
if field.name in dest_fields and (not exclude_none or (exclude_none and getattr(src, field.name, None) is not None)):
|
|
40
|
+
result[field.name] = getattr(src, field.name)
|
|
41
|
+
return result
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def as_dict(src, exclude: typing.List[str] = None, exclude_private_fields: bool = True):
|
|
45
|
+
"""
|
|
46
|
+
Converts a dataclass to a dict while also obeys rules for exclusion.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
src:
|
|
50
|
+
exclude: List of fields to exclude
|
|
51
|
+
exclude_private_fields: Should fields that star
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Data class as dict
|
|
55
|
+
"""
|
|
56
|
+
if exclude is None:
|
|
57
|
+
exclude = []
|
|
58
|
+
|
|
59
|
+
result = dict()
|
|
60
|
+
# get metadata
|
|
61
|
+
metadata = dataclasses.fields(src.__class__)
|
|
62
|
+
for field in metadata:
|
|
63
|
+
field_metadata = field.metadata
|
|
64
|
+
if (not exclude_private_fields or field.name[0] != '_') and \
|
|
65
|
+
('exclude_from_metadata' not in field_metadata or not field_metadata['exclude_from_metadata']) and \
|
|
66
|
+
field.name not in exclude:
|
|
67
|
+
result[field.name] = getattr(src, field.name)
|
|
68
|
+
return result
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def validate_user_inputs_against_dataclass(field_type, field_value):
|
|
72
|
+
"""
|
|
73
|
+
Validates user entered data against dataclass fields and types.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
field_type: Field type
|
|
77
|
+
field_value: Fields value
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Validates user values
|
|
81
|
+
"""
|
|
82
|
+
fs_kwargs = set(field_type.keys()).intersection(set(field_value.keys()))
|
|
83
|
+
for fn in fs_kwargs:
|
|
84
|
+
ft = field_type[fn]
|
|
85
|
+
try:
|
|
86
|
+
if ft in (int, float, str):
|
|
87
|
+
field_value[fn] = ft(field_value[fn]) if field_value[fn] is not None else field_value[fn]
|
|
88
|
+
elif ft is bool:
|
|
89
|
+
field_value[fn] = ast.literal_eval(field_value[fn]) if isinstance(field_value[fn], str) else \
|
|
90
|
+
field_value[fn]
|
|
91
|
+
except ValueError as e:
|
|
92
|
+
user_logger.error(f"The field {fn} requires a value of type {ft.__name__}. You provided <{field_value[fn]}>")
|
|
93
|
+
raise e
|
|
94
|
+
return fs_kwargs
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_default_tags() -> typing.Dict[str, str]:
|
|
98
|
+
"""
|
|
99
|
+
Get common default tags. Currently this is the version of idmtools.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Default tags which is idmtools version
|
|
103
|
+
"""
|
|
104
|
+
from idmtools import __version__
|
|
105
|
+
return dict(idmtools=__version__)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def save_id_as_file_as_hook(item: typing.Union['Experiment', 'IWorkflowItem'], platform: 'IPlatform'):
|
|
109
|
+
"""
|
|
110
|
+
Predefined hook that will save ids to files for Experiment or WorkItems.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
item:
|
|
114
|
+
platform:
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
None
|
|
118
|
+
"""
|
|
119
|
+
# Import locally because of circular deps
|
|
120
|
+
from idmtools.entities.experiment import Experiment
|
|
121
|
+
from idmtools.entities.iworkflow_item import IWorkflowItem
|
|
122
|
+
if not isinstance(item, (Experiment, IWorkflowItem)):
|
|
123
|
+
raise NotImplementedError("Saving id is currently only support for Experiments and Workitems")
|
|
124
|
+
id_file = Path(f'{item.item_type}.{item.name}.id')
|
|
125
|
+
if os.path.exists(id_file):
|
|
126
|
+
os.remove(id_file)
|
|
127
|
+
item.to_id_file(id_file)
|
idmtools/utils/file.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
utilities for files.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
from io import BytesIO
|
|
8
|
+
from os import DirEntry
|
|
9
|
+
from typing import Iterable, Generator, List, Union
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def scan_directory(basedir: str, recursive: bool = True, ignore_directories: List[str] = None) -> Iterable[DirEntry]:
|
|
13
|
+
"""
|
|
14
|
+
Scan a directory recursively or not.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
basedir: The root directory to start from.
|
|
18
|
+
recursive: True to search the sub-folders recursively; False to stay in the root directory.
|
|
19
|
+
ignore_directories: Ignore directories
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
An iterator yielding all the files found.
|
|
23
|
+
"""
|
|
24
|
+
for entry in os.scandir(basedir):
|
|
25
|
+
if entry.is_file():
|
|
26
|
+
yield entry
|
|
27
|
+
elif recursive:
|
|
28
|
+
if ignore_directories is None or entry.name not in ignore_directories:
|
|
29
|
+
yield from scan_directory(entry.path)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def file_content_to_generator(absolute_path, chunk_size=128) -> Generator[bytearray, None, None]:
|
|
33
|
+
"""
|
|
34
|
+
Create a generator from file contents in chunks(useful for streaming binary data and piping).
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
absolute_path: absolute path to file
|
|
38
|
+
chunk_size: chunk size
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Generator that return bytes in chunks of size chunk_size
|
|
42
|
+
"""
|
|
43
|
+
with open(absolute_path, 'rb') as i:
|
|
44
|
+
while True:
|
|
45
|
+
res = i.read(chunk_size)
|
|
46
|
+
if res == b'':
|
|
47
|
+
break
|
|
48
|
+
else:
|
|
49
|
+
yield res
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def content_generator(content: Union[str, bytes], chunk_size=128) -> Generator[bytearray, None, None]:
|
|
53
|
+
"""
|
|
54
|
+
Create a generator from file contents in chunks(useful for streaming binary data and piping).
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
content : file content
|
|
58
|
+
chunk_size : chunk size
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Generator that return bytes in chunks of size chunk_size
|
|
62
|
+
"""
|
|
63
|
+
if isinstance(content, str):
|
|
64
|
+
content_io = BytesIO(content.encode())
|
|
65
|
+
else:
|
|
66
|
+
content_io = BytesIO(content)
|
|
67
|
+
while True:
|
|
68
|
+
chunk = content_io.read(chunk_size)
|
|
69
|
+
if chunk == b'':
|
|
70
|
+
break
|
|
71
|
+
else:
|
|
72
|
+
yield chunk
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File parser utility. Used to automatically load data.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from logging import getLogger
|
|
9
|
+
from typing import Dict
|
|
10
|
+
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
from io import StringIO, BytesIO
|
|
14
|
+
|
|
15
|
+
logger = getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FileParser:
|
|
19
|
+
"""
|
|
20
|
+
FileParser to load contents in analysis.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def parse(cls, filename, content=None):
|
|
25
|
+
"""
|
|
26
|
+
Parse filename and load the content.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
filename: Filename to load
|
|
30
|
+
content: Content to load
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Content loaded
|
|
34
|
+
"""
|
|
35
|
+
file_extension = os.path.splitext(filename)[1][1:].lower()
|
|
36
|
+
content = BytesIO(content)
|
|
37
|
+
|
|
38
|
+
if file_extension == 'json':
|
|
39
|
+
return cls.load_json_file(filename, content)
|
|
40
|
+
|
|
41
|
+
if file_extension == 'csv':
|
|
42
|
+
return cls.load_csv_file(filename, content)
|
|
43
|
+
|
|
44
|
+
if file_extension == 'xlsx':
|
|
45
|
+
return cls.load_xlsx_file(filename, content)
|
|
46
|
+
|
|
47
|
+
if file_extension == 'txt':
|
|
48
|
+
return cls.load_txt_file(filename, content)
|
|
49
|
+
|
|
50
|
+
if file_extension == 'bin' and 'SpatialReport' in filename:
|
|
51
|
+
return cls.load_bin_file(filename, content)
|
|
52
|
+
|
|
53
|
+
return cls.load_raw_file(filename, content)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def load_json_file(cls, filename, content) -> Dict:
|
|
57
|
+
"""
|
|
58
|
+
Load JSON File.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
filename: Filename to load
|
|
62
|
+
content: Content
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
JSOn as dict
|
|
66
|
+
"""
|
|
67
|
+
return json.load(content)
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def load_raw_file(self, filename, content):
|
|
71
|
+
"""
|
|
72
|
+
Load content raw.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
filename: Filename is none
|
|
76
|
+
content: Content to load
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Content as it was
|
|
80
|
+
"""
|
|
81
|
+
return content
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def load_csv_file(cls, filename, content) -> pd.DataFrame:
|
|
85
|
+
"""
|
|
86
|
+
Load csv file.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
filename: Filename to load
|
|
90
|
+
content: Content is loading
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Loaded csv file
|
|
94
|
+
"""
|
|
95
|
+
if not isinstance(content, StringIO) and not isinstance(content, BytesIO):
|
|
96
|
+
content = StringIO(content)
|
|
97
|
+
|
|
98
|
+
csv_read = pd.read_csv(content, skipinitialspace=True)
|
|
99
|
+
return csv_read
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def load_xlsx_file(cls, filename, content) -> Dict[str, pd.ExcelFile]:
|
|
103
|
+
"""
|
|
104
|
+
Load excel_file.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
filename: Filename to load
|
|
108
|
+
content: Content to load
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Loaded excel file
|
|
112
|
+
"""
|
|
113
|
+
excel_file = pd.ExcelFile(content)
|
|
114
|
+
return {sheet_name: excel_file.parse(sheet_name) for sheet_name in excel_file.sheet_names}
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def load_txt_file(cls, filename, content):
|
|
118
|
+
"""
|
|
119
|
+
Load text file.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
filename: Filename to load
|
|
123
|
+
content: Content to load
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Content
|
|
127
|
+
"""
|
|
128
|
+
return str(content.getvalue().decode())
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
def load_bin_file(cls, filename, content):
|
|
132
|
+
"""
|
|
133
|
+
Load a bin file.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
filename: Filename to load
|
|
137
|
+
content: Content to load
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Loaded bin file
|
|
141
|
+
|
|
142
|
+
Notes:
|
|
143
|
+
We should move this to a plugin in emodpy. We need to figure out how to structure that.
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
from idmtools_platform_comps.utils.spatial_output import SpatialOutput
|
|
147
|
+
so = SpatialOutput.from_bytes(content.read(), 'Filtered' in filename)
|
|
148
|
+
return so.to_dict()
|
|
149
|
+
except ImportError as ex:
|
|
150
|
+
logger.exception(ex)
|
|
151
|
+
logger.error("Could not import item. Most likely dtk.tools is not installed")
|