Source code for fastcfg.validation.policies

import re
from typing import Any

from fastcfg.config.value_wrapper import ValueWrapper
from fastcfg.exceptions import MissingDependencyError
from fastcfg.validation import IConfigValidator

try:
    from pydantic import BaseModel, ValidationError
except ImportError:
    BaseModel, ValidationError = None, None


[docs] class RangeValidator(IConfigValidator): def __init__( self, min_value: int, max_value: int, validate_immediately: bool = True ): super().__init__(validate_immediately=validate_immediately) self.min_value = min_value self.max_value = max_value
[docs] def validate(self, value: Any) -> bool: try: return self.min_value <= value <= self.max_value except TypeError: return False
[docs] def error_message(self) -> str: return f"Value must support <= operator and be between {self.min_value} and {self.max_value}."
[docs] class TypeValidator(IConfigValidator): def __init__(self, expected_type: type, validate_immediately: bool = True): super().__init__(validate_immediately=validate_immediately) self.expected_type = expected_type
[docs] def validate(self, value: Any) -> bool: return isinstance(value, self.expected_type)
[docs] def error_message(self) -> str: return f"Value must be of type {self.expected_type.__name__}."
[docs] class LengthValidator(IConfigValidator): def __init__(self, length: int, validate_immediately: bool = True): super().__init__(validate_immediately=validate_immediately) self.length = length
[docs] def validate(self, value: Any) -> bool: return len(value) == self.length
[docs] def error_message(self) -> str: return f"Value must be of length {self.length}."
[docs] class RegexValidator(IConfigValidator): def __init__(self, pattern: str, validate_immediately: bool = True): super().__init__(validate_immediately=validate_immediately) self.pattern = pattern
[docs] def validate(self, value: Any) -> bool: return bool(re.match(self.pattern, value))
[docs] def error_message(self) -> str: return f"Value must match the pattern {self.pattern}."
[docs] class URLValidator(RegexValidator): def __init__(self, validate_immediately: bool = True): # Regular expression pattern to validate URLs pattern = r"^https?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+$" super().__init__(pattern, validate_immediately=validate_immediately)
[docs] def error_message(self) -> str: return "Value must be a valid URL."
[docs] class PydanticValidator(IConfigValidator): def __init__( self, model: BaseModel, validate_immediately: bool = True, transform: bool = False, ): super().__init__(validate_immediately=validate_immediately) self.model = model self._latest_error = None
[docs] def validate(self, value: Any) -> bool: if BaseModel is None: raise MissingDependencyError("Pydantic") try: unwrapped_value = ValueWrapper.unwrap(value) self.model.model_validate(unwrapped_value) # TODO: Transform the value if transform is True # This will involve mapping the end result of the model # to the Config object return True except ValidationError as exc: self._latest_error = exc return False
[docs] def error_message(self) -> str: msg = f"Value does not conform to the Pydantic model {self.model.__name__}." if self._latest_error: msg += f" Latest Exception: {self._latest_error}"