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)
221 """Validate XML file path parameters for WeberPennTree."""
223 def wrapper(self, filename: str, silent: bool =
False, *args, **kwargs):
224 from pathlib
import Path
225 from .exceptions
import create_validation_error
228 validate_filename(filename,
"filename", func.__name__, allowed_extensions=[
'.xml'])
231 xml_path = Path(filename)
234 if not xml_path.exists():
235 raise create_validation_error(
236 f
"XML file not found: {filename}",
237 param_name=
"filename",
238 function_name=func.__name__,
239 expected_type=
"path to existing .xml file",
240 actual_value=filename,
241 suggestion=f
"Ensure the file exists at: {xml_path.resolve()}"
245 if not isinstance(silent, bool):
246 raise create_validation_error(
247 f
"Parameter must be a boolean, got {type(silent).__name__}",
249 function_name=func.__name__,
250 expected_type=
"bool",
252 suggestion=
"Use True or False for silent parameter."
255 return func(self, filename, silent, *args, **kwargs)
261 """Validate parameters for energy balance run method."""
263 def wrapper(self, uuids: Optional[List[int]] =
None, dt: Optional[float] =
None, *args, **kwargs):
264 if uuids
is not None:
265 validate_uuid_list(uuids,
"uuids", func.__name__, allow_empty=
False)
267 validate_positive_value(dt,
"dt", func.__name__)
268 return func(self, uuids, dt, *args, **kwargs)
273 """Validate parameters for energy balance band operations."""
275 def wrapper(self, band: Union[str, List[str]], *args, **kwargs):
276 if isinstance(band, str):
278 from .exceptions
import create_validation_error
279 raise create_validation_error(
280 f
"Parameter must be a non-empty string",
282 function_name=func.__name__,
283 expected_type=
"non-empty string",
284 actual_value=repr(band),
285 suggestion=
"Provide a non-empty band name."
287 elif isinstance(band, list):
289 from .exceptions
import create_validation_error
290 raise create_validation_error(
291 f
"Parameter cannot be empty",
293 function_name=func.__name__,
294 expected_type=
"non-empty list",
296 suggestion=
"Provide at least one band name."
298 for i, b
in enumerate(band):
299 if not isinstance(b, str)
or not b.strip():
300 from .exceptions
import create_validation_error
301 raise create_validation_error(
302 f
"All band names must be non-empty strings, got {type(b).__name__} at index {i}",
303 param_name=f
"band[{i}]",
304 function_name=func.__name__,
305 expected_type=
"non-empty string",
307 suggestion=
"All band names must be non-empty strings."
310 from .exceptions
import create_validation_error
311 raise create_validation_error(
312 f
"Parameter must be a string or list of strings, got {type(band).__name__}",
314 function_name=func.__name__,
315 expected_type=
"string or list of strings",
317 suggestion=
"Provide a band name string or list of band names."
319 return func(self, band, *args, **kwargs)
324 """Validate parameters for air energy balance."""
326 def wrapper(self, canopy_height_m: Optional[float] =
None, reference_height_m: Optional[float] =
None, *args, **kwargs):
327 if canopy_height_m
is not None:
328 validate_positive_value(canopy_height_m,
"canopy_height_m", func.__name__)
329 if reference_height_m
is not None:
330 validate_positive_value(reference_height_m,
"reference_height_m", func.__name__)
333 if (canopy_height_m
is None) != (reference_height_m
is None):
334 from .exceptions
import create_validation_error
335 raise create_validation_error(
336 f
"Canopy height and reference height must be provided together or omitted together",
337 param_name=
"canopy_height_m, reference_height_m",
338 function_name=func.__name__,
339 expected_type=
"both parameters provided or both None",
340 actual_value=f
"canopy_height_m={canopy_height_m}, reference_height_m={reference_height_m}",
341 suggestion=
"Provide both canopy_height_m and reference_height_m, or omit both for automatic detection."
344 return func(self, canopy_height_m, reference_height_m, *args, **kwargs)
349 """Validate parameters for evaluating air energy balance."""
351 def wrapper(self, dt_sec: float, time_advance_sec: float, UUIDs: Optional[List[int]] =
None, *args, **kwargs):
352 validate_time_value(dt_sec,
"dt_sec", func.__name__)
353 validate_time_value(time_advance_sec,
"time_advance_sec", func.__name__)
355 if time_advance_sec < dt_sec:
356 from .exceptions
import create_validation_error
357 raise create_validation_error(
358 f
"Time advance ({time_advance_sec}) must be greater than or equal to time step ({dt_sec})",
359 param_name=
"time_advance_sec",
360 function_name=func.__name__,
361 expected_type=f
"number >= {dt_sec}",
362 actual_value=time_advance_sec,
363 suggestion=f
"Use time_advance_sec >= {dt_sec}."
366 if UUIDs
is not None:
367 validate_uuid_list(UUIDs,
"UUIDs", func.__name__, allow_empty=
False)
369 return func(self, dt_sec, time_advance_sec, UUIDs, *args, **kwargs)
374 """Validate parameters for optional output data."""
376 def wrapper(self, label: str, *args, **kwargs):
377 if not isinstance(label, str):
378 from .exceptions
import create_validation_error
379 raise create_validation_error(
380 f
"Parameter must be a string, got {type(label).__name__}",
382 function_name=func.__name__,
383 expected_type=
"string",
385 suggestion=
"Provide a string label for the output data."
387 if not label.strip():
388 from .exceptions
import create_validation_error
389 raise create_validation_error(
390 f
"Parameter cannot be empty or whitespace-only",
392 function_name=func.__name__,
393 expected_type=
"non-empty string",
394 actual_value=repr(label),
395 suggestion=
"Provide a non-empty label for the output data."
397 return func(self, label, *args, **kwargs)
402 """Validate parameters for print report methods."""
404 def wrapper(self, UUIDs: Optional[List[int]] =
None, *args, **kwargs):
405 if UUIDs
is not None:
406 validate_uuid_list(UUIDs,
"UUIDs", func.__name__, allow_empty=
False)
407 return func(self, UUIDs, *args, **kwargs)
413 """Validate parameters for building visualizer geometry."""
415 def wrapper(self, context, uuids: Optional[List[int]] =
None, *args, **kwargs):
418 from ..Context
import Context
420 isinstance(context, Context)
or
421 (hasattr(context,
'__class__')
and
422 context.__class__.__name__ ==
'Context' and
423 'pyhelios.Context' in context.__class__.__module__)
426 if not is_valid_context:
427 from .exceptions
import create_validation_error
428 raise create_validation_error(
429 f
"Parameter must be a Context instance, got {type(context).__name__}",
430 param_name=
"context",
431 function_name=func.__name__,
432 expected_type=
"Context",
433 actual_value=context,
434 suggestion=
"Provide a valid PyHelios Context instance."
437 if uuids
is not None:
438 validate_uuid_list(uuids,
"uuids", func.__name__, allow_empty=
True)
439 return func(self, context, uuids, *args, **kwargs)
444 """Validate parameters for printing window to file."""
446 def wrapper(self, filename: str, *args, **kwargs):
447 validate_filename(filename,
"filename", func.__name__,
448 allowed_extensions=[
'.png',
'.jpg',
'.jpeg',
'.bmp',
'.tga'])
449 return func(self, filename, *args, **kwargs)
455 """Validate photosynthesis species parameters."""
457 def wrapper(self, species: str, *args, **kwargs):
458 validate_species_name(species,
"species", func.__name__)
459 return func(self, species, *args, **kwargs)
464 """Validate temperature parameters for photosynthesis."""
466 def wrapper(self, temperature: float, *args, **kwargs):
467 validate_temperature(temperature,
"temperature", func.__name__)
468 return func(self, temperature, *args, **kwargs)
473 """Validate CO2 concentration parameters."""
475 def wrapper(self, co2_concentration: float, *args, **kwargs):
476 validate_co2_concentration(co2_concentration,
"co2_concentration", func.__name__)
477 return func(self, co2_concentration, *args, **kwargs)
482 """Validate PAR flux parameters."""
484 def wrapper(self, par_flux: float, *args, **kwargs):
485 validate_par_flux(par_flux,
"par_flux", func.__name__)
486 return func(self, par_flux, *args, **kwargs)
491 """Validate conductance parameters."""
493 def wrapper(self, conductance: float, *args, **kwargs):
494 validate_conductance(conductance,
"conductance", func.__name__)
495 return func(self, conductance, *args, **kwargs)
500 """Validate empirical model coefficient parameters."""
502 def wrapper(self, coefficients, *args, **kwargs):
503 validate_empirical_coefficients(coefficients,
"coefficients", func.__name__)
504 return func(self, coefficients, *args, **kwargs)
509 """Validate Farquhar model coefficient parameters."""
511 def wrapper(self, coefficients, *args, **kwargs):
512 validate_farquhar_coefficients(coefficients,
"coefficients", func.__name__)
513 return func(self, coefficients, *args, **kwargs)
518 """Validate Vcmax parameters for Farquhar model."""
520 def wrapper(self, vcmax: float, *args, **kwargs):
521 validate_vcmax(vcmax,
"vcmax", func.__name__)
522 return func(self, vcmax, *args, **kwargs)
527 """Validate Jmax parameters for Farquhar model."""
529 def wrapper(self, jmax: float, *args, **kwargs):
530 validate_jmax(jmax,
"jmax", func.__name__)
531 return func(self, jmax, *args, **kwargs)
536 """Validate quantum efficiency parameters."""
538 def wrapper(self, efficiency: float, *args, **kwargs):
539 validate_quantum_efficiency(efficiency,
"efficiency", func.__name__)
540 return func(self, efficiency, *args, **kwargs)
545 """Validate dark respiration parameters."""
547 def wrapper(self, respiration: float, *args, **kwargs):
548 validate_dark_respiration(respiration,
"respiration", func.__name__)
549 return func(self, respiration, *args, **kwargs)
554 """Validate oxygen concentration parameters."""
556 def wrapper(self, o2_concentration: float, *args, **kwargs):
557 validate_oxygen_concentration(o2_concentration,
"o2_concentration", func.__name__)
558 return func(self, o2_concentration, *args, **kwargs)
563 """Validate temperature response parameters."""
565 def wrapper(self, params, *args, **kwargs):
567 return func(self, params, *args, **kwargs)
572 """Validate photosynthetic rate parameters."""
574 def wrapper(self, rate: float, *args, **kwargs):
575 validate_photosynthetic_rate(rate,
"rate", func.__name__)
576 return func(self, rate, *args, **kwargs)
581 """Validate UUID parameters for photosynthesis methods."""
583 def wrapper(self, uuids: Union[List[int], int], *args, **kwargs):
584 if isinstance(uuids, int):
586 validate_non_negative_value(uuids,
"uuids", func.__name__)
587 elif isinstance(uuids, list):
588 validate_uuid_list(uuids,
"uuids", func.__name__, allow_empty=
False)
590 from .exceptions
import create_validation_error
591 raise create_validation_error(
592 f
"Parameter must be an integer or list of integers, got {type(uuids).__name__}",
594 function_name=func.__name__,
595 expected_type=
"integer or list of integers",
597 suggestion=
"Provide a UUID (integer) or list of UUIDs."
599 return func(self, uuids, *args, **kwargs)
605 Validate parameters for addRadiationCamera method.
607 Handles validation and type conversion for camera creation parameters:
608 - camera_label: string validation
609 - band_labels: list of strings validation
610 - position: converts lists/tuples to vec3 if needed
611 - lookat_or_direction: handles vec3 or SphericalCoord
612 - antialiasing_samples: positive integer validation
615 def wrapper(self, camera_label, band_labels, position, lookat_or_direction,
616 camera_properties=None, antialiasing_samples: int = 100, *args, **kwargs):
617 from ..wrappers.DataTypes
import vec3, SphericalCoord, make_vec3
620 validated_label = validate_camera_label(camera_label,
"camera_label", func.__name__)
621 validated_bands = validate_band_labels_list(band_labels,
"band_labels", func.__name__)
622 validated_samples = validate_antialiasing_samples(antialiasing_samples,
"antialiasing_samples", func.__name__)
625 validated_position = validate_vec3(position,
"position", func.__name__)
628 validated_direction =
None
629 if hasattr(lookat_or_direction,
'radius')
and hasattr(lookat_or_direction,
'elevation'):
631 validated_direction = lookat_or_direction
634 validated_direction = validate_vec3(lookat_or_direction,
"lookat_or_direction", func.__name__)
637 if camera_properties
is not None:
638 if not hasattr(camera_properties,
'to_array'):
639 from ..validation.exceptions
import create_validation_error
640 raise create_validation_error(
641 f
"camera_properties must be a CameraProperties instance or None, got {type(camera_properties).__name__}",
642 param_name=
"camera_properties",
643 function_name=func.__name__,
644 expected_type=
"CameraProperties or None",
645 actual_value=camera_properties,
646 suggestion=
"Use a CameraProperties instance or None for default properties."
649 return func(self, validated_label, validated_bands, validated_position, validated_direction,
650 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.