-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconfig.py
184 lines (146 loc) · 5.17 KB
/
config.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
# Batteries
import json
import os
from functools import reduce
# Third-party Imports
import jsonschema
from loguru import logger
# Local Imports
from shared.utils import keyreplace, DefaultValidatingDraft7Validator
class InvalidConfiguration(Exception):
"""
Thrown when the read configuration is missing
mandatory parameters or is an invalid JSON format.
Args:
builtins.Exception (class): Builtin exception class.
"""
...
class Config(object):
"""
Base configuration class. Reads data from config.json file
and allows retrieving that data.
Configurations are cached in memory for faster access.
Args:
builtins.object (class): Builtin object class.
"""
# Base directory
BASE_DIR = os.path.realpath(os.path.dirname(os.path.realpath(__file__)))
# Default configuration file
CONFIG_FILE_PATH = f'config.json'
# Configuration Syntax
CONFIG_FILE_SYNTAX = {
'type': 'object',
'properties': {
'log': {
'type': 'object',
'properties': {
'level': {
'type': 'string',
'enum': ['TRACE', 'DEBUG', 'INFO', 'SUCCESS', 'WARNING', 'ERROR', 'CRITICAL'],
'default': 'DEBUG'
},
'file': {'type': 'string', 'default': 'logs/ping.log'},
'error': {'type': 'string', 'default': 'logs/ping-error.log'}
},
'required': ['file', 'error', 'level']
},
'pidfile': {'type': 'string', 'default': 'ping.pid'},
'datastore': {'type': 'string'}
}
}
# Class parameters
_configfile = None
_configdict = None
@classmethod
def getconfigpath(cls):
"""
Returns the configuration file path.
Returns:
str: The configuration file path.
"""
return cls._configfile
@classmethod
def validate(cls, path):
"""
Validates the configuration file syntax.
Args:
path (str): The path for the configuration file.
"""
try:
# Open file and read configuration into memory
with open(path, 'r') as configfile:
configdict = json.load(configfile)
# Replace key dashes with underscores
configdict = keyreplace(configdict, '-', '_')
# Validate configuration JSON
DefaultValidatingDraft7Validator(cls.CONFIG_FILE_SYNTAX).validate(configdict)
# Return configuration
return configdict
except FileNotFoundError:
raise InvalidConfiguration(f'Configuration file {path} does not exist.')
except json.JSONDecodeError:
raise InvalidConfiguration('Configuration file contains invalid JSON format.')
except jsonschema.ValidationError as e:
raise InvalidConfiguration(f'Configurations file contains errors: {str(e)}')
@classmethod
def load(cls, path):
"""
Loads the configuration file into memory.
Args:
path (str): The path for the configuration file.
"""
# Set configuration file
cls._configfile = path
# Validate and load configuration
cls._configdict = cls.validate(path)
@classmethod
def reload(cls):
"""
Reloads the configuration file into memory.
"""
try:
cls.load(cls._configfile)
except InvalidConfiguration as e:
logger.warning(f'Invalid configurations, not reloading. Error: {str(e)}')
return False
return True
@classmethod
def get(cls, key, default=None):
"""
Retrieves the value for a key from the configuration file.
Args:
key (str): The key from which to get the value. These
can be several splitted by a dot.
default (object): What to return if the key is not found.
Returns:
object: The configuration value.
"""
# Retrieve configurations if not loaded
if not cls._configdict:
raise InvalidConfiguration('Configurations have not yet been loaded.')
try:
value = reduce(dict.get, key.replace('-', '_').split('.'), cls._configdict)
except TypeError:
return default
return default if value is None else value
@classmethod
def getpath(cls, key):
"""
Utility function to retrieve a key whose value is a
filesystem path. This will read both absolute and
relative paths and always return an absolute path.
If relative, the path will be prefixed with the
project's root.
Args:
key (str): The key from which to get the value. These
can be several splitted by a dot.
Returns:
str: The absolute path.
"""
# Read path from configuration
path = str(cls.get(key))
# Prevent empty value
if not path:
return None
# Return path or absolute path from base dir
return path if os.path.isabs(path) else f'{cls.BASE_DIR}/{path}'