Source code for core_mixins.interfaces.factory

# -*- coding: utf-8 -*-

"""
Base interface for classes that acts like factories.
"""

import threading
from abc import ABC, abstractmethod
from inspect import isabstract
from typing import Type, Dict, Set, Optional

from core_mixins.compatibility import Self


[docs] class IFactory(ABC): """ Base interface for classes that acts like factories. The IFactory pattern allows you to: - Automatically register concrete implementations. - Discover available implementations at runtime. - Create instances by string reference. - Maintain separate registries for different factory types. ``IFactory`` is a pure protocol, it provides the registration mechanism but does **not** store any references itself. Registries live exclusively on concrete sub-hierarchies (abstract subclasses of ``IFactory``). Every abstract subclass automatically gets its own isolated ``_impls`` dict when it is defined. Usage pattern:: class IAnimalFactory(IFactory, ABC): pass # gets its own registry class Dog(IAnimalFactory): @classmethod def registration_key(cls) -> str: return "dog" IAnimalFactory.get_registered_classes() # {"dog"} IFactory.get_registered_classes() # always empty """ _impls: Dict[str, Type[Self]] = {} _lock = threading.Lock() def __init_subclass__(cls, **kwargs) -> None: """ Hook called whenever ``IFactory`` is subclassed. - **Abstract subclass**: receives a fresh, empty ``_impls`` dict so it owns an isolated registry. - **Concrete subclass**: registered in every abstract base along its MRO, *excluding* ``IFactory`` itself (which is a protocol, not a registry). """ super().__init_subclass__(**kwargs) if isabstract(cls): # Each abstract (new base) class will have its own # register. This way each new base interface does not need # to redefine `_impls`... cls._impls = {} else: with IFactory._lock: key = cls.registration_key() for base in type.mro(cls): if ( base is not IFactory and issubclass(base, IFactory) and isabstract(base) and hasattr(base, "_impls") ): if key in base._impls: raise ValueError( f"Registration key '{key}' already exists " f"in {base.__name__} registry. " f"Existing class: {base._impls[key].__name__}, " f"Attempting to register: {cls.__name__}" ) base._impls[key] = cls
[docs] @classmethod @abstractmethod def registration_key(cls) -> str: """ It returns the name (reference) for the key used to register, like: return `self.__name__` """
[docs] @classmethod def get_registered_classes(cls) -> Set[str]: """It returns the current registered implementations""" return set(cls._impls)
[docs] @classmethod def get_class(cls, cls_reference: str) -> Optional[Type[Self]]: """It returns the reference (class type) by reference""" return cls._impls.get(cls_reference, None)