Merge pull request #1378 from cdhorn/ch-configmgr-comment
This commit is contained in:
commit
5b540d65bc
@ -139,6 +139,19 @@ class CompleteCheck(unittest.TestCase):
|
|||||||
self.assertEqual(self.CM.get("section2.dict"), {'a': "apple", "b": "banana"})
|
self.assertEqual(self.CM.get("section2.dict"), {'a': "apple", "b": "banana"})
|
||||||
self.assertEqual(self.CM.get("section2.unicode"), "Raötröme")
|
self.assertEqual(self.CM.get("section2.unicode"), "Raötröme")
|
||||||
|
|
||||||
|
self.assertRaises(AttributeError, self.CM.save, TEST2_INI, comments=123)
|
||||||
|
self.assertRaises(AttributeError, self.CM.save, TEST2_INI, comments={"key":"pair"})
|
||||||
|
self.assertRaises(AttributeError, self.CM.save, TEST2_INI, comments=[123])
|
||||||
|
self.assertRaises(AttributeError, self.CM.save, TEST2_INI, comments=["line1", 123])
|
||||||
|
|
||||||
|
self.CM.save(TEST2_INI, comments=["test comment1"])
|
||||||
|
self.CM.load(TEST2_INI)
|
||||||
|
self.assertEqual(self.CM.get("section.setting3"), "Another String")
|
||||||
|
|
||||||
|
self.CM.save(TEST2_INI, comments=["test comment1", "test comment2"])
|
||||||
|
self.CM.load(TEST2_INI)
|
||||||
|
self.assertEqual(self.CM.get("section.setting3"), "Another String")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
# Copyright (C) 2005-2007 Donald N. Allingham
|
# Copyright (C) 2005-2007 Donald N. Allingham
|
||||||
# Copyright (C) 2008-2009 Gary Burton
|
# Copyright (C) 2008-2009 Gary Burton
|
||||||
# Copyright (C) 2009 Doug Blank <doug.blank@gmail.com>
|
# Copyright (C) 2009 Doug Blank <doug.blank@gmail.com>
|
||||||
|
# Copyright (C) 2023 Christopher Horn
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@ -24,46 +25,45 @@
|
|||||||
This package implements access to Gramps configuration.
|
This package implements access to Gramps configuration.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# System imports
|
# Python modules
|
||||||
#
|
#
|
||||||
#---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
|
import configparser
|
||||||
|
import copy
|
||||||
|
import errno
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import configparser
|
|
||||||
import errno
|
|
||||||
import copy
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Gramps modules
|
||||||
|
#
|
||||||
|
# ---------------------------------------------------------------
|
||||||
from ..const import GRAMPS_LOCALE as glocale
|
from ..const import GRAMPS_LOCALE as glocale
|
||||||
|
|
||||||
_ = glocale.translation.gettext
|
_ = glocale.translation.gettext
|
||||||
|
|
||||||
|
|
||||||
def safe_eval(exp):
|
def safe_eval(exp):
|
||||||
# restrict eval to empty environment
|
# restrict eval to empty environment
|
||||||
return eval(exp, {})
|
return eval(exp, {})
|
||||||
|
|
||||||
##try:
|
|
||||||
## from ast import literal_eval as safe_eval
|
|
||||||
## # this leaks memory !!
|
|
||||||
##except:
|
|
||||||
## # PYTHON2.5 COMPATIBILITY: no ast present
|
|
||||||
## # not as safe as literal_eval, but works for python2.5:
|
|
||||||
## def safe_eval(exp):
|
|
||||||
## # restrict eval to empty environment
|
|
||||||
## return eval(exp, {})
|
|
||||||
|
|
||||||
#---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# Classes
|
# ConfigManager
|
||||||
#
|
#
|
||||||
#---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
class ConfigManager:
|
class ConfigManager:
|
||||||
"""
|
"""
|
||||||
Class to construct the singleton CONFIGMAN where all
|
Class to construct the singleton CONFIGMAN where all
|
||||||
settings are stored.
|
settings are stored.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PLUGINS = {}
|
PLUGINS = {}
|
||||||
|
|
||||||
def __init__(self, filename=None, plugins=None):
|
def __init__(self, filename=None, plugins=None):
|
||||||
@ -101,18 +101,20 @@ class ConfigManager:
|
|||||||
:param plugins: (if given) is a relative path to filename.
|
:param plugins: (if given) is a relative path to filename.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self._cb_id = 0 # callback id counter
|
self._cb_id = 0 # callback id counter
|
||||||
self.config_path, config_filename = \
|
self.config_path, dummy_config_filename = os.path.split(
|
||||||
os.path.split(os.path.abspath(filename))
|
os.path.abspath(filename)
|
||||||
self.filename = filename # fullpath and filename, or None
|
)
|
||||||
self.plugins = plugins # relative directory name, or None
|
self.filename = filename # fullpath and filename, or None
|
||||||
|
self.plugins = plugins # relative directory name, or None
|
||||||
self.callbacks = {}
|
self.callbacks = {}
|
||||||
self.default = {}
|
self.default = {}
|
||||||
self.data = {}
|
self.data = {}
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
def register_manager(self, name, override="", use_plugins_path=True,
|
def register_manager(
|
||||||
use_config_path=False):
|
self, name, override="", use_plugins_path=True, use_config_path=False
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Register a plugin manager.
|
Register a plugin manager.
|
||||||
|
|
||||||
@ -151,13 +153,15 @@ class ConfigManager:
|
|||||||
use_plugins_path=False)
|
use_plugins_path=False)
|
||||||
# will use /tmp/Other.ini
|
# will use /tmp/Other.ini
|
||||||
"""
|
"""
|
||||||
if isinstance(override, str): # directory or filename
|
if isinstance(override, str): # directory or filename
|
||||||
if override:
|
if override:
|
||||||
path, ininame = os.path.split(os.path.abspath(override))
|
path, ininame = os.path.split(os.path.abspath(override))
|
||||||
else:
|
else:
|
||||||
path, ininame = os.path.split(sys._getframe(1).f_code.co_filename)
|
path, ininame = os.path.split(
|
||||||
|
sys._getframe(1).f_code.co_filename
|
||||||
|
)
|
||||||
if not ininame.endswith(".ini"):
|
if not ininame.endswith(".ini"):
|
||||||
ininame = "%s.ini" % name
|
ininame = f"{name}.ini"
|
||||||
if use_config_path:
|
if use_config_path:
|
||||||
path = self.config_path
|
path = self.config_path
|
||||||
elif use_plugins_path:
|
elif use_plugins_path:
|
||||||
@ -172,12 +176,17 @@ class ConfigManager:
|
|||||||
return ConfigManager.PLUGINS[name]
|
return ConfigManager.PLUGINS[name]
|
||||||
|
|
||||||
def get_manager(self, name):
|
def get_manager(self, name):
|
||||||
|
"""
|
||||||
|
Return manager for a plugin.
|
||||||
|
"""
|
||||||
if name in ConfigManager.PLUGINS:
|
if name in ConfigManager.PLUGINS:
|
||||||
return ConfigManager.PLUGINS[name]
|
return ConfigManager.PLUGINS[name]
|
||||||
else:
|
raise AttributeError(f"config '{name}': does not exist")
|
||||||
raise AttributeError("config '%s': does not exist"% name)
|
|
||||||
|
|
||||||
def has_manager(self, name):
|
def has_manager(self, name):
|
||||||
|
"""
|
||||||
|
Check if have manager for a plugin.
|
||||||
|
"""
|
||||||
return name in ConfigManager.PLUGINS
|
return name in ConfigManager.PLUGINS
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
@ -212,25 +221,29 @@ class ConfigManager:
|
|||||||
setting = None
|
setting = None
|
||||||
elif "." in key:
|
elif "." in key:
|
||||||
section, setting = key.split(".", 1)
|
section, setting = key.split(".", 1)
|
||||||
else: # key is not None and doesn't have a "."
|
else: # key is not None and doesn't have a "."
|
||||||
section = key
|
section = key
|
||||||
setting = None
|
setting = None
|
||||||
|
|
||||||
# Now, do the reset on the right parts:
|
# Now, do the reset on the right parts:
|
||||||
if section is None:
|
if section is None:
|
||||||
self.data = {}
|
self.data = {}
|
||||||
for section in self.default:
|
for section in self.default:
|
||||||
self.data[section] = {}
|
self.data[section] = {}
|
||||||
for setting in self.default[section]:
|
for setting in self.default[section]:
|
||||||
self.data[section][setting] = \
|
self.data[section][setting] = copy.deepcopy(
|
||||||
copy.deepcopy(self.default[section][setting])
|
self.default[section][setting]
|
||||||
|
)
|
||||||
elif setting is None:
|
elif setting is None:
|
||||||
self.data[section] = {}
|
self.data[section] = {}
|
||||||
for setting in self.default[section]:
|
for setting in self.default[section]:
|
||||||
self.data[section][setting] = \
|
self.data[section][setting] = copy.deepcopy(
|
||||||
copy.deepcopy(self.default[section][setting])
|
self.default[section][setting]
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.data[section][setting] = \
|
self.data[section][setting] = copy.deepcopy(
|
||||||
copy.deepcopy(self.default[section][setting])
|
self.default[section][setting]
|
||||||
|
)
|
||||||
# Callbacks are still connected
|
# Callbacks are still connected
|
||||||
|
|
||||||
def get_sections(self):
|
def get_sections(self):
|
||||||
@ -253,80 +266,97 @@ class ConfigManager:
|
|||||||
filename = self.filename
|
filename = self.filename
|
||||||
if filename and os.path.exists(filename):
|
if filename and os.path.exists(filename):
|
||||||
parser = configparser.RawConfigParser()
|
parser = configparser.RawConfigParser()
|
||||||
try: # see bugs 5356, 5490, 5591, 5651, 5718, etc.
|
try: # see bugs 5356, 5490, 5591, 5651, 5718, etc.
|
||||||
parser.read(filename, encoding='utf8')
|
parser.read(filename, encoding="utf8")
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
msg1 = _("WARNING: could not parse file:\n%(file)s\n"
|
msg1 = _(
|
||||||
"because %(error)s -- recreating it\n") % {
|
"WARNING: could not parse file:\n%(file)s\n"
|
||||||
'file' : filename,
|
"because %(error)s -- recreating it\n"
|
||||||
'error' : str(err)}
|
) % {"file": filename, "error": str(err)}
|
||||||
logging.warn(msg1)
|
logging.warning(msg1)
|
||||||
return
|
return
|
||||||
for sec in parser.sections():
|
|
||||||
name = sec.lower()
|
|
||||||
if name not in self.data:
|
|
||||||
# Add the setting from file
|
|
||||||
# These might be old settings, or third-party settings
|
|
||||||
self.data[name] = {}
|
|
||||||
for opt in parser.options(sec):
|
|
||||||
raw_value = parser.get(sec, opt).strip()
|
|
||||||
if raw_value[:2] == "u'":
|
|
||||||
raw_value = raw_value[1:]
|
|
||||||
elif raw_value.startswith('['):
|
|
||||||
raw_value = raw_value.replace(", u'", ", '")
|
|
||||||
raw_value = raw_value.replace("[u'", "['")
|
|
||||||
setting = opt.lower()
|
|
||||||
if oldstyle:
|
|
||||||
####################### Upgrade from oldstyle < 3.2
|
|
||||||
# Oldstyle didn't mark setting type, but had it
|
|
||||||
# set in preferences. New style gets it from evaling
|
|
||||||
# the setting's value
|
|
||||||
#######################
|
|
||||||
# if we know this setting, convert type
|
|
||||||
key = "%s.%s" % (name, setting)
|
|
||||||
if self.has_default(key):
|
|
||||||
vtype = type(self.get_default(key))
|
|
||||||
if vtype == bool:
|
|
||||||
value = raw_value in ["1", "True"]
|
|
||||||
elif vtype == list:
|
|
||||||
logging.warning("WARNING: ignoring old key '%s'" % key)
|
|
||||||
continue # there were no lists in oldstyle
|
|
||||||
else:
|
|
||||||
value = vtype(raw_value)
|
|
||||||
else:
|
|
||||||
# else, ignore it
|
|
||||||
logging.warning("WARNING: ignoring old key '%s'" % key)
|
|
||||||
continue # with next setting
|
|
||||||
####################### End upgrade code
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
value = safe_eval(raw_value)
|
|
||||||
except:
|
|
||||||
# most likely exception is SyntaxError but
|
|
||||||
# others are possible ex: '0L' from Python2 days
|
|
||||||
value = None
|
|
||||||
####################### Now, let's test and set:
|
|
||||||
if (name in self.default and
|
|
||||||
setting in self.default[name]):
|
|
||||||
if isinstance(self.default[name][setting], bool):
|
|
||||||
#make sure 0 and 1 are False and True
|
|
||||||
if value == 0:
|
|
||||||
value = False
|
|
||||||
elif value == 1:
|
|
||||||
value = True
|
|
||||||
if self.check_type(self.default[name][setting], value):
|
|
||||||
self.data[name][setting] = value
|
|
||||||
else:
|
|
||||||
logging.warning("WARNING: ignoring key with wrong type "
|
|
||||||
"'%s.%s' %s needed instead of %s" %
|
|
||||||
(name, setting,
|
|
||||||
type(self.data[name][setting]),
|
|
||||||
type(value)))
|
|
||||||
else:
|
|
||||||
# this could be a third-party setting; add it:
|
|
||||||
self.data[name][setting] = value
|
|
||||||
|
|
||||||
def save(self, filename = None):
|
if oldstyle:
|
||||||
|
loader = self._load_oldstyle_section
|
||||||
|
else:
|
||||||
|
loader = self._load_section
|
||||||
|
|
||||||
|
for section in parser.sections():
|
||||||
|
name = section.lower()
|
||||||
|
if name not in self.data:
|
||||||
|
self.data[name] = {}
|
||||||
|
loader(section, parser)
|
||||||
|
|
||||||
|
def _load_section(self, section, parser):
|
||||||
|
"""
|
||||||
|
Load a section of an .ini file into self.data
|
||||||
|
"""
|
||||||
|
name = section.lower()
|
||||||
|
for option in parser.options(section):
|
||||||
|
raw_value = get_raw_value(parser, section, option)
|
||||||
|
setting = option.lower()
|
||||||
|
|
||||||
|
try:
|
||||||
|
value = safe_eval(raw_value)
|
||||||
|
except:
|
||||||
|
# most likely exception is SyntaxError but
|
||||||
|
# others are possible ex: '0L' from Python2 days
|
||||||
|
value = None
|
||||||
|
|
||||||
|
self._load_setting(name, setting, value)
|
||||||
|
|
||||||
|
def _load_oldstyle_section(self, section, parser):
|
||||||
|
"""
|
||||||
|
Load a section of an .ini file into self.data
|
||||||
|
"""
|
||||||
|
name = section.lower()
|
||||||
|
for option in parser.options(section):
|
||||||
|
raw_value = get_raw_value(parser, section, option)
|
||||||
|
setting = option.lower()
|
||||||
|
|
||||||
|
####################### Upgrade from oldstyle < 3.2
|
||||||
|
# Oldstyle didn't mark setting type, but had it
|
||||||
|
# set in preferences. New style gets it from evaling
|
||||||
|
# the setting's value
|
||||||
|
#######################
|
||||||
|
# if we know this setting, convert type
|
||||||
|
key = f"{name}.{setting}"
|
||||||
|
if self.has_default(key):
|
||||||
|
vtype = type(self.get_default(key))
|
||||||
|
if vtype == bool:
|
||||||
|
value = raw_value in ["1", "True"]
|
||||||
|
elif vtype == list:
|
||||||
|
logging.warning("WARNING: ignoring old key '%s'", key)
|
||||||
|
continue # there were no lists in oldstyle
|
||||||
|
else:
|
||||||
|
value = vtype(raw_value)
|
||||||
|
else:
|
||||||
|
# else, ignore it
|
||||||
|
logging.warning("WARNING: ignoring old key '%s'", key)
|
||||||
|
continue # with next setting
|
||||||
|
####################### End upgrade code
|
||||||
|
self._load_setting(name, setting, value)
|
||||||
|
|
||||||
|
def _load_setting(self, name, setting, value):
|
||||||
|
if name in self.default and setting in self.default[name]:
|
||||||
|
if isinstance(self.default[name][setting], bool):
|
||||||
|
value = bool(value)
|
||||||
|
if self.check_type(self.default[name][setting], value):
|
||||||
|
self.data[name][setting] = value
|
||||||
|
else:
|
||||||
|
logging.warning(
|
||||||
|
"WARNING: ignoring key with wrong type "
|
||||||
|
"'%s.%s' %s needed instead of %s",
|
||||||
|
name,
|
||||||
|
setting,
|
||||||
|
type(self.data[name][setting]),
|
||||||
|
type(value),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# this could be a third-party setting; add it:
|
||||||
|
self.data[name][setting] = value
|
||||||
|
|
||||||
|
def save(self, filename=None, comments=None):
|
||||||
"""
|
"""
|
||||||
Saves the current section/settings to an .ini file. Optional filename
|
Saves the current section/settings to an .ini file. Optional filename
|
||||||
will override the default filename to save to, if given.
|
will override the default filename to save to, if given.
|
||||||
@ -343,26 +373,30 @@ class ConfigManager:
|
|||||||
try:
|
try:
|
||||||
with open(filename, "w", encoding="utf-8") as key_file:
|
with open(filename, "w", encoding="utf-8") as key_file:
|
||||||
key_file.write(";; Gramps key file\n")
|
key_file.write(";; Gramps key file\n")
|
||||||
key_file.write(";; Automatically created at %s" %
|
key_file.write(
|
||||||
time.strftime("%Y/%m/%d %H:%M:%S") + "\n\n")
|
";; Automatically created at "
|
||||||
|
f"{time.strftime('%Y/%m/%d %H:%M:%S')}\n\n"
|
||||||
|
)
|
||||||
|
write_comments(key_file, comments)
|
||||||
for section in sorted(self.data):
|
for section in sorted(self.data):
|
||||||
key_file.write("[%s]\n" % section)
|
key_file.write(f"[{section}]\n")
|
||||||
for key in sorted(self.data[section]):
|
for key in sorted(self.data[section]):
|
||||||
value = self.data[section][key]
|
value = self.data[section][key]
|
||||||
default = "" # might be a third-party setting
|
default = "" # might be a third-party setting
|
||||||
if self.has_default("%s.%s" % (section, key)):
|
if self.has_default(f"{section}.{key}"):
|
||||||
if value == self.get_default("%s.%s"
|
if value == self.get_default(
|
||||||
% (section, key)):
|
f"{section}.{key}"
|
||||||
|
):
|
||||||
default = ";;"
|
default = ";;"
|
||||||
if isinstance(value, int):
|
if isinstance(value, int):
|
||||||
value = int(value) # TODO why is this needed?
|
value = int(value) # TODO why is this needed?
|
||||||
key_file.write("%s%s=%s\n" % (default, key,
|
key_file.write(f"{default}{key}={repr(value)}\n")
|
||||||
repr(value)))
|
|
||||||
key_file.write("\n")
|
key_file.write("\n")
|
||||||
# else, no filename given; nothing to save so do nothing quietly
|
# else, no filename given; nothing to save so do nothing quietly
|
||||||
except IOError as err:
|
except IOError as err:
|
||||||
logging.warn("Failed to open %s because %s",
|
logging.warning(
|
||||||
filename, str(err))
|
"Failed to open %s because %s", filename, str(err)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
def get(self, key):
|
def get(self, key):
|
||||||
@ -370,27 +404,37 @@ class ConfigManager:
|
|||||||
Get the setting's value. raise an error if an invalid section.setting.
|
Get the setting's value. raise an error if an invalid section.setting.
|
||||||
Key is a string in the "section.setting" format.
|
Key is a string in the "section.setting" format.
|
||||||
"""
|
"""
|
||||||
if "." in key:
|
try:
|
||||||
section, setting = key.split(".", 1)
|
section, setting = key.split(".", 1)
|
||||||
else:
|
except ValueError as error:
|
||||||
raise AttributeError("Invalid config section.setting name: '%s'" %
|
raise AttributeError(
|
||||||
key)
|
f"Invalid config section.setting name: '{key}'"
|
||||||
if section not in self.data:
|
) from error
|
||||||
raise AttributeError("No such config section name: '%s'" % section)
|
|
||||||
if setting not in self.data[section]:
|
self._validate_section_and_setting(section, setting)
|
||||||
raise AttributeError("No such config setting name: '%s.%s'" %
|
|
||||||
(section, setting))
|
|
||||||
return self.data[section][setting]
|
return self.data[section][setting]
|
||||||
|
|
||||||
|
def _validate_section_and_setting(self, section, setting):
|
||||||
|
"""
|
||||||
|
Validate section and setting present in the loaded data.
|
||||||
|
"""
|
||||||
|
if section not in self.data:
|
||||||
|
raise AttributeError(f"No such config section name: '{section}'")
|
||||||
|
if setting not in self.data[section]:
|
||||||
|
raise AttributeError(
|
||||||
|
f"No such config setting name: '{section}.{setting}'"
|
||||||
|
)
|
||||||
|
|
||||||
def is_set(self, key):
|
def is_set(self, key):
|
||||||
"""
|
"""
|
||||||
Does the setting exist? Returns True if does, False otherwise.
|
Does the setting exist? Returns True if does, False otherwise.
|
||||||
Key is a string in the "section.setting" format.
|
Key is a string in the "section.setting" format.
|
||||||
"""
|
"""
|
||||||
if "." in key:
|
try:
|
||||||
section, setting = key.split(".", 1)
|
section, setting = key.split(".", 1)
|
||||||
else:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if section not in self.data:
|
if section not in self.data:
|
||||||
return False
|
return False
|
||||||
if setting not in self.data[section]:
|
if setting not in self.data[section]:
|
||||||
@ -402,10 +446,11 @@ class ConfigManager:
|
|||||||
Does the setting have a default value? Returns True if it does,
|
Does the setting have a default value? Returns True if it does,
|
||||||
False otherwise. Key is a string in the "section.setting" format.
|
False otherwise. Key is a string in the "section.setting" format.
|
||||||
"""
|
"""
|
||||||
if "." in key:
|
try:
|
||||||
section, setting = key.split(".", 1)
|
section, setting = key.split(".", 1)
|
||||||
else:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if section not in self.default:
|
if section not in self.default:
|
||||||
return False
|
return False
|
||||||
if setting not in self.default[section]:
|
if setting not in self.default[section]:
|
||||||
@ -417,16 +462,19 @@ class ConfigManager:
|
|||||||
Get the setting's default value. Raises an error if invalid key is
|
Get the setting's default value. Raises an error if invalid key is
|
||||||
give. Key is a string in the "section.setting" format.
|
give. Key is a string in the "section.setting" format.
|
||||||
"""
|
"""
|
||||||
if "." in key:
|
try:
|
||||||
section, setting = key.split(".", 1)
|
section, setting = key.split(".", 1)
|
||||||
else:
|
except ValueError as error:
|
||||||
raise AttributeError("Invalid config section.setting name: '%s'" %
|
raise AttributeError(
|
||||||
key)
|
f"Invalid config section.setting name: '{key}'"
|
||||||
|
) from error
|
||||||
|
|
||||||
if section not in self.default:
|
if section not in self.default:
|
||||||
raise AttributeError("No such config section name: '%s'" % section)
|
raise AttributeError(f"No such config section name: '{section}'")
|
||||||
if setting not in self.default[section]:
|
if setting not in self.default[section]:
|
||||||
raise AttributeError("No such config setting name: '%s.%s'" %
|
raise AttributeError(
|
||||||
(section, setting))
|
f"No such config setting name: '{section}.{setting}'"
|
||||||
|
)
|
||||||
return self.default[section][setting]
|
return self.default[section][setting]
|
||||||
|
|
||||||
def register(self, key, default):
|
def register(self, key, default):
|
||||||
@ -435,11 +483,13 @@ class ConfigManager:
|
|||||||
Will overwrite any previously set default, and set setting if not one.
|
Will overwrite any previously set default, and set setting if not one.
|
||||||
The default value deterimines the type of the setting.
|
The default value deterimines the type of the setting.
|
||||||
"""
|
"""
|
||||||
if "." in key:
|
try:
|
||||||
section, setting = key.split(".", 1)
|
section, setting = key.split(".", 1)
|
||||||
else:
|
except ValueError as error:
|
||||||
raise AttributeError("Invalid config section.setting name: '%s'" %
|
raise AttributeError(
|
||||||
key)
|
f"Invalid config section.setting name: '{key}'"
|
||||||
|
) from error
|
||||||
|
|
||||||
if section not in self.data:
|
if section not in self.data:
|
||||||
self.data[section] = {}
|
self.data[section] = {}
|
||||||
if section not in self.default:
|
if section not in self.default:
|
||||||
@ -458,16 +508,14 @@ class ConfigManager:
|
|||||||
"""
|
"""
|
||||||
Connect a callback func that gets called when key is changed.
|
Connect a callback func that gets called when key is changed.
|
||||||
"""
|
"""
|
||||||
if "." in key:
|
try:
|
||||||
section, setting = key.split(".", 1)
|
section, setting = key.split(".", 1)
|
||||||
else:
|
except ValueError as error:
|
||||||
raise AttributeError("Invalid config section.setting name: '%s'" %
|
raise AttributeError(
|
||||||
key)
|
f"Invalid config section.setting name: '{key}'"
|
||||||
if section not in self.data:
|
) from error
|
||||||
raise AttributeError("No such config section name: '%s'" % section)
|
|
||||||
if setting not in self.data[section]:
|
self._validate_section_and_setting(section, setting)
|
||||||
raise AttributeError("No such config setting name: '%s.%s'" %
|
|
||||||
(section, setting))
|
|
||||||
self._cb_id += 1
|
self._cb_id += 1
|
||||||
self.callbacks[section][setting].append((self._cb_id, func))
|
self.callbacks[section][setting].append((self._cb_id, func))
|
||||||
return self._cb_id
|
return self._cb_id
|
||||||
@ -477,28 +525,31 @@ class ConfigManager:
|
|||||||
Removes a callback given its callback ID. The ID is generated and
|
Removes a callback given its callback ID. The ID is generated and
|
||||||
returned when the function is connected to the key (section.setting).
|
returned when the function is connected to the key (section.setting).
|
||||||
"""
|
"""
|
||||||
for section in self.callbacks:
|
for dummy_section, settings in self.callbacks.items():
|
||||||
for setting in self.callbacks[section]:
|
for dummy_setting, callbacks in settings.items():
|
||||||
for (cbid, func) in self.callbacks[section][setting]:
|
for cbid, func in callbacks:
|
||||||
if callback_id == cbid:
|
if callback_id == cbid:
|
||||||
self.callbacks[section][setting].remove((cbid, func))
|
callbacks.remove((cbid, func))
|
||||||
|
|
||||||
def emit(self, key):
|
def emit(self, key):
|
||||||
"""
|
"""
|
||||||
Emits the signal "key" which will call the callbacks associated
|
Emits the signal "key" which will call the callbacks associated
|
||||||
with that setting.
|
with that setting.
|
||||||
"""
|
"""
|
||||||
if "." in key:
|
try:
|
||||||
section, setting = key.split(".", 1)
|
section, setting = key.split(".", 1)
|
||||||
else:
|
except ValueError as error:
|
||||||
raise AttributeError("Invalid config section.setting name: '%s'" %
|
raise AttributeError(
|
||||||
key)
|
f"Invalid config section.setting name: '{key}'"
|
||||||
|
) from error
|
||||||
|
|
||||||
if section not in self.callbacks:
|
if section not in self.callbacks:
|
||||||
raise AttributeError("No such config section name: '%s'" % section)
|
raise AttributeError(f"No such config section name: '{section}'")
|
||||||
if setting not in self.callbacks[section]:
|
if setting not in self.callbacks[section]:
|
||||||
raise AttributeError("No such config setting name: '%s.%s'" %
|
raise AttributeError(
|
||||||
(section, setting))
|
f"No such config setting name: '{section}.{setting}'"
|
||||||
for (cbid, func) in self.callbacks[section][setting]:
|
)
|
||||||
|
for dummy_cbid, func in self.callbacks[section][setting]:
|
||||||
func(self, 0, str(self.data[section][setting]), None)
|
func(self, 0, str(self.data[section][setting]), None)
|
||||||
|
|
||||||
def set(self, key, value):
|
def set(self, key, value):
|
||||||
@ -507,33 +558,37 @@ class ConfigManager:
|
|||||||
the data dictionary: via the :meth:`load` method that reads a file,
|
the data dictionary: via the :meth:`load` method that reads a file,
|
||||||
or from this method.
|
or from this method.
|
||||||
"""
|
"""
|
||||||
if "." in key:
|
try:
|
||||||
section, setting = key.split(".", 1)
|
section, setting = key.split(".", 1)
|
||||||
else:
|
except ValueError as error:
|
||||||
raise AttributeError("Invalid config section.setting name: '%s'" %
|
raise AttributeError(
|
||||||
key)
|
"Invalid config section.setting name: '{key}'"
|
||||||
if section not in self.data:
|
) from error
|
||||||
raise AttributeError("No such config section name: '%s'" % section)
|
|
||||||
if setting not in self.data[section]:
|
self._validate_section_and_setting(section, setting)
|
||||||
raise AttributeError("No such config setting name: '%s.%s'" %
|
|
||||||
(section, setting))
|
|
||||||
# Check value to see if right type:
|
# Check value to see if right type:
|
||||||
if self.has_default(key):
|
if self.has_default(key):
|
||||||
if not self.check_type(self.get_default(key), value):
|
if not self.check_type(self.get_default(key), value):
|
||||||
raise AttributeError("attempting to set '%s' to wrong type "
|
raise AttributeError(
|
||||||
"'%s'; should be '%s'" %
|
f"attempting to set '{key}' to wrong type "
|
||||||
(key, type(value),
|
f"'{type(value)}'; should be "
|
||||||
type(self.get_default(key))))
|
f"'{type(self.get_default(key))}'"
|
||||||
if (setting in self.data[section] and
|
)
|
||||||
self.data[section][setting] == value):
|
if (
|
||||||
|
setting in self.data[section]
|
||||||
|
and self.data[section][setting] == value
|
||||||
|
):
|
||||||
# Do nothing if existed and is the same
|
# Do nothing if existed and is the same
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
# Set the value:
|
# Set the value:
|
||||||
self.data[section][setting] = value
|
self.data[section][setting] = value
|
||||||
# Only call callback if the value changed!
|
# Only call callback if the value changed!
|
||||||
if (section in self.callbacks and
|
if (
|
||||||
setting in self.callbacks[section]):
|
section in self.callbacks
|
||||||
|
and setting in self.callbacks[section]
|
||||||
|
):
|
||||||
self.emit(key)
|
self.emit(key)
|
||||||
|
|
||||||
def check_type(self, value1, value2):
|
def check_type(self, value1, value2):
|
||||||
@ -544,11 +599,37 @@ class ConfigManager:
|
|||||||
type2 = type(value2)
|
type2 = type(value2)
|
||||||
if type1 == type2:
|
if type1 == type2:
|
||||||
return True
|
return True
|
||||||
elif (isinstance(value1, str) and
|
if isinstance(value1, str) and isinstance(value2, str):
|
||||||
isinstance(value2, str)):
|
|
||||||
return True
|
return True
|
||||||
elif (type1 in [int, float] and
|
if type1 in [int, float] and type2 in [int, float]:
|
||||||
type2 in [int, float]):
|
|
||||||
return True
|
return True
|
||||||
else:
|
return False
|
||||||
return False
|
|
||||||
|
|
||||||
|
def get_raw_value(parser, section, option):
|
||||||
|
"""
|
||||||
|
Prepare and return raw value.
|
||||||
|
"""
|
||||||
|
raw_value = parser.get(section, option).strip()
|
||||||
|
if raw_value[:2] == "u'":
|
||||||
|
raw_value = raw_value[1:]
|
||||||
|
elif raw_value.startswith("["):
|
||||||
|
raw_value = raw_value.replace(", u'", ", '")
|
||||||
|
raw_value = raw_value.replace("[u'", "['")
|
||||||
|
return raw_value
|
||||||
|
|
||||||
|
|
||||||
|
def write_comments(output_file, comments):
|
||||||
|
"""
|
||||||
|
Sanity check and write comments out to a .ini file.
|
||||||
|
"""
|
||||||
|
if comments:
|
||||||
|
if not isinstance(comments, list):
|
||||||
|
raise AttributeError("Comments should be a list")
|
||||||
|
|
||||||
|
output_file.write("\n")
|
||||||
|
for comment in comments:
|
||||||
|
if not isinstance(comment, str):
|
||||||
|
raise AttributeError("Comment should be a string")
|
||||||
|
clean_comment = comment.strip("; \n")
|
||||||
|
output_file.write(f";; {clean_comment}\n")
|
||||||
|
Loading…
Reference in New Issue
Block a user