# SPDX-License-Identifier: GPLv3
# Copyright © 2025 pygaindalf Rui Pinheiro
from typing import override, Any
from .hierarchical import HierarchicalMixin, HierarchicalProtocol, HierarchicalMixinMinimal
from .named import FinalNamedProtocol, NamedProtocol, NamedMixin, NamedMixinMinimal
from ..helpers.classinstanceproperty import classinstanceproperty
from ..helpers.classinstancemethod import classinstancemethod
from ..helpers import mro
from ..logging import Logger, getLogger, LoggableProtocol
[docs]
class LoggableMixin:
"""
Mixin that adds a logger to a class.
Provides a .log property for hierarchical logging, and integrates with instance naming and hierarchy if present.
Used throughout pygaindalf for consistent, contextual logging.
"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
mro.ensure_mro_order(cls, LoggableMixin, before=(NamedMixinMinimal, NamedMixin, NamedProtocol, HierarchicalMixinMinimal, HierarchicalMixin, HierarchicalProtocol))
[docs]
def __init__(self, *args, **kwargs):
"""
Initialize the mixin and set up the logger.
Args:
*args: Additional positional arguments for superclasses.
**kwargs: Additional keyword arguments for superclasses.
"""
super(LoggableMixin, self).__init__(*args, **kwargs)
# Start with no logger
self._reset_log_cache()
# MARK: Logging
@classinstanceproperty
def log(self) -> Logger:
"""
Returns a logger for the current object. If self.name is 'None', uses the class name.
Returns:
logging.Logger: The logger instance for the object.
"""
log : Logger|None = getattr(self, '__log', None)
if log is None:
parent = getattr(self, 'instance_parent', None)
if not isinstance(parent, LoggableProtocol):
parent = None
log = getLogger(self.__log_name__, parent=parent)
setattr(self, '__log', log)
return log
@classinstancemethod
def _reset_log_cache(self) -> None:
setattr(self, '__log', None)
@classinstanceproperty
def __default_log_name__(self) -> str:
"""
Get the default log name for the current object.
Returns:
str: The default log name.
"""
name = getattr(self, '__name__', None)
if name is None:
name = getattr(self.__class__, '__name__', None)
if name is None:
raise ValueError("Could not determine default name")
if isinstance(self, type):
name = f"T({name})"
return name
@classinstanceproperty
def __log_name__(self) -> str:
"""
Get the log name for the current object.
Returns:
str: The log name.
"""
if not isinstance(self, type):
if isinstance(self, FinalNamedProtocol):
return self.final_instance_name
if isinstance(self, NamedProtocol):
name = self.instance_name
if name is not None:
return name
return self.__default_log_name__
@classinstanceproperty
def __log_hierarchy__(self) -> str:
"""
Get the log hierarchy for the current object.
Returns:
str: The log hierarchy.
"""
return self.instance_hierarchy if isinstance(self, HierarchicalProtocol) else self.__log_name__
# MARK: Printing
@property
def __repr_name(self) -> str:
"""
Get the representation name for the current object.
Returns:
str: The representation name.
"""
nm = self.__log_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 current object.
Returns:
str: The string representation.
"""
return f"<{self.__repr_name}>"
@override
def __str__(self) -> str:
"""
Get the string representation of the current object.
Returns:
str: The string representation.
"""
if isinstance(self, NamedProtocol):
return super().__str__()
else:
return f"{self.__class__.__name__}"