-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path__init__.py
More file actions
196 lines (180 loc) · 9.13 KB
/
Copy path__init__.py
File metadata and controls
196 lines (180 loc) · 9.13 KB
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
""" Author: Megarushing
This is a simple script to automate configuration persistence
and accessibility throughout the system using python default config parser,
the only thing you need to edit is the default_values field for the default
settings desired, you may do this by inheriting this class or modifying
that field before creating an instance.
You can also provide a filename when instatiating if you desire so,
instead of the standard config.ini. If the file does not exist,
it will be created with the default settings.
Multiple file names in a list are also possible, in which case all are used to load the
settings in order, same settings take precedence on later files while
only the most precedent last file is used for saving new settings"""
import os
import sys
import json
import logging
if sys.version_info >= (3, 0): #Python 3 configparser
from configparser import ConfigParser
from configparser import Error as ParserError
from json import JSONDecodeError
else:
from ConfigParser import ConfigParser
from ConfigParser import Error as ParserError
from simplejson import JSONDecodeError
logging.basicConfig()
logger = logging.getLogger('easyfig')
class Easyfig(object):
# set _config_filename to the desired config file(s), this can be either a string or a list
# of multiple strings, when multiple config files are detected, settings are
# loaded from all files in order, but saved only to the last one
_config_filename = 'config.ini'
# In default_values set all possible configs, and their default values
# the exact config type will be inferred based on its default values type
# every dict key will become a global variable under this lib for ease of access
# NOTE: if config name starts with a _(underscore) it will be protected from the script
# and only modifiable through manually editing the ini file
defaults = {
"GENERAL" : {
"example" : 1,
"_protected_example": 0
}
}
@staticmethod
def set_defaults(defaults_dict,section="GENERAL"):
""" We can use this method to set the default values for the configuration,
this needs to be set before creating the instance, so the correct file is created
automatically on first run
:param dict defaults_dict: dictionary with variable names and default values
to be persisted accross multiple runs, this goes
in the form
{
"example" : 1,
"_protected_example": 0
}
the names that begin with _ are protected and can
only be edited by _set or by modifying the config file
:param str section: variables are usually created in the default "GENERAL"
section, in case you need additional sections you can
specify default values for it by setting this variable
"""
Easyfig.defaults[section] = defaults_dict
# override this function if you need to load additional custom sections not
# controlled by this script
def load_additional_sections(self):
""" override this function if you need to load additional custom sections not
controlled by easyfig, you can use self._parser as the preloaded ConfigParser object """
return
# set filename to the desired config file(s) if you do not wish to use the default config.ini
def __init__(self,filename=None):
""" loads the configuration file as fields in this object
filename accepts both a list of config files to load or a single string"""
if filename:
self._config_filename = filename #updates config filename
#os.path.dirname(os.path.realpath(__file__)) was writing to module path
if type(self._config_filename) == str:
self._config_filename = [self._config_filename]
if os.path.isdir(os.path.realpath(sys.argv[0])):
self._script_dir = os.path.realpath(sys.argv[0])
else:
self._script_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
self._parser = None
self._save_parser = None
self.reload()
def reload(self):
# for saving we use only last file
self._save_parser = ConfigParser()
self._save_parser.read(os.path.join(self._script_dir, self._config_filename[-1]))
#for loading we use all files
self._parser = ConfigParser()
for filename in self._config_filename:
#loads all ini files, last one has precedence over first
self._parser.read(os.path.join(self._script_dir,filename))
#generates all global variables representing config sections
for section,defaults in self.defaults.items():
for option,default_value in defaults.items():
varname = option
if option.startswith("_"):
varname = option.replace("_","",1)
#if there is a config with same name remove it, only protected one stays
if self._parser.has_section(section) and varname in self._parser.items(section):
self._parser.remove_option(section=section,option=varname)
if self._save_parser.has_section(section) and varname in self._save_parser.items(section):
self._save_parser.remove_option(section=section,option=varname)
if section != "GENERAL":
varname = section.lower() + "_" + varname
#cast read string variable to type from default_values
val = self.get(option,default=default_value,section=section)
self._set_attribute(varname, val, default_value)
self.save()
self.load_additional_sections()
def _set_attribute(self, varname, value, default):
""" Helper function to cast value type according to default value type
from string and setting the ambient global variable of variable_name"""
if type(default) in [list, dict, tuple]: # load as json instead
try:
setattr(self,varname,json.loads(value.replace("'", '"')))
except JSONDecodeError:
setattr(self,varname,type(default)(value))
else:
try:
setattr(self,varname,type(default)(value))
except Exception as e:
logger.warning("Error setting config variable %s, keeping old value: %s", varname, e)
def get(self,key,default=None,section="GENERAL"):
"""gets config value string representation, if not possible
creates it with the default value provided"""
value = default
try:
value = self._parser.get(section,key)
except ParserError as e:
logger.warning("Error getting config %s from section %s: %s\nSetting default value: %s",key,
section,e,default)
value = self._set(key,value,section=section)
return str(value)
def _set(self,option,value,section="GENERAL"):
""" This is the internal set function which does not verify value protection """
if not self._save_parser.has_section(section):
self._save_parser.add_section(section)
# decide wether we should save it as small json or regular string
setval = value
if type(value) in [list, dict, tuple]: # save as json instead
try:
self._save_parser.set(section, option, json.dumps(value))
setval = json.dumps(value)
except JSONDecodeError:
self._save_parser.set(section, option, str(value))
setval = str(value)
else:
self._save_parser.set(section, option, str(value))
setval = str(value)
return setval
def set(self,option,value,section="GENERAL"):
""" For security only allow setting values that previously exist,
also, do not allow setting protected values """
if not self._parser.has_section(section):
self.reload() #probably just generated the config
if self._parser.has_section(section) and\
option in dict(self._parser.items(section))\
and not option.startswith("_"):
setval = self._set(option,value,section)
self.save()
self.reload()
return setval
else:
return False
def save(self):
"""saves current configs (only the ones set with set() method)"""
# Writing to last configuration file
with open(os.path.join(self._script_dir, self._config_filename[-1]), 'w') as configfile:
self._save_parser.write(configfile)
def tostring(self,section="GENERAL"):
"""
:return: Returns string representation of section configs
"""
items = dict(self._parser.items(section))
out = ""
for k, v in items.items():
if not k.startswith("_"):
out += "{} : {}\n".format(k,v)
return out