class RawConfigParser(MutableMapping): """ConfigParser that does not do interpolation.""" # Regular expressions for parsing section headers and options _SECT_TMPL = r""" \[ # [ (?P<header>[^]]+) # very permissive! \] # ] """ _OPT_TMPL = r""" (?P<option>.*?) # very permissive! \s*(?P<vi>{delim})\s* # any number of space/tab, # followed by any of the # allowed delimiters, # followed by any space/tab (?P<value>.*)$ # everything up to eol """ _OPT_NV_TMPL = r""" (?P<option>.*?) # very permissive! \s*(?: # any number of space/tab, (?P<vi>{delim})\s* # optionally followed by # any of the allowed # delimiters, followed by any # space/tab (?P<value>.*))?$ # everything up to eol """ # Interpolation algorithm to be used if the user does not specify another _DEFAULT_INTERPOLATION = Interpolation() # Compiled regular expression for matching sections SECTCRE = re.compile(_SECT_TMPL, re.VERBOSE) # Compiled regular expression for matching options with typical separators OPTCRE = re.compile(_OPT_TMPL.format(delim="=|:"), re.VERBOSE) # Compiled regular expression for matching options with optional values # delimited using typical separators OPTCRE_NV = re.compile(_OPT_NV_TMPL.format(delim="=|:"), re.VERBOSE) # Compiled regular expression for matching leading whitespace in a line NONSPACECRE = re.compile(r"\S") # Possible boolean values in the configuration. BOOLEAN_STATES = { '1': True, 'yes': True, 'true': True, 'on': True, '0': False, 'no': False, 'false': False, 'off': False } def __init__(self, defaults=None, dict_type=_default_dict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=('#', ';'), inline_comment_prefixes=None, strict=True, empty_lines_in_values=True, default_section=DEFAULTSECT, interpolation=_UNSET): self._dict = dict_type self._sections = self._dict() self._defaults = UserList() self._proxies = self._dict() self._proxies[default_section] = UserList( [SectionProxy(self, default_section, 0)]) if defaults: for key, value in defaults.items(): self._defaults[self.optionxform(key)] = value self._delimiters = tuple(delimiters) if delimiters == ('=', ':'): self._optcre = self.OPTCRE_NV if allow_no_value else self.OPTCRE else: d = "|".join(re.escape(d) for d in delimiters) if allow_no_value: self._optcre = re.compile(self._OPT_NV_TMPL.format(delim=d), re.VERBOSE) else: self._optcre = re.compile(self._OPT_TMPL.format(delim=d), re.VERBOSE) self._comment_prefixes = tuple(comment_prefixes or ()) self._inline_comment_prefixes = tuple(inline_comment_prefixes or ()) self._strict = strict self._allow_no_value = allow_no_value self._empty_lines_in_values = empty_lines_in_values self.default_section = default_section self._interpolation = interpolation if self._interpolation is _UNSET: self._interpolation = self._DEFAULT_INTERPOLATION if self._interpolation is None: self._interpolation = Interpolation() def defaults(self): return self._defaults def sections(self): """Return a list of section names, excluding [DEFAULT]""" # self._sections will never have [DEFAULT] in it return list(self._sections.keys()) def add_section(self, section): """Create a new section in the configuration. Raise DuplicateSectionError if a section by the specified name already exists. Raise ValueError if name is DEFAULT. """ if section == self.default_section: raise ValueError('Invalid section name: %r' % section) # if section in self._sections: # raise DuplicateSectionError(section) self._sections[section].append(self._dict()) res = len(self._sections[section]) - 1 self._proxies[section].append( SectionProxy(self, section, len(self._sections[section]) - 1)) return res def has_section(self, section): """Indicate whether the named section is present in the configuration. The DEFAULT section is not acknowledged. """ return section in self._sections def options(self, section, idx): """Return a list of option names for the given section name.""" # print(section) # print(idx) try: opts = self._sections[section][idx].copy() except KeyError: raise NoSectionError(section) opts.update(self._defaults) return list(opts.keys()) def get_sections_count(self, section): """Return a list of option names for the given section name.""" # print(section) # print(idx) try: res = len(self._sections[section]) except KeyError: raise NoSectionError(section) return res def read(self, filenames, encoding=None): """Read and parse a filename or a list of filenames. Files that cannot be opened are silently ignored; this is designed so that you can specify a list of potential configuration file locations (e.g. current directory, user's home directory, systemwide directory), and all existing configuration files in the list will be read. A single filename may also be given. Return list of successfully read files. """ if isinstance(filenames, str): filenames = [filenames] read_ok = [] for filename in filenames: try: with codecs.open(filename, "r", "utf-8") as fp: self._read(fp, filename) except IOError: continue read_ok.append(filename) return read_ok def read_file(self, f, source=None): """Like read() but the argument must be a file-like object. The `f' argument must be iterable, returning one line at a time. Optional second argument is the `source' specifying the name of the file being read. If not given, it is taken from f.name. If `f' has no `name' attribute, `<???>' is used. """ if source is None: try: source = f.name except AttributeError: source = '<???>' self._read(f, source) def read_string(self, string, source='<string>'): """Read configuration from a given string.""" sfile = io.StringIO(string) self.read_file(sfile, source) def read_dict(self, dictionary, source='<dict>'): """Read configuration from a dictionary. Keys are section names, values are dictionaries with keys and values that should be present in the section. If the used dictionary type preserves order, sections and their keys will be added in order. All types held in the dictionary are converted to strings during reading, including section names, option names and keys. Optional second argument is the `source' specifying the name of the dictionary being read. """ elements_added = set() for section, keys in dictionary.items(): section = str(section) try: self.add_section(section) except (DuplicateSectionError, ValueError): if self._strict and section in elements_added: raise elements_added.add(section) for key, value in keys.items(): key = self.optionxform(str(key)) if value is not None: value = str(value) if self._strict and (section, key) in elements_added: raise DuplicateOptionError(section, key, source) elements_added.add((section, key)) self.set(section, key, value) def readfp(self, fp, filename=None): """Deprecated, use read_file instead.""" warnings.warn( "This method will be removed in future versions. " "Use 'parser.read_file()' instead.", DeprecationWarning, stacklevel=2) self.read_file(fp, source=filename) def get(self, section, idx, option, raw=False, vars=None, fallback=_UNSET): """Get an option value for a given section. If `vars' is provided, it must be a dictionary. The option is looked up in `vars' (if provided), `section', and in `DEFAULTSECT' in that order. If the key is not found and `fallback' is provided, it is used as a fallback value. `None' can be provided as a `fallback' value. If interpolation is enabled and the optional argument `raw' is False, all interpolations are expanded in the return values. Arguments `raw', `vars', and `fallback' are keyword only. The section DEFAULT is special. """ # print(option) try: d = self._unify_values(section, idx, vars) except NoSectionError: if fallback is _UNSET: raise else: return fallback option = self.optionxform(option) # print(d) try: value = d[option] except KeyError: if fallback is _UNSET: raise NoOptionError(option, section[idx]) else: return fallback if raw or value is None: return value else: return self._interpolation.before_get(self, section, idx, option, value, d) def _get(self, section, conv, option, **kwargs): return conv(self.get(section, option, **kwargs)) def getint(self, section, option, raw=False, vars=None, fallback=_UNSET): try: return self._get(section, int, option, raw=raw, vars=vars) except (NoSectionError, NoOptionError): if fallback is _UNSET: raise else: return fallback def getfloat(self, section, option, raw=False, vars=None, fallback=_UNSET): try: return self._get(section, float, option, raw=raw, vars=vars) except (NoSectionError, NoOptionError): if fallback is _UNSET: raise else: return fallback def getboolean(self, section, option, raw=False, vars=None, fallback=_UNSET): try: return self._get(section, self._convert_to_boolean, option, raw=raw, vars=vars) except (NoSectionError, NoOptionError): if fallback is _UNSET: raise else: return fallback def items(self, section=_UNSET, idx=0, raw=False, vars=None): """Return a list of (name, value) tuples for each option in a section. All % interpolations are expanded in the return values, based on the defaults passed into the constructor, unless the optional argument `raw' is true. Additional substitutions may be provided using the `vars' argument, which must be a dictionary whose contents overrides any pre-existing defaults. The section DEFAULT is special. """ if section is _UNSET: return super().items() d = self._defaults.copy() try: d.update(self._sections[section][idx]) except KeyError: if section != self.default_section: raise NoSectionError(section) # Update with the entry specific variables if vars: for key, value in vars.items(): d[self.optionxform(key)] = value value_getter = lambda option: self._interpolation.before_get( self, section, idx, option, d[option], d) if raw: value_getter = lambda option: d[option] return [(option, value_getter(option)) for option in d.keys()] def optionxform(self, optionstr): return optionstr.lower() def has_option(self, section, idx, option): """Check for the existence of a given option in a given section. If the specified `section' is None or an empty string, DEFAULT is assumed. If the specified `section' does not exist, returns False.""" if not section or section == self.default_section: option = self.optionxform(option) return option in self._defaults elif section not in self._sections: return False else: option = self.optionxform(option) return (option in self._sections[section][idx] or option in self._defaults) # def set(self, section, option, value=None): # """Set an option.""" # if value: # value = self._interpolation.before_set(self, section, option, # value) # if not section or section == self.default_section: # sectdict = self._defaults # else: # try: # sectdict = self._sections[section] # except KeyError: # raise NoSectionError(section) # sectdict[self.optionxform(option)] = value def set(self, section, idx, option, value=None): """Set an option.""" if value: value = self._interpolation.before_set(self, section, option, value) if not section or section == self.default_section: sectdict = self._defaults else: try: sectdict = self._sections[section][idx] except KeyError: raise NoSectionError(section) if not self.optionxform(option) in sectdict: sectdict[self.optionxform(option)] = UserList() sectdict[self.optionxform(option)].append(value) def write(self, fp, space_around_delimiters=True): """Write an .ini-format representation of the configuration state. If `space_around_delimiters' is True (the default), delimiters between keys and values are surrounded by spaces. """ if space_around_delimiters: d = " {} ".format(self._delimiters[0]) else: d = self._delimiters[0] if self._defaults: self._write_section(fp, self.default_section, self._defaults.items(), d) for section in self._sections: union_of_dicts = [(k, v) for d in self._sections[section] for k, v in d.items()] self._write_section(fp, section, union_of_dicts, d) def _write_section(self, fp, section_name, section_items, delimiter): """Write a single section to the specified `fp'.""" fp.write("[{}]\n".format(section_name)) for key, value in section_items: value = self._interpolation.before_write(self, section_name, key, value) if value is not None or not self._allow_no_value: value = delimiter + str(value).replace('\n', '\n\t') else: value = "" fp.write("{}{}\n".format(key, value)) fp.write("\n") def remove_option(self, section, idx, option): """Remove an option.""" if not section or section == self.default_section: sectdict = self._defaults else: try: sectdict = self._sections[section][idx] except KeyError: raise NoSectionError(section) option = self.optionxform(option) existed = option in sectdict if existed: del sectdict[option] return existed def remove_section(self, section): """Remove a file section.""" existed = section in self._sections if existed: del self._sections[section] del self._proxies[section] return existed def __getitem__(self, key): if key != self.default_section and not self.has_section(key): raise KeyError(key) return self._proxies[key] def __setitem__(self, key, value): # To conform with the mapping protocol, overwrites existing values in # the section. # XXX this is not atomic if read_dict fails at any point. Then again, # no update method in configparser is atomic in this implementation. self.remove_section(key) self.read_dict({key: value}) def __delitem__(self, key): if key == self.default_section: raise ValueError("Cannot remove the default section.") if not self.has_section(key): raise KeyError(key) self.remove_section(key) def __contains__(self, key): return key == self.default_section or self.has_section(key) def __len__(self): return len(self._sections) + 1 # the default section def __iter__(self): # XXX does it break when underlying container state changed? return itertools.chain((self.default_section, ), self._sections.keys()) def _read(self, fp, fpname): """Parse a sectioned configuration file. Each section in a configuration file contains a header, indicated by a name in square brackets (`[]'), plus key/value options, indicated by `name' and `value' delimited with a specific substring (`=' or `:' by default). Values can span multiple lines, as long as they are indented deeper than the first line of the value. Depending on the parser's mode, blank lines may be treated as parts of multiline values or ignored. Configuration files may include comments, prefixed by specific characters (`#' and `;' by default). Comments may appear on their own in an otherwise empty line or may be entered in lines holding values or section names. """ elements_added = set() # cursect = None # None, or a dictionary cursect = self._dict() self._sections["default"] = UserList() self._sections["default"].append(cursect) self._proxies["default"] = UserList() self._proxies["default"].append(SectionProxy(self, "default", 0)) elements_added.add("default") sectname = None optname = None lineno = 0 indent_level = 0 e = None # None, or an exception for lineno, line in enumerate(fp, start=1): comment_start = None # strip inline comments for prefix in self._inline_comment_prefixes: index = line.find(prefix) if index == 0 or (index > 0 and line[index - 1].isspace()): comment_start = index break # strip full line comments for prefix in self._comment_prefixes: if line.strip().startswith(prefix): comment_start = 0 break value = line[:comment_start].strip() if not value: if self._empty_lines_in_values: # add empty line to the value, but only if there was no # comment on the line if (comment_start is None and cursect is not None and optname and cursect[optname] is not None): cursect[optname].append('') # newlines added at join else: # empty line marks end of value indent_level = sys.maxsize continue # continuation line? first_nonspace = self.NONSPACECRE.search(line) cur_indent_level = first_nonspace.start() if first_nonspace else 0 if (cursect is not None and optname and cur_indent_level > indent_level): cursect[optname].append(value) # a section header or option header? else: indent_level = cur_indent_level # is it a section header? mo = self.SECTCRE.match(value) if mo: sectname = mo.group('header') if sectname in self._sections: if self._strict and sectname in elements_added: raise DuplicateSectionError( sectname, fpname, lineno) self._sections[sectname].append(self._dict()) cursect = self._sections[sectname][-1] self._proxies[sectname].append( SectionProxy(self, sectname, len(self._sections[sectname]) - 1)) elements_added.add(sectname) elif sectname == self.default_section: cursect = self._defaults else: cursect = self._dict() self._sections[sectname] = UserList() self._sections[sectname].append(cursect) self._proxies[sectname] = UserList() self._proxies[sectname].append( SectionProxy(self, sectname, 0)) elements_added.add(sectname) # So sections can't start with a continuation line optname = None # no section header in the file? elif cursect is None: raise MissingSectionHeaderError(fpname, lineno, line) # an option line? else: mo = self._optcre.match(value) if mo: optname, vi, optval = mo.group('option', 'vi', 'value') if not optname: e = self._handle_error(e, fpname, lineno, line) optname = self.optionxform(optname.rstrip()) if (self._strict and (sectname, optname) in elements_added): raise DuplicateOptionError(sectname, optname, fpname, lineno) elements_added.add((sectname, optname)) # This check is fine because the OPTCRE cannot # match if it would set optval to None if optval is not None: optval = optval.strip() if optname in cursect: cursect[optname] += [optval] else: cursect[optname] = [optval] else: # valueless option handling cursect[optname] = None else: # a non-fatal parsing error occurred. set up the # exception but keep going. the exception will be # raised at the end of the file and will contain a # list of all bogus lines e = self._handle_error(e, fpname, lineno, line) # if any parsing errors occurred, raise an exception if e: raise e # self._join_multiline_values() def _join_multiline_values(self): defaults = self.default_section, self._defaults all_sections = itertools.chain((defaults, ), self._sections.items()) idx = 0 for section, l in all_sections: for options in l: for name, val in options.items(): if isinstance(val, list): val = '\n'.join(val).rstrip() options[name] = self._interpolation.before_read( self, section, idx, name, val) idx += 1 def _handle_error(self, exc, fpname, lineno, line): if not exc: exc = ParsingError(fpname) exc.append(lineno, repr(line)) return exc def _unify_values(self, section, idx, vars): """Create a sequence of lookups with 'vars' taking priority over the 'section' which takes priority over the DEFAULTSECT. """ sectiondict = {} try: sectiondict = self._sections[section][idx] # print(sectiondict) except KeyError: if section != self.default_section: raise NoSectionError(section) return sectiondict def _convert_to_boolean(self, value): """Return a boolean value translating from other types if necessary. """ if value.lower() not in self.BOOLEAN_STATES: raise ValueError('Not a boolean: %s' % value) return self.BOOLEAN_STATES[value.lower()] def _validate_value_types(self, section="", option="", value=""): """Raises a TypeError for non-string values. The only legal non-string value if we allow valueless options is None, so we need to check if the value is a string if: - we do not allow valueless options, or - we allow valueless options but the value is not None For compatibility reasons this method is not used in classic set() for RawConfigParsers. It is invoked in every case for mapping protocol access and in ConfigParser.set(). """ if not isinstance(section, str): raise TypeError("section names must be strings") if not isinstance(option, str): raise TypeError("option keys must be strings") if not self._allow_no_value or value: if not isinstance(value, str): raise TypeError("option values must be strings")