Mercurial > hgrepos > Python > libs > ConfigMix
view configmix/ini.py @ 654:0d6673d06c2c
Add support for using "tomllib" (in Python's stdlib since 3.11) and "tomli" TOML packages.
They are preferred if they are found to be installed.
But note that the declared dependency for the "toml" extra nevertheless
is the "toml" package. Because it is available for all supported Python
versions.
So use Python 3.11+ or install "tomli" manually if you want to use the
alternate packages.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Thu, 19 May 2022 22:10:59 +0200 |
| parents | 301cf2337fde |
| children |
line wrap: on
line source
# -*- coding: utf-8 -*- # :- # :Copyright: (c) 2015-2022, Franz Glasner. All rights reserved. # :License: BSD-3-Clause. See LICENSE.txt for details. # :- """Read INI-style configuration files. """ from __future__ import division, absolute_import, print_function __all__ = ["INIConfigParser", "NoSectionError", "NoOptionError", "load"] import sys import os import io import locale try: from configparser import ConfigParser, NoSectionError, NoOptionError # SafeConfigParser is deprecated in Python 3.2+ (together with "readfp()") if hasattr(ConfigParser, "read_file"): _ConfigParserBase = ConfigParser else: from configparser import SafeConfigParser _ConfigParserBase = SafeConfigParser except ImportError: from ConfigParser import SafeConfigParser, NoSectionError, NoOptionError _ConfigParserBase = SafeConfigParser try: from collections import OrderedDict as DictImpl except ImportError: try: from ordereddict import OrderedDict as DictImpl except ImportError: DictImpl = dict from .compat import u, u2fs class INIConfigParser(_ConfigParserBase): """A case sensitive config parser that returns all-unicode string values. """ def __init__(self, filename, executable=None, encoding=None): _ConfigParserBase.__init__(self) if executable is None: executable = sys.argv[0] filename = u(filename, locale.getpreferredencoding()) executable = u(executable, locale.getpreferredencoding()) self.executable = os.path.normpath(os.path.abspath(executable)) if encoding is None: encoding = locale.getpreferredencoding() self.encoding = encoding with io.open(u2fs(filename), mode="rt", encoding=self.encoding) as cf: self.read_file(cf, filename) def optionxform(self, option): return option def get_path_list(self, section, option): v = self.get(section, option) return v.split(os.pathsep) def read(self, filenames): """Not implemented. Use :meth:`read_file` instead.""" raise NotImplementedError("use `read_file()' instead") def readfp(self, fp, filename): """Compatibility for older Python versions. Use :meth:`.read_file` instead. """ return self.read_file(fp, filename) def read_file(self, fp, filename): """Read from a file-like object `fp`. The `fp` argument must be iterable (Python 3.2+) or have a `readline()` method (Python 2, <3.2). """ if hasattr(self, "filename"): raise RuntimeError("already initialized") filename = os.path.normpath(os.path.abspath(filename)) filename = u(filename, locale.getpreferredencoding()) # self.set(None, u("self"), filename) # self.set(None, u("here"), os.path.dirname(filename)) # self.set(None, u("root"), os.path.dirname(self.executable)) if hasattr(_ConfigParserBase, "read_file"): _ConfigParserBase.read_file(self, fp, source=filename) else: _ConfigParserBase.readfp(self, fp, filename=filename) self.filename = filename # self.root = os.path.dirname(self.executable) def getx(self, section, option): """Extended `get()` with some automatic type conversion support. Default: Fetch as string (like :meth:`get`). If annotated with ``:bool:`` fetch as bool, if annotated with ``:int:`` fetch as int, if annotated with ``:float:`` fetch as float. """ v = self.get(section, option) if v.startswith(u(":bool:")): v = v[6:].lower() if v not in self._BOOL_CVT: raise ValueError("Not a boolean: %r" % v) return self._BOOL_CVT[v] elif v.startswith(u(":int:")): return int(v[5:], 0) elif v.startswith(u(":float:")): return float(v[7:]) else: return v _BOOL_CVT = {u('1'): True, u('yes'): True, u('true'): True, u('on'): True, u('0'): False, u('no'): False, u('false'): False, u('off'): False} def itemsx(self, section, options): """Get all the options given in `options` of section `section`. Fetch them with :meth:`getx` in the order given. Return a list of ``(name, value)`` pairs for each option in `options` in the given `section`. """ d = [] for option in options: try: val = self.getx(section, option) except (NoSectionError, NoOptionError): pass else: d.append((option, val, )) return d def items_as_dictx(self, section, options): """Similar to :meth:`itemsx` but return a (possibly ordered) dict instead of a list of key-value pairs. """ return DictImpl(self.itemsx(section, options)) def load(filename, extract=["config"], encoding="utf-8"): """Load a single INI file and read/interpolate the sections given in `extract`. Flattens the given sections into the resulting dictionary. Then build a tree out of sections which start with any of the `extract` content value and a dot ``.``. The encoding of the file is given in `encoding`. """ conf = DictImpl() ini = INIConfigParser(filename, encoding=encoding) for sect in extract: try: cfg = ini.options(sect) except NoSectionError: pass else: for option in cfg: value = ini.getx(sect, option) conf[option] = value # try to read "<extract>.xxx" sections as tree for treemarker in [e + '.' for e in extract]: sections = list(ini.sections()) sections.sort() for section in sections: cur_cfg = conf if section.startswith(treemarker): treestr = section[len(treemarker):] for treepart in treestr.split('.'): cur_cfg = cur_cfg.setdefault(treepart, DictImpl()) for option in ini.options(section): value = ini.getx(section, option) cur_cfg[option] = value return conf
