sipa/model/fancy_property.py

Summary

Maintainability
A
25 mins
Test Coverage
import typing as t
from dataclasses import dataclass
from functools import wraps

from flask_babel import gettext
from abc import ABC, abstractmethod
from sipa.utils import argstr


class Capabilities(t.NamedTuple):
    edit: bool
    delete: bool

    @classmethod
    def edit_if(cls, condition: bool) -> t.Self:
        return cls(edit=condition, delete=False)

    @classmethod
    def edit_delete_if(cls, condition: bool) -> t.Self:
        return cls(edit=condition, delete=condition)


NO_CAPABILITIES = Capabilities(edit=False, delete=False)

TVal = t.TypeVar("TVal")
TRawVal = t.TypeVar("TRawVal")


STYLE = t.Literal[
    "muted",
    "primary",
    "success",
    "info",
    "warning",
    "danger",
    "password",
]


@dataclass
class PropertyBase(ABC, t.Generic[TVal, TRawVal]):
    name: str
    value: TVal
    raw_value: TRawVal = None
    capabilities: Capabilities = NO_CAPABILITIES
    style: STYLE | None = None
    # TODO actually is not None due to post_init. More elegantly solved with
    # separate `InitVar`
    empty: bool | None = None
    description_url: str | None = None

    def __post_init__(self):
        if self.empty is None:
            self.empty = not bool(self.value)

    @property
    @abstractmethod
    def supported(self) -> bool:
        pass

    def __eq__(self, other):
        """Check other for equality in each important attribute. If this is
        failing, compare against `self.raw_value`
        """
        try:
            return all((
                self.name == other.name,
                self.value == other.value,
                self.capabilities == other.capabilities,
                self.style == other.style,
                self.empty == other.empty,
            ))
        except AttributeError:
            return self.raw_value == other

    def __contains__(self, item):
        return self.raw_value.__contains__(item)

    def __bool__(self):
        """The boolean value represents whether the property is not empty"""
        return not self.empty


class UnsupportedProperty(PropertyBase[str, None]):
    supported = False

    def __init__(self, name):
        super().__init__(
            name=name,
            value=gettext("Nicht unterstützt"),
            raw_value=None,
            style='muted',
            empty=True,
        )

    def __repr__(self):
        return "{}.{}({})".format(__name__, type(self).__name__, argstr(
            name=self.name
        ))

    def __eq__(self, other):
        try:
            return self.name == other.name
        except AttributeError:
            return False


class ActiveProperty(PropertyBase[TVal, TRawVal]):
    supported = True

    def __post_init__(self):
        if self.empty is None:
            self.empty = not bool(self.value)
        if self.raw_value is None:
            self.raw_value = self.value
        if self.value is None:
            self.value = gettext("Nicht angegeben")
        if self.style is None:
            self.style = "muted" if self.empty else None

    def __repr__(self):
        return "<{cls} {name}='{value}' [{empty}]>".format(
            cls=type(self).__name__,
            name=self.name,
            value=self.value,
            empty=('empty' if self.empty else 'nonempty'),
        )


def connection_dependent(func):
    """A decorator to “deactivate” the property if the user's not active."""

    @wraps(func)
    def _connection_dependent(self, *args, **kwargs) -> ActiveProperty:
        if not self.has_connection:
            return ActiveProperty(
                name=func.__name__,
                value=gettext("Nicht verfügbar"),
                empty=True,
                capabilities=NO_CAPABILITIES,
            )

        ret = func(self, *args, **kwargs)
        return ret

    return _connection_dependent