-
Notifications
You must be signed in to change notification settings - Fork 0
/
configuration.py
217 lines (174 loc) · 8.26 KB
/
configuration.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
import configparser
import logging
import multiprocessing
from pathlib import Path
import sys
from helix import metadata, mtDNA
if sys.platform == "win32":
from helix import third_party
else:
# As it is right now, on linux third_party is not installed and
# not needed.
# This will probably change in the future but for the moment just
# provide anything else that have a __file__ so that nothing here
# crashes.
third_party = sys.modules[__name__]
logger = logging.getLogger("configuration")
logging.getLogger().setLevel(logging.DEBUG)
# Parse is very chatty for everything less than INFO
logging.getLogger("parse").setLevel(logging.INFO)
# There libs are very chatty for everything less than WARNING
logging.getLogger("requests").setLevel(logging.WARNING)
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("google").setLevel(logging.WARNING)
class HelixDefaults:
"""Specify some directory where to find configuration files."""
HELIX_FOLDER = Path(__file__).parents[1]
LOCAL_FOLDER = Path(Path.home(), ".helix")
LOCAL_CONFIG = Path(HELIX_FOLDER, "configuration", "main.ini")
GLOBAL_CONFIG = Path(Path.home(), ".helix", "main.ini")
class GeneralConfig:
"""General configuration for the app.
Every attribute of this class can be loaded from the configuration
files if present. See the documentation for ConfigurationManager.
NOTE: you can add every type of variables as long as they can be
constructed with a (single) string. Otherwise this will fail.
Attributes:
last_path (Path): path for the last file opened
log_level (str): minimum log level. Possible values: DEBUG,
INFO, WARNING, CRITICAL
"""
def __init__(self) -> None:
self.last_path: Path = Path.home()
self.log_level: str = "DEBUG"
class ExternalConfig:
"""Configuration for 3rd party dependencies.
Every attribute of this class can be loaded from the configuration
files if present. See the documentation for ConfigurationManager.
NOTE: you can add every type of variables as long as they can be
constructed with a (single) string. Otherwise this will fail.
Attributes:
root (Path): folder that contains 3rd party dependencies.
If this does not point to a valid directory, the files
should be under PATH.
threads (int): How many threads is possible to use.
"""
def __init__(self) -> None:
self.root: Path = Path(third_party.__file__).parent
self.threads: int = multiprocessing.cpu_count()
class RepositoryConfig:
"""Configuration for reference genome and metadata repository.
Every attribute of this class can be loaded from the configuration
files if present. See the documentation for ConfigurationManager.
NOTE: you can add every type of variables as long as they can be
constructed with a (single) string. Otherwise this will fail.
Attributes:
repository (Path): Root folder for reference genomes.
temporary (Path): Temporary files directory.
metadata (Path): Root folder for metadata.
mtdna (Path): Root folder for mtDNA files.
"""
def __init__(self) -> None:
self.genomes: Path = Path(HelixDefaults.LOCAL_FOLDER, "genomes")
self.temporary: Path = Path(HelixDefaults.LOCAL_FOLDER, "temp")
self.log_path: Path = Path(HelixDefaults.LOCAL_FOLDER, "logs")
self.metadata: Path = Path(metadata.__file__).parent
self.mtdna: Path = Path(mtDNA.__file__).parent
class AlignmentStatsConfig:
"""Configuration for alignment stats calculation.
Every attribute of this class can be loaded from the configuration
files if present. See the documentation for ConfigurationManager.
NOTE: you can add every type of variables as long as they can be
constructed with a (single) string. Otherwise this will fail.
Attributes:
skip (int): How many samples to skip at the beginning
of the file (where lots of repetitive and low quality
sequences usually are).
samples (int): How many samples to consider when calculating
the stats.
"""
def __init__(self) -> None:
self.skip: int = 40000
self.samples: int = 20000
class ConfigurationManager:
"""Initialize the configuration.
Every static attribute declared here will be initialized
when the class is initialized by looking for sections
with the same as the attribute, containing the same attributes
as the attribute.
Attributes:
GENERAL (GeneralConfig): Configuration parameters for the whole
app.
EXTERNAL (ExternalConfig): Configuration for 3rd party dependencies.
REPOSITORY (RepositoryConfig): Configuration for reference genome
and metadata repository.
ALIGNMENT_STATS (AlignmentStatsConfig): Configuration for alignment
stats calculation.
"""
GENERAL: GeneralConfig = GeneralConfig()
EXTERNAL: ExternalConfig = ExternalConfig()
REPOSITORY: RepositoryConfig = RepositoryConfig()
ALIGNMENT_STATS: AlignmentStatsConfig = AlignmentStatsConfig()
def __init__(self) -> None:
self.load()
def load(self) -> None:
self._parser = configparser.ConfigParser(interpolation=None)
if HelixDefaults.LOCAL_CONFIG.exists():
logging.info(f"Loading {HelixDefaults.LOCAL_CONFIG}")
if HelixDefaults.GLOBAL_CONFIG.exists():
logging.info(f"Loading {HelixDefaults.GLOBAL_CONFIG}")
self._parser.read(HelixDefaults.LOCAL_CONFIG)
self._parser.read(HelixDefaults.GLOBAL_CONFIG)
for var_name, var_value in ConfigurationManager.__dict__.items():
if var_name.startswith("__"):
continue
if getattr(var_value, "__dict__") is None:
continue
if type(var_value) is type(ConfigurationManager.load):
continue
section = var_name.lower()
if section not in self._parser:
continue
for key, value in self._parser[section].items():
if key not in var_value.__dict__:
logging.warning(f"Configuration {section}.{key} not known")
continue
if value is None:
continue
var_type = type(var_value.__dict__[key])
var_value.__dict__[key] = var_type(value)
def save(self, save_defaults=False):
for var_name, var_value in ConfigurationManager.__dict__.items():
if var_name.startswith("__"):
continue
if getattr(var_value, "__dict__") is None:
continue
if type(var_value) is type(ConfigurationManager.load):
continue
default_item = type(var_value)()
section = var_name.lower()
if section not in self._parser:
self._parser.add_section(section)
for key, value in var_value.__dict__.items():
if default_item.__dict__[key] != value or save_defaults:
self._parser[section][key] = str(value)
if len(self._parser[section]) == 0 and not save_defaults:
self._parser.remove_section(section)
ordered_config_paths = [HelixDefaults.GLOBAL_CONFIG, HelixDefaults.LOCAL_CONFIG]
if not any([x.exists() for x in ordered_config_paths]):
with ordered_config_paths[1].open("wt") as f:
pass
for config in ordered_config_paths:
if not config.exists():
continue
with config.open("wt") as f:
self._parser.write(f)
break
MANAGER_CFG = None
if MANAGER_CFG is None:
HelixDefaults.GLOBAL_CONFIG.parent.mkdir(parents=True, exist_ok=True)
HelixDefaults.GLOBAL_CONFIG.touch(exist_ok=True)
MANAGER_CFG = ConfigurationManager()
MANAGER_CFG.REPOSITORY.temporary.mkdir(parents=True, exist_ok=True)
MANAGER_CFG.REPOSITORY.genomes.mkdir(parents=True, exist_ok=True)
MANAGER_CFG.REPOSITORY.mtdna.mkdir(parents=True, exist_ok=True)