Source code for text_lint.operations.mixins.parameter_validation

"""ParameterValidationMixin class."""

from typing import Any, Type, cast

from text_lint.exceptions.operations import InvalidParameterValidation
from text_lint.utilities.translations import _
from text_lint.utilities.whitespace import new_line
from .constants import (
    TYPES_ALL,
    TYPES_CONTAINER,
    TYPES_NUMERIC,
    AliasParameterOfType,
    AliasParameterValidator,
)
from .parameter_definition import ParameterDefinition


[docs] class ParameterValidationMixin: """A mixin class to validate constructor parameters.""" parameter_definition_keys = ("type", "of", "optional", "validators") parameter_definition_width = len(max(parameter_definition_keys, key=len)) parameter_schema_class_name = "Parameters" msg_fmt_parameter_invalid_value = _( "The value '{0}' is not valid for parameter '{1}'" ) msg_fmt_parameter_invalid_definition_description = _( "The typing schema for parameter '{0}' is invalid." ) msg_fmt_parameter_invalid_definition_detail_tuple = ( _("Valid parameter definitions must take the form of a dictionary:"), _(" {2} specifies the expected python type."), _(" supported types: {0}."), _(" {3} optional type used only with the container types."), _(" container types: {1}."), _( " (dicts should specify a tuple of types in " "(key, value) format.)" ), _(" {4} optional bool to accept 'None' as a valid value."), _(" {5} optional list of validator functions."), )
[docs] def validate_parameters(self) -> None: """Validate the parameters used to instantiate a mixed-in class. :raises: InvalidParameterValidation, TypeError """ parameter_schema = getattr(self, self.parameter_schema_class_name, None) if parameter_schema: for attribute_name in filter( lambda name: not name.startswith('_'), dir(parameter_schema), ): definition = self.__parse_parameter_definition__(attribute_name) self.__validate_attribute__(definition)
[docs] def __parse_parameter_definition__( self, attribute_name: str, ) -> ParameterDefinition: """Generate a ParameterDefinition instance from the given attribute name. :param attribute_name: The name of the attribute that will be parsed. :returns: The generated ParameterDefinition instance. :raises: InvalidParameterValidation """ attribute = getattr(self, attribute_name) schema_definition = dict( getattr( getattr( self, self.parameter_schema_class_name, ), attribute_name, ) ) try: return ParameterDefinition( attribute=attribute, attribute_name=attribute_name, expected_type=schema_definition.pop( self.parameter_definition_keys[0] ), **schema_definition, ) except (KeyError, TypeError, AssertionError) as exc: raise InvalidParameterValidation( translated_description=self. msg_fmt_parameter_invalid_definition_description. format(attribute_name), translated_detail=new_line( new_line().join( self.msg_fmt_parameter_invalid_definition_detail_tuple ).format( (", ".join([value.__name__ for value in TYPES_ALL])), (", ".join([value.__name__ for value in TYPES_CONTAINER])), *[ (value + ":").ljust(self.parameter_definition_width + 1) for value in self.parameter_definition_keys ], ) ), operation_class=self.__class__, ) from exc
[docs] def __validate_attribute__( self, definition: ParameterDefinition, ) -> None: """Validate the mixed-in class with a ParameterDefinition. :param definition: The ParameterDefinition instance that will be used. :returns: The generated ParameterDefinition instance. :raises: TypeError """ if definition.optional is False: if definition.attribute is None: self.__raise_type_error__(definition) if definition.expected_type in TYPES_NUMERIC: self.__validate_numeric_attribute__(definition) if ( definition.attribute is not None and not isinstance(definition.attribute, definition.expected_type) ): self.__raise_type_error__(definition) if definition.expected_type in TYPES_CONTAINER: self.__validate_container_attribute__(definition) if definition.attribute is not None: for validator in definition.validators: if not validator(definition.attribute): self.__raise_type_error__(definition)
[docs] def __validate_container_attribute__( self, definition: ParameterDefinition, ) -> None: """Validate a container-type attribute value with a ParameterDefinition. :param definition: The ParameterDefinition instance that will be used. :raises: TypeError """ if isinstance(definition.attribute, dict): self.__validate_dict_attribute__(definition) else: self.__validate_iterable_attribute__(definition)
[docs] def __validate_dict_attribute__( self, definition: ParameterDefinition, ) -> None: """Validate a dictionary attribute value with a ParameterDefinition. :param definition: The ParameterDefinition instance that will be used. :raises: TypeError """ if definition.of and isinstance(definition.of, tuple): for nested_key, nested_value in definition.attribute.items(): self.__validate_attribute__( ParameterDefinition( attribute=nested_key, attribute_name=definition.attribute_name, expected_type=definition.of[0], ) ) self.__validate_attribute__( ParameterDefinition( attribute=nested_value, attribute_name=definition.attribute_name, expected_type=cast(Type[Any], definition.of[1]), ) )
[docs] def __validate_iterable_attribute__( self, definition: ParameterDefinition, ) -> None: """Validate an iterable attribute value with a ParameterDefinition. :param definition: The ParameterDefinition instance that will be used. :raises: TypeError """ if definition.of and isinstance(definition.of, type): for nested_attribute in definition.attribute: self.__validate_attribute__( ParameterDefinition( attribute=nested_attribute, attribute_name=definition.attribute_name, expected_type=definition.of, ) )
[docs] def __validate_numeric_attribute__( self, definition: ParameterDefinition, ) -> None: """Validate a numeric attribute value with a ParameterDefinition. :param definition: The ParameterDefinition instance that will be used. :raises: TypeError """ if not isinstance(definition.attribute, (int, float)): self.__raise_type_error__(definition) casted_value = definition.expected_type(definition.attribute) if casted_value != definition.attribute: self.__raise_type_error__(definition) definition.attribute = definition.expected_type(definition.attribute)
[docs] def __raise_type_error__( self, definition: ParameterDefinition, ) -> None: """Raise a TypeError derived from the given ParameterDefinition. :param definition: The ParameterDefinition instance that will be used. :raises: TypeError """ raise TypeError( self.msg_fmt_parameter_invalid_value.format( definition.attribute, definition.attribute_name, ) )