src.pydasa.validations.decorators ================================= .. py:module:: src.pydasa.validations.decorators .. autoapi-nested-parse:: Module decorators.py =========================================== Decorator-based validation system for **PyDASA** property setters, reducing boilerplate code with reusable validation logic. Functions: - **validate_type**: Validates value against expected type(s). - **validate_emptiness**: Ensures string values are non-empty. - **validate_choices**: Validates value is in allowed set of choices. - **validate_range**: Validates numeric value is within specified range. - **validate_index**: Validates integer values with negativity control. - **validate_pattern**: Validates string matches regex pattern(s) or is alphanumeric. - **validate_custom**: Custom validation logic. Functions --------- .. autoapisummary:: src.pydasa.validations.decorators.validate_type src.pydasa.validations.decorators.validate_emptiness src.pydasa.validations.decorators.validate_choices src.pydasa.validations.decorators.validate_index src.pydasa.validations.decorators.validate_range src.pydasa.validations.decorators.validate_pattern src.pydasa.validations.decorators.validate_list_types src.pydasa.validations.decorators.validate_dict_types src.pydasa.validations.decorators.validate_custom Module Contents --------------- .. py:function:: validate_type(*expected_types, allow_none = True, allow_nan = False) Decorator to validate argument type against expected type(s). :param \*expected_types: One or more expected types for validation. :type \*expected_types: type :param allow_none: Whether None values are allowed. Defaults to True. :type allow_none: bool, optional :param allow_nan: Whether np.nan values are allowed. Defaults to False. :type allow_nan: bool, optional :raises ValueError: If value is None when allow_none is False. :raises ValueError: If value is np.nan when allow_nan is False. :raises ValueError: If value type does not match any of the expected types. :returns: Decorated function with type validation. :rtype: Callable Example:: @property def unit(self) -> str: return self._unit @unit.setter @validate_type(str) def unit(self, val: str) -> None: self._unit = val # Multiple types @value.setter @validate_type(int, float) def value(self, val: Union[int, float]) -> None: self._value = val # Allow np.nan @mean.setter @validate_type(int, float, allow_nan=True) def mean(self, val: Optional[float]) -> None: self._mean = val .. py:function:: validate_emptiness(strip = True) Decorator to ensure values are non-empty. Handles strings, dictionaries, lists, tuples, and other collections. For strings, optionally strips whitespace before checking. :param strip: Whether to strip whitespace before checking strings. Defaults to True. :type strip: bool, optional :raises ValueError: If string is empty/whitespace-only, or if collection has no elements. :returns: Decorated function with non-empty validation. :rtype: Callable Example:: @unit.setter @validate_type(str) @validate_emptiness() def unit(self, val: str) -> None: self._unit = val @variables.setter @validate_type(dict) @validate_emptiness() def variables(self, val: dict) -> None: self._variables = val .. py:function:: validate_choices(choices, allow_none = False, case_sensitive = False) Decorator to validate value is in allowed set of choices. :param choices: Dictionary, set, list, tuple, or Enum type of allowed values. :type choices: Union[dict, set, list, tuple, Type[Enum]] :param allow_none: Whether None values are allowed. Defaults to False. :type allow_none: bool, optional :param case_sensitive: Whether string comparison is case-sensitive. Defaults to False. :type case_sensitive: bool, optional :raises ValueError: If value is not in the allowed choices. :returns: Decorated function with choice validation. :rtype: Callable Example:: from pydasa.core.setup import Frameworks @fwk.setter @validate_type(str) @validate_choices(Frameworks.values()) def fwk(self, val: str) -> None: self._fwk = val.upper() # Case-sensitive choices @status.setter @validate_choices(["Active", "Inactive"], case_sensitive=True) def status(self, val: str) -> None: self._status = val .. py:function:: validate_index(allow_zero = True, allow_negative = False) Decorator to validate integer values with negativity and zero control. :param allow_zero: Whether zero is allowed. Defaults to True. :type allow_zero: bool, optional :param allow_negative: Whether negative integers are allowed. Defaults to False. :type allow_negative: bool, optional :raises ValueError: If value is not an integer. :raises ValueError: If negative value when allow_negative is False. :raises ValueError: If zero value when allow_zero is False. :returns: Decorated function with integer validation. :rtype: Callable Example:: # Non-negative integers only @idx.setter @validate_index(allow_negative=False) def idx(self, val: int) -> None: self._idx = val # Positive integers only (no zero) @count.setter @validate_index(allow_negative=False, allow_zero=False) def count(self, val: int) -> None: self._count = val .. py:function:: validate_range(min_value = None, max_value = None, min_inclusive = True, max_inclusive = True, min_attr = None, max_attr = None) Decorator to validate numeric value is within specified range. :param min_value: Static minimum value. Defaults to None. :type min_value: Optional[float], optional :param max_value: Static maximum value. Defaults to None. :type max_value: Optional[float], optional :param min_inclusive: Whether minimum is inclusive (>=) or exclusive (>). Defaults to True. :type min_inclusive: bool, optional :param max_inclusive: Whether maximum is inclusive (<=) or exclusive (<). Defaults to True. :type max_inclusive: bool, optional :param min_attr: Attribute name for dynamic minimum (e.g., '_min'). Defaults to None. :type min_attr: Optional[str], optional :param max_attr: Attribute name for dynamic maximum (e.g., '_max'). Defaults to None. :type max_attr: Optional[str], optional :raises ValueError: If value is outside the specified range. :returns: Decorated function with range validation. :rtype: Callable Example:: # Static range @age.setter @validate_type(int) @validate_range(min_value=0, max_value=150) def age(self, val: int) -> None: self._age = val # Dynamic range based on other attributes @mean.setter @validate_type(int, float) @validate_range(min_attr='_min', max_attr='_max') def mean(self, val: float) -> None: self._mean = val .. py:function:: validate_pattern(pattern = None, allow_alnum = False, error_msg = None, examples = None) Decorator to validate string matches regex pattern(s) or is alphanumeric. This unified decorator handles: - Single pattern matching - Multiple pattern matching (OR logic - matches any pattern) - Optional alphanumeric validation - Scientific/mathematical symbols (alphanumeric OR LaTeX) :param pattern: Single regex pattern string, or list/tuple of patterns to match (OR logic). :type pattern: Union[str, list, tuple] :param allow_alnum: Whether to accept alphanumeric strings. Defaults to False. :type allow_alnum: bool, optional :param error_msg: Custom error message (overrides default). Defaults to None. :type error_msg: Optional[str], optional :param examples: Example strings to show in error messages. Defaults to None. :type examples: Optional[str], optional :raises ValueError: If value does not match any pattern and is not alphanumeric (when allowed). :returns: Decorated function with pattern validation. :rtype: Callable Examples:: # Simple pattern matching @code.setter @validate_pattern(r'^[A-Z]\d{3}$') def code(self, val: str) -> None: self._code = val # Symbol validation (alphanumeric OR LaTeX) from pydasa.validations.patterns import LATEX_RE @sym.setter @validate_type(str) @validate_emptiness() @validate_pattern(LATEX_RE, allow_alnum=True) def sym(self, val: str) -> None: self._sym = val # Multiple patterns (match any) @validate_pattern([r'^\\[a-z]+$', r'^\d+$']) def value(self, val: str) -> None: self._value = val .. py:function:: validate_list_types(*elm_types) Decorator to validate list contains only specified element types. It asumes the list exists. :param \*elm_types: One or more expected types for list elements. :type \*elm_types: type :raises ValueError: If value is not a list. :raises ValueError: If list contains elements of wrong type. :returns: Decorated function with list type validation. :rtype: Callable Example:: @dim_col.setter @validate_type(list, allow_none=False) @validate_emptiness() @validate_list_types(int, float) def dim_col(self, val: List[int]) -> None: self._dim_col = [int(x) for x in val] .. py:function:: validate_dict_types(key_type, val_types) Decorator to validate dict has correct key and value types. It asumes the dict exists. :param key_type: Expected type for dictionary keys. :type key_type: type :param val_types: Expected types for dictionary values. At least one type must be provided. :type val_types: type | Tuple[type, ...] :raises ValueError: If dict keys or values have wrong types. :returns: Decorated function with dict type validation. :rtype: Callable Example:: @variables.setter @validate_type(dict, allow_none=False) @validate_emptiness() @validate_dict_types(str, (Variable, dict)) def variables(self, val: Dict[str, Variable]) -> None: self._variables = val .. py:function:: validate_custom(validator_func) Decorator for custom validation logic. Allows implementing custom validation logic by providing a validator function. The validator function should raise ValueError if validation fails. NOTE: this is too abstract and should be used sparingly. :param validator_func: Function(self, value) that raises ValueError if invalid. :type validator_func: Callable[[Any, Any], None] :raises ValueError: If custom validator function raises ValueError. :returns: Decorated function with custom validation. :rtype: Callable Example:: def check_range_consistency(self, value): '''Ensure minimum does not exceed maximum.''' if value is not None and self._max is not None and value > self._max: raise ValueError(f"min {value} > max {self._max}") @min.setter @validate_type(int, float) @validate_custom(check_range_consistency) def min(self, val: float) -> None: self._min = val