-
Notifications
You must be signed in to change notification settings - Fork 5
/
python_config.py
171 lines (116 loc) · 4.65 KB
/
python_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
"""Python configuration file parser."""
from __future__ import unicode_literals
import imp
import sys
_PY2 = sys.version_info < (3,)
if _PY2:
str = unicode
_BASIC_TYPES = (bool, int, float, bytes, str)
"""Python basic types."""
if _PY2:
_BASIC_TYPES += (long,)
_COMPLEX_TYPES = (tuple, list, set, dict)
"""Python complex types."""
_VALID_TYPES = _BASIC_TYPES + _COMPLEX_TYPES
"""Option value must be one of these types."""
class Error(Exception):
"""The base class for all exceptions that the module raises."""
def __init__(self, error, *args, **kwargs):
super(Error, self).__init__(error.format(*args, **kwargs) if args or kwargs else error)
class FileReadingError(Error):
"""Error while reading a configuration file."""
def __init__(self, path, error):
super(FileReadingError, self).__init__(
"Error while reading '{0}' configuration file: {1}.", path, error.strerror)
self.errno = error.errno
class ParsingError(Error):
"""Error while parsing a configuration file."""
def __init__(self, path, error):
super(ParsingError, self).__init__(
"Error while parsing '{0}' configuration file: {1}.", path, error)
class ValidationError(Error):
"""Error during validation of a configuration file."""
def __init__(self, path, error):
super(ValidationError, self).__init__(
"Error while parsing '{0}' configuration file: {1}.", path, error)
self.option_name = error.option_name
class _ValidationError(Error):
"""Same as ValidationError, but for internal usage."""
def __init__(self, option_name, *args, **kwargs):
super(_ValidationError, self).__init__(*args, **kwargs)
self.option_name = option_name
def load(path, contents=None):
"""Loads a configuration file."""
config_module = imp.new_module("config")
config_module.__file__ = path
if contents is None:
try:
with open(path) as config_file:
contents = config_file.read()
except EnvironmentError as e:
raise FileReadingError(path, e)
try:
exec(compile(contents, path, "exec"), config_module.__dict__)
except Exception as e:
raise ParsingError(path, e)
config = {}
for option, value in config_module.__dict__.items():
if not option.startswith("_") and option.isupper():
try:
config[option.lower()] = _validate_value(option, value)
except _ValidationError as e:
raise ValidationError(path, e)
return config
def _validate_value(option, value, valid_types=_VALID_TYPES):
"""Validates an option value."""
value_type = type(value)
if value_type not in valid_types:
raise _ValidationError(option,
"{option} has an invalid value type ({type}). Allowed types: {valid_types}.",
option=option, type=value_type.__name__,
valid_types=", ".join(t.__name__ for t in valid_types))
if value_type is dict:
value = _validate_dict(option, value)
elif value_type is list:
value = _validate_list(option, value)
elif value_type is tuple:
value = _validate_tuple(option, value)
elif value_type is set:
value = _validate_set(option, value)
elif value_type is bytes:
try:
value = value.decode()
except UnicodeDecodeError as e:
raise _ValidationError(option, "{0} has an invalid value: {1}.", option, e)
return value
def _validate_dict(option, dictionary):
"""Validates a dictionary."""
for key, value in tuple(dictionary.items()):
valid_key = _validate_value("A {0}'s key".format(option),
key, valid_types=_BASIC_TYPES)
valid_value = _validate_value("{0}[{1}]".format(option, repr(key)), value)
if valid_key is not key:
del dictionary[key]
dictionary[valid_key] = valid_value
elif valid_value is not value:
dictionary[valid_key] = valid_value
return dictionary
def _validate_list(option, sequence):
"""Validates a list."""
for index, value in enumerate(sequence):
valid_value = _validate_value("{0}[{1}]".format(option, index), value)
if valid_value is not value:
sequence[index] = valid_value
return sequence
def _validate_tuple(option, sequence):
"""Validates a tuple."""
return [
_validate_value("{0}[{1}]".format(option, index), value)
for index, value in enumerate(sequence)
]
def _validate_set(option, sequence):
"""Validates a set."""
return [
_validate_value("A {0}'s key".format(option), value)
for value in sequence
]