Source code for app.components.component.component_meta
# SPDX-License-Identifier: GPLv3-or-later
# Copyright © 2025 pygaindalf Rui Pinheiro
import inspect
from abc import ABCMeta
from typing import Any, TypeVar
from typing import cast as typing_cast
from pydantic import GetCoreSchemaHandler
from pydantic_core import CoreSchema, core_schema
from app.util.mixins import LoggableHierarchicalNamedMixin
from ...util.callguard import CALLGUARD_ENABLED
from ...util.helpers import generics
from .component_config import ComponentConfig
# MARK: Component subclassing mechanism
[docs]
class ComponentMeta[C: ComponentConfig](LoggableHierarchicalNamedMixin, metaclass=ABCMeta):
config: C
config_class: type[C]
@classmethod
def _introspect_config_class(cls) -> type[C]:
"""Introspects the class to find the configuration class."""
arg = generics.get_parent_argument(cls, ComponentMeta, "C")
return typing_cast("type[C]", arg)
@classmethod
def __init_subclass__(cls, **kwargs) -> None:
"""Validate that all ComponentField descriptors are subclasses of their base class and create a '*_class' property for each descriptor."""
super().__init_subclass__(**kwargs)
# Introspect the original bases to get the configuration class
cls.config_class = cls._introspect_config_class()
if not isinstance(cls.config_class, TypeVar):
cls.config_class.component_class = cls # pyright: ignore[reportAttributeAccessIssue] as we are overriding the component_class class property on purpose
# If callguard is disabled, manually decorate all public methods with the callguard decorator
if not CALLGUARD_ENABLED and callable(dec := getattr(cls, "__callguard_decorator__", None)):
for name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
if not name.startswith("_"):
setattr(cls, name, dec(method, method_name=name))
@classmethod
def __get_pydantic_core_schema__(cls, source: Any, handler: GetCoreSchemaHandler) -> CoreSchema:
assert cls is source, f"Expected cls to be source, got {type(source).__name__} instead."
return core_schema.is_instance_schema(cls)
[docs]
def __init__(self, config: C, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
if isinstance(self.config_class, TypeVar):
msg = f"{type(self).__name__} is a generic class and must not be instantiated without providing an explicit configuration class type argument to override the TypeVar {self.config_class.__name__}."
raise TypeError(msg)
self.config = config