Source code for domprob.sensors.validate.chain_val

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, TypeVar

from domprob.sensors.exc import SensorException
from domprob.sensors.validate.base_val import BaseValidator

if TYPE_CHECKING:
    from domprob.sensors.validate.chain import (  # pragma: no cover
        ValidationChain,
    )

# Typing helper: defines a validator implementing the abstract class
_ChainLink = TypeVar("_ChainLink", bound=BaseValidator)


[docs] class ValidationChainException(SensorException): """Base exception class for errors related to validate chains. This exception serves as the root for all validate chain-related errors, such as issues with chain construction, execution, or invalid links. It is designed to be extended by more specific exceptions within the validate framework. """
[docs] class InvalidLinkException(ValidationChainException): """Exception raised when an invalid link is added to a validate chain. This exception is used to indicate that a link in the validate chain does not meet the required criteria or fails validate. It provides detailed information about the invalid link and the expected type. Attributes: link (Any): The invalid link that caused the exception. expected_type (type[Any]): The expected type for valid links in the chain. Args: link (Any): The link object that is invalid. expected_type (type[Any]): The expected type for links in the validate chain. Examples: >>> from domprob.exceptions import InvalidLinkException >>> >>> def raise_invalid_link(): ... raise InvalidLinkException("InvalidLink", expected_type=int) ... >>> try: ... raise_invalid_link() ... except InvalidLinkException as e: ... print(str(e)) Invalid link of type 'str', expected type 'int' """ def __init__(self, link: Any, expected_type: type[Any]) -> None: self.link = link self.expected_type = expected_type super().__init__(self.msg) @property def msg(self) -> str: """Constructs a detailed error message for the exception. This property dynamically generates a human-readable string that describes the invalid link and the expected type. Returns: str: A string describing the invalid link and its expected type. Examples: >>> exc = InvalidLinkException("InvalidLink", expected_type=int) >>> exc.msg "Invalid link of type 'str', expected type 'int'" """ l_name = type(self.link).__name__ e_name = self.expected_type.__name__ return f"Invalid link of type '{l_name}', expected type '{e_name}'"
[docs] class EmptyChainException(ValidationChainException): """Exception raised when a validate chain is empty. This exception indicates that no validators have been added to a validate chain, which prevents the chain from performing any meaningful validate. Args: chain (ValidationChain): The empty validate chain. Attributes: chain (ValidationChain): The empty validate chain. Examples: >>> from domprob.exceptions import EmptyChainException >>> from domprob.sensors.validate.base_val import BaseValidator >>> from domprob.sensors.validate.chain import ValidationChain >>> >>> try: ... raise EmptyChainException(ValidationChain(BaseValidator)) ... except EmptyChainException as e: ... print(e) ... Nothing to validate, no links added to chain 'ValidationChain(base='BaseValidator')' """ def __init__(self, chain: ValidationChain) -> None: self.chain = chain super().__init__(self.msg) @property def msg(self) -> str: """Returns the error message associated with the exception. Returns: str: The error message describing the empty validate chain issue. Examples: >>> from domprob.exceptions import EmptyChainException >>> from domprob.sensors.validate.base_val import BaseValidator >>> from domprob.sensors.validate.chain import ValidationChain >>> >>> exc = EmptyChainException(ValidationChain(BaseValidator)) >>> exc.msg "Nothing to validate, no links added to chain 'ValidationChain(base='BaseValidator')'" """ return f"Nothing to validate, no links added to chain '{self.chain!r}'"
[docs] class LinkExistsException(ValidationChainException): # pylint: disable=line-too-long """Exception raised when a duplicate link is added to a validate chain. This exception is used to indicate that the specified link already exists in the validate chain and duplicates are not allowed. It provides details about the duplicate link and the chain where the conflict occurred. Args: link (Any): The duplicate link that caused the exception. chain (ValidationChain): The validate chain where the duplicate was found. Attributes: link (Any): The duplicate link. chain (ValidationChain): The chain where the duplicate was detected. Examples: >>> from domprob.exceptions import LinkExistsException >>> from domprob.sensors.validate.base_val import BaseValidator >>> from domprob.sensors.validate.chain import ValidationChain >>> >>> try: ... raise LinkExistsException("DuplicateLink", chain=ValidationChain(BaseValidator)) # type: ignore ... except LinkExistsException as e: ... print(e) ... Link ''DuplicateLink'' already exists in chain 'ValidationChain(base='BaseValidator')' """ def __init__(self, link: _ChainLink, chain: ValidationChain) -> None: self.link = link self.chain = chain super().__init__(self.msg) @property def msg(self) -> str: """Constructs a detailed error message indicating a duplicate link in the validate chain. Returns: str: A message indicating the duplicate link and the affected chain. Examples: >>> from domprob.exceptions import LinkExistsException >>> from domprob.sensors.validate.base_val import BaseValidator >>> from domprob.sensors.validate.chain import ValidationChain >>> >>> chain = ValidationChain(base=BaseValidator) >>> exc = LinkExistsException("DuplicateLink", chain) # type: ignore >>> exc.msg "Link ''DuplicateLink'' already exists in chain 'ValidationChain(base='BaseValidator')'" """ return f"Link '{self.link!r}' already exists in chain '{self.chain!r}'"
# pylint: disable=too-few-public-methods
[docs] class ABCLinkValidator(ABC): """Abstract base class for validators in a validate chain. This class defines the structure for implementing validators that perform specific checks on links within a validate chain. Attributes: chain (ValidationChain): The validate chain associated with this validator. It provides context for the validate process. Args: chain (ValidationChain): The validate chain to associate with this validator. Examples: >>> from domprob.sensors.validate.chain import ABCLinkValidator >>> from domprob.sensors.validate.base_val import BaseValidator >>> >>> class ExampleValidator(ABCLinkValidator): ... def validate(self, link: BaseValidator) -> None: ... pass ... """ def __init__(self, chain: ValidationChain) -> None: self.chain = chain
[docs] @abstractmethod def validate(self, link: _ChainLink) -> None: """Validates a single link in the validate chain. This abstract method must be implemented by subclasses to define specific validate logic. Args: link (_ChainLink): The link to validate. Raises: NotImplementedError: If the subclass does not implement this method. """ raise NotImplementedError # pragma: no cover
[docs] def __repr__(self) -> str: """Returns a string representation of the validator. Returns: str: A string representation of the validator, including its class name and the validate chain it belongs to. Examples: >>> from domprob.sensors.validate.chain import ABCLinkValidator >>> from domprob.sensors.validate.base_val import BaseValidator >>> >>> class ExampleValidator(ABCLinkValidator): ... def validate(self, link: BaseValidator) -> None: ... pass ... >>> from domprob.sensors.validate.chain import ValidationChain >>> >>> validator = ExampleValidator(ValidationChain(BaseValidator)) >>> repr(validator) "ExampleValidator(ValidationChain(base='BaseValidator'))" """ return f"{self.__class__.__name__}({self.chain!r})"
[docs] class LinkTypeValidator(ABCLinkValidator): """Validator to ensure that links in the validate chain are of the expected type. This validator checks whether each link added to the validate chain is an instance of the chain's base type. If a link does not match the expected type, it raises an `InvalidLinkException`. This ensures type safety and consistency within the chain. Attributes: chain (ValidationChain): The validate chain this validator is associated with. Examples: >>> from domprob.sensors.validate.chain_val import LinkTypeValidator >>> from domprob.sensors.validate.chain import ValidationChain >>> from domprob.sensors.validate.base_val import BaseValidator >>> >>> validator = LinkTypeValidator(ValidationChain(BaseValidator)) >>> >>> try: ... validator.validate(123) # type: ignore ... except InvalidLinkException as e: ... print(e) ... Invalid link of type 'int', expected type 'BaseValidator' """
[docs] def validate(self, link: _ChainLink) -> None: """Validates that the provided link is of the expected type. This method ensures that the given link is an instance of the chain's base type. If the link is not of the expected type, it raises an `InvalidLinkException`. Args: link (_ChainLink): The link to validate. Raises: InvalidLinkException: If the link is not of the expected type. Examples: >>> from domprob.sensors.validate.chain_val import LinkTypeValidator >>> from domprob.sensors.validate.chain import ValidationChain >>> from domprob.sensors.validate.base_val import BaseValidator >>> >>> validator = LinkTypeValidator(ValidationChain(BaseValidator)) >>> >>> try: ... validator.validate(123) # type: ignore ... except InvalidLinkException as e: ... print(e) ... Invalid link of type 'int', expected type 'BaseValidator' """ if not isinstance(link, self.chain.base): raise InvalidLinkException(link, self.chain.base)
[docs] class UniqueLinkValidator(ABCLinkValidator): # pylint: disable=line-too-long """Validator to ensure that links in the validate chain are unique. This validator checks whether a link already exists in the validate chain. If a duplicate link is detected, it raises a `LinkExistsException`. This ensures that all links in the chain are unique. Attributes: chain (ValidationChain): The validate chain this validator is associated with. Examples: >>> from domprob.sensors.validate.chain_val import LinkTypeValidator >>> from domprob.sensors.validate.chain import ValidationChain >>> from domprob.sensors.validate.base_val import BaseValidator >>> >>> chain = ValidationChain(BaseValidator) >>> validator = UniqueLinkValidator(chain) >>> >>> class ExampleValidator(BaseValidator): ... def validate(self, link: BaseValidator) -> None: ... pass ... >>> example_validator = ExampleValidator() >>> chain.append(example_validator) >>> >>> try: ... validator.validate(example_validator) ... except LinkExistsException as e: ... print(e) ... Link 'ExampleValidator(next_=None)' already exists in chain 'ValidationChain(base='BaseValidator')' """
[docs] def validate(self, link: _ChainLink) -> None: # noinspection PyShadowingNames """Validates that the provided link does not already exist in the validate chain. This method checks whether the given link is already present in the validate chain. If the link is a duplicate, it raises a `LinkExistsException`. This ensures that all links within the chain are unique. Args: link (_ChainLink): The link to validate. Raises: LinkExistsException: If the link already exists in the validate chain. Examples: >>> from domprob.sensors.validate.chain_val import LinkTypeValidator >>> from domprob.sensors.validate.chain import ValidationChain >>> from domprob.sensors.validate.base_val import BaseValidator >>> >>> chain = ValidationChain(BaseValidator) >>> validator = UniqueLinkValidator(chain) >>> >>> class ExampleValidator(BaseValidator): ... def validate(self, link: BaseValidator) -> None: ... pass ... >>> example_validator = ExampleValidator() >>> chain.append(example_validator) >>> >>> try: ... validator.validate(example_validator) ... except LinkExistsException as e: ... print(e) ... Link 'ExampleValidator(next_=None)' already exists in chain 'ValidationChain(base='BaseValidator')' """ if link in self.chain: raise LinkExistsException(link, self.chain)
[docs] class ABCLinkValidatorContext(ABC): """Abstract base class for context-aware link validators in a validate chain. This class provides an interface for validators that need additional context about the validate chain during the validate process. It enforces the implementation of the `validate` method, allowing subclasses to perform more sophisticated validations that depend on the state of the chain. Attributes: chain (ValidationChain): The validate chain associated with this validator. It provides context for the validate process. Args: chain (ValidationChain): The validate chain to associate with this context-aware validator. Examples: >>> from domprob.sensors.validate.chain import ( ... ABCLinkValidatorContext, ValidationChain ... ) >>> from domprob.sensors.validate.base_val import BaseValidator >>> >>> class ExampleValidator(BaseValidator): ... def validate(self, link: BaseValidator) -> None: ... pass ... >>> >>> class ExampleContext(ABCLinkValidatorContext): ... def add_validators(self, *validators: type[BaseValidator]) -> None: ... pass ... ... def validate(self, link: BaseValidator) -> None: ... pass ... >>> chain = ValidationChain(BaseValidator) >>> validator_context = ExampleContext(chain) >>> validator_context.validate(ExampleValidator()) """ def __init__( self, chain: ValidationChain, *validators: type[ABCLinkValidator] ) -> None: self.chain = chain self.validators: list[type[ABCLinkValidator]] = [] self.add_validators(*validators)
[docs] @abstractmethod def add_validators(self, *validators: type[ABCLinkValidator]) -> None: # noinspection PyShadowingNames """Adds one or more validators to the validate chain. This abstract method is designed to allow additional validators to be dynamically added to the validate chain during runtime. Each validator is appended to the chain, enabling customisation and extensibility of the validate process. Args: *validators (ABCLinkValidator): One or more validator instances to add to the validate chain. Examples: >>> from domprob.sensors.validate.chain import ( ... ABCLinkValidatorContext, ValidationChain ... ) >>> from domprob.sensors.validate.base_val import BaseValidator >>> >>> class ExampleValidator(BaseValidator): ... def validate(self, link: BaseValidator) -> None: ... pass ... >>> class ExampleContext(ABCLinkValidatorContext): ... def add_validators(self, *validators: type[BaseValidator]) -> None: ... pass ... ... def validate(self, link: BaseValidator) -> None: ... pass ... >>> chain = ValidationChain(ExampleValidator) >>> context = ExampleContext(chain) >>> context.add_validators(ExampleValidator, ExampleValidator) """ raise NotImplementedError # pragma: no cover
[docs] @abstractmethod def validate(self, link: _ChainLink) -> None: # noinspection PyShadowingNames """Abstract method to validate a link in the chain using additional context. This method must be implemented by subclasses to provide logic for validating a link with additional contextual information. This allows the validator to account for dynamic rules or states during validate. Args: link (_ChainLink): The link to validate. Raises: NotImplementedError: If a subclass does not implement this method. Examples: >>> from domprob.sensors.validate.chain import ( ... ABCLinkValidatorContext, ValidationChain ... ) >>> >>> class ExampleValidator(BaseValidator): ... def validate(self, link: BaseValidator) -> None: ... pass ... >>> >>> class ExampleContext(ABCLinkValidatorContext): ... def add_validators(self, *validators: type[BaseValidator]) -> None: ... pass ... ... def validate(self, link: BaseValidator) -> None: ... pass ... >>> chain = ValidationChain(ExampleValidator) >>> validator_context = ExampleContext(chain) >>> validator_context.validate(ExampleValidator()) """ raise NotImplementedError # pragma: no cover
[docs] def __repr__(self) -> str: """Returns a string representation of the context-aware validator. Returns: str: A string representation of the validator, showing the class name and the validate chain it is associated with. Examples: >>> from domprob.sensors.validate.chain import ( ... ABCLinkValidatorContext, ... BaseValidator, ... ValidationChain ... ) >>> >>> class ExampleValidator(BaseValidator): ... def validate(self, link: BaseValidator) -> None: ... pass ... >>> class ExampleContext(ABCLinkValidatorContext): ... def add_validators(self, *validators: type[BaseValidator]) -> None: ... pass ... ... def validate(self, link: BaseValidator) -> None: ... pass ... >>> chain = ValidationChain(ExampleValidator) >>> context = ExampleContext(chain) >>> repr(context) "ExampleContext(chain=ValidationChain(base='ExampleValidator'))" """ return f"{self.__class__.__name__}(chain={self.chain!r})"
[docs] class LinkValidatorContext(ABCLinkValidatorContext): """Concrete implementation of a context-aware link validator. The `LinkValidatorContext` class extends `ABCLinkValidatorContext` to provide validate logic for links in a chain, utilising additional contextual information. It allows flexible and dynamic validate of links, depending on the state of the chain or external conditions. Attributes: chain (ValidationChain): The validate chain associated with this context-aware validator. Args: chain (ValidationChain): The validate chain to associate with the validator. Examples: >>> from domprob.sensors.validate.chain import ( ... ABCLinkValidatorContext, ValidationChain ... ) >>> from domprob.sensors.validate.chain_val import InvalidLinkException >>> >>> class ExampleLink: ... pass ... >>> chain = ValidationChain(BaseValidator) >>> validator_context = LinkValidatorContext(chain) >>> try: ... validator_context.validate("invalid_link") # type: ignore ... except InvalidLinkException as e: ... print(e) Invalid link of type 'str', expected type 'BaseValidator' """ DEFAULT_VALIDATORS: tuple[type[ABCLinkValidator], ...] = ( LinkTypeValidator, UniqueLinkValidator, ) def __init__( self, chain: ValidationChain, *validators: type[ABCLinkValidator] ) -> None: super().__init__(chain, *self.DEFAULT_VALIDATORS, *validators)
[docs] def add_validators(self, *validators: type[ABCLinkValidator]) -> None: """Adds one or more validators to the validate chain. This method allows the dynamic addition of validators to the validate chain at runtime. Each provided validator is appended to the chain, enabling custom validate logic and extensibility. Args: *validators (ABCLinkValidator): One or more validator instances to add to the validate chain. Examples: >>> from domprob.sensors.validate.chain import ( ... ABCLinkValidatorContext, ValidationChain ... ) >>> from domprob.sensors.validate.base_val import BaseValidator >>> >>> class ExampleValidator(ABCLinkValidator): ... def validate(self, link: BaseValidator) -> None: ... pass ... >>> chain = ValidationChain(BaseValidator) >>> context = LinkValidatorContext(chain) >>> context.add_validators(ExampleValidator, ExampleValidator) """ self.validators.extend(validators)
[docs] def validate(self, *links: _ChainLink) -> None: """Validates a link in the chain. This method performs validate on the given links using the validators in the validate chain. It ensures that the link meets all the criteria enforced by the chain's validators. Args: *links (_ChainLink): The links to validate. Raises: Any: Exceptions raised by the individual validators in the chain if the link does not meet the required criteria. Examples: >>> from domprob.sensors.validate.chain import ( ... ABCLinkValidatorContext, ValidationChain ... ) >>> from domprob.sensors.validate.chain_val import InvalidLinkException >>> >>> class ExampleLink: ... pass ... >>> chain = ValidationChain(BaseValidator) >>> validator_context = LinkValidatorContext(chain) >>> try: ... validator_context.validate("invalid_link") # type: ignore ... except InvalidLinkException as e: ... print(e) Invalid link of type 'str', expected type 'BaseValidator' """ for validator in self.validators: for link in links: validator(self.chain).validate(link)
[docs] def __repr__(self) -> str: """Returns a string representation of the `LinkValidatorContext`. Returns: str: A string representation of the context, including its class name and the associated validate chain. Examples: >>> from domprob.sensors.validate.chain import ( ... ABCLinkValidatorContext, ValidationChain ... ) >>> >>> class ExampleLink: ... pass ... >>> chain = ValidationChain(BaseValidator) >>> validator_context = LinkValidatorContext(chain) >>> repr(validator_context) "LinkValidatorContext(ValidationChain(base='BaseValidator'))" """ return f"{self.__class__.__name__}({self.chain!r})"