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__)
169 validate_non_negative_value(energy,
"energy", func.__name__)
170 return func(self, label, energy, *args, **kwargs)
176 """Validate tree ID parameters for WeberPennTree methods."""
178 def wrapper(self, tree_id: int, *args, **kwargs):
179 validate_tree_id(tree_id,
"tree_id", func.__name__)
180 return func(self, tree_id, *args, **kwargs)
185 """Validate recursion level parameters."""
187 def wrapper(self, level: int, *args, **kwargs):
188 validate_recursion_level(level,
"level", func.__name__)
189 return func(self, level, *args, **kwargs)
194 """Validate trunk segment resolution parameters."""
196 def wrapper(self, trunk_segs: int, *args, **kwargs):
197 validate_segment_resolution(trunk_segs, min_val=3, max_val=100, param_name=
"trunk_segs", function_name=func.__name__)
198 return func(self, trunk_segs, *args, **kwargs)
203 """Validate branch segment resolution parameters."""
205 def wrapper(self, branch_segs: int, *args, **kwargs):
206 validate_segment_resolution(branch_segs, min_val=3, max_val=100, param_name=
"branch_segs", function_name=func.__name__)
207 return func(self, branch_segs, *args, **kwargs)
212 """Validate leaf subdivision parameters."""
214 def wrapper(self, leaf_segs_x: int, leaf_segs_y: int, *args, **kwargs):
215 validate_subdivision_count(leaf_segs_x,
"leaf_segs_x", func.__name__)
216 validate_subdivision_count(leaf_segs_y,
"leaf_segs_y", func.__name__)
217 return func(self, leaf_segs_x, leaf_segs_y, *args, **kwargs)
222 """Validate XML file path parameters for WeberPennTree."""
224 def wrapper(self, filename: str, silent: bool =
False, *args, **kwargs):
225 from pathlib
import Path
226 from .exceptions
import create_validation_error
229 validate_filename(filename,
"filename", func.__name__, allowed_extensions=[
'.xml'])
232 xml_path = Path(filename)
235 if not xml_path.exists():
236 raise create_validation_error(
237 f
"XML file not found: {filename}",
238 param_name=
"filename",
239 function_name=func.__name__,
240 expected_type=
"path to existing .xml file",
241 actual_value=filename,
242 suggestion=f
"Ensure the file exists at: {xml_path.resolve()}"
246 if not isinstance(silent, bool):
247 raise create_validation_error(
248 f
"Parameter must be a boolean, got {type(silent).__name__}",
250 function_name=func.__name__,
251 expected_type=
"bool",
253 suggestion=
"Use True or False for silent parameter."
256 return func(self, filename, silent, *args, **kwargs)
262 """Validate parameters for energy balance run method."""
264 def wrapper(self, uuids: Optional[List[int]] =
None, dt: Optional[float] =
None, *args, **kwargs):
265 if uuids
is not None:
266 validate_uuid_list(uuids,
"uuids", func.__name__, allow_empty=
False)
268 validate_positive_value(dt,
"dt", func.__name__)
269 return func(self, uuids, dt, *args, **kwargs)
274 """Validate parameters for energy balance band operations."""
276 def wrapper(self, band: Union[str, List[str]], *args, **kwargs):
277 if isinstance(band, str):
279 from .exceptions
import create_validation_error
280 raise create_validation_error(
281 f
"Parameter must be a non-empty string",
283 function_name=func.__name__,
284 expected_type=
"non-empty string",
285 actual_value=repr(band),
286 suggestion=
"Provide a non-empty band name."
288 elif isinstance(band, list):
290 from .exceptions
import create_validation_error
291 raise create_validation_error(
292 f
"Parameter cannot be empty",
294 function_name=func.__name__,
295 expected_type=
"non-empty list",
297 suggestion=
"Provide at least one band name."
299 for i, b
in enumerate(band):
300 if not isinstance(b, str)
or not b.strip():
301 from .exceptions
import create_validation_error
302 raise create_validation_error(
303 f
"All band names must be non-empty strings, got {type(b).__name__} at index {i}",
304 param_name=f
"band[{i}]",
305 function_name=func.__name__,
306 expected_type=
"non-empty string",
308 suggestion=
"All band names must be non-empty strings."
311 from .exceptions
import create_validation_error
312 raise create_validation_error(
313 f
"Parameter must be a string or list of strings, got {type(band).__name__}",
315 function_name=func.__name__,
316 expected_type=
"string or list of strings",
318 suggestion=
"Provide a band name string or list of band names."
320 return func(self, band, *args, **kwargs)
325 """Validate parameters for air energy balance."""
327 def wrapper(self, canopy_height_m: Optional[float] =
None, reference_height_m: Optional[float] =
None, *args, **kwargs):
328 if canopy_height_m
is not None:
329 validate_positive_value(canopy_height_m,
"canopy_height_m", func.__name__)
330 if reference_height_m
is not None:
331 validate_positive_value(reference_height_m,
"reference_height_m", func.__name__)
334 if (canopy_height_m
is None) != (reference_height_m
is None):
335 from .exceptions
import create_validation_error
336 raise create_validation_error(
337 f
"Canopy height and reference height must be provided together or omitted together",
338 param_name=
"canopy_height_m, reference_height_m",
339 function_name=func.__name__,
340 expected_type=
"both parameters provided or both None",
341 actual_value=f
"canopy_height_m={canopy_height_m}, reference_height_m={reference_height_m}",
342 suggestion=
"Provide both canopy_height_m and reference_height_m, or omit both for automatic detection."
345 return func(self, canopy_height_m, reference_height_m, *args, **kwargs)
350 """Validate parameters for evaluating air energy balance."""
352 def wrapper(self, dt_sec: float, time_advance_sec: float, UUIDs: Optional[List[int]] =
None, *args, **kwargs):
353 validate_time_value(dt_sec,
"dt_sec", func.__name__)
354 validate_time_value(time_advance_sec,
"time_advance_sec", func.__name__)
356 if time_advance_sec < dt_sec:
357 from .exceptions
import create_validation_error
358 raise create_validation_error(
359 f
"Time advance ({time_advance_sec}) must be greater than or equal to time step ({dt_sec})",
360 param_name=
"time_advance_sec",
361 function_name=func.__name__,
362 expected_type=f
"number >= {dt_sec}",
363 actual_value=time_advance_sec,
364 suggestion=f
"Use time_advance_sec >= {dt_sec}."
367 if UUIDs
is not None:
368 validate_uuid_list(UUIDs,
"UUIDs", func.__name__, allow_empty=
False)
370 return func(self, dt_sec, time_advance_sec, UUIDs, *args, **kwargs)
375 """Validate parameters for optional output data."""
377 def wrapper(self, label: str, *args, **kwargs):
378 if not isinstance(label, str):
379 from .exceptions
import create_validation_error
380 raise create_validation_error(
381 f
"Parameter must be a string, got {type(label).__name__}",
383 function_name=func.__name__,
384 expected_type=
"string",
386 suggestion=
"Provide a string label for the output data."
388 if not label.strip():
389 from .exceptions
import create_validation_error
390 raise create_validation_error(
391 f
"Parameter cannot be empty or whitespace-only",
393 function_name=func.__name__,
394 expected_type=
"non-empty string",
395 actual_value=repr(label),
396 suggestion=
"Provide a non-empty label for the output data."
398 return func(self, label, *args, **kwargs)
403 """Validate parameters for print report methods."""
405 def wrapper(self, UUIDs: Optional[List[int]] =
None, *args, **kwargs):
406 if UUIDs
is not None:
407 validate_uuid_list(UUIDs,
"UUIDs", func.__name__, allow_empty=
False)
408 return func(self, UUIDs, *args, **kwargs)
414 """Validate parameters for building visualizer geometry."""
416 def wrapper(self, context, uuids: Optional[List[int]] =
None, *args, **kwargs):
419 from ..Context
import Context
421 isinstance(context, Context)
or
422 (hasattr(context,
'__class__')
and
423 context.__class__.__name__ ==
'Context' and
424 'pyhelios.Context' in context.__class__.__module__)
427 if not is_valid_context:
428 from .exceptions
import create_validation_error
429 raise create_validation_error(
430 f
"Parameter must be a Context instance, got {type(context).__name__}",
431 param_name=
"context",
432 function_name=func.__name__,
433 expected_type=
"Context",
434 actual_value=context,
435 suggestion=
"Provide a valid PyHelios Context instance."
438 if uuids
is not None:
439 validate_uuid_list(uuids,
"uuids", func.__name__, allow_empty=
True)
440 return func(self, context, uuids, *args, **kwargs)
445 """Validate parameters for printing window to file."""
447 def wrapper(self, filename: str, *args, **kwargs):
448 validate_filename(filename,
"filename", func.__name__,
449 allowed_extensions=[
'.png',
'.jpg',
'.jpeg',
'.bmp',
'.tga'])
450 return func(self, filename, *args, **kwargs)
456 """Validate photosynthesis species parameters."""
458 def wrapper(self, species: str, *args, **kwargs):
459 validate_species_name(species,
"species", func.__name__)
460 return func(self, species, *args, **kwargs)
465 """Validate temperature parameters for photosynthesis."""
467 def wrapper(self, temperature: float, *args, **kwargs):
468 validate_temperature(temperature,
"temperature", func.__name__)
469 return func(self, temperature, *args, **kwargs)
474 """Validate CO2 concentration parameters."""
476 def wrapper(self, co2_concentration: float, *args, **kwargs):
477 validate_co2_concentration(co2_concentration,
"co2_concentration", func.__name__)
478 return func(self, co2_concentration, *args, **kwargs)
483 """Validate PAR flux parameters."""
485 def wrapper(self, par_flux: float, *args, **kwargs):
486 validate_par_flux(par_flux,
"par_flux", func.__name__)
487 return func(self, par_flux, *args, **kwargs)
492 """Validate conductance parameters."""
494 def wrapper(self, conductance: float, *args, **kwargs):
495 validate_conductance(conductance,
"conductance", func.__name__)
496 return func(self, conductance, *args, **kwargs)
501 """Validate empirical model coefficient parameters."""
503 def wrapper(self, coefficients, *args, **kwargs):
504 validate_empirical_coefficients(coefficients,
"coefficients", func.__name__)
505 return func(self, coefficients, *args, **kwargs)
510 """Validate Farquhar model coefficient parameters."""
512 def wrapper(self, coefficients, *args, **kwargs):
513 validate_farquhar_coefficients(coefficients,
"coefficients", func.__name__)
514 return func(self, coefficients, *args, **kwargs)
519 """Validate Vcmax parameters for Farquhar model."""
521 def wrapper(self, vcmax: float, *args, **kwargs):
522 validate_vcmax(vcmax,
"vcmax", func.__name__)
523 return func(self, vcmax, *args, **kwargs)
528 """Validate Jmax parameters for Farquhar model."""
530 def wrapper(self, jmax: float, *args, **kwargs):
531 validate_jmax(jmax,
"jmax", func.__name__)
532 return func(self, jmax, *args, **kwargs)
537 """Validate quantum efficiency parameters."""
539 def wrapper(self, efficiency: float, *args, **kwargs):
540 validate_quantum_efficiency(efficiency,
"efficiency", func.__name__)
541 return func(self, efficiency, *args, **kwargs)
546 """Validate dark respiration parameters."""
548 def wrapper(self, respiration: float, *args, **kwargs):
549 validate_dark_respiration(respiration,
"respiration", func.__name__)
550 return func(self, respiration, *args, **kwargs)
555 """Validate oxygen concentration parameters."""
557 def wrapper(self, o2_concentration: float, *args, **kwargs):
558 validate_oxygen_concentration(o2_concentration,
"o2_concentration", func.__name__)
559 return func(self, o2_concentration, *args, **kwargs)
564 """Validate temperature response parameters."""
566 def wrapper(self, params, *args, **kwargs):
568 return func(self, params, *args, **kwargs)
573 """Validate photosynthetic rate parameters."""
575 def wrapper(self, rate: float, *args, **kwargs):
576 validate_photosynthetic_rate(rate,
"rate", func.__name__)
577 return func(self, rate, *args, **kwargs)
582 """Validate UUID parameters for photosynthesis methods."""
584 def wrapper(self, uuids: Union[List[int], int], *args, **kwargs):
585 if isinstance(uuids, int):
587 validate_non_negative_value(uuids,
"uuids", func.__name__)
588 elif isinstance(uuids, list):
589 validate_uuid_list(uuids,
"uuids", func.__name__, allow_empty=
False)
591 from .exceptions
import create_validation_error
592 raise create_validation_error(
593 f
"Parameter must be an integer or list of integers, got {type(uuids).__name__}",
595 function_name=func.__name__,
596 expected_type=
"integer or list of integers",
598 suggestion=
"Provide a UUID (integer) or list of UUIDs."
600 return func(self, uuids, *args, **kwargs)
606 Validate parameters for addRadiationCamera method.
608 Handles validation and type conversion for camera creation parameters:
609 - camera_label: string validation
610 - band_labels: list of strings validation
611 - position: converts lists/tuples to vec3 if needed
612 - lookat_or_direction: handles vec3 or SphericalCoord
613 - antialiasing_samples: positive integer validation
616 def wrapper(self, camera_label, band_labels, position, lookat_or_direction,
617 camera_properties=None, antialiasing_samples: int = 100, *args, **kwargs):
618 from ..wrappers.DataTypes
import vec3, SphericalCoord, make_vec3
621 validated_label = validate_camera_label(camera_label,
"camera_label", func.__name__)
622 validated_bands = validate_band_labels_list(band_labels,
"band_labels", func.__name__)
623 validated_samples = validate_antialiasing_samples(antialiasing_samples,
"antialiasing_samples", func.__name__)
626 validated_position = validate_vec3(position,
"position", func.__name__)
629 validated_direction =
None
630 if hasattr(lookat_or_direction,
'radius')
and hasattr(lookat_or_direction,
'elevation'):
632 validated_direction = lookat_or_direction
635 validated_direction = validate_vec3(lookat_or_direction,
"lookat_or_direction", func.__name__)
638 if camera_properties
is not None:
639 if not hasattr(camera_properties,
'to_array'):
640 from ..validation.exceptions
import create_validation_error
641 raise create_validation_error(
642 f
"camera_properties must be a CameraProperties instance or None, got {type(camera_properties).__name__}",
643 param_name=
"camera_properties",
644 function_name=func.__name__,
645 expected_type=
"CameraProperties or None",
646 actual_value=camera_properties,
647 suggestion=
"Use a CameraProperties instance or None for default properties."
650 return func(self, validated_label, validated_bands, validated_position, validated_direction,
651 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_xml_file_params(Callable func)
Validate XML file path parameters for WeberPennTree.
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.