Source code for kwhelp.rules

# coding: utf-8
from abc import ABC, abstractmethod
import numbers
import os
from pathlib import Path
from typing import Optional
from ..helper import is_iterable
# region Interface


[docs]class IRule(ABC): """ Abstract Interface Class for rules See Also: :doc:`/source/general/rules` """
[docs] def __init__(self, key: str, name: str, value: object, raise_errors: bool, originator: object): """ Constructor Args: key (str): the key that rule is to apply to. name (str): the name of the field that value was assigned value (object): the value that is assigned to ``field_name`` raise_errors (bool): determinins if rule could raise an error when validation fails originator (object): the object that attributes validated for Raises: TypeError: If any arg is not of the correct type """ if not isinstance(name, str): msg = self._get_type_error_msg(name, 'name', 'str') raise TypeError(msg) self._name: str = name if not isinstance(key, str): msg = self._get_type_error_msg(key, 'key', 'str') raise TypeError(msg) self._key: str = key if not isinstance(raise_errors, bool): msg = self._get_type_error_msg( raise_errors, 'raise_errors', 'bool') raise TypeError(msg) self._raise_errors = raise_errors self._value: object = value self._originator: object = originator
# region Abstract Methods
[docs] @abstractmethod def validate(self) -> bool: '''Gets attrib field and value are valid'''
# endregion Abstract Methods def _get_type_error_msg(self, arg: Optional[object] = None, arg_name: Optional[str] = None, expected_type: Optional[str] = None) -> str: _arg = self.field_value if arg is None else arg _arg_name = self.key if arg_name is None else arg_name if expected_type: msg = f"Argument Error: '{_arg_name}' is expecting type of '{expected_type}'. Got type of '{type(_arg).__name__}'" else: msg = f"Argument Error: '{_arg_name}' is not expecting '{type(_arg).__name__}'" return msg def _get_not_type_error_msg(self, arg: Optional[object] = None, arg_name: Optional[str] = None, not_type: Optional[str] = None) -> str: _arg = self.field_value if arg is None else arg _arg_name = self.key if arg_name is None else arg_name if not_type: msg = f"Argument Error: '{_arg_name}' is expecting non '{not_type}'. Got type of '{type(_arg).__name__}'" else: msg = f"Argument Error: '{_arg_name}' is expecting non '{type(_arg).__name__}'." return msg # region Properties @property def field_name(self) -> str: ''' Name of the field assigned. :getter: Gets the name of the field assigned :setter: Sets the name of the field assigned ''' return self._name @field_name.setter def field_name(self, value: str): if not isinstance(value, str): msg = self._get_type_error_msg(value, 'field_name', 'str') raise TypeError(msg) self._name = value @property def field_value(self) -> object: """ The value assigned to ``field_name`` :getter: Gets value assigned to ``field_name`` :setter: Sets value assigned to ``field_name`` """ return self._value @field_value.setter def field_value(self, value: object): self._value = value @property def key(self) -> str: '''Gets the key currently being read''' return self._key @key.setter def key(self, value: str): if not isinstance(value, str): msg = self._get_type_error_msg(value, 'key', 'str') raise TypeError(msg) self._key = value @property def raise_errors(self) -> bool: """ Determines if a rule can raise an error when validation fails. :getter: Gets if a rule could raise an error when validation fails :setter: Sets if a rule could raise an error when validation fails """ return self._raise_errors @raise_errors.setter def raise_errors(self, value: bool): if not isinstance(value, bool): msg = self._get_type_error_msg(value, 'raise_errors', 'bool') raise TypeError(msg) self._raise_errors = value @property def originator(self) -> object: '''Gets object that attributes validated for''' return self._originator
# endregion Properties # endregion Interface # region Attrib rules
[docs]class RuleAttrNotExist(IRule): ''' Rule to ensure an attribute does not exist before it is added to class. '''
[docs] def validate(self) -> bool: """ Validates that ``field_name`` is not an existing attribute of ``originator`` instance. Raises: AttributeError: If ``raise_errors`` is ``True`` and ``field_name`` is already an attribue of ``originator`` instance. Returns: bool: ``True`` if ``field_name`` is not an existing attribue of ``originator`` instance; Otherwise, ``False``. """ result = not hasattr(self.originator, self.field_name) if result == False and self.raise_errors == True: raise AttributeError( f"'{self.field_name}' attribute already exist in current instance of '{type(self.originator).__name__}'") return result
[docs]class RuleAttrExist(IRule): ''' Rule to ensure an attribute does exist before its value is set. '''
[docs] def validate(self) -> bool: """ Validates that ``field_name`` is an existing attribute of ``originator`` instance. Raises: AttributeError: If ``raise_errors`` is ``True`` and ``field_name`` is not an attribue of ``originator`` instance. Returns: bool: ``True`` if ``field_name`` is an existing attribue of ``originator`` instance; Otherwise, ``False``. """ result = hasattr(self.originator, self.field_name) if result == False and self.raise_errors == True: raise AttributeError( f"'{self.field_name}' attribute does not exist in current instance of '{type(self.originator).__name__}'") return result
# endregion Attrib rules # region None
[docs]class RuleNone(IRule): ''' Rule that matched only if value is ``None``. '''
[docs] def validate(self) -> bool: """ Validates that value to assign to attribute is ``None``. Raises: ValueError: If ``raise_errors`` is ``True`` and ``field_value`` is not ``None``. Returns: bool: ``True`` if ``field_value`` is ``None``; Otherwise, ``False``. """ if self.field_value is not None: if self.raise_errors: raise ValueError( f"Arg error: {self.key} must be assigned a value") return False return True
[docs]class RuleNotNone(IRule): ''' Rule that matched only if value is not ``None``. '''
[docs] def validate(self) -> bool: """ Validates that value to assign to attribute is not ``None``. Raises: ValueError: If ``raise_errors`` is ``True`` and ``field_value`` is ``None``. Returns: bool: ``True`` if ``field_value`` is not ``None``; Otherwise, ``False``. """ if self.field_value is None: if self.raise_errors: raise ValueError( f"Arg error: {self.key} must be assigned a value") return False return True
# endregion None # region Number
[docs]class RuleNumber(IRule): ''' Rule that matched only if value is a valid number. Note: If value is a of type ``bool`` then validation will fail for this rule. '''
[docs] def validate(self) -> bool: """ Validates that value to assign is a number Raises: TypeError: If ``raise_errors`` is ``True`` and ``field_value`` is not a number. Returns: bool: ``True`` if ``field_value`` is a number; Otherwise, ``False``. """ # isinstance(False, int) is True # print(int(True)) 1 # print(int(False)) 0 if not isinstance(self.field_value, numbers.Number) or isinstance(self.field_value, bool): if self.raise_errors: raise TypeError(self._get_type_error_msg( self.field_value, self.key, 'Number')) return False return True
# region Integer
[docs]class RuleInt(IRule): ''' Rule that matched only if value is instance of ``int``. Note: If value is a of type ``bool`` then validation will fail for this rule. '''
[docs] def validate(self) -> bool: """ Validates that value to assign is an int Raises: TypeError: If ``raise_errors`` is ``True`` and ``field_value`` is not an int. Returns: bool: ``True`` if ``field_value`` is an ``int``; Otherwise, ``False``. """ # isinstance(False, int) is True # print(int(True)) 1 # print(int(False)) 0 if not isinstance(self.field_value, int) or isinstance(self.field_value, bool): if self.raise_errors: raise TypeError(self._get_type_error_msg(expected_type='int')) return False return True
[docs]class RuleIntZero(RuleInt): ''' Rule that matched only if value is equal to ``0``. '''
[docs] def validate(self) -> bool: """ Validates that value to assign is equal to ``0`` int. Raises: ValueError: If ``raise_errors`` is ``True`` and ``field_value`` is not equal to ``0`` int. Returns: bool: ``True`` if ``field_value`` equals ``0`` int; Otherwise, ``False``. """ if not super().validate(): return False if self.field_value != 0: if self.raise_errors: raise ValueError( f"Arg error: '{self.key}' must be equal to 0 int value") return False return True
[docs]class RuleIntPositive(RuleInt): ''' Rule that matched only if value is equal or greater than ``0``. '''
[docs] def validate(self) -> bool: """ Validates that value to assign is a posivite int Raises: TypeError: If ``raise_errors`` is ``True`` and ``field_value`` is not a positive int. Returns: bool: ``True`` if ``field_value`` is a positive int; Otherwise, ``False``. """ if not super().validate(): return False if self.field_value < 0: if self.raise_errors: raise ValueError( f"Arg error: '{self.key}' must be a positive int value") return False return True
[docs]class RuleIntNegative(RuleInt): ''' Rule that matched only if value is less than ``0``. '''
[docs] def validate(self) -> bool: """ Validates that value to assign is a negative int Raises: TypeError: If ``raise_errors`` is ``True`` and ``field_value`` is not a negative int. Returns: bool: ``True`` if ``field_value`` is a negative int; Otherwise, ``False``. """ if not super().validate(): return False if self.field_value >= 0: if self.raise_errors: raise ValueError( f"Arg error: '{self.key}' must be a negative int value") return False return True
[docs]class RuleIntNegativeOrZero(RuleInt): ''' Rule that matched only if value is equal or less than ``0``. '''
[docs] def validate(self) -> bool: """ Validates that value to assign is equal to zero or a negative int Raises: TypeError: If ``raise_errors`` is ``True`` and ``field_value`` is not a negative int. Returns: bool: ``True`` if ``field_value`` is equal to zero or a negative int; Otherwise, ``False``. """ if not super().validate(): return False if self.field_value > 0: if self.raise_errors: raise ValueError( f"Arg error: '{self.key}' must be equal to zero or a negative int value") return False return True
[docs]class RuleByteUnsigned(RuleInt): ''' Unsigned Byte rule, range from ``0`` to ``255``. '''
[docs] def validate(self) -> bool: """ Valids Raises: ValueError: If ``raise_errors`` is ``False`` and value is less then ``0`` or greater than ``255``. Returns: bool: ``True`` if Validation passes; Otherwise, ``False``. """ if not super().validate(): return False if self.field_value < 0 or self.field_value > 255: if self.raise_errors: raise ValueError( f"Arg error: '{self.key}' must be a num from 0 to 255") return False return True
[docs]class RuleByteSigned(RuleInt): ''' Signed Byte rule, range from ``-128`` to ``127``. '''
[docs] def validate(self) -> bool: """ Valids Raises: ValueError: If ``raise_errors`` is ``False`` and value is less then ``-128`` or greater than ``128``. Returns: bool: ``True`` if Validation passes; Otherwise, ``False``. """ if not super().validate(): return False if self.field_value < -128 or self.field_value > 127: if self.raise_errors: raise ValueError( f"Arg error: '{self.key}' must be a num from -128 to 127") return False return True
# endregion Integer # region Float Rules
[docs]class RuleFloat(IRule): ''' Rule that matched only if value is to type ``float``. '''
[docs] def validate(self) -> bool: """ Validates that value to assign is a float Raises: TypeError: If ``raise_errors`` is ``True`` and ``field_value`` is not a float. Returns: bool: ``True`` if ``field_value`` is a positive float; Otherwise, ``False``. """ if not isinstance(self.field_value, float): if self.raise_errors: raise TypeError(self._get_type_error_msg( self.field_value, self.key, 'float')) return False return True
[docs]class RuleFloatZero(RuleFloat): ''' Rule that matched only if value is equal to ``0.0``. '''
[docs] def validate(self) -> bool: """ Validates that value to assign equals ``0.0`` float Raises: ValueError: If ``raise_errors`` is ``True`` and ``field_value`` is not equal to ``0.0`` float. Returns: bool: ``True`` if ``field_value`` equals ``0.0`` float; Otherwise, ``False``. """ if not super().validate(): return False if self.field_value != 0.0: if self.raise_errors: raise ValueError( f"Arg error: '{self.key}' must be equal to 0.0 float value") return False return True
[docs]class RuleFloatPositive(RuleFloat): ''' Rule that matched only if value is equal or greater than ``0.0``. '''
[docs] def validate(self) -> bool: """ Validates that value to assign is a positive float Raises: ValueError: If ``raise_errors`` is ``True`` and ``field_value`` is not a positive float. Returns: bool: ``True`` if ``field_value`` is a positive float; Otherwise, ``False``. """ if not super().validate(): return False if self.field_value < 0.0: if self.raise_errors: raise ValueError( f"Arg error: '{self.key}' must be a positive float value") return False return True
[docs]class RuleFloatNegative(RuleFloat): ''' Rule that matched only if value is less than ``0.0``. '''
[docs] def validate(self) -> bool: """ Validates that value to assign is a negative float Raises: ValueError: If ``raise_errors`` is ``True`` and ``field_value`` is not a negative float. Returns: bool: ``True`` if ``field_value`` is a negative float; Otherwise, ``False``. """ if not super().validate(): return False if self.field_value >= 0.0: if self.raise_errors: raise ValueError( f"Arg error: '{self.key}' must be a negative float value") return False return True
[docs]class RuleFloatNegativeOrZero(RuleFloat): ''' Rule that matched only if value is equal or less than ``0.0``. '''
[docs] def validate(self) -> bool: """ Validates that value to assign is equal to ``0.0`` or a negative float Raises: ValueError: If ``raise_errors`` is ``True`` and ``field_value`` is not a negative float. Returns: bool: ``True`` if ``field_value`` is equal to ``0.0`` or a negative float; Otherwise, ``False``. """ if not super().validate(): return False if self.field_value > 0.0: if self.raise_errors: raise ValueError( f"Arg error: '{self.key}' must be equal to 0.0 or a negative float value") return False return True
# endregion Float Rules # endregion Number # region String
[docs]class RuleStr(IRule): ''' Rule that matched only if value is of type ``str``. '''
[docs] def validate(self) -> bool: """ Validates that value to assign is a string Raises: TypeError: If ``raise_errors`` is ``True`` and ``field_value`` is not instance of string. Returns: bool: ``True`` if ``field_value`` is a string; Otherwise, ``False``. """ if not isinstance(self.field_value, str): if self.raise_errors: raise TypeError(self._get_type_error_msg( self.field_value, self.key, 'str')) return False return True
[docs]class RuleStrEmpty(RuleStr): ''' Rule that matched only if value is equal to empty string. '''
[docs] def validate(self) -> bool: """ Validates that value to assign is a string and is an empty string. Raises: ValueError: If ``raise_errors`` is ``True`` and ``field_value`` is not an empty string. Returns: bool: ``True`` if value is an empty string; Otherwise; ``False``. """ if not super().validate(): return False value = self.field_value if len(value) != 0: if self.raise_errors: raise ValueError( f"Arg error: {self.key} must be empty str") return False return True
[docs]class RuleStrNotNullOrEmpty(RuleStr): ''' Rule that matched only if value is not ``None`` or empty string. '''
[docs] def validate(self) -> bool: """ Validates that value to assign is a string and is not a empty string. Raises: ValueError: If ``raise_errors`` is ``True`` and ``field_value`` is not instance of string or is empty string Returns: bool: ``True`` if value is valid; Otherwise; ``False``. """ if not super().validate(): return False value = self.field_value if len(value) == 0: if self.raise_errors: raise ValueError( f"Arg error: {self.key} must not be empty str") return False return True
[docs]class RuleStrNotNullEmptyWs(RuleStrNotNullOrEmpty): ''' Rule that matched only if value is not ``None``, empty or whitespace. '''
[docs] def validate(self) -> bool: """ Validates that value to assign is a string and is not a empty or whitespace string. Raises: ValueError: If ``raise_errors`` is ``True`` and ``field_value`` is not instance of string or is empty or whitespace string Returns: bool: ``True`` if value is valid; Otherwise; ``False``. """ if not super().validate(): return False value = self.field_value.strip() if len(value) == 0: if self.raise_errors: raise ValueError( f"Arg error: '{self.key}' must not be empty or whitespace str") return False return True
# endregion String # region boolean
[docs]class RuleBool(IRule): """ Rule that matched only if value is instance of bool. """
[docs] def validate(self) -> bool: """ Validates that value to assign is a bool Raises: TypeError: If ``raise_errors`` is ``True`` and ``field_value`` is not instance of bool. Returns: bool: ``True`` if ``field_value`` is a bool; Otherwise, ``False``. """ if not isinstance(self.field_value, bool): if self.raise_errors: raise TypeError(self._get_type_error_msg(expected_type='bool')) return False return True
# endregion boolean # region Iterable
[docs]class RuleIterable(IRule): """ Rule that matched only if value is iterable such as list, tuple, set. """
[docs] def validate(self) -> bool: """ Validates that value to assign is iterable Raises: TypeError: If ``raise_errors`` is ``True`` and ``field_value`` is not iterable. Returns: bool: ``True`` if ``field_value`` is a iterable; Otherwise, ``False``. """ if not is_iterable(self.field_value): if self.raise_errors: raise TypeError(self._get_type_error_msg(expected_type="iterable")) return False return True
[docs]class RuleNotIterable(IRule): """ Rule that matched only if value is not iterable. """
[docs] def validate(self) -> bool: """ Validates that value to assign is not iterable Raises: TypeError: If ``raise_errors`` is ``True`` and ``field_value`` is iterable. Returns: bool: ``True`` if ``field_value`` is a not iterable; Otherwise, ``False``. """ if is_iterable(self.field_value): if self.raise_errors: raise TypeError(self._get_not_type_error_msg(not_type="iterable")) return False return True
# endregion Iterable # region Path
[docs]class RulePath(IRule): """ Rule that matched only if value is instance of ``Path``. """
[docs] def validate(self) -> bool: """ Validates that value to assign is a Path Raises: TypeError: If ``raise_errors`` is ``True`` and ``field_value`` is not instance of Path. Returns: bool: ``True`` if ``field_value`` is a bool; Otherwise, ``False``. """ if not isinstance(self.field_value, Path): if self.raise_errors: raise TypeError(self._get_type_error_msg(expected_type='Path')) return False return True
[docs]class RulePathExist(RulePath): """ Rule that matched only if value is instance of ``Path`` and path exist. """
[docs] def validate(self) -> bool: """ Validates that value to assign is a path that exist Raises: FileNotFoundError: If ``raise_errors`` is ``True`` and ``field_value`` is path does not exist. Returns: bool: ``True`` if ``field_value`` is an existing path; Otherwise, ``False``. """ if not super().validate(): return False if not os.path.exists(self.field_value): if self.raise_errors: raise FileNotFoundError( f"Unable to find path: '{self.field_value}'") return False return True
[docs]class RulePathNotExist(RulePath): """ Rule that matched only if value is instance of ``Path`` and path does not exist. """
[docs] def validate(self) -> bool: """ Validates that value to assign is a Path that does not exist Raises: FileExistsError: If ``raise_errors`` is ``True`` and ``field_value`` is path that is existing. Returns: bool: ``True`` if ``field_value`` is a p ath that does not exist; Otherwise, ``False``. """ if not super().validate(): return False if os.path.exists(self.field_value): if self.raise_errors: raise FileExistsError( f"File already exist: '{self.field_value}'") return False return True
[docs]class RuleStrPathExist(RuleStr): """ Rule that matched only if value is instance of str and path exist. """
[docs] def validate(self) -> bool: """ Validates that value to assign is a str path that exist Raises: FileNotFoundError: If ``raise_errors`` is ``True`` and ``field_value`` is path does not exist. Returns: bool: ``True`` if ``field_value`` is a path that does exist; Otherwise, ``False``. """ if not super().validate(): return False if not os.path.exists(self.field_value): if self.raise_errors: raise FileNotFoundError( f"Unable to find path: '{self.field_value}'") return False return True
[docs]class RuleStrPathNotExist(RuleStr): """ Rule that matched only if value is instance of str and path is not existing. """
[docs] def validate(self) -> bool: """ Validates that value to assign is a path not existing Raises: FileExistsError: If ``raise_errors`` is ``True`` and ``field_value`` is a path that is not existing. Returns: bool: ``True`` if ``field_value`` is a path that does not exist; Otherwise, ``False``. """ if not super().validate(): return False if os.path.exists(self.field_value): if self.raise_errors: raise FileExistsError( f"File already exist: '{self.field_value}'") return False return True
# end Region Path