Source code for assets_guardian.core.domain.models.validator

[docs] def validate_field( instance: object, field: str, expected_type: type | tuple[type, ...], *, # The following arguments are keyword-only parameters for clarity optional: bool = False, empty: bool = False, ) -> None: """Validates the type and non-emptiness of a dataclass field. The validation is performed in three ordered steps: 1. If optional is True and the value is None, the field is valid. 2. If the value is not of the expected type, a TypeError is raised. 3. If empty is False and the value is falsy (empty string, empty list, etc.), a ValueError is raised. Numeric types (int, float) are excluded from this check to allow 0 and 0.0. Args: instance: Instance of the dataclass to validate. field: Name of the field to check. expected_type: Expected type or tuple of types for the field. optional: If True, accepts None as a valid value. Defaults to False. empty: If True, allows falsy values (empty string, empty list, etc.). Defaults to False (strict). Raises: TypeError: If the field is not of the expected type (or None when optional is False). ValueError: If the field has a falsy non-numeric value and empty is False. """ value = getattr(instance, field) # If the field is optional and the value is None, it is valid. if optional and value is None: return # If the value is not of the expected type, it's an error. if not isinstance(value, expected_type): # isinstance natively accepts a tuple of types if isinstance(expected_type, tuple): type_names = " | ".join(t.__name__ for t in expected_type) else: type_names = expected_type.__name__ raise TypeError( f"Field {field!r} must be a {type_names}" f"{' or None' if optional else ''}, got " f"{type(value).__name__}." ) # If the value is falsy, empty is False, and it is not a number, it's an error. # We allow numbers to be 0 or 0.0 even though they are falsy. if not empty and not value and not isinstance(value, int | float): raise ValueError(f"Field {field!r} must be a non-empty value.")