from datetime import datetime import re from typing import List, Optional, Tuple from dataclasses import dataclass, field ALLOWED_INPUT_TYPES = {"text", "number", "date", "select"} @dataclass class Attribute: """Base class for all attribute types.""" attrib_name: str display_name: str html_input_type: str required: bool = False unique: bool = False primary: bool = False default_val: Optional[str] = None compareto: Optional[List[Tuple[str, str]]] = None def validate(self) -> Optional[str]: """Validate common attributes. Returns an error message if invalid, otherwise None.""" if not self.attrib_name: return "Missing attribute name." if not self.display_name: return f"Missing display name for attribute '{self.attrib_name}'." if not self.html_input_type: return f"Missing input type for attribute '{self.attrib_name}'." if self.html_input_type not in ALLOWED_INPUT_TYPES: return f"Invalid input type '{self.html_input_type}' for attribute '{self.attrib_name}'." return None @dataclass class textAttribute(Attribute): """Class for text attributes.""" regex: Optional[str] = None min_length: Optional[int] = None max_length: Optional[int] = 50 allowed_chars: Optional[str] = None def __post_init__(self): """Post-initialization to set the HTML input type.""" self.html_input_type = "text" def validate(self) -> Optional[str]: """Validate text-specific attributes.""" error = super().validate() if error: return error if self.min_length is not None and self.max_length is not None: if not isinstance(self.min_length, int) or not isinstance(self.max_length, int): return f"Min and max lengths must be integers for '{self.attrib_name}'." if int(self.min_length) > int(self.max_length): return f"Max length must be greater than min length for '{self.attrib_name}'." if self.default_val is not None: if self.regex is not None: compiled_regex = re.compile(self.regex) if not compiled_regex.match(str(self.default_val)): return f"Default value for '{self.attrib_name}' must match the pattern: {self.regex}" if self.allowed_chars is not None: for char in self.default_val: if char not in self.allowed_chars: return f"Invalid character '{char}' in default value for '{self.attrib_name}'. Allowed characters are: {self.allowed_chars}" if self.min_length is not None: if len(self.default_val) < int(self.min_length): return f"Invalid default value for '{self.attrib_name}'. The minimum length is: {self.min_length}" if self.max_length is not None: if len(self.default_val) > int(self.max_length): return f"Invalid default value for '{self.attrib_name}'. The maximum length is: {self.max_length}" return None @dataclass class intAttribute(Attribute): """Class for integer attributes.""" min_val: Optional[int] = None max_val: Optional[int] = None step: int = 1 def __post_init__(self): """Post-initialization to set the HTML input type.""" self.html_input_type = "number" def validate(self) -> Optional[str]: """Validate integer-specific attributes.""" error = super().validate() if error: return error if self.min_val is not None and not isinstance(self.min_val, int): return f"min_val must be an integer for attribute '{self.attrib_name}'." if self.max_val is not None and not isinstance(self.max_val, int): return f"max_val must be an integer for attribute '{self.attrib_name}'." if self.min_val is not None and self.max_val is not None and self.min_val > self.max_val: return f"min_val cannot be greater than max_val for attribute '{self.attrib_name}'." if self.step is not None: if not isinstance(self.step, int): return f"Step must be an integer for attribute '{self.attrib_name}'." if self.step <= 0: return f"Step must be a positive integer for attribute '{self.attrib_name}'." if self.min_val is not None and self.max_val is not None: range_val = self.max_val - self.min_val if self.step > range_val: return f"Step value is too large for attribute '{self.attrib_name}'." if self.default_val is not None: if not isinstance(self.default_val, int): return f"default_val must be an integer for attribute '{self.attrib_name}'." if self.min_val is not None and self.default_val < self.min_val: return f"default_val cannot be less than min_val for attribute '{self.attrib_name}'." if self.max_val is not None and self.default_val > self.max_val: return f"default_val cannot be greater than max_val for attribute '{self.attrib_name}'." return None @dataclass class floatAttribute(Attribute): """Class for float attributes.""" min_val: Optional[float] = None max_val: Optional[float] = None step: float = 0.1 def __post_init__(self): """Post-initialization to set the HTML input type.""" self.html_input_type = "number" def validate(self) -> Optional[str]: """Validate float-specific attributes.""" error = super().validate() if error: return error if self.min_val is not None and not isinstance(self.min_val, (int, float)): return f"min_val must be a number for attribute '{self.attrib_name}'." if self.max_val is not None and not isinstance(self.max_val, (int, float)): return f"max_val must be a number for attribute '{self.attrib_name}'." if self.min_val is not None and self.max_val is not None and self.min_val > self.max_val: return f"min_val cannot be greater than max_val for attribute '{self.attrib_name}'." if self.step is not None: if not isinstance(self.step, (int, float)): return f"Step must be an float for attribute '{self.attrib_name}'." if self.step <= 0: return f"Step must be a positive float for attribute '{self.attrib_name}'." if self.min_val is not None and self.max_val is not None: range_val = self.max_val - self.min_val if self.step > range_val: return f"Step value is too large for attribute '{self.attrib_name}'." if self.default_val is not None: if not isinstance(self.default_val, float): return f"default_val must be a number for attribute '{self.attrib_name}'." if self.min_val is not None and self.default_val < self.min_val: return f"default_val cannot be less than min_val for attribute '{self.attrib_name}'." if self.max_val is not None and self.default_val > self.max_val: return f"default_val cannot be greater than max_val for attribute '{self.attrib_name}'." return None @dataclass class dateAttribute(Attribute): """Class for date attributes.""" min_val: Optional[str] = None max_val: Optional[str] = None def __post_init__(self): """Post-initialization to set the HTML input type.""" self.html_input_type = "date" def _is_date(self, value: str) -> bool: """Check if a value is a valid date in YYYY-MM-DD format.""" try: datetime.strptime(value, "%Y-%m-%d") return True except ValueError: return False def validate(self) -> Optional[str]: """Validate date-specific attributes.""" error = super().validate() if error: return error if self.min_val is not None and not self._is_date(self.min_val): return f"min_val must be a valid date (YYYY-MM-DD) for attribute '{self.attrib_name}'." if self.max_val is not None and not self._is_date(self.max_val): return f"max_val must be a valid date (YYYY-MM-DD) for attribute '{self.attrib_name}'." if self.min_val is not None and self.max_val is not None: min_date = datetime.strptime(self.min_val, "%Y-%m-%d") max_date = datetime.strptime(self.max_val, "%Y-%m-%d") if max_date < min_date: return f"max_val cannot be earlier than min_val for attribute '{self.attrib_name}'." if self.default_val is not None and not self._is_date(self.default_val): return f"default_val must be a valid date (YYYY-MM-DD) for attribute '{self.attrib_name}'." if self.default_val is not None: default_date = datetime.strptime(self.default_val, "%Y-%m-%d") if self.min_val is not None: min_date = datetime.strptime(self.min_val, "%Y-%m-%d") if default_date < min_date: return f"default_val cannot be earlier than min_val for attribute '{self.attrib_name}'." if self.max_val is not None: max_date = datetime.strptime(self.max_val, "%Y-%m-%d") if default_date > max_date: return f"default_val cannot be later than max_val for attribute '{self.attrib_name}'." return None @dataclass class selectAttribute(Attribute): """Class for select attributes.""" options: List[str] def __post_init__(self): """Post-initialization to set the HTML input type.""" self.html_input_type = "select" def validate(self) -> Optional[str]: """Validate select-specific attributes.""" error = super().validate() if error: return error if not self.options: return f"options cannot be empty for attribute '{self.attrib_name}'." if self.default_val is not None and self.default_val not in self.options: return f"default_val must be one of the options for attribute '{self.attrib_name}'." return None