2Validation decorators for PyHelios plugin methods.
4This module provides validation decorators that ensure parameter validation
5for all plugin methods, maintaining consistency across the PyHelios API.
8from functools
import wraps
9from typing
import Any, Callable, List, Optional, Union
11 validate_band_label, validate_source_id, validate_source_id_list,
12 validate_flux_value, validate_ray_count, validate_direction_vector,
13 validate_angle_degrees, validate_scaling_factor, validate_uuid_list,
14 validate_tree_id, validate_segment_resolution, validate_filename,
15 validate_recursion_level, validate_subdivision_count, validate_time_value,
17 validate_species_name, validate_temperature, validate_co2_concentration,
18 validate_photosynthetic_rate, validate_conductance, validate_par_flux,
19 validate_empirical_coefficients, validate_farquhar_coefficients,
20 validate_vcmax, validate_jmax, validate_quantum_efficiency, validate_dark_respiration,
21 validate_oxygen_concentration, validate_temperature_response_params,
23 validate_camera_label, validate_band_labels_list, validate_antialiasing_samples
25from .datatypes
import validate_vec3, validate_rgb_color
26from .core
import validate_positive_value, validate_non_negative_value
31 """Validate parameters for radiation band operations."""
33 def wrapper(self, old_label: str, new_label: str, *args, **kwargs):
34 validate_band_label(old_label,
"old_label", func.__name__)
35 validate_band_label(new_label,
"new_label", func.__name__)
36 return func(self, old_label, new_label, *args, **kwargs)
41 """Validate parameters for collimated radiation source creation."""
43 def wrapper(self, direction=None, *args, **kwargs):
44 if direction
is not None:
47 if hasattr(direction,
'x')
and hasattr(direction,
'y')
and hasattr(direction,
'z'):
48 validate_direction_vector(direction,
"direction", func.__name__)
49 return func(self, direction, *args, **kwargs)
54 """Validate parameters for sphere radiation source creation."""
56 def wrapper(self, position, radius: float, *args, **kwargs):
57 validate_vec3(position,
"position", func.__name__)
58 validate_positive_value(radius,
"radius", func.__name__)
59 return func(self, position, radius, *args, **kwargs)
64 """Validate parameters for sun sphere radiation source."""
66 def wrapper(self, radius: float, zenith: float, azimuth: float,
67 position_scaling: float = 1.0, angular_width: float = 0.53,
68 flux_scaling: float = 1.0, *args, **kwargs):
69 validate_positive_value(radius,
"radius", func.__name__)
70 validate_angle_degrees(zenith,
"zenith", func.__name__)
71 validate_angle_degrees(azimuth,
"azimuth", func.__name__)
72 validate_scaling_factor(position_scaling, param_name=
"position_scaling", function_name=func.__name__)
73 validate_positive_value(angular_width,
"angular_width", func.__name__)
74 validate_scaling_factor(flux_scaling, param_name=
"flux_scaling", function_name=func.__name__)
75 return func(self, radius, zenith, azimuth, position_scaling, angular_width, flux_scaling, *args, **kwargs)
80 """Validate parameters for setting multiple source flux."""
82 def wrapper(self, source_ids: List[int], label: str, flux: float, *args, **kwargs):
83 validate_source_id_list(source_ids,
"source_ids", func.__name__)
84 validate_band_label(label,
"label", func.__name__)
85 validate_flux_value(flux,
"flux", func.__name__)
86 return func(self, source_ids, label, flux, *args, **kwargs)
91 """Validate parameters for getting source flux."""
93 def wrapper(self, source_id: int, label: str, *args, **kwargs):
94 validate_source_id(source_id,
"source_id", func.__name__)
95 validate_band_label(label,
"label", func.__name__)
96 return func(self, source_id, label, *args, **kwargs)
101 """Validate parameters for geometry updates."""
103 def wrapper(self, uuids: Optional[List[int]] =
None, *args, **kwargs):
104 if uuids
is not None:
105 validate_uuid_list(uuids,
"uuids", func.__name__, allow_empty=
True)
106 return func(self, uuids, *args, **kwargs)
111 """Validate parameters for running radiation bands."""
113 def wrapper(self, band_label: Union[str, List[str]], *args, **kwargs):
114 if isinstance(band_label, str):
115 validate_band_label(band_label,
"band_label", func.__name__)
116 elif isinstance(band_label, list):
118 from .exceptions
import create_validation_error
119 raise create_validation_error(
120 f
"Parameter cannot be empty",
121 param_name=
"band_label",
122 function_name=func.__name__,
123 expected_type=
"non-empty list",
124 actual_value=band_label,
125 suggestion=
"Provide at least one band label."
127 for i, label
in enumerate(band_label):
128 validate_band_label(label, f
"band_label[{i}]", func.__name__)
130 from .exceptions
import create_validation_error
131 raise create_validation_error(
132 f
"Parameter must be a string or list of strings, got {type(band_label).__name__}",
133 param_name=
"band_label",
134 function_name=func.__name__,
135 expected_type=
"string or list of strings",
136 actual_value=band_label,
137 suggestion=
"Provide a band label string or list of band labels."
139 return func(self, band_label, *args, **kwargs)
144 """Validate parameters for scattering depth."""
146 def wrapper(self, label: str, depth: int, *args, **kwargs):
147 validate_band_label(label,
"label", func.__name__)
148 if not isinstance(depth, int):
149 from .exceptions
import create_validation_error
150 raise create_validation_error(
151 f
"Parameter must be an integer, got {type(depth).__name__}",
153 function_name=func.__name__,
154 expected_type=
"integer",
156 suggestion=
"Scattering depth must be an integer."
158 validate_non_negative_value(depth,
"depth", func.__name__)
159 return func(self, label, depth, *args, **kwargs)
164 """Validate parameters for minimum scatter energy."""
166 def wrapper(self, label: str, energy: float, *args, **kwargs):
167 validate_band_label(label,
"label", func.__name__)
168 validate_positive_value(energy,
"energy", func.__name__)
169 return func(self, label, energy, *args, **kwargs)
175 """Validate tree ID parameters for WeberPennTree methods."""
177 def wrapper(self, tree_id: int, *args, **kwargs):
178 validate_tree_id(tree_id,
"tree_id", func.__name__)
179 return func(self, tree_id, *args, **kwargs)
184 """Validate recursion level parameters."""
186 def wrapper(self, level: int, *args, **kwargs):
187 validate_recursion_level(level,
"level", func.__name__)
188 return func(self, level, *args, **kwargs)
193 """Validate trunk segment resolution parameters."""
195 def wrapper(self, trunk_segs: int, *args, **kwargs):
196 validate_segment_resolution(trunk_segs, min_val=3, max_val=100, param_name=
"trunk_segs", function_name=func.__name__)
197 return func(self, trunk_segs, *args, **kwargs)
202 """Validate branch segment resolution parameters."""
204 def wrapper(self, branch_segs: int, *args, **kwargs):
205 validate_segment_resolution(branch_segs, min_val=3, max_val=100, param_name=
"branch_segs", function_name=func.__name__)
206 return func(self, branch_segs, *args, **kwargs)
211 """Validate leaf subdivision parameters."""
213 def wrapper(self, leaf_segs_x: int, leaf_segs_y: int, *args, **kwargs):
214 validate_subdivision_count(leaf_segs_x,
"leaf_segs_x", func.__name__)
215 validate_subdivision_count(leaf_segs_y,
"leaf_segs_y", func.__name__)
216 return func(self, leaf_segs_x, leaf_segs_y, *args, **kwargs)
222 """Validate parameters for energy balance run method."""
224 def wrapper(self, uuids: Optional[List[int]] =
None, dt: Optional[float] =
None, *args, **kwargs):
225 if uuids
is not None:
226 validate_uuid_list(uuids,
"uuids", func.__name__, allow_empty=
False)
228 validate_positive_value(dt,
"dt", func.__name__)
229 return func(self, uuids, dt, *args, **kwargs)
234 """Validate parameters for energy balance band operations."""
236 def wrapper(self, band: Union[str, List[str]], *args, **kwargs):
237 if isinstance(band, str):
239 from .exceptions
import create_validation_error
240 raise create_validation_error(
241 f
"Parameter must be a non-empty string",
243 function_name=func.__name__,
244 expected_type=
"non-empty string",
245 actual_value=repr(band),
246 suggestion=
"Provide a non-empty band name."
248 elif isinstance(band, list):
250 from .exceptions
import create_validation_error
251 raise create_validation_error(
252 f
"Parameter cannot be empty",
254 function_name=func.__name__,
255 expected_type=
"non-empty list",
257 suggestion=
"Provide at least one band name."
259 for i, b
in enumerate(band):
260 if not isinstance(b, str)
or not b.strip():
261 from .exceptions
import create_validation_error
262 raise create_validation_error(
263 f
"All band names must be non-empty strings, got {type(b).__name__} at index {i}",
264 param_name=f
"band[{i}]",
265 function_name=func.__name__,
266 expected_type=
"non-empty string",
268 suggestion=
"All band names must be non-empty strings."
271 from .exceptions
import create_validation_error
272 raise create_validation_error(
273 f
"Parameter must be a string or list of strings, got {type(band).__name__}",
275 function_name=func.__name__,
276 expected_type=
"string or list of strings",
278 suggestion=
"Provide a band name string or list of band names."
280 return func(self, band, *args, **kwargs)
285 """Validate parameters for air energy balance."""
287 def wrapper(self, canopy_height_m: Optional[float] =
None, reference_height_m: Optional[float] =
None, *args, **kwargs):
288 if canopy_height_m
is not None:
289 validate_positive_value(canopy_height_m,
"canopy_height_m", func.__name__)
290 if reference_height_m
is not None:
291 validate_positive_value(reference_height_m,
"reference_height_m", func.__name__)
294 if (canopy_height_m
is None) != (reference_height_m
is None):
295 from .exceptions
import create_validation_error
296 raise create_validation_error(
297 f
"Canopy height and reference height must be provided together or omitted together",
298 param_name=
"canopy_height_m, reference_height_m",
299 function_name=func.__name__,
300 expected_type=
"both parameters provided or both None",
301 actual_value=f
"canopy_height_m={canopy_height_m}, reference_height_m={reference_height_m}",
302 suggestion=
"Provide both canopy_height_m and reference_height_m, or omit both for automatic detection."
305 return func(self, canopy_height_m, reference_height_m, *args, **kwargs)
310 """Validate parameters for evaluating air energy balance."""
312 def wrapper(self, dt_sec: float, time_advance_sec: float, UUIDs: Optional[List[int]] =
None, *args, **kwargs):
313 validate_time_value(dt_sec,
"dt_sec", func.__name__)
314 validate_time_value(time_advance_sec,
"time_advance_sec", func.__name__)
316 if time_advance_sec < dt_sec:
317 from .exceptions
import create_validation_error
318 raise create_validation_error(
319 f
"Time advance ({time_advance_sec}) must be greater than or equal to time step ({dt_sec})",
320 param_name=
"time_advance_sec",
321 function_name=func.__name__,
322 expected_type=f
"number >= {dt_sec}",
323 actual_value=time_advance_sec,
324 suggestion=f
"Use time_advance_sec >= {dt_sec}."
327 if UUIDs
is not None:
328 validate_uuid_list(UUIDs,
"UUIDs", func.__name__, allow_empty=
False)
330 return func(self, dt_sec, time_advance_sec, UUIDs, *args, **kwargs)
335 """Validate parameters for optional output data."""
337 def wrapper(self, label: str, *args, **kwargs):
338 if not isinstance(label, str):
339 from .exceptions
import create_validation_error
340 raise create_validation_error(
341 f
"Parameter must be a string, got {type(label).__name__}",
343 function_name=func.__name__,
344 expected_type=
"string",
346 suggestion=
"Provide a string label for the output data."
348 if not label.strip():
349 from .exceptions
import create_validation_error
350 raise create_validation_error(
351 f
"Parameter cannot be empty or whitespace-only",
353 function_name=func.__name__,
354 expected_type=
"non-empty string",
355 actual_value=repr(label),
356 suggestion=
"Provide a non-empty label for the output data."
358 return func(self, label, *args, **kwargs)
363 """Validate parameters for print report methods."""
365 def wrapper(self, UUIDs: Optional[List[int]] =
None, *args, **kwargs):
366 if UUIDs
is not None:
367 validate_uuid_list(UUIDs,
"UUIDs", func.__name__, allow_empty=
False)
368 return func(self, UUIDs, *args, **kwargs)
374 """Validate parameters for building visualizer geometry."""
376 def wrapper(self, context, uuids: Optional[List[int]] =
None, *args, **kwargs):
379 from ..Context
import Context
381 isinstance(context, Context)
or
382 (hasattr(context,
'__class__')
and
383 context.__class__.__name__ ==
'Context' and
384 'pyhelios.Context' in context.__class__.__module__)
387 if not is_valid_context:
388 from .exceptions
import create_validation_error
389 raise create_validation_error(
390 f
"Parameter must be a Context instance, got {type(context).__name__}",
391 param_name=
"context",
392 function_name=func.__name__,
393 expected_type=
"Context",
394 actual_value=context,
395 suggestion=
"Provide a valid PyHelios Context instance."
398 if uuids
is not None:
399 validate_uuid_list(uuids,
"uuids", func.__name__, allow_empty=
True)
400 return func(self, context, uuids, *args, **kwargs)
405 """Validate parameters for printing window to file."""
407 def wrapper(self, filename: str, *args, **kwargs):
408 validate_filename(filename,
"filename", func.__name__,
409 allowed_extensions=[
'.png',
'.jpg',
'.jpeg',
'.bmp',
'.tga'])
410 return func(self, filename, *args, **kwargs)
416 """Validate photosynthesis species parameters."""
418 def wrapper(self, species: str, *args, **kwargs):
419 validate_species_name(species,
"species", func.__name__)
420 return func(self, species, *args, **kwargs)
425 """Validate temperature parameters for photosynthesis."""
427 def wrapper(self, temperature: float, *args, **kwargs):
428 validate_temperature(temperature,
"temperature", func.__name__)
429 return func(self, temperature, *args, **kwargs)
434 """Validate CO2 concentration parameters."""
436 def wrapper(self, co2_concentration: float, *args, **kwargs):
437 validate_co2_concentration(co2_concentration,
"co2_concentration", func.__name__)
438 return func(self, co2_concentration, *args, **kwargs)
443 """Validate PAR flux parameters."""
445 def wrapper(self, par_flux: float, *args, **kwargs):
446 validate_par_flux(par_flux,
"par_flux", func.__name__)
447 return func(self, par_flux, *args, **kwargs)
452 """Validate conductance parameters."""
454 def wrapper(self, conductance: float, *args, **kwargs):
455 validate_conductance(conductance,
"conductance", func.__name__)
456 return func(self, conductance, *args, **kwargs)
461 """Validate empirical model coefficient parameters."""
463 def wrapper(self, coefficients, *args, **kwargs):
464 validate_empirical_coefficients(coefficients,
"coefficients", func.__name__)
465 return func(self, coefficients, *args, **kwargs)
470 """Validate Farquhar model coefficient parameters."""
472 def wrapper(self, coefficients, *args, **kwargs):
473 validate_farquhar_coefficients(coefficients,
"coefficients", func.__name__)
474 return func(self, coefficients, *args, **kwargs)
479 """Validate Vcmax parameters for Farquhar model."""
481 def wrapper(self, vcmax: float, *args, **kwargs):
482 validate_vcmax(vcmax,
"vcmax", func.__name__)
483 return func(self, vcmax, *args, **kwargs)
488 """Validate Jmax parameters for Farquhar model."""
490 def wrapper(self, jmax: float, *args, **kwargs):
491 validate_jmax(jmax,
"jmax", func.__name__)
492 return func(self, jmax, *args, **kwargs)
497 """Validate quantum efficiency parameters."""
499 def wrapper(self, efficiency: float, *args, **kwargs):
500 validate_quantum_efficiency(efficiency,
"efficiency", func.__name__)
501 return func(self, efficiency, *args, **kwargs)
506 """Validate dark respiration parameters."""
508 def wrapper(self, respiration: float, *args, **kwargs):
509 validate_dark_respiration(respiration,
"respiration", func.__name__)
510 return func(self, respiration, *args, **kwargs)
515 """Validate oxygen concentration parameters."""
517 def wrapper(self, o2_concentration: float, *args, **kwargs):
518 validate_oxygen_concentration(o2_concentration,
"o2_concentration", func.__name__)
519 return func(self, o2_concentration, *args, **kwargs)
524 """Validate temperature response parameters."""
526 def wrapper(self, params, *args, **kwargs):
528 return func(self, params, *args, **kwargs)
533 """Validate photosynthetic rate parameters."""
535 def wrapper(self, rate: float, *args, **kwargs):
536 validate_photosynthetic_rate(rate,
"rate", func.__name__)
537 return func(self, rate, *args, **kwargs)
542 """Validate UUID parameters for photosynthesis methods."""
544 def wrapper(self, uuids: Union[List[int], int], *args, **kwargs):
545 if isinstance(uuids, int):
547 validate_non_negative_value(uuids,
"uuids", func.__name__)
548 elif isinstance(uuids, list):
549 validate_uuid_list(uuids,
"uuids", func.__name__, allow_empty=
False)
551 from .exceptions
import create_validation_error
552 raise create_validation_error(
553 f
"Parameter must be an integer or list of integers, got {type(uuids).__name__}",
555 function_name=func.__name__,
556 expected_type=
"integer or list of integers",
558 suggestion=
"Provide a UUID (integer) or list of UUIDs."
560 return func(self, uuids, *args, **kwargs)
566 Validate parameters for addRadiationCamera method.
568 Handles validation and type conversion for camera creation parameters:
569 - camera_label: string validation
570 - band_labels: list of strings validation
571 - position: converts lists/tuples to vec3 if needed
572 - lookat_or_direction: handles vec3 or SphericalCoord
573 - antialiasing_samples: positive integer validation
576 def wrapper(self, camera_label, band_labels, position, lookat_or_direction,
577 camera_properties=None, antialiasing_samples: int = 100, *args, **kwargs):
578 from ..wrappers.DataTypes
import vec3, SphericalCoord, make_vec3
581 validated_label = validate_camera_label(camera_label,
"camera_label", func.__name__)
582 validated_bands = validate_band_labels_list(band_labels,
"band_labels", func.__name__)
583 validated_samples = validate_antialiasing_samples(antialiasing_samples,
"antialiasing_samples", func.__name__)
586 validated_position = validate_vec3(position,
"position", func.__name__)
589 validated_direction =
None
590 if hasattr(lookat_or_direction,
'radius')
and hasattr(lookat_or_direction,
'elevation'):
592 validated_direction = lookat_or_direction
595 validated_direction = validate_vec3(lookat_or_direction,
"lookat_or_direction", func.__name__)
598 if camera_properties
is not None:
599 if not hasattr(camera_properties,
'to_array'):
600 from ..validation.exceptions
import create_validation_error
601 raise create_validation_error(
602 f
"camera_properties must be a CameraProperties instance or None, got {type(camera_properties).__name__}",
603 param_name=
"camera_properties",
604 function_name=func.__name__,
605 expected_type=
"CameraProperties or None",
606 actual_value=camera_properties,
607 suggestion=
"Use a CameraProperties instance or None for default properties."
610 return func(self, validated_label, validated_bands, validated_position, validated_direction,
611 camera_properties, validated_samples, *args, **kwargs)
Callable validate_branch_segment_params(Callable func)
Validate branch segment resolution parameters.
Callable validate_jmax_params(Callable func)
Validate Jmax parameters for Farquhar model.
Callable validate_radiation_camera_params(Callable func)
Validate parameters for addRadiationCamera method.
Callable validate_photosynthesis_co2_params(Callable func)
Validate CO2 concentration parameters.
Callable validate_recursion_params(Callable func)
Validate recursion level parameters.
Callable validate_quantum_efficiency_params(Callable func)
Validate quantum efficiency parameters.
Callable validate_collimated_source_params(Callable func)
Validate parameters for collimated radiation source creation.
Callable validate_print_window_params(Callable func)
Validate parameters for printing window to file.
Callable validate_min_scatter_energy_params(Callable func)
Validate parameters for minimum scatter energy.
Callable validate_update_geometry_params(Callable func)
Validate parameters for geometry updates.
Callable validate_tree_uuid_params(Callable func)
Validate tree ID parameters for WeberPennTree methods.
Callable validate_get_source_flux_params(Callable func)
Validate parameters for getting source flux.
Callable validate_energy_band_params(Callable func)
Validate parameters for energy balance band operations.
Callable validate_photosynthesis_temperature_params(Callable func)
Validate temperature parameters for photosynthesis.
Callable validate_evaluate_air_energy_params(Callable func)
Validate parameters for evaluating air energy balance.
Callable validate_sphere_source_params(Callable func)
Validate parameters for sphere radiation source creation.
Callable validate_trunk_segment_params(Callable func)
Validate trunk segment resolution parameters.
Callable validate_empirical_model_params(Callable func)
Validate empirical model coefficient parameters.
Callable validate_radiation_band_params(Callable func)
Validate parameters for radiation band operations.
Callable validate_dark_respiration_params(Callable func)
Validate dark respiration parameters.
Callable validate_photosynthesis_rate_params(Callable func)
Validate photosynthetic rate parameters.
Callable validate_leaf_subdivisions_params(Callable func)
Validate leaf subdivision parameters.
Callable validate_temperature_response_params(Callable func)
Validate temperature response parameters.
Callable validate_build_geometry_params(Callable func)
Validate parameters for building visualizer geometry.
Callable validate_photosynthesis_uuid_params(Callable func)
Validate UUID parameters for photosynthesis methods.
Callable validate_farquhar_model_params(Callable func)
Validate Farquhar model coefficient parameters.
Callable validate_oxygen_concentration_params(Callable func)
Validate oxygen concentration parameters.
Callable validate_print_report_params(Callable func)
Validate parameters for print report methods.
Callable validate_vcmax_params(Callable func)
Validate Vcmax parameters for Farquhar model.
Callable validate_photosynthesis_species_params(Callable func)
Validate photosynthesis species parameters.
Callable validate_energy_run_params(Callable func)
Validate parameters for energy balance run method.
Callable validate_run_band_params(Callable func)
Validate parameters for running radiation bands.
Callable validate_photosynthesis_conductance_params(Callable func)
Validate conductance parameters.
Callable validate_sun_sphere_params(Callable func)
Validate parameters for sun sphere radiation source.
Callable validate_air_energy_params(Callable func)
Validate parameters for air energy balance.
Callable validate_scattering_depth_params(Callable func)
Validate parameters for scattering depth.
Callable validate_photosynthesis_par_params(Callable func)
Validate PAR flux parameters.
Callable validate_output_data_params(Callable func)
Validate parameters for optional output data.
Callable validate_source_flux_multiple_params(Callable func)
Validate parameters for setting multiple source flux.