Skip to content

inputs

inputs

Input parameter models and validation.

Classes

InputError

Bases: Exception

Base input error

Input

Input(**data)

Bases: Base

Represents a slider in the front end. Inputs have default values, units and keys and may have a user value or be disabled by a coupling setting.

Source code in src/pyetm/models/base.py
def __init__(self, **data: Any) -> None:
    """
    Initialize the model, converting validation errors to warnings.
    """
    super(BaseModel, self).__setattr__("__pydantic_private__", {})

    # Initialize all private attributes with their defaults
    private_dict: Dict[str, Any] = self.__pydantic_private__  # type: ignore[assignment]
    for attr_name, attr_info in self.__class__.__private_attributes__.items():
        if (
            hasattr(attr_info, "default_factory")
            and attr_info.default_factory is not None
        ):
            # Call factory - signature varies between pydantic versions
            private_dict[attr_name] = attr_info.default_factory()
        elif hasattr(attr_info, "default"):
            private_dict[attr_name] = attr_info.default
        else:
            private_dict[attr_name] = None

    try:
        super().__init__(**data)
    except ValidationError as e:
        # Check if data is None or empty - this indicates API error
        if not data or data is None:
            # Re-raise with clearer message
            raise ValueError(
                f"Cannot create {self.__class__.__name__} with empty data. "
                "This usually indicates an authentication or API error."
            ) from e

        # If validation fails, create model without validation and collect warnings
        # Use model_construct to bypass validation
        temp_instance = self.__class__.model_construct(**data)

        # Copy the constructed data to this instance
        for field_name, field_value in temp_instance.__dict__.items():
            if not field_name.startswith("_"):
                object.__setattr__(self, field_name, field_value)

        # Ensure required Pydantic slot attributes exist to prevent AttributeError
        for slot in ("__pydantic_fields_set__", "__pydantic_extra__"):
            try:
                value = object.__getattribute__(temp_instance, slot)
                object.__setattr__(self, slot, value)
            except AttributeError:
                # Initialize missing slot attributes with defaults
                if slot == "__pydantic_extra__":
                    object.__setattr__(self, slot, {})
                elif slot == "__pydantic_fields_set__":
                    object.__setattr__(self, slot, set())

        # Convert validation errors to warnings
        for error in e.errors():
            field_path = ".".join(str(part) for part in error.get("loc", []))
            message = error.get("msg", "Validation failed")
            self._warning_collector.add(field_path, message, "error")
Attributes
merged_value property
merged_value

Returns the merged value: user value if set, otherwise default value. Raises a warning if both user and default are None/NaN.

Functions
is_valid_update
is_valid_update(value)

Returns a WarningCollector with validation warnings without updating the current object.

Source code in src/pyetm/models/inputs.py
def is_valid_update(self, value: Any) -> WarningCollector:
    """
    Returns a WarningCollector with validation warnings without updating the current object.
    """
    new_obj_dict = self.model_dump()
    new_obj_dict["user"] = value

    try:
        warnings_obj = self.__class__(**new_obj_dict)
        if isinstance(warnings_obj.warnings, WarningCollector):
            return warnings_obj.warnings
        return WarningCollector()
    except Exception:
        return WarningCollector()
from_json classmethod
from_json(data)

Initialize an Input from a JSON-like tuple coming from .items()

Source code in src/pyetm/models/inputs.py
@classmethod
def from_json(cls, data: tuple[str, Dict[str, Any]]) -> "Input":
    """
    Initialize an Input from a JSON-like tuple coming from .items()
    """
    key, payload = data
    payload["key"] = key

    try:
        klass = cls.class_type(payload["unit"])
        input_instance = klass.model_validate(payload)
        return input_instance
    except Exception as e:
        # Create a basic Input with warning attached
        basic_input = cls.model_construct(**payload)  # Bypass validation
        basic_input.add_warning(key, f"Failed to create specialized input: {e}")
        return basic_input
class_type staticmethod
class_type(unit)

Return the appropriate Input subclass for the given unit

Source code in src/pyetm/models/inputs.py
@staticmethod
def class_type(unit: str) -> type[Input]:
    """Return the appropriate Input subclass for the given unit"""
    if unit == "bool":
        return BoolInput
    elif unit == "enum":
        return EnumInput
    else:
        return FloatInput
check_reset classmethod
check_reset(value)

If a reset value is sent, treat it as setting the user value to None

Source code in src/pyetm/models/inputs.py
@field_validator("user", mode="before")
@classmethod
def check_reset(cls, value: Any) -> Any:
    """If a reset value is sent, treat it as setting the user value to None"""
    if isinstance(value, str) and value == "reset":
        return None
    else:
        return value

BoolInput

BoolInput(**data)

Bases: Input

Input representing a boolean. Uses floats to represent bools (1.0 true, 0.0 false)

Source code in src/pyetm/models/base.py
def __init__(self, **data: Any) -> None:
    """
    Initialize the model, converting validation errors to warnings.
    """
    super(BaseModel, self).__setattr__("__pydantic_private__", {})

    # Initialize all private attributes with their defaults
    private_dict: Dict[str, Any] = self.__pydantic_private__  # type: ignore[assignment]
    for attr_name, attr_info in self.__class__.__private_attributes__.items():
        if (
            hasattr(attr_info, "default_factory")
            and attr_info.default_factory is not None
        ):
            # Call factory - signature varies between pydantic versions
            private_dict[attr_name] = attr_info.default_factory()
        elif hasattr(attr_info, "default"):
            private_dict[attr_name] = attr_info.default
        else:
            private_dict[attr_name] = None

    try:
        super().__init__(**data)
    except ValidationError as e:
        # Check if data is None or empty - this indicates API error
        if not data or data is None:
            # Re-raise with clearer message
            raise ValueError(
                f"Cannot create {self.__class__.__name__} with empty data. "
                "This usually indicates an authentication or API error."
            ) from e

        # If validation fails, create model without validation and collect warnings
        # Use model_construct to bypass validation
        temp_instance = self.__class__.model_construct(**data)

        # Copy the constructed data to this instance
        for field_name, field_value in temp_instance.__dict__.items():
            if not field_name.startswith("_"):
                object.__setattr__(self, field_name, field_value)

        # Ensure required Pydantic slot attributes exist to prevent AttributeError
        for slot in ("__pydantic_fields_set__", "__pydantic_extra__"):
            try:
                value = object.__getattribute__(temp_instance, slot)
                object.__setattr__(self, slot, value)
            except AttributeError:
                # Initialize missing slot attributes with defaults
                if slot == "__pydantic_extra__":
                    object.__setattr__(self, slot, {})
                elif slot == "__pydantic_fields_set__":
                    object.__setattr__(self, slot, set())

        # Convert validation errors to warnings
        for error in e.errors():
            field_path = ".".join(str(part) for part in error.get("loc", []))
            message = error.get("msg", "Validation failed")
            self._warning_collector.add(field_path, message, "error")

EnumInput

EnumInput(**data)

Bases: Input

Input representing an enumeration

Source code in src/pyetm/models/base.py
def __init__(self, **data: Any) -> None:
    """
    Initialize the model, converting validation errors to warnings.
    """
    super(BaseModel, self).__setattr__("__pydantic_private__", {})

    # Initialize all private attributes with their defaults
    private_dict: Dict[str, Any] = self.__pydantic_private__  # type: ignore[assignment]
    for attr_name, attr_info in self.__class__.__private_attributes__.items():
        if (
            hasattr(attr_info, "default_factory")
            and attr_info.default_factory is not None
        ):
            # Call factory - signature varies between pydantic versions
            private_dict[attr_name] = attr_info.default_factory()
        elif hasattr(attr_info, "default"):
            private_dict[attr_name] = attr_info.default
        else:
            private_dict[attr_name] = None

    try:
        super().__init__(**data)
    except ValidationError as e:
        # Check if data is None or empty - this indicates API error
        if not data or data is None:
            # Re-raise with clearer message
            raise ValueError(
                f"Cannot create {self.__class__.__name__} with empty data. "
                "This usually indicates an authentication or API error."
            ) from e

        # If validation fails, create model without validation and collect warnings
        # Use model_construct to bypass validation
        temp_instance = self.__class__.model_construct(**data)

        # Copy the constructed data to this instance
        for field_name, field_value in temp_instance.__dict__.items():
            if not field_name.startswith("_"):
                object.__setattr__(self, field_name, field_value)

        # Ensure required Pydantic slot attributes exist to prevent AttributeError
        for slot in ("__pydantic_fields_set__", "__pydantic_extra__"):
            try:
                value = object.__getattribute__(temp_instance, slot)
                object.__setattr__(self, slot, value)
            except AttributeError:
                # Initialize missing slot attributes with defaults
                if slot == "__pydantic_extra__":
                    object.__setattr__(self, slot, {})
                elif slot == "__pydantic_fields_set__":
                    object.__setattr__(self, slot, set())

        # Convert validation errors to warnings
        for error in e.errors():
            field_path = ".".join(str(part) for part in error.get("loc", []))
            message = error.get("msg", "Validation failed")
            self._warning_collector.add(field_path, message, "error")

FloatInput

FloatInput(**data)

Bases: Input

Input representing a float

Source code in src/pyetm/models/base.py
def __init__(self, **data: Any) -> None:
    """
    Initialize the model, converting validation errors to warnings.
    """
    super(BaseModel, self).__setattr__("__pydantic_private__", {})

    # Initialize all private attributes with their defaults
    private_dict: Dict[str, Any] = self.__pydantic_private__  # type: ignore[assignment]
    for attr_name, attr_info in self.__class__.__private_attributes__.items():
        if (
            hasattr(attr_info, "default_factory")
            and attr_info.default_factory is not None
        ):
            # Call factory - signature varies between pydantic versions
            private_dict[attr_name] = attr_info.default_factory()
        elif hasattr(attr_info, "default"):
            private_dict[attr_name] = attr_info.default
        else:
            private_dict[attr_name] = None

    try:
        super().__init__(**data)
    except ValidationError as e:
        # Check if data is None or empty - this indicates API error
        if not data or data is None:
            # Re-raise with clearer message
            raise ValueError(
                f"Cannot create {self.__class__.__name__} with empty data. "
                "This usually indicates an authentication or API error."
            ) from e

        # If validation fails, create model without validation and collect warnings
        # Use model_construct to bypass validation
        temp_instance = self.__class__.model_construct(**data)

        # Copy the constructed data to this instance
        for field_name, field_value in temp_instance.__dict__.items():
            if not field_name.startswith("_"):
                object.__setattr__(self, field_name, field_value)

        # Ensure required Pydantic slot attributes exist to prevent AttributeError
        for slot in ("__pydantic_fields_set__", "__pydantic_extra__"):
            try:
                value = object.__getattribute__(temp_instance, slot)
                object.__setattr__(self, slot, value)
            except AttributeError:
                # Initialize missing slot attributes with defaults
                if slot == "__pydantic_extra__":
                    object.__setattr__(self, slot, {})
                elif slot == "__pydantic_fields_set__":
                    object.__setattr__(self, slot, set())

        # Convert validation errors to warnings
        for error in e.errors():
            field_path = ".".join(str(part) for part in error.get("loc", []))
            message = error.get("msg", "Validation failed")
            self._warning_collector.add(field_path, message, "error")

Inputs

Inputs(**data)

Bases: Base

A collection of Inputs.

Source code in src/pyetm/models/inputs.py
def __init__(self, **data: Any) -> None:
    super().__init__(**data)
Functions
get_input_by_key
get_input_by_key(key)

Get input by its key

Source code in src/pyetm/models/inputs.py
def get_input_by_key(self, key: str) -> Optional[Input]:
    """Get input by its key"""
    for input_obj in self.inputs:
        if input_obj.key == key:
            return input_obj
    return None
is_valid_update
is_valid_update(key_vals)

Returns a dict mapping input keys to their WarningCollectors when errors were found.

Source code in src/pyetm/models/inputs.py
def is_valid_update(self, key_vals: Dict[str, Any]) -> Dict[str, WarningCollector]:
    """
    Returns a dict mapping input keys to their WarningCollectors when errors were found.
    """
    warnings: Dict[str, WarningCollector] = {}
    input_map = {inp.key: inp for inp in self.inputs}

    for key, value in key_vals.items():
        input_obj = input_map.get(key)
        if input_obj is None:
            warnings[key] = WarningCollector.with_warning(key, "Key does not exist")
            continue

        input_warnings = input_obj.is_valid_update(value)
        if len(input_warnings) > 0:
            warnings[key] = input_warnings

    return warnings
update
update(key_vals)

Update the values of certain inputs with validation and warning display.

Invalid values are rejected (not applied) to maintain data integrity. Warnings are automatically displayed for invalid values and non-existent keys. Warnings from previous updates are cleared to show only current operation issues.

Parameters:

Name Type Description Default
key_vals Dict[str, Any]

Dictionary mapping input keys to new values

required
Source code in src/pyetm/models/inputs.py
def update(self, key_vals: Dict[str, Any]) -> None:
    """
    Update the values of certain inputs with validation and warning display.

    Invalid values are rejected (not applied) to maintain data integrity.
    Warnings are automatically displayed for invalid values and non-existent keys.
    Warnings from previous updates are cleared to show only current operation issues.

    Args:
        key_vals: Dictionary mapping input keys to new values
    """
    # Auto-clear stale warnings from previous updates
    self.warnings.clear()

    # Check for non-existent keys
    valid_keys = {inp.key for inp in self.inputs}
    for key in key_vals.keys():
        if key not in valid_keys:
            self.add_warning(key, f"Input '{key}' does not exist")

    # Apply updates (Base.__setattr__ will validate and add warnings)
    for input_obj in self.inputs:
        if input_obj.key in key_vals:
            input_obj.user = key_vals[input_obj.key]
            # Collect warnings from individual inputs
            if input_obj.warnings.has_warnings("user"):
                for warning in input_obj.warnings.get_by_field("user"):
                    self.add_warning(input_obj.key, warning.message)

    # Auto-display warnings if any exist
    if len(self.warnings) > 0:
        self.auto_show_warnings()