-
Notifications
You must be signed in to change notification settings - Fork 0
/
configuration.py
155 lines (131 loc) · 6.08 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
import os
import logging
from typing import Dict, List, Tuple, Union
import json
import _io
from io import StringIO, TextIOWrapper
import re
import yaml
from jsonschema import validate as validate_json_schema
logger = logging.getLogger('Configuration')
class Configuration:
__slots__ = ('config', 'config_path', 'spotify', 'switchbot')
config: Dict
config_path: str
spotify: Dict
switchbot: Dict
config_attributes: List = []
env_variable_tag: str = '!ENV'
env_variable_pattern: str = r'.*?\${(\w+)}.*?' # ${var}
def __init__(self, config_src: Union[TextIOWrapper, StringIO, str], config_schema_path: str = 'yml_schema.json'):
"""
The basic constructor. Creates a new instance of the Configuration class.
:param config_src:
:param config_schema_path:
"""
# Load the predefined schema of the configuration
configuration_schema = self.load_configuration_schema(config_schema_path=config_schema_path)
# Load the configuration
self.config, self.config_path = self.load_yml(config_src=config_src,
env_tag=self.env_variable_tag,
env_pattern=self.env_variable_pattern)
logger.debug("Loaded config: %s" % self.config)
# Validate the config
validate_json_schema(self.config, configuration_schema)
# Set the config properties as instance attributes
all_config_attributes = ('spotify', 'switchbot')
for config_attribute in all_config_attributes:
if config_attribute in self.config.keys():
setattr(self, config_attribute, self.config[config_attribute])
self.config_attributes.append(config_attribute)
else:
setattr(self, config_attribute, None)
if not all(spoti_var != '' for spoti_var in self.get_spotifies()[0]):
logger.error('Spotify config has some empty values!\n%s' % self.get_spotifies()[0])
raise Exception('Spotify config has some empty values!')
# if not all(switch_var != '' for switch_var in self.get_switchbots()[0]):
# logger.error('switchbot config has some empty values\n%s' % self.get_switchbots()[0])
# raise Exception('switchbot config has some empty values!')
@staticmethod
def load_configuration_schema(config_schema_path: str) -> Dict:
with open('/'.join([os.path.dirname(os.path.realpath(__file__)), config_schema_path])) as f:
configuration_schema = json.load(f)
return configuration_schema
@staticmethod
def load_yml(config_src: Union[TextIOWrapper, StringIO, str], env_tag: str, env_pattern: str) -> Tuple[Dict, str]:
pattern = re.compile(env_pattern)
loader = yaml.SafeLoader
loader.add_implicit_resolver(env_tag, pattern, None)
def constructor_env_variables(loader, node):
"""
Extracts the environment variable from the node's value
:param yaml.Loader loader: the yaml loader
:param node: the current node in the yaml
:return: the parsed string that contains the value of the environment
variable
"""
value = loader.construct_scalar(node)
match = pattern.findall(value) # to find all env variables in line
if match:
full_value = value
for g in match:
full_value = full_value.replace(
f'${{{g}}}', os.environ.get(g, g)
)
return full_value
return value
loader.add_constructor(env_tag, constructor_env_variables)
if isinstance(config_src, TextIOWrapper):
logging.debug("Loading yaml from TextIOWrapper")
config = yaml.load(config_src, Loader=loader)
config_path = config_src.name
elif isinstance(config_src, StringIO):
logging.debug("Loading yaml from StringIO")
config = yaml.load(config_src, Loader=loader)
config_path = "StringIO"
elif isinstance(config_src, str):
logging.debug("Loading yaml from path")
with open(config_src) as f:
config = yaml.load(f, Loader=loader)
config_path = config_src
else:
raise TypeError('Config file must be TextIOWrapper or path to a file')
return config, config_path
def get_spotifies(self) -> List:
if 'spotify' in self.config_attributes:
return [sub_config['config'] for sub_config in self.spotify]
else:
raise ConfigurationError('Config property spotify not set!')
def get_switchbots(self) -> List:
if 'switchbot' in self.config_attributes:
return [sub_config['config'] for sub_config in self.switchbot]
else:
raise ConfigurationError('Config property switchbot not set!')
def to_yml(self, fn: Union[str, _io.TextIOWrapper]) -> None:
"""
Writes the configuration to a stream. For example a file.
:param fn:
:return: None
"""
dict_conf = dict()
for config_attribute in self.config_attributes:
dict_conf[config_attribute] = getattr(self, config_attribute)
if isinstance(fn, str):
with open(fn, 'w') as f:
yaml.dump(dict_conf, f, default_flow_style=False)
elif isinstance(fn, _io.TextIOWrapper):
yaml.dump(dict_conf, fn, default_flow_style=False)
else:
raise TypeError('Expected str or _io.TextIOWrapper not %s' % (type(fn)))
to_yaml = to_yml
def to_json(self) -> Dict:
dict_conf = dict()
for config_attribute in self.config_attributes:
dict_conf[config_attribute] = getattr(self, config_attribute)
return dict_conf
def __getitem__(self, item):
return self.__getattribute__(item)
class ConfigurationError(Exception):
def __init__(self, message):
# Call the base class constructor with the parameters it needs
super().__init__(message)