Source code for tendril.validation.base

#!/usr/bin/env python
# encoding: utf-8

# Copyright (C) 2016-2019 Chintalagiri Shashank
#
# This file is part of tendril.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""
Tendril Validation Base (:mod:`tendril.validation.base`)
========================================================
"""

from copy import copy
from colorama import Style
from colorama import Fore

from tendril.utils import terminal
from tendril.utils import log
logger = log.get_logger(__name__, log.DEFAULT)


[docs]class ValidatableBase(object): def __init__(self, vctx=None): self._validated = False self._validation_context = vctx or ValidationContext(self.__class__.__name__) self._validation_errors = ErrorCollector() @property def ident(self): return '<unidentifiable {0}>'.format(self.__class__.__name__) @ident.setter def ident(self, value): raise NotImplementedError
[docs] def _validate(self): raise NotImplementedError
[docs] def validate(self): if not self._validated: logger.debug("Validating {0}".format(self.ident)) self._validate()
@property def validation_errors(self): if not self._validated: self._validate() return self._validation_errors
[docs]class ValidationContext(object): def __init__(self, mod, locality=None): self.mod = mod self.locality = locality def __repr__(self): if self.locality: return '/'.join([self.mod, self.locality]) else: return self.mod
[docs] def render(self): if self.locality: return self.locality else: return self.mod
[docs] def child(self, locality): if self.locality: locality = '/'.join([self.locality, locality]) cctx = copy(self) cctx.locality = locality return cctx
[docs]class ValidationPolicy(object): def __init__(self, context, is_error=True): self.context = context self.is_error = is_error
[docs]class ValidationError(Exception): msg = "Validation Error" def __init__(self, policy): self._policy = policy self.detail = None @property def policy(self): return self._policy
[docs] def render(self): return { 'is_error': self.policy.is_error, 'group': self.msg, 'headline': self._policy.context.render(), 'detail': self.detail, }
def __str__(self): rd = self.render() return "{0}\n\t{1}\n\t{2}".format( rd['group'], rd['headline'], rd['detail'] )
[docs]class ErrorCollector(ValidationError): def __init__(self): self._errors = []
[docs] def add(self, e): if isinstance(e, ErrorCollector): for error in e.errors: self._errors.append(error) else: self._errors.append(e)
@property def errors(self): return self._errors @property def terrors(self): return len(self._errors) @property def derrors(self): return [x for x in self._errors if x.policy.is_error] @property def dwarnings(self): return [x for x in self._errors if not x.policy.is_error] @property def nerrors(self): return len(self.derrors) @property def nwarnings(self): return len(self.dwarnings)
[docs] @staticmethod def _group_errors(errors): rval = {} for error in errors: etype = error['group'] if etype in rval.keys(): rval[etype].append(error) else: rval[etype] = [error] return rval
@property def errors_by_type(self): lerrors = [x.render() for x in self.derrors] return self._group_errors(lerrors) @property def warnings_by_type(self): lwarnings = [x.render() for x in self.dwarnings] return self._group_errors(lwarnings) def __repr__(self): rval = 'Collected Errors:\n' for e in self._errors: rval += ' {0}\n'.format(repr(e)) return rval
[docs] def _render_cli_group(self, g): for idx, i in enumerate(g): if 'detail_core' in i.keys(): detail = i['detail_core'] else: detail = i['detail'] print("{0}.{1} : {2}" "".format(idx + 1, i['headline'], detail))
[docs] def render_cli(self, name): width = terminal.get_terminal_width() hline = '-' * width print(hline + Style.BRIGHT) titleformat = "{0:<" + str(width - 13) + "} {1:>2} {2}" print(titleformat.format(name, self.terrors, 'ALERTS') + Style.NORMAL) if self.nerrors: print(Fore.RED + hline) print(titleformat.format('', self.nerrors, 'ERRORS')) for n, g in self.errors_by_type.items(): print(hline + Style.BRIGHT) print(titleformat.format(n, len(g), 'INSTANCES') + Style.NORMAL) self._render_cli_group(g) if self.nwarnings: print(Fore.YELLOW + hline) print(titleformat.format('', self.nwarnings, 'WARNINGS')) for n, g in self.warnings_by_type.items(): print(hline + Style.BRIGHT) print(titleformat.format(n, len(g), 'INSTANCES') + Style.NORMAL) self._render_cli_group(g) print(Fore.RESET + Style.BRIGHT + hline + Style.NORMAL)