Mercurial > hgrepos > Python > libs > ConfigMix
view configmix/yaml.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 | f454889e41fa |
| children | 80d203ed3556 |
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. # :- """Simple wrapper for :mod:`yaml` to support all-unicode strings when loading configuration files. """ from __future__ import division, print_function, absolute_import __all__ = ["safe_load", "safe_load_all", "load", "load_all"] try: from collections import OrderedDict except ImportError: try: from ordereddict import OrderedDict except ImportError: OrderedDict = None import yaml import yaml.constructor from .compat import u DictImpl = OrderedDict or dict class ConfigLoader(yaml.Loader): """A YAML loader, which makes all ``!!str`` strings to Unicode. Standard PyYAML does this only in the non-ASCII case. If an `OrderedDict` implementation is available then all "map" and "omap" nodes are constructed as `OrderedDict`. This is against YAML specs but within configuration files it seems more natural. """ def __init__(self, *args, **kwds): strict = kwds.pop("strict", False) self.__allow_duplicate_keys = not strict yaml.Loader.__init__(self, *args, **kwds) def construct_yaml_str(self, node): return self.construct_scalar(node) # # From https://pypi.python.org/pypi/yamlordereddictloader/0.1.1 # (MIT License) # def construct_yaml_map(self, node): data = DictImpl() yield data value = self.construct_mapping(node) data.update(value) def construct_mapping(self, node, deep=False): if isinstance(node, yaml.MappingNode): self.flatten_mapping(node) else: raise yaml.constructor.ConstructorError( None, None, 'expected a mapping node, but found %s' % node.id, node.start_mark) mapping = DictImpl() for key_node, value_node in node.value: key = self.construct_object(key_node, deep=deep) try: hash(key) except TypeError as err: raise yaml.constructor.ConstructorError( 'while constructing a mapping', node.start_mark, 'found unacceptable key (%s)' % (err, key_node.start_mark) ) value = self.construct_object(value_node, deep=deep) if not self.__allow_duplicate_keys and key in mapping: raise yaml.constructor.ConstructorError( 'while constructing a mapping', node.start_mark, 'found duplicate key %r (%s)' % (key, key_node.start_mark) ) mapping[key] = value return mapping ConfigLoader.add_constructor( u("tag:yaml.org,2002:str"), ConfigLoader.construct_yaml_str) ConfigLoader.add_constructor( u("tag:yaml.org,2002:map"), ConfigLoader.construct_yaml_map) ConfigLoader.add_constructor( u("tag:yaml.org,2002:omap"), ConfigLoader.construct_yaml_map) class ConfigSafeLoader(yaml.SafeLoader): """A safe YAML loader, which makes all ``!!str`` strings to Unicode. Standard PyYAML does this only in the non-ASCII case. If an `OrderedDict` implementation is available then all "map" and "omap" nodes are constructed as `OrderedDict`. This is against YAML specs but within configuration files it seems more natural. """ def __init__(self, *args, **kwds): strict = kwds.pop("strict", False) self.__allow_duplicate_keys = not strict yaml.SafeLoader.__init__(self, *args, **kwds) def construct_yaml_str(self, node): return self.construct_scalar(node) # # From https://pypi.python.org/pypi/yamlordereddictloader/0.1.1 # (MIT License) # def construct_yaml_map(self, node): data = DictImpl() yield data value = self.construct_mapping(node) data.update(value) def construct_mapping(self, node, deep=False): if isinstance(node, yaml.MappingNode): self.flatten_mapping(node) else: raise yaml.constructor.ConstructorError( None, None, 'expected a mapping node, but found %s' % node.id, node.start_mark) mapping = DictImpl() for key_node, value_node in node.value: key = self.construct_object(key_node, deep=deep) try: hash(key) except TypeError as err: raise yaml.constructor.ConstructorError( 'while constructing a mapping', node.start_mark, 'found unacceptable key (%s)' % (err, key_node.start_mark) ) value = self.construct_object(value_node, deep=deep) if not self.__allow_duplicate_keys and key in mapping: raise yaml.constructor.ConstructorError( 'while constructing a mapping', node.start_mark, 'found duplicate key %r (%s)' % (key, key_node.start_mark) ) mapping[key] = value return mapping ConfigSafeLoader.add_constructor( u("tag:yaml.org,2002:str"), ConfigSafeLoader.construct_yaml_str) ConfigSafeLoader.add_constructor( u("tag:yaml.org,2002:map"), ConfigSafeLoader.construct_yaml_map) ConfigSafeLoader.add_constructor( u("tag:yaml.org,2002:omap"), ConfigSafeLoader.construct_yaml_map) def config_loader_factory(strict=False): def _real_factory(*args, **kwds): kwds["strict"] = strict return ConfigLoader(*args, **kwds) return _real_factory def config_safe_loader_factory(strict=False): def _real_factory(*args, **kwds): kwds["strict"] = strict return ConfigSafeLoader(*args, **kwds) return _real_factory def load(stream, Loader=None, strict=False): """Parse the given `stream` and return a Python object constructed from for the first document in the stream. If `strict` is `True` then duplicate mapping keys within a YAML document are detected and prevented. If a `Loader` is given then `strict` does not apply. """ if Loader is None: Loader = config_loader_factory(strict=strict) data = yaml.load(stream, Loader) # Map an empty document to an empty dict if data is None: return DictImpl() if not isinstance(data, DictImpl): raise TypeError("YAML root object must be a mapping") return data def load_all(stream, Loader=None, strict=False): """Parse the given `stream` and return a sequence of Python objects corresponding to the documents in the `stream`. If `strict` is `True` then duplicate mapping keys within a YAML document are detected and prevented. If a `Loader` is given then `strict` does not apply. """ if Loader is None: Loader = config_loader_factory(strict=strict) data_all = yaml.load_all(stream, Loader) rdata = [] for data in data_all: if data is None: rdata.append(DictImpl()) else: if not isinstance(data, DictImpl): raise TypeError("YAML root object must be a mapping") rdata.append(data) return rdata def safe_load(stream, strict=False): """Parse the given `stream` and return a Python object constructed from for the first document in the stream. Recognizes only standard YAML tags and cannot construct an arbitrary Python object. If `strict` is `True` then duplicate mapping keys within a YAML document are detected and prevented. """ data = yaml.load(stream, Loader=config_safe_loader_factory(strict=strict)) # Map an empty document to an empty dict if data is None: return DictImpl() if not isinstance(data, DictImpl): raise TypeError("YAML root object must be a mapping") return data def safe_load_all(stream, strict=False): """Return the list of all decoded YAML documents in the file `stream`. Recognizes only standard YAML tags and cannot construct an arbitrary Python object. If `strict` is `True` then duplicate mapping keys within a YAML document are detected and prevented. """ data_all = yaml.load_all(stream, Loader=config_safe_loader_factory(strict=strict)) rdata = [] for data in data_all: if data is None: rdata.append(DictImpl()) else: if not isinstance(data, DictImpl): raise TypeError("YAML root object must be a mapping") rdata.append(data) return data_all
