assettrack/definitions/attribute.py

247 lines
11 KiB
Python

from datetime import datetime
import re
from typing import List, Optional, Tuple
ALLOWED_INPUT_TYPES = {"text", "number", "date", "select"}
class Attribute:
"""Base class for all attribute types."""
def __init__(
self,
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,
):
self.attrib_name = attrib_name
self.display_name = display_name
self.html_input_type = html_input_type
self.required = required
self.unique = unique
self.primary = primary
self.default_val = default_val
self.compareto = compareto
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
class textAttribute(Attribute):
"""Class for text attributes."""
def __init__(
self,
attrib_name: str,
display_name: str,
regex: Optional[str] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = 50,
allowed_chars: Optional[str] = None,
**kwargs
):
super().__init__(attrib_name, display_name, html_input_type="text", **kwargs)
self.regex = regex
self.allowed_chars = allowed_chars
self.min_length = min_length
self.max_length = max_length
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:
if not re.match(self.regex, 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
class intAttribute(Attribute):
"""Class for integer attributes."""
def __init__(
self,
attrib_name: str,
display_name: str,
min_val: Optional[int] = None,
max_val: Optional[int] = None,
step: int = 1,
**kwargs
):
super().__init__(attrib_name, display_name, html_input_type="number", **kwargs)
self.min_val = min_val
self.max_val = max_val
self.step = step
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 and self.step > (self.max_val - self.min_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
class floatAttribute(Attribute):
"""Class for float attributes."""
def __init__(
self,
attrib_name: str,
display_name: str,
min_val: Optional[float] = None,
max_val: Optional[float] = None,
step: float = 0.1,
**kwargs
):
super().__init__(attrib_name, display_name, html_input_type="number", **kwargs)
self.min_val = min_val
self.max_val = max_val
self.step = step
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, 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 and self.step > (self.max_val - self.min_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
class dateAttribute(Attribute):
"""Class for date attributes."""
def __init__(
self,
attrib_name: str,
display_name: str,
min_val: Optional[str] = None,
max_val: Optional[str] = None,
**kwargs
):
super().__init__(attrib_name, display_name, html_input_type="date", **kwargs)
self.min_val = min_val
self.max_val = max_val
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
class selectAttribute(Attribute):
"""Class for select attributes."""
def __init__(
self,
attrib_name: str,
display_name: str,
options: List[str],
**kwargs
):
super().__init__(attrib_name, display_name, html_input_type="select", **kwargs)
self.options = options
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