# MusicDB, a music manager with web-bases UI that focus on music.
# Copyright (C) 2017 - 2021 Ralf Stemmer <ralf.stemmer@gmx.net>
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
This module handles the reading and writing of ini-files.
The definition of names is shown in the ini file example below.
.. code-block:: ini
[section]
option = value
"""
import configparser
import os
[docs]class Config(configparser.ConfigParser, object):
def __init__(self, filename=None):
"""
If a file name is given, the file gets read.
"""
super(Config, self).__init__()
self.__filename = None
self.__allownonevalue = False
if filename:
self.Load(filename)
[docs] def AllowNoneValue(self, allow=True):
"""
This method enables the possibility to configure (read) a ``None`` value.
In case a value has the string ``"None"``, the :meth:`~musicdb.lib.cfg.config.Config.Get` method returns ``None``.
The upper case N is important!
Default behavior of this class is to return the string ``"None"`` as regular string.
Args:
allow (bool): if ``True`` none values are allowed, otherwise the string ``"None"`` gets returned as a string
Returns:
``None``
Example:
.. code-block:: ini
[test]
option = None
.. code-block:: python
cfg = Config("test.ini")
x = cfg.Get(str, "test", "option")
print(type(x)) # str
cfg.AllowNoneValue()
x = cfg.Get(str, "test", "option")
print(type(x)) # NoneType
"""
self.__allownonevalue = allow
return None
[docs] def Load(self, path=None):
"""
This method loads a configuration file.
If the *path* parameter is not ``None``, the path given while instantiating the class will be overwritten.
If the file was already loaded, it gets reloaded.
Args:
path (str): Absolute path to an ini file
Returns:
*Nothing*
Raises:
AssertionError: If internal path variable and parameter is ``None``. (No path given)
"""
if path == None and self.__filename == None:
raise AssertionError("No filename was given to load")
if path:
self.__filename = path
self.read(self.__filename)
[docs] def Reload(self):
"""
This method just calls :meth:`~musicdb.lib.cfg.config.Config.Load` without a path argument.
"""
self.Load()
[docs] def Save(self):
"""
This method saves all set values back to the configuration file.
.. attention:
Calling this method removes all comments from the file.
The used library recreates and reformates the whole file.
Changing configuration can be prevented by setting the file attributes to read only.
If writing fails not because of missing write permission, an exception gets raised.
"""
try:
with open(self.__filename, "w") as configfile:
self.write(configfile)
except IOError as e:
if e[0] != 13:
raise e # The user shall be able to forbid me messing up his config :D
# sync may be too aggressive.
# If issue #65 (Syncing genre selection fails) is not solved without it, I add this later to the code.
# To make sure it is actually propagated to the file system ``os.sync`` is executed after saving.
#os.sync()
[docs] def OptionAvailable(self, section, option):
"""
This method can be used to check if an option exists in the configuration file.
Args:
section (str): Name of the section
option (str): Name of the option
Returns:
``True`` if the option exists, otherwise ``False``
"""
if self.has_option(section, option):
return True
else:
return False
# todo: auf allownonevalue eingegeh und dass None auch als default einfließen kann
[docs] def Get(self, datatype, section, option, default=None, islist=False):
"""
This method reads a value from the configuration.
The values in the file are all stored as string.
Using the *datatype* parameter will convert that string into a python data type.
The default parameter can be used to define a value that shall be returned in case the option does not exist.
This value can be of a different type than defined by the datatype parameter.
If the value of an option is ``"None"``, and none type is allowed by calling :meth:`~musicdb.lib.cfg.config.Config.AllowNoneValue`, ``None`` gets returned.
If the *islist* parameter is set to ``True``, the read value gets interpreted as comma separated list and split.
A list of the striped values (without leading or trailing spaces) gets returned casted to the data type given by the *datatype* parameter.
Args:
datatype: The data type of the value. This can be ``bool``, ``int``, ``float`` or ``str``.
section (str): Name of the section to read from
option (str): Name of the option to read
default: Default value in case the option does not exist
islist (bool): Interpret the value as a comma separated list
Returns:
The value of an option
Example:
.. code-block:: ini
[test]
integer = 1000
list = 13, 23, 42
.. code-block:: python
cfg = Config("test.ini")
x = cfg.Get(int, "test", "integer")
print(x) # 1000
x = cfg.Get(int, "test", "list", [], True)
print(x) # [13, 23, 42]
x = cfg.Get(int, "test", "noneexisting", 1337)
print(x) # 1337
"""
if not self.has_option(section, option):
return default
try:
if self.__allownonevalue == True:
entry = self.get(section, option)
if entry == "None":
return None
if islist == True:
entries = self.get(section, option)
values = [datatype(x.strip()) for x in entries.split(",")]
return values
#value = datatype(self.get(section, option))
if datatype == bool:
value = self.getboolean(section, option)
elif datatype == int:
value = self.getint(section, option)
elif datatype == str:
value = self.get(section, option)
elif datatype == float:
value = self.getfloat(section, option)
except ValueError:
return default
return value
[docs] def Set(self, section, option, value):
"""
This method sets an option.
The value gets casted to a string.
If the section or option does not exist, it will be created.
After setting the option, the :meth:`~musicdb.lib.cfg.config.Config.Save` method gets called to store the changes.
This is necessary because there is no reliable destructor in python to.
Args:
section (str): Section to write at
option (str): Name of the option to set
value: Value to store
Returns:
*Nothing*
"""
if not self.has_section(section):
self.add_section(section)
self.set(section, option, str(value))
try:
secobj = getattr(self, section)
setattr(secobj, option, value)
except Exception as e:
pass
self.Save() # save after every change… necessary because there are no reliable destructors in python
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4