from typing import List, Tuple, Callable, Any from .base import ILoaderClass, Loader, CAPTURE_EXCEPTIONS from .exception import CompositeStructureError from .types import method from .utils import raw MAPPING_ERROR_ITEM = Tuple[str, Exception] MAPPING_ERRORS = List[MAPPING_ERROR_ITEM] class MappingError(CompositeStructureError): """ Overview: Mapping error. Interfaces: ``__init__``, ``errors`` """ def __init__(self, key_errors: MAPPING_ERRORS, value_errors: MAPPING_ERRORS): """ Overview: Initialize the MappingError. Arguments: - key_errors (:obj:`MAPPING_ERRORS`): The key errors. - value_errors (:obj:`MAPPING_ERRORS`): The value errors. """ self.__key_errors = list(key_errors or []) self.__value_errors = list(value_errors or []) self.__errors = self.__key_errors + self.__value_errors def key_errors(self) -> MAPPING_ERRORS: """ Overview: Get the key errors. """ return self.__key_errors def value_errors(self) -> MAPPING_ERRORS: """ Overview: Get the value errors. """ return self.__value_errors def errors(self) -> MAPPING_ERRORS: """ Overview: Get the errors. """ return self.__errors def mapping(key_loader, value_loader, type_back: bool = True) -> ILoaderClass: """ Overview: Create a mapping loader. Arguments: - key_loader (:obj:`ILoaderClass`): The key loader. - value_loader (:obj:`ILoaderClass`): The value loader. - type_back (:obj:`bool`): Whether to convert the type back. """ key_loader = Loader(key_loader) value_loader = Loader(value_loader) def _load(value): _key_errors = [] _value_errors = [] _result = {} for key_, value_ in value.items(): key_error, value_error = None, None key_result, value_result = None, None try: key_result = key_loader(key_) except CAPTURE_EXCEPTIONS as err: key_error = err try: value_result = value_loader(value_) except CAPTURE_EXCEPTIONS as err: value_error = err if not key_error and not value_error: _result[key_result] = value_result else: if key_error: _key_errors.append((key_, key_error)) if value_error: _value_errors.append((key_, value_error)) if not _key_errors and not _value_errors: if type_back: _result = type(value)(_result) return _result else: raise MappingError(_key_errors, _value_errors) return method('items') & Loader(_load) def mpfilter(check: Callable[[Any, Any], bool], type_back: bool = True) -> ILoaderClass: """ Overview: Create a mapping filter loader. Arguments: - check (:obj:`Callable[[Any, Any], bool]`): The check function. - type_back (:obj:`bool`): Whether to convert the type back. """ def _load(value): _result = {key_: value_ for key_, value_ in value.items() if check(key_, value_)} if type_back: _result = type(value)(_result) return _result return method('items') & Loader(_load) def mpkeys() -> ILoaderClass: """ Overview: Create a mapping keys loader. """ return method('items') & method('keys') & Loader(lambda v: set(v.keys())) def mpvalues() -> ILoaderClass: """ Overview: Create a mapping values loader. """ return method('items') & method('values') & Loader(lambda v: set(v.values())) def mpitems() -> ILoaderClass: """ Overview: Create a mapping items loader. """ return method('items') & Loader(lambda v: set([(key, value) for key, value in v.items()])) _INDEX_PRECHECK = method('__getitem__') def item(key) -> ILoaderClass: """ Overview: Create a item loader. Arguments: - key (:obj:`Any`): The key. """ return _INDEX_PRECHECK & Loader( (lambda v: key in v.keys(), lambda v: v[key], KeyError('key {key} not found'.format(key=repr(key)))) ) def item_or(key, default) -> ILoaderClass: """ Overview: Create a item or loader. Arguments: - key (:obj:`Any`): The key. - default (:obj:`Any`): The default value. """ return _INDEX_PRECHECK & (item(key) | raw(default))