betty.attr - Betty documentation

Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
Toggle table of contents sidebar

# Source code for betty.attr

    """
    The Attr API.
    """

    from __future__ import annotations

    from abc import ABC, abstractmethod
    from typing import overload, TypeVar, Generic, Self, cast

    _InstanceT = TypeVar("_InstanceT")
    _ValueT = TypeVar("_ValueT")
    _SetT = TypeVar("_SetT")

    class Attr(Generic[_InstanceT, _ValueT], ABC):
        """
        A base class for an immutable property-like attribute.

        To test your own subclasses, use :py:class:`betty.test_utils.attr.AttrTestBase`.
        """

        def __init__(self, attr_name: str):
            self._attr_name = f"_{attr_name}"

        @overload
        def __get__(self, instance: None, _: type[_InstanceT]) -> Self:
            pass

        @overload
        def __get__(self, instance: _InstanceT, _: type[_InstanceT]) -> _ValueT:
            pass

        def __get__(self, instance, _):
            if instance is None:
                return self  # type: ignore[return-value]
            return self.get_attr(instance)

        @abstractmethod
        def new_attr(self, instance: _InstanceT) -> _ValueT:
            """
            Create a new attribute value.
            """
            pass

        def get_attr(self, instance: _InstanceT) -> _ValueT:
            """
            Get the attribute value.
            """
            try:
                return cast(
                    _ValueT,
                    getattr(instance, self._attr_name),
                )
            except AttributeError:
                value = self.new_attr(instance)
                setattr(instance, self._attr_name, value)
                return value

    class MutableAttr(Generic[_InstanceT, _ValueT, _SetT], Attr[_InstanceT, _ValueT]):
        """
        A base class for a mutable property-like attribute.

        To test your own subclasses, use :py:class:`betty.test_utils.attr.MutableAttrTestBase`.
        """

        def __set__(self, instance: _InstanceT, value: _SetT) -> None:
            self.set_attr(instance, value)

        def __delete__(self, instance: _InstanceT) -> None:
            self.del_attr(instance)

        @abstractmethod
        def set_attr(self, instance: _InstanceT, value: _SetT) -> None:
            """
            Set the attribute value.
            """
            pass

        @abstractmethod
        def del_attr(self, instance: _InstanceT) -> None:
            """
            Delete the attribute value.
            """
            pass