ansible-later/env_27/lib/python2.7/site-packages/anyconfig/api.py
2019-04-11 13:00:36 +02:00

529 lines
20 KiB
Python

#
# Copyright (C) 2012 - 2018 Satoru SATOH <ssato @ redhat.com>
# License: MIT
#
# pylint: disable=unused-import,import-error,invalid-name
r"""Public APIs of anyconfig module.
.. versionadded:: 0.9.8
- Added new API load_plugins to [re-]load plugins
.. versionadded:: 0.9.5
- Added pathlib support. Now all of load and dump APIs can process
pathlib.Path object basically.
- 'ignore_missing' keyword option for load APIs are now marked as deprecated
and will be removed soon.
- Allow to load data other than mapping objects for some backends such as
JSON and YAML.
.. versionadded:: 0.8.3
- Added ac_dict keyword option to pass dict factory (any callable like
function or class) to make dict-like object in backend parsers.
- Added ac_query keyword option to query data with JMESPath expression.
- Added experimental query api to query data with JMESPath expression.
- Removed ac_namedtuple keyword option.
- Export :func:`merge`.
- Stop exporting :func:`to_container` which was deprecated and removed.
.. versionadded:: 0.8.2
- Added new API, version to provide version information.
.. versionadded:: 0.8.0
- Removed set_loglevel API as it does not help much.
- Added :func:`open` API to open files with appropriate open mode.
- Added custom exception classes, :class:`UnknownProcessorTypeError` and
:class:`UnknownFileTypeError` to express specific errors.
- Change behavior of the API :func:`find_loader` and others to make them
fail firt and raise exceptions (ValueError, UnknownProcessorTypeError or
UnknownFileTypeError) as much as possible if wrong parser type for uknown
file type was given.
.. versionadded:: 0.5.0
- Most keyword arguments passed to APIs are now position independent.
- Added ac_namedtuple parameter to \*load and \*dump APIs.
.. versionchanged:: 0.3
- Replaced `forced_type` optional argument of some public APIs with
`ac_parser` to allow skip of config parser search by passing parser object
previously found and instantiated.
Also removed some optional arguments, `ignore_missing`, `merge` and
`marker`, from definitions of some public APIs as these may not be changed
from default in common use cases.
.. versionchanged:: 0.2
- Now APIs :func:`find_loader`, :func:`single_load`, :func:`multi_load`,
:func:`load` and :func:`dump` can process a file/file-like object or a
list of file/file-like objects instead of a file path or a list of file
paths.
.. versionadded:: 0.2
- Export factory method (create) of anyconfig.mergeabledict.MergeableDict
"""
from __future__ import absolute_import
import os.path
import warnings
# Import some global constants will be re-exported:
from anyconfig.globals import (
LOGGER, IOI_PATH_OBJ, UnknownProcessorTypeError, UnknownFileTypeError
)
import anyconfig.query
import anyconfig.globals
import anyconfig.dicts
import anyconfig.ioinfo
import anyconfig.template
import anyconfig.utils
from anyconfig.dicts import (
MS_REPLACE, MS_NO_REPLACE, MS_DICTS, MS_DICTS_AND_LISTS, MERGE_STRATEGIES,
get, set_, merge # flake8: noqa
)
from anyconfig.backends import Parsers
from anyconfig.schema import validate, gen_schema
def version():
"""
:return: A tuple of version info, (major, minor, release), e.g. (0, 8, 2)
"""
return anyconfig.globals.VERSION.split('.')
def load_plugins():
"""[Re-]Load pluggable parsers.
"""
Parsers().load_plugins()
def list_types():
return Parsers().list_types()
def _try_validate(cnf, schema, **options):
"""
:param cnf: Mapping object represents configuration data
:param schema: JSON schema object
:param options: Keyword options passed to :func:`~jsonschema.validate`
:return: Given `cnf` as it is if validation succeeds else None
"""
valid = True
if schema:
(valid, msg) = validate(cnf, schema, **options)
if msg:
LOGGER.warning(msg)
if valid:
return cnf
return None
def find_loader(path, parser_or_type=None):
"""
Find out parser object appropriate to load configuration from a file of
given path or file or file-like object.
:param path:
Configuration file path or file or file-like object or pathlib.Path
object if it's available
:param parser_or_type:
Forced configuration parser type or parser object itself
:return:
An instance of a class inherits :class:`~anyconfig.backend.base.Parser`
or None
"""
try:
return Parsers().find(path, forced_type=parser_or_type)
except (ValueError, UnknownProcessorTypeError, UnknownFileTypeError):
raise
def _maybe_schema(**options):
"""
:param options: Optional keyword arguments such as
- ac_template: Assume configuration file may be a template file and try
to compile it AAR if True
- ac_context: Mapping object presents context to instantiate template
- ac_schema: JSON schema file path to validate configuration files
:return: Mapping object or None means some errors
"""
ac_schema = options.get("ac_schema", None)
if ac_schema is not None:
# Try to detect the appropriate parser to load the schema data as it
# may be different from the original config file's format, perhaps.
options["ac_parser"] = None
options["ac_schema"] = None # Avoid infinite loop.
LOGGER.info("Loading schema: %s", ac_schema)
return load(ac_schema, **options)
return None
# pylint: disable=redefined-builtin
def open(path, mode=None, ac_parser=None, **options):
"""
Open given configuration file with appropriate open flag.
:param path: Configuration file path
:param mode:
Can be 'r' and 'rb' for reading (default) or 'w', 'wb' for writing.
Please note that even if you specify 'r' or 'w', it will be changed to
'rb' or 'wb' if selected backend, xml and configobj for example, for
given config file prefer that.
:param options:
Optional keyword arguments passed to the internal file opening APIs of
each backends such like 'buffering' optional parameter passed to
builtin 'open' function.
:return: A file object or None on any errors
:raises: ValueError, UnknownProcessorTypeError, UnknownFileTypeError
"""
psr = Parsers().find(path, forced_type=ac_parser)
if mode is not None and mode.startswith('w'):
return psr.wopen(path, **options)
return psr.ropen(path, **options)
def _single_load(input_, ac_parser=None, ac_template=False,
ac_context=None, **options):
"""
:param input_:
File path or file or file-like object or pathlib.Path object represents
the file or a namedtuple `~anyconfig.globals.IOInfo` object represents
some input to load some data from
:param ac_parser: Forced parser type or parser object itself
:param ac_template:
Assume configuration file may be a template file and try to compile it
AAR if True
:param ac_context: A dict presents context to instantiate template
:param options:
Optional keyword arguments :func:`single_load` supports except for
ac_schema and ac_query
:return: Mapping object
:raises: ValueError, UnknownProcessorTypeError, UnknownFileTypeError
"""
ioi = anyconfig.ioinfo.make(input_, forced_type=ac_parser)
psr = Parsers().find(ioi, forced_type=ac_parser)
filepath = ioi.path
# .. note::
# This will be kept for backward compatibility until 'ignore_missing'
# option is deprecated and removed completely.
if "ignore_missing" in options:
warnings.warn("keyword option 'ignore_missing' is deprecated, use "
"'ac_ignore_missing' instead", DeprecationWarning)
options["ac_ignore_missing"] = options["ignore_missing"]
LOGGER.info("Loading: %s", filepath)
if ac_template and filepath is not None:
content = anyconfig.template.try_render(filepath=filepath,
ctx=ac_context, **options)
if content is not None:
return psr.loads(content, **options)
return psr.load(ioi, **options)
def single_load(input_, ac_parser=None, ac_template=False,
ac_context=None, **options):
"""
Load single configuration file.
.. note::
:func:`load` is a preferable alternative and this API should be used
only if there is a need to emphasize given input `input_` is single one.
:param input_:
File path or file or file-like object or pathlib.Path object represents
the file or a namedtuple `~anyconfig.globals.IOInfo` object represents
some input to load some data from
:param ac_parser: Forced parser type or parser object itself
:param ac_template:
Assume configuration file may be a template file and try to compile it
AAR if True
:param ac_context: A dict presents context to instantiate template
:param options: Optional keyword arguments such as:
- Options common in :func:`single_load`, :func:`multi_load`,
:func:`load` and :func:`loads`:
- ac_dict: callable (function or class) to make mapping objects from
loaded data if the selected backend can customize that such as JSON
which supports that with 'object_pairs_hook' option, or None. If
this option was not given or None, dict or :class:`OrderedDict`
will be used to make result as mapping object depends on if
ac_ordered (see below) is True and selected backend can keep the
order of items loaded. See also :meth:`_container_factory` of
:class:`~anyconfig.backend.base.Parser` for more implementation
details.
- ac_ordered: True if you want to keep resuls ordered. Please note
that order of items may be lost depends on the selected backend.
- ac_schema: JSON schema file path to validate given config file
- ac_query: JMESPath expression to query data
- Common backend options:
- ac_ignore_missing:
Ignore and just return empty result if given file ``input_`` does
not exist actually.
- Backend specific options such as {"indent": 2} for JSON backend
:return: Mapping object
:raises: ValueError, UnknownProcessorTypeError, UnknownFileTypeError
"""
cnf = _single_load(input_, ac_parser=ac_parser, ac_template=ac_template,
ac_context=ac_context, **options)
schema = _maybe_schema(ac_template=ac_template, ac_context=ac_context,
**options)
cnf = _try_validate(cnf, schema, **options)
return anyconfig.query.query(cnf, **options)
def multi_load(inputs, ac_parser=None, ac_template=False, ac_context=None,
**options):
r"""
Load multiple config files.
.. note::
:func:`load` is a preferable alternative and this API should be used
only if there is a need to emphasize given inputs are multiple ones.
The first argument `inputs` may be a list of a file paths or a glob pattern
specifying them or a pathlib.Path object represents file[s] or a namedtuple
`~anyconfig.globals.IOInfo` object represents some inputs to load some data
from.
About glob patterns, for example, is, if a.yml, b.yml and c.yml are in the
dir /etc/foo/conf.d/, the followings give same results::
multi_load(["/etc/foo/conf.d/a.yml", "/etc/foo/conf.d/b.yml",
"/etc/foo/conf.d/c.yml", ])
multi_load("/etc/foo/conf.d/*.yml")
:param inputs:
A list of file path or a glob pattern such as r'/a/b/\*.json'to list of
files, file or file-like object or pathlib.Path object represents the
file or a namedtuple `~anyconfig.globals.IOInfo` object represents some
inputs to load some data from
:param ac_parser: Forced parser type or parser object
:param ac_template: Assume configuration file may be a template file and
try to compile it AAR if True
:param ac_context: Mapping object presents context to instantiate template
:param options: Optional keyword arguments:
- ac_dict, ac_ordered, ac_schema and ac_query are the options common in
:func:`single_load`, :func:`multi_load`, :func:`load`: and
:func:`loads`. See the descriptions of them in :func:`single_load`.
- Options specific to this function and :func:`load`:
- ac_merge (merge): Specify strategy of how to merge results loaded
from multiple configuration files. See the doc of
:mod:`anyconfig.dicts` for more details of strategies. The default
is anyconfig.dicts.MS_DICTS.
- ac_marker (marker): Globbing marker to detect paths patterns.
- Common backend options:
- ignore_missing: Ignore and just return empty result if given file
``path`` does not exist.
- Backend specific options such as {"indent": 2} for JSON backend
:return: Mapping object or any query result might be primitive objects
:raises: ValueError, UnknownProcessorTypeError, UnknownFileTypeError
"""
marker = options.setdefault("ac_marker", options.get("marker", '*'))
schema = _maybe_schema(ac_template=ac_template, ac_context=ac_context,
**options)
options["ac_schema"] = None # Avoid to load schema more than twice.
paths = anyconfig.utils.expand_paths(inputs, marker=marker)
if anyconfig.utils.are_same_file_types(paths):
ac_parser = Parsers().find(paths[0], forced_type=ac_parser)
cnf = ac_context
for path in paths:
opts = options.copy()
cups = _single_load(path, ac_parser=ac_parser,
ac_template=ac_template, ac_context=cnf, **opts)
if cups:
if cnf is None:
cnf = cups
else:
merge(cnf, cups, **options)
if cnf is None:
return anyconfig.dicts.convert_to({}, **options)
cnf = _try_validate(cnf, schema, **options)
return anyconfig.query.query(cnf, **options)
def load(path_specs, ac_parser=None, ac_dict=None, ac_template=False,
ac_context=None, **options):
r"""
Load single or multiple config files or multiple config files specified in
given paths pattern or pathlib.Path object represents config files or a
namedtuple `~anyconfig.globals.IOInfo` object represents some inputs.
:param path_specs:
A list of file path or a glob pattern such as r'/a/b/\*.json'to list of
files, file or file-like object or pathlib.Path object represents the
file or a namedtuple `~anyconfig.globals.IOInfo` object represents some
inputs to load some data from.
:param ac_parser: Forced parser type or parser object
:param ac_dict:
callable (function or class) to make mapping object will be returned as
a result or None. If not given or ac_dict is None, default mapping
object used to store resutls is dict or
:class:`~collections.OrderedDict` if ac_ordered is True and selected
backend can keep the order of items in mapping objects.
:param ac_template: Assume configuration file may be a template file and
try to compile it AAR if True
:param ac_context: A dict presents context to instantiate template
:param options:
Optional keyword arguments. See also the description of `options` in
:func:`single_load` and :func:`multi_load`
:return: Mapping object or any query result might be primitive objects
:raises: ValueError, UnknownProcessorTypeError, UnknownFileTypeError
"""
marker = options.setdefault("ac_marker", options.get("marker", '*'))
if anyconfig.utils.is_path_like_object(path_specs, marker):
return single_load(path_specs, ac_parser=ac_parser, ac_dict=ac_dict,
ac_template=ac_template, ac_context=ac_context,
**options)
if not anyconfig.utils.is_paths(path_specs, marker):
raise ValueError("Something goes wrong with your input %r", path_specs)
return multi_load(path_specs, ac_parser=ac_parser, ac_dict=ac_dict,
ac_template=ac_template, ac_context=ac_context,
**options)
def loads(content, ac_parser=None, ac_dict=None, ac_template=False,
ac_context=None, **options):
"""
:param content: Configuration file's content (a string)
:param ac_parser: Forced parser type or ID or parser object
:param ac_dict:
callable (function or class) to make mapping object will be returned as
a result or None. If not given or ac_dict is None, default mapping
object used to store resutls is dict or
:class:`~collections.OrderedDict` if ac_ordered is True and selected
backend can keep the order of items in mapping objects.
:param ac_template: Assume configuration file may be a template file and
try to compile it AAR if True
:param ac_context: Context dict to instantiate template
:param options:
Optional keyword arguments. See also the description of `options` in
:func:`single_load` function.
:return: Mapping object or any query result might be primitive objects
:raises: ValueError, UnknownProcessorTypeError
"""
if ac_parser is None:
LOGGER.warning("ac_parser was not given but it's must to find correct "
"parser to load configurations from string.")
return None
psr = Parsers().find_by_type_or_id(ac_parser)
schema = None
ac_schema = options.get("ac_schema", None)
if ac_schema is not None:
options["ac_schema"] = None
schema = loads(ac_schema, ac_parser=psr, ac_dict=ac_dict,
ac_template=ac_template, ac_context=ac_context,
**options)
if ac_template:
compiled = anyconfig.template.try_render(content=content,
ctx=ac_context, **options)
if compiled is not None:
content = compiled
cnf = psr.loads(content, ac_dict=ac_dict, **options)
cnf = _try_validate(cnf, schema, **options)
return anyconfig.query.query(cnf, **options)
def dump(data, out, ac_parser=None, **options):
"""
Save `data` to `out`.
:param data: A mapping object may have configurations data to dump
:param out:
An output file path or a file or a file-like object or a `pathlib.Path`
object represents the file or a namedtuple `~anyconfig.globals.IOInfo`
object represents output to dump some data to.
:param ac_parser: Forced parser type or parser object
:param options:
Backend specific optional arguments, e.g. {"indent": 2} for JSON
loader/dumper backend
:raises: ValueError, UnknownProcessorTypeError, UnknownFileTypeError
"""
ioi = anyconfig.ioinfo.make(out, forced_type=ac_parser)
psr = Parsers().find(ioi, forced_type=ac_parser)
LOGGER.info("Dumping: %s", ioi.path)
psr.dump(data, ioi, **options)
def dumps(data, ac_parser=None, **options):
"""
Return string representation of `data` in forced type format.
:param data: Config data object to dump
:param ac_parser: Forced parser type or ID or parser object
:param options: see :func:`dump`
:return: Backend-specific string representation for the given data
:raises: ValueError, UnknownProcessorTypeError
"""
psr = Parsers().find_by_type_or_id(ac_parser)
return psr.dumps(data, **options)
def query(data, expression, **options):
"""
API just wraps :func:`anyconfig.query.query`.
:param data: Config data object to query
:param expression: JMESPath expression to query data
:param options: Ignored in current implementation
:return: Query result object may be a primitive (int, str, etc.) or dict.
"""
return anyconfig.query.query(data, ac_query=expression)
# vim:sw=4:ts=4:et: