2Core validation utilities for PyHelios.
4Provides decorators, type coercion, and standardized error handling
5following PyHelios's fail-fast philosophy.
10from typing
import Any, Callable, Dict, Union
12from .exceptions
import ValidationError, create_validation_error
16 type_coercions: Dict[str, Callable] =
None):
18 Decorator for comprehensive parameter validation.
20 Performs type coercion first, then validation, following the pattern:
21 1. Coerce types where safe (e.g., list to vec3)
22 2. Validate all parameters meet requirements
23 3. Call original function if validation passes
26 param_validators: Dict mapping parameter names to validation functions
27 type_coercions: Dict mapping parameter names to coercion functions
31 param_validators={'center': validate_vec3, 'radius': validate_positive_value},
32 type_coercions={'center': coerce_to_vec3}
34 def addSphere(self, center, radius, **kwargs):
35 # center is guaranteed to be vec3, radius is positive
38 @functools.wraps(func)
39 def wrapper(*args, **kwargs):
42 for param, coercion_func
in type_coercions.items():
45 kwargs[param] = coercion_func(kwargs[param], param_name=param)
46 except ValidationError:
48 except Exception
as e:
49 raise create_validation_error(
50 f
"Failed to coerce parameter to expected type: {str(e)}",
52 function_name=func.__name__
57 for param, validator
in param_validators.items():
60 validator(kwargs[param], param_name=param, function_name=func.__name__)
61 except ValidationError:
63 except Exception
as e:
64 raise create_validation_error(
65 f
"Parameter validation failed: {str(e)}",
67 function_name=func.__name__
70 return func(*args, **kwargs)
76 """Check if value is a finite number (not NaN or inf)."""
78 float_value = float(value)
79 return math.isfinite(float_value)
80 except (ValueError, TypeError, OverflowError):
86 Validate value is positive and finite.
89 value: Value to validate
90 param_name: Parameter name for error messages
91 function_name: Function name for error messages
94 ValidationError: If value is not positive or not finite
97 raise create_validation_error(
98 f
"Parameter must be a finite number, got {value} ({type(value).__name__})",
99 param_name=param_name,
100 function_name=function_name,
101 expected_type=
"positive finite number",
106 raise create_validation_error(
107 f
"Parameter must be positive, got {value}",
108 param_name=param_name,
109 function_name=function_name,
110 expected_type=
"positive number",
112 suggestion=
"Use a value greater than 0."
118 Validate value is non-negative and finite.
121 value: Value to validate
122 param_name: Parameter name for error messages
123 function_name: Function name for error messages
126 ValidationError: If value is negative or not finite
129 raise create_validation_error(
130 f
"Parameter must be a finite number, got {value} ({type(value).__name__})",
131 param_name=param_name,
132 function_name=function_name,
133 expected_type=
"non-negative finite number",
138 raise create_validation_error(
139 f
"Parameter must be non-negative, got {value}",
140 param_name=param_name,
141 function_name=function_name,
142 expected_type=
"non-negative number",
144 suggestion=
"Use a value >= 0."
148def coerce_to_vec3(value: Any, param_name: str =
"parameter") ->
'vec3':
150 Safely coerce list/tuple to vec3 with validation.
153 value: Value to coerce (vec3, list, or tuple)
154 param_name: Parameter name for error messages
160 ValidationError: If coercion fails or values are invalid
162 from ..wrappers.DataTypes
import vec3
165 if hasattr(value,
'x')
and hasattr(value,
'y')
and hasattr(value,
'z')
and hasattr(value,
'to_list'):
168 if isinstance(value, (list, tuple)):
170 raise create_validation_error(
171 f
"Parameter must have exactly 3 elements for vec3 conversion, got {len(value)} elements: {value}",
172 param_name=param_name,
173 expected_type=
"3-element list or tuple",
175 suggestion=
"Provide exactly 3 numeric values like [x, y, z] or (x, y, z)."
179 for i, component
in enumerate(value):
181 raise create_validation_error(
182 f
"Parameter element [{i}] must be a finite number, got {component} ({type(component).__name__})",
183 param_name=f
"{param_name}[{i}]",
184 expected_type=
"finite number",
185 actual_value=component,
186 suggestion=
"Ensure all coordinate values are finite numbers (not NaN or infinity)."
189 return vec3(float(value[0]), float(value[1]), float(value[2]))
191 raise create_validation_error(
192 f
"Parameter must be a vec3, list, or tuple, got {type(value).__name__}",
193 param_name=param_name,
194 expected_type=
"vec3, list, or tuple",
196 suggestion=
"Use vec3(x, y, z), [x, y, z], or (x, y, z) format."
200def coerce_to_vec2(value: Any, param_name: str =
"parameter") ->
'vec2':
202 Safely coerce list/tuple to vec2 with validation.
205 value: Value to coerce (vec2, list, or tuple)
206 param_name: Parameter name for error messages
212 ValidationError: If coercion fails or values are invalid
214 from ..wrappers.DataTypes
import vec2
217 if hasattr(value,
'x')
and hasattr(value,
'y')
and hasattr(value,
'to_list')
and not hasattr(value,
'z'):
220 if isinstance(value, (list, tuple)):
222 raise create_validation_error(
223 f
"Parameter must have exactly 2 elements for vec2 conversion, got {len(value)} elements: {value}",
224 param_name=param_name,
225 expected_type=
"2-element list or tuple",
227 suggestion=
"Provide exactly 2 numeric values like [x, y] or (x, y)."
231 for i, component
in enumerate(value):
233 raise create_validation_error(
234 f
"Parameter element [{i}] must be a finite number, got {component} ({type(component).__name__})",
235 param_name=f
"{param_name}[{i}]",
236 expected_type=
"finite number",
237 actual_value=component,
238 suggestion=
"Ensure all coordinate values are finite numbers (not NaN or infinity)."
241 return vec2(float(value[0]), float(value[1]))
243 raise create_validation_error(
244 f
"Parameter must be a vec2, list, or tuple, got {type(value).__name__}",
245 param_name=param_name,
246 expected_type=
"vec2, list, or tuple",
248 suggestion=
"Use vec2(x, y), [x, y], or (x, y) format."
'vec3' coerce_to_vec3(Any value, str param_name="parameter")
Safely coerce list/tuple to vec3 with validation.
validate_positive_value(Any value, str param_name="value", str function_name=None)
Validate value is positive and finite.
'vec2' coerce_to_vec2(Any value, str param_name="parameter")
Safely coerce list/tuple to vec2 with validation.
validate_non_negative_value(Any value, str param_name="value", str function_name=None)
Validate value is non-negative and finite.
validate_input(Dict[str, Callable] param_validators=None, Dict[str, Callable] type_coercions=None)
Decorator for comprehensive parameter validation.
bool is_finite_numeric(Any value)
Check if value is a finite number (not NaN or inf).