Source code for daitum_ui.data

# Copyright 2026 Daitum
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Data types and value wrappers for the UI Generator system.

This module provides a comprehensive type system for representing and validating
data values in UI definitions. It includes type-safe wrappers for primitive and
complex data types, along with configuration enums and filter modes.

Main Components
---------------

**Configuration Enums:**
    - DefaultValueType: Specifies the source of default values (FIELD or NAMED_VALUE)
    - DefaultValueBehaviour: Controls how defaults interact with user overrides
    - DataValidationType: Defines validation types (LIST or RANGE)
    - ValidationFlag: Specifies boundary inclusion/exclusion for range validation

**Value Classes:**
    All value classes extend the generic `Value[T]` base class and are serializable
    to JSON with type information.

    Primitive Values:
        - IntegerValue, StringValue, BooleanValue, DecimalValue
        - DateValue, TimeValue, DateTimeValue

    Array Values:
        - IntegerArrayValue, StringArrayValue, BooleanArrayValue, DecimalArrayValue
        - DateArrayValue, TimeArrayValue, DateTimeArrayValue

    Object References:
        - ObjectValue: References a single row by rowUid or stringKey
        - ObjectArrayValue: References multiple rows

    Map Values:
        Maps associate row identifiers with typed values:
        - IntegerMapValue, DecimalMapValue, StringMapValue, BooleanMapValue
        - DateMapValue, TimeMapValue, DateTimeMapValue

**Filter Classes:**
    - ObjectReferenceFilter: References source table/field for filtering
    - FilterMode: Base class for filter mode definitions
    - MatchRowFilterMode: Filters by matching row index from context variable
    - MatchFieldFilterMode: Filters by matching field value from context variable

**Base Classes:**
    - Value[T]: Generic base class for all value types
    - MapValue[T]: Generic base class for map types
    - Condition: Base class for conditional logic (supports negation)

Type Safety and Validation
---------------------------
All classes use @typechecked decorators and implement validation in __post_init__
or __init__ methods to ensure type correctness at runtime. Date/time values are
serialized to arrays for JSON compatibility ([year, month, day] format, etc.).

Examples
--------
Creating values::

    # Primitive value
    age = IntegerValue(25)

    # Array value
    tags = StringArrayValue(["python", "ui", "generator"])

    # Date value (serializes to [2024, 1, 15])
    start_date = DateValue(date(2024, 1, 15))

    # Object reference by row number
    user = ObjectValue(row_num=42)

    # Object reference by string key (requires table with id_field)
    user = ObjectValue(string_key="user_123", table=user_table)

    # Map value
    scores = IntegerMapValue({"row1": 100, "row2": 95})
"""

from dataclasses import dataclass, field
from datetime import date, datetime, time
from enum import Enum
from typing import Generic, TypeVar

from daitum_model import Table
from typeguard import typechecked

from daitum_ui._buildable import Buildable, json_type_info
from daitum_ui.context_variable import ContextVariable


[docs] class DefaultValueType(Enum): """ Specifies the source type of the default value. Attributes: FIELD: The default value is derived from another field. NAMED_VALUE: The default value is specified as a named constant or parameter. """ FIELD = "FIELD" NAMED_VALUE = "NAMED_VALUE"
[docs] class DefaultValueBehaviour(Enum): """ Specifies how the default value behaves in relation to user-overridden values. Attributes: DEFAULT: If the current value differs from the reference value, a reset icon is shown to restore it. REFERENCE_WITH_OVERRIDE: The reference value is shown when the underlying value is blank. If a user enters a value, it overrides the reference, and a reset icon will clear it. """ DEFAULT = "DEFAULT" REFERENCE_WITH_OVERRIDE = "REFERENCE_WITH_OVERRIDE"
[docs] class DataValidationType(Enum): """ The type of data validation to apply. Attributes: LIST: The value must match an item in a predefined list (like a dropdown). RANGE: The value must fall within a minimum and maximum range. """ LIST = "LIST" RANGE = "RANGE"
[docs] class ValidationFlag(Enum): """ Specifies whether the range boundaries are inclusive or exclusive. Attributes: INCLUSIVE: The boundary values (min and max) are included as valid values. EXCLUSIVE: The boundary values are excluded from the valid range. """ INCLUSIVE = "INCLUSIVE" EXCLUSIVE = "EXCLUSIVE"
T = TypeVar("T")
[docs] @dataclass @typechecked class Value(Generic[T], Buildable): """ Base class for all value types in the UI generator. This generic class wraps primitive and complex values, providing a consistent interface for building and retrieving typed values. Attributes: value (T): The underlying value of generic type T. """ value: T
[docs] def get_value(self): """ Retrieve the underlying value. Returns: T: The stored value of type T. """ return self.value
[docs] @dataclass @typechecked @json_type_info("INTEGER") class IntegerValue(Value[int]): """ Represents a single integer value. This class wraps an integer value for use in the UI definition system. """ pass
[docs] @dataclass @typechecked @json_type_info("STRING") class StringValue(Value[str]): """ Represents a single string value. This class wraps a string value for use in the UI definition system. """ pass
[docs] @dataclass @typechecked @json_type_info("BOOLEAN") class BooleanValue(Value[bool]): """ Represents a single boolean value. This class wraps a boolean value for use in the UI definition system. """ pass
[docs] @dataclass @typechecked @json_type_info("DECIMAL") class DecimalValue(Value[float]): """ Represents a single decimal (floating-point) value. This class wraps a float value for use in the UI definition system. """ pass
[docs] @dataclass @typechecked @json_type_info("INTEGER_ARRAY") class IntegerArrayValue(Value[list[int]]): """ Represents an array of integer values. This class wraps a list of integers for use in the UI definition system. Validates that all items in the array are integers. Raises: TypeError: If the value is not a list or if any item is not an integer. """ def __post_init__(self): """Validate that the value is a list of integers.""" if not isinstance(self.value, list): raise TypeError(f"Expected list for IntArrayValue, got {type(self.value).__name__}") if not all(isinstance(item, int) for item in self.value): raise TypeError("All items in IntArrayValue must be integers")
[docs] @dataclass @typechecked @json_type_info("STRING_ARRAY") class StringArrayValue(Value[list[str]]): """ Represents an array of string values. This class wraps a list of strings for use in the UI definition system. Validates that all items in the array are strings. Raises: TypeError: If the value is not a list or if any item is not a string. """ def __post_init__(self): """Validate that the value is a list of strings.""" if not isinstance(self.value, list): raise TypeError(f"Expected list for StringArrayValue, got {type(self.value).__name__}") if not all(isinstance(item, str) for item in self.value): raise TypeError("All items in StringArrayValue must be strings")
[docs] @dataclass @typechecked @json_type_info("BOOLEAN_ARRAY") class BooleanArrayValue(Value[list[bool]]): """ Represents an array of boolean values. This class wraps a list of booleans for use in the UI definition system. Validates that all items in the array are booleans. Raises: TypeError: If the value is not a list or if any item is not a boolean. """ def __post_init__(self): """Validate that the value is a list of booleans.""" if not isinstance(self.value, list): raise TypeError(f"Expected list for BooleanArrayValue, got {type(self.value).__name__}") if not all(isinstance(item, bool) for item in self.value): raise TypeError("All items in BooleanArrayValue must be booleans")
[docs] @dataclass @typechecked @json_type_info("DECIMAL_ARRAY") class DecimalArrayValue(Value[list[float]]): """ Represents an array of decimal (floating-point) values. This class wraps a list of floats for use in the UI definition system. Validates that all items in the array are numeric (int or float). Raises: TypeError: If the value is not a list or if any item is not numeric. """ def __post_init__(self): """Validate that the value is a list of numbers.""" if not isinstance(self.value, list): raise TypeError(f"Expected list for DecimalArrayValue, got {type(self.value).__name__}") if not all(isinstance(item, (int, float)) for item in self.value): raise TypeError("All items in DecimalArrayValue must be numbers")
[docs] @dataclass @typechecked @json_type_info("DATE") class DateValue(Value[date]): """ Represents a single date value. This class wraps a Python date object for the UI definition system. """ pass
[docs] @dataclass @typechecked @json_type_info("DATE_ARRAY") class DateArrayValue(Value[list[date]]): """ Represents an array of date values. This class wraps a list of Python date objects for the UI definition system. Raises: TypeError: If the value is not a list or if any item is not a date object. """ def __post_init__(self): """Initialize a DateArrayValue with a list of date objects.""" if not isinstance(self.value, list): raise TypeError(f"Expected list for DateArrayValue, got {type(self.value).__name__}") if not all( isinstance(item, date) and not isinstance(item, datetime) for item in self.value ): raise TypeError("All items in DateArrayValue must be date objects (not datetime)")
[docs] @dataclass @typechecked @json_type_info("TIME") class TimeValue(Value[time]): """ Represents a single time value. This class wraps a Python time object for the UI definition system. """ pass
[docs] @dataclass @typechecked @json_type_info("TIME_ARRAY") class TimeArrayValue(Value[list[time]]): """ Represents an array of time values. This class wraps a list of Python time objects and serializes each as [hour, minute, second] for the UI definition system. Raises: TypeError: If the value is not a list or if any item is not a time object. """ def __post_init__(self): """Initialize a TimeArrayValue with a list of time objects.""" if not isinstance(self.value, list): raise TypeError(f"Expected list for TimeArrayValue, got {type(self.value).__name__}") if not all(isinstance(item, time) for item in self.value): raise TypeError("All items in TimeArrayValue must be time objects")
[docs] @dataclass @typechecked @json_type_info("DATETIME") class DateTimeValue(Value[datetime]): """ Represents a single datetime value. This class wraps a Python datetime object for the UI definition system. """ pass
[docs] @dataclass @typechecked @json_type_info("DATETIME_ARRAY") class DateTimeArrayValue(Value[list[datetime]]): """ Represents an array of datetime values. This class wraps a list of Python datetime objects for the UI definition system. Raises: TypeError: If the value is not a list or if any item is not a datetime object. """ def __post_init__(self): """Initialize a DateTimeArrayValue with a list of datetime objects.""" if not isinstance(self.value, list): raise TypeError( f"Expected list for DateTimeArrayValue, got {type(self.value).__name__}" ) if not all(isinstance(item, datetime) for item in self.value): raise TypeError("All items in DateTimeArrayValue must be datetime objects")
[docs] @typechecked @json_type_info("OBJECT") class ObjectValue(Value[dict]): """ Represents an object row reference. Exactly one of (row_num, string_key) must be provided. - row_num -> serialises to: {"rowUid": <int>} - string_key -> serialises to: {"stringKey": <str>} Only valid if the referenced table has an id_field. """ def __init__( self, table: Table, row_num: int | None = None, string_key: str | ContextVariable | None = None, ): if row_num is None and string_key is None: raise ValueError("Exactly one of 'row_num' or 'string_key' must be provided") if row_num is not None: super().__init__({"rowUid": row_num, "tableId": table.id}) else: if table is None or table.id_field is None: raise ValueError("string_key is only valid when table has an id_field") if isinstance(string_key, ContextVariable): super().__init__({"stringKey": string_key.id, "tableId": table.id}) self._string_key: str | None = string_key.id else: super().__init__({"stringKey": string_key, "tableId": table.id}) self._string_key = string_key self._row_num = row_num
[docs] @typechecked @json_type_info("OBJECT_ARRAY") class ObjectArrayValue(Value[list[dict]]): """ Represents an array of object row references. Each element must be either: - row_num -> {"rowUid": <int>} - string_key -> {"stringKey": <str>} Only valid if the referenced table has an id_field. """ def __init__( self, objects: list[ObjectValue], ): if not objects: raise ValueError("ObjectArrayValue requires at least one ObjectValue") super().__init__([obj.get_value() for obj in objects]) self._objects = objects
[docs] @dataclass @typechecked class MapValue(Value[dict[int, T]]): """ Base class for all MAP values. Maps associate row identifiers (string keys) with primitive values of type T. This provides a dictionary-like structure for mapping rows to values in the UI. Attributes: value (dict[int, T]): The underlying dictionary mapping row IDs to values. """
[docs] def add_mapping(self, row: int, value: T) -> None: """ Add or update a row-to-value mapping. Args: row (int): The row identifier (key). value (T): The value to associate with this row. """ self.value[row] = value
[docs] @dataclass @typechecked @json_type_info("INTEGER_MAP") class IntegerMapValue(MapValue[int]): """ Represents a map of row identifiers to integer values. This class wraps a dictionary with string keys and integer values for use in the UI definition system. Raises: TypeError: If the value is not a dict, if keys are not strings, or if values are not integers. """ def __post_init__(self): """Validate that the value is a dict with string keys and integer values.""" if not isinstance(self.value, dict): raise TypeError("IntegerMapValue expects a dict") for k, v in self.value.items(): if not isinstance(k, str): raise TypeError("Map keys must be strings") if not isinstance(v, int): raise TypeError("All values in IntegerMapValue must be integers")
[docs] @dataclass @typechecked @json_type_info("DECIMAL_MAP") class DecimalMapValue(MapValue[float]): """ Represents a map of row identifiers to decimal (floating-point) values. This class wraps a dictionary with string keys and numeric (int or float) values for use in the UI definition system. Raises: TypeError: If the value is not a dict, if keys are not strings, or if values are not numeric. """ def __post_init__(self): """Validate that the value is a dict with string keys and numeric values.""" if not isinstance(self.value, dict): raise TypeError("DecimalMapValue expects a dict") for k, v in self.value.items(): if not isinstance(k, str): raise TypeError("Map keys must be strings") if not isinstance(v, (int, float)): raise TypeError("All values in DecimalMapValue must be numbers")
[docs] @dataclass @typechecked @json_type_info("STRING_MAP") class StringMapValue(MapValue[str]): """ Represents a map of row identifiers to string values. This class wraps a dictionary with string keys and string values for use in the UI definition system. Raises: TypeError: If the value is not a dict, if keys are not strings, or if values are not strings. """ def __post_init__(self): """Validate that the value is a dict with string keys and string values.""" if not isinstance(self.value, dict): raise TypeError("StringMapValue expects a dict") for k, v in self.value.items(): if not isinstance(k, str): raise TypeError("Map keys must be strings") if not isinstance(v, str): raise TypeError("All values in StringMapValue must be strings")
[docs] @dataclass @typechecked @json_type_info("BOOLEAN_MAP") class BooleanMapValue(MapValue[bool]): """ Represents a map of row identifiers to boolean values. This class wraps a dictionary with string keys and boolean values for use in the UI definition system. Raises: TypeError: If the value is not a dict, if keys are not strings, or if values are not booleans. """ def __post_init__(self): """Validate that the value is a dict with string keys and boolean values.""" if not isinstance(self.value, dict): raise TypeError("BooleanMapValue expects a dict") for k, v in self.value.items(): if not isinstance(k, str): raise TypeError("Map keys must be strings") if not isinstance(v, bool): raise TypeError("All values in BooleanMapValue must be booleans")
[docs] @dataclass @typechecked @json_type_info("DATE_MAP") class DateMapValue(MapValue[date]): """ Represents a map of row identifiers to date values. This class wraps a dictionary with string keys and date values, for the UI definition system. Raises: TypeError: If the value is not a dict, if keys are not strings, or if values are not date objects (datetime objects are not allowed). """ def __post_init__(self): """Initialize a DateMapValue with a dictionary of dates.""" if not isinstance(self.value, dict): raise TypeError("DateMapValue expects a dict") for k, v in self.value.items(): if not isinstance(k, str): raise TypeError("Map keys must be strings") if not isinstance(v, date) or isinstance(v, datetime): raise TypeError("All values in DateMapValue must be date objects (not datetime)")
[docs] @dataclass @typechecked @json_type_info("TIME_MAP") class TimeMapValue(MapValue[time]): """ Represents a map of row identifiers to time values. This class wraps a dictionary with string keys and time values for the UI definition system. Raises: TypeError: If the value is not a dict, if keys are not strings, or if values are not time objects. """ def __post_init__(self): """Initialize a TimeMapValue with a dictionary of times.""" if not isinstance(self.value, dict): raise TypeError("TimeMapValue expects a dict") for k, v in self.value.items(): if not isinstance(k, str): raise TypeError("Map keys must be strings") if not isinstance(v, time): raise TypeError("All values in TimeMapValue must be time objects")
[docs] @dataclass @typechecked @json_type_info("DATETIME_MAP") class DateTimeMapValue(MapValue[datetime]): """ Represents a map of row identifiers to datetime values. This class wraps a dictionary with string keys and datetime values, for the UI definition system. Raises: TypeError: If the value is not a dict, if keys are not strings, or if values are not datetime objects. """ def __post_init__(self): """Initialize a DateTimeMapValue with a dictionary of datetimes.""" if not isinstance(self.value, dict): raise TypeError("DateTimeMapValue expects a dict") for k, v in self.value.items(): if not isinstance(k, str): raise TypeError("Map keys must be strings") if not isinstance(v, datetime): raise TypeError("All values in DateTimeMapValue must be datetime objects")
[docs] @dataclass @typechecked class ObjectReferenceFilter(Buildable): """ Represents a reference to a source table and field used in filtering. Attributes: source_table (str): The name of the source table. source_field (str): The field in the source table. """ source_table: str source_field: str
[docs] @dataclass @typechecked class FilterMode(Buildable): """ Base class for defining filter modes in a UI model. Attributes: context_variable_ids (Dict[str, str]): A mapping of context variable names to their associated IDs. object_reference_filter (Optional[ObjectReferenceFilter]): When populated, specifies an external table and field to use as the source of the filter value. """ context_variable_ids: dict[str, str] = field(default_factory=dict) object_reference_filter: ObjectReferenceFilter | None = None
[docs] @json_type_info("MATCH_ROW_INDEX") @dataclass @typechecked class MatchRowFilterMode(FilterMode): """ Filter mode that matches rows based on a context variable identifying the row index. This filter mode uses a context variable containing a row index to filter and display only the matching row. """
[docs] def set_filter_row(self, filter_row: str): """ Set the context variable used to identify the row for filtering. Args: filter_row (str): The ID of the context variable containing the row index. """ self.context_variable_ids["filterRow"] = filter_row
[docs] @json_type_info("MATCH_FIELD_VALUE") @dataclass @typechecked class MatchFieldFilterMode(FilterMode): """ Filter mode that matches rows based on a comparison between a context variable value and a target field value in the table. This filter mode compares a context variable's value against a specified field in the table, displaying only rows where the field value matches. Attributes: filter_target_field (Optional[str]): The field in the target table to compare against. """ filter_target_field: str | None = None
[docs] def set_filter_source_value(self, filter_source_value: str): """ Set the context variable that provides the source value for filtering. Args: filter_source_value (str): The ID of the context variable containing the value to match against the target field. """ self.context_variable_ids["filterSourceValue"] = filter_source_value
[docs] @dataclass @typechecked class Condition(Buildable): """ Base condition class. Supports logical negation. Attributes: negate: If True, the result of the condition will be logically negated. """ negate: bool = False