"""Validation functions for the user entered argument values and kwargs."""
import re
from strenum import enum
from typing import Any, Dict
from numpy import ndarray
from ..graphics import all_enums, __version__
from ..graphics.all_enums import *
from ..colors import Color
# Validation functions. They return True if the value is valid, False otherwise.
[docs]
class VersionConflict(Exception):
"""Exception raised for version conflicts."""
[docs]
def check_version(required_version: str) -> bool:
"""
Check if the current version is compatible with the required version.
Args:
required_version (str): The required version as a string.
Raises:
VersionConflict: If the current version is lower than the required version.
Returns:
bool: True if the current version is compatible.
"""
def version_value(str_version: str) -> int:
digits = str_version.split(".")
return int(digits[0]) * 100 + int(digits[1]) * 10 + int(digits[2])
if version_value(required_version) > version_value(__version__):
msg = (
f"Version conflict: Minimum required version is {required_version}. "
f"This version is {__version__}\n"
"Please update the simetri package using: pip install -U simetri"
)
raise VersionConflict(msg)
return True
[docs]
def check_str(value: Any) -> bool:
"""
Check if the value is a string.
Args:
value (Any): The value to check.
Returns:
bool: True if the value is a string, False otherwise.
"""
return isinstance(value, str)
[docs]
def check_int(value: Any) -> bool:
"""
Check if the value is an integer.
Args:
value (Any): The value to check.
Returns:
bool: True if the value is an integer, False otherwise.
"""
return isinstance(value, int)
[docs]
def check_number(number: Any) -> bool:
"""
Check if the number is a valid number.
Args:
number (Any): The number to check.
Returns:
bool: True if the number is a valid number, False otherwise.
"""
return isinstance(number, (int, float))
[docs]
def check_color(color: Any) -> bool:
"""
Check if the color is a valid color.
Args:
color (Any): The color to check.
Returns:
bool: True if the color is a valid color, False otherwise.
"""
return isinstance(color, (Color, str, tuple, list, ndarray))
[docs]
def check_dash_array(dash_array: Any) -> bool:
"""
Check if the dash array is a list of numbers or predefined.
Args:
dash_array (Any): The dash array to check.
Returns:
bool: True if the dash array is valid, False otherwise.
"""
if dash_array in LineDashArray:
res = True
elif dash_array is None:
res = True
else:
res = isinstance(dash_array, (list, tuple, ndarray)) and all(
isinstance(x, (int, float)) for x in dash_array
)
return res
[docs]
def check_bool(value: Any) -> bool:
"""
Check if the value is a boolean.
Boolean values need to be explicitly set to True or False.
None is not a valid boolean value.
Args:
value (Any): The value to check.
Returns:
bool: True if the value is a boolean, False otherwise.
"""
return isinstance(value, bool)
[docs]
def check_enum(value: Any, enum: Any) -> bool:
"""
Check if the value is a valid enum value.
Args:
value (Any): The value to check.
enum (Any): The enum to check against.
Returns:
bool: True if the value is a valid enum value, False otherwise.
"""
return value in enum
[docs]
def check_blend_mode(blend_mode: Any) -> bool:
"""
Check if the blend mode is a valid blend mode.
Args:
blend_mode (Any): The blend mode to check.
Returns:
bool: True if the blend mode is valid, False otherwise.
"""
return blend_mode in BlendMode
[docs]
def check_position(pos: Any) -> bool:
"""
Check if the position is a valid position.
Args:
pos (Any): The position to check.
Returns:
bool: True if the position is valid, False otherwise.
"""
return (
isinstance(pos, (list, tuple, ndarray))
and len(pos) >= 2
and all(isinstance(x, (int, float)) for x in pos)
)
[docs]
def check_points(points: Any) -> bool:
"""
Check if the points are a valid list of points.
Args:
points (Any): The points to check.
Returns:
bool: True if the points are valid, False otherwise.
"""
return isinstance(points, (list, tuple, ndarray)) and all(
isinstance(x, (list, tuple, ndarray)) for x in points
)
[docs]
def check_subtype(subtype: Any) -> bool:
"""
This check is done in Shape class.
Args:
subtype (Any): The subtype to check.
Returns:
bool: True
"""
return True
[docs]
def check_mask(mask: Any) -> bool:
"""
This check is done in Batch class.
Args:
mask (Any): The mask to check.
Returns:
bool: True if the mask is valid, False otherwise.
"""
return mask.type == Types.Shape
[docs]
def check_line_width(line_width: Any) -> bool:
"""
Check if the line width is a valid line width.
Args:
line_width (Any): The line width to check.
Returns:
bool: True if the line width is valid, False otherwise.
"""
if isinstance(line_width, (int, float)):
res = line_width >= 0
elif line_width in all_enums.LineWidth:
res = True
else:
res = False
return res
[docs]
def check_anchor(anchor: Any) -> bool:
"""
Check if the anchor is a valid anchor.
Args:
anchor (Any): The anchor to check.
Returns:
bool: True if the anchor is valid, False otherwise.
"""
return anchor in Anchor
# Create a dictionary of enums for validation.
items = (item for item in all_enums.__dict__.items() if item[0][0] != "_")
# from https://stackoverflow.com/questions/1175208/elegant-python-function-to-
# convert-camelcase-to-snake-case
pattern = re.compile(r"(?<!^)(?=[A-Z])") # convert CamelCase to snake_case
enum_map = {}
exclude = [
"TypeAlias",
"Union",
"StrEnum",
"CI_StrEnum",
"Comparable",
"IUC",
"drawable_types",
"shape_types",
]
for item in items:
name = item[0]
if isinstance(item[1], enum.EnumMeta) and name not in exclude:
key_ = pattern.sub("_", name).lower()
enum_map[key_] = item[1]
d_validators = {
"alpha": check_number,
"clip": check_bool,
"color": check_color,
"dist_tol": check_number,
"dist_tol2": check_number,
"double_distance": check_number,
"double_lines": check_bool,
"draw_fillets": check_bool,
"draw_frame": check_bool,
"draw_markers": check_bool,
"even_odd_rule": check_bool,
"fill": check_bool,
"fill_alpha": check_number,
"fill_blend_mode": check_blend_mode,
"fill_color": check_color,
"fillet_radius": check_number,
"font_color": check_color,
"font_family": check_str,
"frame_inner_sep": check_number,
"frame_inner_xsep": check_number,
"frame_inner_ysep": check_number,
"frame_min_height": check_number,
"frame_min_width": check_number,
"grid_alpha": check_number,
"grid_back_color": check_color,
"grid_line_color": check_color,
"grid_line_width": check_number,
"line_alpha": check_number,
"line_blend_mode": check_blend_mode,
"line_color": check_color,
"line_dash_array": check_dash_array,
"line_dash_phase": check_number,
"line_miter_limit": check_number,
"line_width": check_line_width,
"marker_color": check_color,
"marker_radius": check_number,
"marker_size": check_number,
"markers_only": check_bool,
"mask": check_mask,
"pattern_angle": check_number,
"pattern_color": check_color,
"pattern_distance": check_number,
"pattern_line_width": check_number,
"pattern_points": check_int,
"pattern_radius": check_number,
"pattern_xshift": check_number,
"pattern_yshift": check_number,
"points": check_points,
"pos": check_position,
"radius": check_number,
"shade_axis_angle": check_number,
"shade_color_wheel": check_number,
"shade_color_wheel_black": check_bool,
"shade_color_wheel_white": check_bool,
"shade_bottom_color": check_color,
"shade_inner_color": check_color,
"shade_left_color": check_color,
"shade_lower_left_color": check_color,
"shade_lower_right_color": check_color,
"shade_middle_color": check_color,
"shade_outer_color": check_color,
"shade_right_color": check_color,
"shade_top_color": check_color,
"shade_upper_left_color": check_color,
"shade_upper_right_color": check_color,
"smooth": check_bool,
"stroke": check_bool,
"xform_matrix": check_xform_matrix,
"subtype": check_subtype,
"text_alpha": check_number,
"transparency_group": check_bool,
}
[docs]
def validate_args(args: Dict[str, Any], valid_args: list[str]) -> None:
"""
Validate the user entered arguments.
Args:
args (Dict[str, Any]): The arguments to validate.
valid_args (list[str]): The list of valid argument keys.
Raises:
ValueError: If an invalid key or value is found.
Returns:
None
"""
for key, value in args.items():
if (key not in valid_args) and (key not in d_validators):
raise ValueError(f"Invalid key: {key}")
if key in d_validators:
if not d_validators[key](value):
raise ValueError(f"Invalid value for {key}: {value}")
elif key in enum_map:
if value not in enum_map[key]:
raise ValueError(f"Invalid value for {key}: {value}")
elif not d_validators[key](value):
raise ValueError(f"Invalid value for {key}: {value}")