2Core validation utilities for PyHelios.
4Provides decorators, type coercion, and standardized error handling
5following PyHelios's fail-fast philosophy.
11from typing
import Any, Callable, Dict, Union
13from .exceptions
import ValidationError, create_validation_error
17 type_coercions: Dict[str, Callable] =
None):
19 Decorator for comprehensive parameter validation.
21 Performs type coercion first, then validation, following the pattern:
22 1. Bind all arguments (positional and keyword) to parameter names
23 2. Coerce types where safe (e.g., list to vec3)
24 3. Validate all parameters meet requirements
25 4. Call original function if validation passes
28 param_validators: Dict mapping parameter names to validation functions
29 type_coercions: Dict mapping parameter names to coercion functions
36 param_names = list(inspect.signature(func).parameters.keys())
38 @functools.wraps(func)
39 def wrapper(*args, **kwargs):
42 positional_params = {}
43 for i, arg
in enumerate(args):
44 if i < len(param_names):
45 positional_params[param_names[i]] = i
48 args_list = list(args)
52 for param, coercion_func
in type_coercions.items():
59 elif param
in positional_params:
60 value = args_list[positional_params[param]]
65 coerced = coercion_func(value, param_name=param)
68 kwargs[param] = coerced
69 elif param
in positional_params:
70 args_list[positional_params[param]] = coerced
71 except ValidationError:
73 except Exception
as e:
74 raise create_validation_error(
75 f
"Failed to coerce parameter to expected type: {str(e)}",
77 function_name=func.__name__
82 for param, validator
in param_validators.items():
89 elif param
in positional_params:
90 value = args_list[positional_params[param]]
95 validator(value, param_name=param, function_name=func.__name__)
96 except ValidationError:
98 except Exception
as e:
99 raise create_validation_error(
100 f
"Parameter validation failed: {str(e)}",
102 function_name=func.__name__
105 return func(*tuple(args_list), **kwargs)
111 """Check if value is a finite number (not NaN or inf)."""
113 float_value = float(value)
114 return math.isfinite(float_value)
115 except (ValueError, TypeError, OverflowError):
121 Validate value is positive and finite.
124 value: Value to validate
125 param_name: Parameter name for error messages
126 function_name: Function name for error messages
129 ValidationError: If value is not positive or not finite
132 raise create_validation_error(
133 f
"Parameter must be a finite number, got {value} ({type(value).__name__})",
134 param_name=param_name,
135 function_name=function_name,
136 expected_type=
"positive finite number",
141 raise create_validation_error(
142 f
"Parameter must be positive, got {value}",
143 param_name=param_name,
144 function_name=function_name,
145 expected_type=
"positive number",
147 suggestion=
"Use a value greater than 0."
153 Validate value is non-negative and finite.
156 value: Value to validate
157 param_name: Parameter name for error messages
158 function_name: Function name for error messages
161 ValidationError: If value is negative or not finite
164 raise create_validation_error(
165 f
"Parameter must be a finite number, got {value} ({type(value).__name__})",
166 param_name=param_name,
167 function_name=function_name,
168 expected_type=
"non-negative finite number",
173 raise create_validation_error(
174 f
"Parameter must be non-negative, got {value}",
175 param_name=param_name,
176 function_name=function_name,
177 expected_type=
"non-negative number",
179 suggestion=
"Use a value >= 0."
183def coerce_to_vec3(value: Any, param_name: str =
"parameter") ->
'vec3':
185 Safely coerce list/tuple to vec3 with validation.
188 value: Value to coerce (vec3, list, or tuple)
189 param_name: Parameter name for error messages
195 ValidationError: If coercion fails or values are invalid
197 from ..wrappers.DataTypes
import vec3
200 if hasattr(value,
'x')
and hasattr(value,
'y')
and hasattr(value,
'z')
and hasattr(value,
'to_list'):
203 if isinstance(value, (list, tuple)):
205 raise create_validation_error(
206 f
"Parameter must have exactly 3 elements for vec3 conversion, got {len(value)} elements: {value}",
207 param_name=param_name,
208 expected_type=
"3-element list or tuple",
210 suggestion=
"Provide exactly 3 numeric values like [x, y, z] or (x, y, z)."
214 for i, component
in enumerate(value):
216 raise create_validation_error(
217 f
"Parameter element [{i}] must be a finite number, got {component} ({type(component).__name__})",
218 param_name=f
"{param_name}[{i}]",
219 expected_type=
"finite number",
220 actual_value=component,
221 suggestion=
"Ensure all coordinate values are finite numbers (not NaN or infinity)."
224 return vec3(float(value[0]), float(value[1]), float(value[2]))
226 raise create_validation_error(
227 f
"Parameter must be a vec3, list, or tuple, got {type(value).__name__}",
228 param_name=param_name,
229 expected_type=
"vec3, list, or tuple",
231 suggestion=
"Use vec3(x, y, z), [x, y, z], or (x, y, z) format."
235def coerce_to_vec2(value: Any, param_name: str =
"parameter") ->
'vec2':
237 Safely coerce list/tuple to vec2 with validation.
240 value: Value to coerce (vec2, list, or tuple)
241 param_name: Parameter name for error messages
247 ValidationError: If coercion fails or values are invalid
249 from ..wrappers.DataTypes
import vec2
252 if hasattr(value,
'x')
and hasattr(value,
'y')
and hasattr(value,
'to_list')
and not hasattr(value,
'z'):
255 if isinstance(value, (list, tuple)):
257 raise create_validation_error(
258 f
"Parameter must have exactly 2 elements for vec2 conversion, got {len(value)} elements: {value}",
259 param_name=param_name,
260 expected_type=
"2-element list or tuple",
262 suggestion=
"Provide exactly 2 numeric values like [x, y] or (x, y)."
266 for i, component
in enumerate(value):
268 raise create_validation_error(
269 f
"Parameter element [{i}] must be a finite number, got {component} ({type(component).__name__})",
270 param_name=f
"{param_name}[{i}]",
271 expected_type=
"finite number",
272 actual_value=component,
273 suggestion=
"Ensure all coordinate values are finite numbers (not NaN or infinity)."
276 return vec2(float(value[0]), float(value[1]))
278 raise create_validation_error(
279 f
"Parameter must be a vec2, list, or tuple, got {type(value).__name__}",
280 param_name=param_name,
281 expected_type=
"vec2, list, or tuple",
283 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).