Source code for console.interfaces.dimensions

"""Interface class for dimensions."""
from __future__ import annotations

from dataclasses import asdict, dataclass, field
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from collections.abc import Callable, Iterable

    from typing_extensions import Self


def _dict_factory(items: Iterable[tuple[str, Any]]) -> dict[str, Any]:
    """Return a dictionary containing only fields whose names do not start with '_'."""
    return {key: value for key, value in items if not key.startswith("_")}


[docs] @dataclass class Dimensions: """Dataclass for definition of dimensional parameters.""" x: int | float # pylint: disable=invalid-name """X dimension.""" y: int | float # pylint: disable=invalid-name """Y dimension.""" z: int | float # pylint: disable=invalid-name """Z dimension.""" _on_change: Callable | None = field(default=None, init=False, repr=False, compare=False)
[docs] def register_on_change_callback(self, callback: Callable) -> None: """Register on change callback to protected attribute.""" if callable(callback): self._on_change = callback
[docs] @classmethod def from_dict(cls, dim: dict[str, int | float]) -> Dimensions: """Create a Dimensions instance from a dictionary.""" required = ("x", "y", "z") # Ensure all required keys exist if missing := [key for key in required if key not in dim]: msg = f"Missing keys for Dimensions: {missing}" raise ValueError(msg) # Extract only the keys relevant for initialization return cls(**{key: dim[key] for key in required})
[docs] def to_dict(self) -> dict[str, int | float]: """Convert to a dictionary.""" return asdict(self, dict_factory=_dict_factory)
[docs] @classmethod def from_list(cls, dim: list[int | float]) -> Dimensions: """Create a Dimensions instance from a list.""" return cls(*dim)
[docs] def to_list(self) -> list[int | float]: """Convert to a list.""" return [self.x, self.y, self.z]
def __setattr__(self, name: str, value: Any) -> None: """Overwrite setter to trigger private on_change method if set.""" if name in ("x", "y", "z") and not isinstance(value, (int, float)): msg = f"Invalid value: {value}, only (int, float) is allowed." raise TypeError(msg) super().__setattr__(name, value) if not hasattr(self, "_on_change"): return if name in ("x", "y", "z") and callable(self._on_change): # Trigger on_change callback to trigger parent save self._on_change() def __format__(self, format_spec: str = "") -> str: """Return formatted string with applied format spec to all values.""" if format_spec: return f"x={self.x:{format_spec}}, y={self.y:{format_spec}}, z={self.z:{format_spec}}" return f"x={self.x}, y={self.y}, z={self.z}" # ---------------- Arithmetic ---------------- # # Note: The arguments do not have an int typ annotation, since float also allows ints. # See https://docs.astral.sh/ruff/rules/redundant-numeric-union/ def __mul__(self, other: float | Dimensions) -> Dimensions: """Multiply dimension.""" if isinstance(other, Dimensions): return Dimensions(x=self.x * other.x, y=self.y * other.y, z=self.z * other.z) return Dimensions(x=self.x * other, y=self.y * other, z=self.z * other) def __truediv__(self, other: float | Dimensions) -> Dimensions: """Divide dimension.""" if isinstance(other, Dimensions): return Dimensions(x=self.x / other.x, y=self.y / other.y, z=self.z / other.z) return Dimensions(x=self.x / other, y=self.y / other, z=self.z / other) def __add__(self, other: float | Dimensions) -> Dimensions: """Add dimension.""" if isinstance(other, Dimensions): return Dimensions(x=self.x + other.x, y=self.y + other.y, z=self.z + other.z) return Dimensions(x=self.x + other, y=self.y + other, z=self.z + other) def __sub__(self, other: float | Dimensions) -> Dimensions: """Subtract dimension.""" if isinstance(other, Dimensions): return Dimensions(x=self.x - other.x, y=self.y - other.y, z=self.z - other.z) return Dimensions(x=self.x - other, y=self.y - other, z=self.z - other) def __imul__(self, other: float | Dimensions) -> Self: """In-place multiplication.""" if isinstance(other, Dimensions): self.x *= other.x self.y *= other.y self.z *= other.z else: self.x *= other self.y *= other self.z *= other return self def __itruediv__(self, other: float | Dimensions) -> Self: """In-place division.""" if isinstance(other, Dimensions): self.x /= other.x self.y /= other.y self.z /= other.z else: self.x /= other self.y /= other self.z /= other return self def __iadd__(self, other: float | Dimensions) -> Self: """In-place addition.""" if isinstance(other, Dimensions): self.x += other.x self.y += other.y self.z += other.z else: self.x += other self.y += other self.z += other return self def __isub__(self, other: float | Dimensions) -> Self: """In-place subtraction.""" if isinstance(other, Dimensions): self.x -= other.x self.y -= other.y self.z -= other.z else: self.x -= other self.y -= other self.z -= other return self