mirror of
https://github.com/thegeeklab/ansible-later.git
synced 2024-11-29 08:10:36 +00:00
286 lines
8.8 KiB
Python
286 lines
8.8 KiB
Python
|
#
|
||
|
# Copyright (C) 2018 Satoru SATOH <ssato @ redhat.com>
|
||
|
# License: MIT
|
||
|
#
|
||
|
# pylint: disable=unidiomatic-typecheck
|
||
|
r"""Abstract processor module.
|
||
|
|
||
|
.. versionadded:: 0.9.5
|
||
|
|
||
|
- Add to abstract processors such like Parsers (loaders and dumpers).
|
||
|
"""
|
||
|
from __future__ import absolute_import
|
||
|
|
||
|
import operator
|
||
|
import pkg_resources
|
||
|
|
||
|
import anyconfig.compat
|
||
|
import anyconfig.ioinfo
|
||
|
import anyconfig.models.processor
|
||
|
|
||
|
from anyconfig.globals import (
|
||
|
UnknownProcessorTypeError, UnknownFileTypeError, IOInfo
|
||
|
)
|
||
|
|
||
|
|
||
|
def _load_plugins_itr(pgroup, safe=True):
|
||
|
"""
|
||
|
.. seealso:: the doc of :func:`load_plugins`
|
||
|
"""
|
||
|
for res in pkg_resources.iter_entry_points(pgroup):
|
||
|
try:
|
||
|
yield res.load()
|
||
|
except ImportError:
|
||
|
if safe:
|
||
|
continue
|
||
|
raise
|
||
|
|
||
|
|
||
|
def load_plugins(pgroup, safe=True):
|
||
|
"""
|
||
|
:param pgroup: A string represents plugin type, e.g. anyconfig_backends
|
||
|
:param safe: Do not raise ImportError during load if True
|
||
|
:raises: ImportError
|
||
|
"""
|
||
|
return list(_load_plugins_itr(pgroup, safe=safe))
|
||
|
|
||
|
|
||
|
def find_with_pred(predicate, prs):
|
||
|
"""
|
||
|
:param predicate: any callable to filter results
|
||
|
:param prs: A list of :class:`anyconfig.models.processor.Processor` classes
|
||
|
:return: Most appropriate processor class or None
|
||
|
|
||
|
>>> class A(anyconfig.models.processor.Processor):
|
||
|
... _type = "json"
|
||
|
... _extensions = ['json', 'js']
|
||
|
>>> class A2(A):
|
||
|
... _priority = 99 # Higher priority than A.
|
||
|
>>> class B(anyconfig.models.processor.Processor):
|
||
|
... _type = "yaml"
|
||
|
... _extensions = ['yaml', 'yml']
|
||
|
>>> prs = [A, A2, B]
|
||
|
|
||
|
>>> find_with_pred(lambda p: 'js' in p.extensions(), prs)
|
||
|
<class 'anyconfig.processors.A2'>
|
||
|
>>> find_with_pred(lambda p: 'yml' in p.extensions(), prs)
|
||
|
<class 'anyconfig.processors.B'>
|
||
|
>>> x = find_with_pred(lambda p: 'xyz' in p.extensions(), prs)
|
||
|
>>> assert x is None
|
||
|
|
||
|
>>> find_with_pred(lambda p: p.type() == "json", prs)
|
||
|
<class 'anyconfig.processors.A2'>
|
||
|
>>> find_with_pred(lambda p: p.type() == "yaml", prs)
|
||
|
<class 'anyconfig.processors.B'>
|
||
|
>>> x = find_with_pred(lambda p: p.type() == "x", prs)
|
||
|
>>> assert x is None
|
||
|
"""
|
||
|
_prs = sorted((p for p in prs if predicate(p)),
|
||
|
key=operator.methodcaller("priority"), reverse=True)
|
||
|
if _prs:
|
||
|
return _prs[0] # Found.
|
||
|
|
||
|
return None
|
||
|
|
||
|
|
||
|
def find_by_type(ptype, prs, cls=anyconfig.models.processor.Processor):
|
||
|
"""
|
||
|
:param ptype:
|
||
|
Type of the data to process or
|
||
|
:class:`anyconfig.models.processor.Processor` class object or its
|
||
|
instance
|
||
|
:param prs: A list of :class:`anyconfig.models.processor.Processor` classes
|
||
|
:param cls: A class object to compare with `ptype`
|
||
|
:return:
|
||
|
Most appropriate processor instance to process files of given data type
|
||
|
`ptype` or None
|
||
|
:raises: UnknownProcessorTypeError
|
||
|
"""
|
||
|
if isinstance(ptype, cls):
|
||
|
return ptype
|
||
|
|
||
|
if type(ptype) == type(cls) and issubclass(ptype, cls):
|
||
|
return ptype()
|
||
|
|
||
|
def pred(pcls):
|
||
|
"""Predicate"""
|
||
|
return pcls.type() == ptype
|
||
|
|
||
|
processor = find_with_pred(pred, prs)
|
||
|
if processor is None:
|
||
|
raise UnknownProcessorTypeError(ptype)
|
||
|
|
||
|
return processor()
|
||
|
|
||
|
|
||
|
def find_by_type_or_id(type_or_id, prs,
|
||
|
cls=anyconfig.models.processor.Processor):
|
||
|
"""
|
||
|
:param type_or_id:
|
||
|
Type of the data to process or ID of the processor class or
|
||
|
:class:`anyconfig.models.processor.Processor` class object or its
|
||
|
instance
|
||
|
:param prs: A list of :class:`anyconfig.models.processor.Processor` classes
|
||
|
:param cls: A class object to compare with `type_or_id`
|
||
|
:return:
|
||
|
Most appropriate processor instance to process files of given data type
|
||
|
or processor `type_or_id` found by its ID or None
|
||
|
:raises: UnknownProcessorTypeError
|
||
|
"""
|
||
|
if isinstance(type_or_id, cls):
|
||
|
return type_or_id
|
||
|
|
||
|
if type(type_or_id) == type(cls) and issubclass(type_or_id, cls):
|
||
|
return type_or_id()
|
||
|
|
||
|
def pred(pcls):
|
||
|
"""Predicate"""
|
||
|
return pcls.cid() == type_or_id or pcls.type() == type_or_id
|
||
|
|
||
|
processor = find_with_pred(pred, prs)
|
||
|
if processor is None:
|
||
|
raise UnknownProcessorTypeError(type_or_id)
|
||
|
|
||
|
return processor()
|
||
|
|
||
|
|
||
|
def find_by_fileext(fileext, prs):
|
||
|
"""
|
||
|
:param fileext: File extension
|
||
|
:param prs: A list of :class:`anyconfig.models.processor.Processor` classes
|
||
|
:return:
|
||
|
Most appropriate processor class to process files with given
|
||
|
extentsions or None
|
||
|
:raises: UnknownFileTypeError
|
||
|
"""
|
||
|
def pred(pcls):
|
||
|
"""Predicate"""
|
||
|
return fileext in pcls.extensions()
|
||
|
|
||
|
return find_with_pred(pred, prs)
|
||
|
|
||
|
|
||
|
def find_by_maybe_file(obj, prs):
|
||
|
"""
|
||
|
:param obj:
|
||
|
a file path, file or file-like object, pathlib.Path object or
|
||
|
`~anyconfig.globals.IOInfo` (namedtuple) object
|
||
|
:param cps_by_ext: A list of processor classes
|
||
|
|
||
|
:return:
|
||
|
An instance of most appropriate processor class to process given data
|
||
|
:raises: UnknownFileTypeError
|
||
|
"""
|
||
|
if not isinstance(obj, IOInfo):
|
||
|
obj = anyconfig.ioinfo.make(obj)
|
||
|
|
||
|
processor = find_by_fileext(obj.extension, prs)
|
||
|
if processor is None:
|
||
|
raise UnknownFileTypeError("file extension={}".format(obj.extension))
|
||
|
|
||
|
return processor()
|
||
|
|
||
|
|
||
|
def find(obj, prs, forced_type=None, cls=anyconfig.models.processor.Processor):
|
||
|
"""
|
||
|
:param obj:
|
||
|
a file path, file or file-like object, pathlib.Path object or
|
||
|
`~anyconfig.globals.IOInfo` (namedtuple) object
|
||
|
:param prs: A list of :class:`anyconfig.models.processor.Processor` classes
|
||
|
:param forced_type:
|
||
|
Forced processor type of the data to process or ID of the processor
|
||
|
class or :class:`anyconfig.models.processor.Processor` class object or
|
||
|
its instance itself
|
||
|
:param cls: A class object to compare with `forced_type` later
|
||
|
|
||
|
:return: an instance of processor class to process `obj` data
|
||
|
:raises: ValueError, UnknownProcessorTypeError, UnknownFileTypeError
|
||
|
"""
|
||
|
if (obj is None or not obj) and forced_type is None:
|
||
|
raise ValueError("The first argument 'obj' or the second argument "
|
||
|
"'forced_type' must be something other than "
|
||
|
"None or False.")
|
||
|
|
||
|
if forced_type is None:
|
||
|
processor = find_by_maybe_file(obj, prs)
|
||
|
if processor is None:
|
||
|
raise UnknownFileTypeError(obj)
|
||
|
|
||
|
return processor
|
||
|
|
||
|
processor = find_by_type_or_id(forced_type, prs, cls=cls)
|
||
|
if processor is None:
|
||
|
raise UnknownProcessorTypeError(forced_type)
|
||
|
|
||
|
return processor
|
||
|
|
||
|
|
||
|
class Processors(object):
|
||
|
"""An abstract class of which instance holding processors.
|
||
|
"""
|
||
|
_pgroup = None # processor group name to load plugins
|
||
|
|
||
|
def __init__(self, processors=None):
|
||
|
"""
|
||
|
:param processors:
|
||
|
A list of :class:`Processor` or its children class objects or None
|
||
|
"""
|
||
|
self._processors = dict() # {<processor_class_id>: <processor_class>}
|
||
|
if processors is not None:
|
||
|
self.register(*processors)
|
||
|
|
||
|
self.load_plugins()
|
||
|
|
||
|
def register(self, *pclss):
|
||
|
"""
|
||
|
:param pclss: A list of :class:`Processor` or its children classes
|
||
|
"""
|
||
|
for pcls in pclss:
|
||
|
if pcls.cid() not in self._processors:
|
||
|
self._processors[pcls.cid()] = pcls
|
||
|
|
||
|
def load_plugins(self):
|
||
|
"""Load and register pluggable processor classes internally.
|
||
|
"""
|
||
|
if self._pgroup:
|
||
|
self.register(*load_plugins(self._pgroup))
|
||
|
|
||
|
def list(self, sort=True):
|
||
|
"""
|
||
|
:return: A list of :class:`Processor` or its children classes
|
||
|
"""
|
||
|
if sort:
|
||
|
return sorted(self._processors.values(),
|
||
|
key=operator.methodcaller("cid"))
|
||
|
|
||
|
return self._processors.values()
|
||
|
|
||
|
def find_by_type(self, ptype):
|
||
|
"""
|
||
|
:param ptype: Processor's type to find
|
||
|
"""
|
||
|
return find_by_type(ptype, self.list(sort=False))
|
||
|
|
||
|
def find_by_type_or_id(self, type_or_id):
|
||
|
"""
|
||
|
:param type_or_id: Processor's type or ID to find
|
||
|
"""
|
||
|
return find_by_type_or_id(type_or_id, self.list(sort=False))
|
||
|
|
||
|
def find(self, obj, forced_type=None,
|
||
|
cls=anyconfig.models.processor.Processor):
|
||
|
"""
|
||
|
:param obj:
|
||
|
a file path, file or file-like object, pathlib.Path object or
|
||
|
`~anyconfig.globals.IOInfo` (namedtuple) object
|
||
|
:param forced_type: Forced processor type to find
|
||
|
:param cls: A class object to compare with `ptype`
|
||
|
|
||
|
:return: an instance of processor class to process `ipath` data later
|
||
|
:raises: ValueError, UnknownProcessorTypeError, UnknownFileTypeError
|
||
|
"""
|
||
|
return find(obj, self.list(sort=False), forced_type=forced_type,
|
||
|
cls=cls)
|
||
|
|
||
|
# vim:sw=4:ts=4:et:
|