Source code for app.util.mixins.hierarchical

# SPDX-License-Identifier: GPLv3
# Copyright © 2025 pygaindalf Rui Pinheiro

from pdb import run
from typing import override, runtime_checkable, Protocol, ClassVar
from abc import abstractmethod, ABCMeta

from ..helpers import mro

from .named import NamedProtocol, FinalNamedProtocol, NamedMixin, NamedMixinMinimal


# MARK: Hierarchical Protocols
[docs] @runtime_checkable class HierarchicalProtocol(Protocol): @property def instance_parent(self) -> 'HierarchicalProtocol | NamedProtocol | None': ... @property def instance_hierarchy(self) -> str: ...
[docs] @runtime_checkable class HierarchicalMutableProtocol(HierarchicalProtocol, Protocol): @property @override def instance_parent(self) -> HierarchicalProtocol | NamedProtocol | None: ... @instance_parent.setter def instance_parent(self, new_parent: HierarchicalProtocol | NamedProtocol | None) -> None: ...
# MARK: Minimal Mixin for Hierarchical Classes
[docs] class HierarchicalMixinMinimal(metaclass=ABCMeta): def __init_subclass__(cls) -> None: super().__init_subclass__() mro.ensure_mro_order(cls, HierarchicalMixinMinimal, before=(NamedMixinMinimal, NamedMixin, NamedProtocol)) @property def instance_hierarchy(self) -> str: """ Get the hierarchy of the instance as a string. Returns: str: The hierarchy string. """ hier = None if isinstance(self, FinalNamedProtocol): hier = self.final_instance_name elif isinstance(self, NamedProtocol): hier = self.instance_name if hier is None: hier = self.__class__.__name__ parent = self.instance_parent # pyright: ignore[reportAttributeAccessIssue] as this mixin must only be used when instance_parent is accessible if parent is None: pass elif isinstance(parent, HierarchicalProtocol): hier = f"{parent.instance_hierarchy}.{hier}" elif isinstance(parent, NamedProtocol) and (name := parent.instance_name): hier = f"{name}.{hier}" elif (name := getattr(parent, '__name__', None)) and isinstance(name, str): hier = f"{name}.{hier}" else: hier = f"{parent.__class__.__name__}.{hier}" return hier @property def __repr_name(self) -> str: """ Get the representation name of the instance. Returns: str: The representation name. """ nm = self.instance_hierarchy cnm = self.__class__.__name__ if cnm in nm: return nm else: return f"{cnm} {nm}" @override def __repr__(self) -> str: """ Get the string representation of the instance. Returns: str: The string representation. """ return f"<{self.__repr_name}>" @override def __str__(self) -> str: """ Get the string representation of the instance. Returns: str: The string representation. """ if isinstance(self, NamedProtocol): return super().__str__() else: return f"<{self.__class__.__name__}>"
# MARK: Mixin for Hierarchical Classes
[docs] class HierarchicalMixin(HierarchicalMixinMinimal): """ Mixin that adds parent/child hierarchy support to a class. Provides instance_parent and instance_hierarchy properties, allowing objects to be organized in a tree structure. Used for logging, naming, and configuration inheritance in pygaindalf. """ """ Attribute name used to store the instance name This is used to allow base classes to customise this without needing to override the instance_name property """ HIERARCHICAL_MIXIN_ATTRIBUTE : ClassVar[str] = '__parent'
[docs] def __init__(self, *args, instance_parent: HierarchicalProtocol | NamedProtocol | None = None, **kwargs): """ Initialize the mixin and set the instance parent. Args: instance_parent: Optional parent for the instance. *args: Additional positional arguments for superclasses. **kwargs: Additional keyword arguments for superclasses. """ super().__init__(*args, **kwargs) self.instance_parent = instance_parent
# MARK: Parent @property def instance_parent(self) -> HierarchicalProtocol | NamedProtocol | None: """ Get the instance parent. Returns: HierarchicalProtocol | NamedProtocol | None: The parent object. """ return getattr(self, self.__class__.HIERARCHICAL_MIXIN_ATTRIBUTE, None) @instance_parent.setter def instance_parent(self, new_parent : HierarchicalProtocol | NamedProtocol | None) -> None: """ Set the instance parent. Args: new_parent: The new parent object. """ if new_parent is not None and not isinstance(new_parent, (HierarchicalProtocol, NamedProtocol)): raise TypeError(f"Expected HierarchicalProtocol, NamedProtocol, or None, got {type(new_parent).__name__}") setattr(self, self.__class__.HIERARCHICAL_MIXIN_ATTRIBUTE, new_parent) from .loggable import LoggableMixin if isinstance(self, LoggableMixin): self._reset_log_cache()