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
|
from __future__ import annotations
import contextlib
from abc import ABC, abstractmethod
from configparser import ConfigParser, NoOptionError, NoSectionError
from plumbum import local
class ConfigBase(ABC):
"""Base class for Config parsers.
:param filename: The file to use
The ``with`` statement can be used to automatically try to read on entering and write if changed on exiting. Otherwise, use ``.read`` and ``.write`` as needed. Set and get the options using ``[]`` syntax.
Usage:
with Config("~/.myprog_rc") as conf:
value = conf.get("option", "default")
value2 = conf["option"] # shortcut for default=None
"""
__slots__ = "filename changed".split()
def __init__(self, filename):
self.filename = local.path(filename)
self.changed = False
def __enter__(self):
with contextlib.suppress(FileNotFoundError):
self.read()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self.changed:
self.write()
@abstractmethod
def read(self):
"""Read in the linked file"""
@abstractmethod
def write(self):
"""Write out the linked file"""
self.changed = False
@abstractmethod
def _get(self, option):
"""Internal get function for subclasses"""
@abstractmethod
def _set(self, option, value):
"""Internal set function for subclasses. Must return the value that was set."""
def get(self, option, default=None):
"Get an item from the store, returns default if fails"
try:
return self._get(option)
except KeyError:
self.changed = True
return self._set(option, default)
def set(self, option, value):
"""Set an item, mark this object as changed"""
self.changed = True
self._set(option, value)
def __getitem__(self, option):
return self._get(option)
def __setitem__(self, option, value):
return self.set(option, value)
class ConfigINI(ConfigBase):
DEFAULT_SECTION = "DEFAULT"
slots = "parser".split()
def __init__(self, filename):
super().__init__(filename)
self.parser = ConfigParser()
def read(self):
self.parser.read(self.filename)
super().read()
def write(self):
with open(self.filename, "w", encoding="utf-8") as f:
self.parser.write(f)
super().write()
@classmethod
def _sec_opt(cls, option):
if "." not in option:
sec = cls.DEFAULT_SECTION
else:
sec, option = option.split(".", 1)
return sec, option
def _get(self, option):
sec, option = self._sec_opt(option)
try:
return self.parser.get(sec, option)
except (NoSectionError, NoOptionError):
raise KeyError(f"{sec}:{option}") from None
def _set(self, option, value):
sec, option = self._sec_opt(option)
try:
self.parser.set(sec, option, str(value))
except NoSectionError:
self.parser.add_section(sec)
self.parser.set(sec, option, str(value))
return str(value)
Config = ConfigINI
|