Исходный код EJIO.utils.resource.manager

## /utils/resource/manager.py

import threading
from collections.abc import Callable
from typing import Any, Literal
from loguru import logger

from .registry import GlobalResourceRegistry

__all__ = [
    'resource',
]

# Создаем потокобезопасное локальное хранилище контекста
_local_context = threading.local()


def _get_local_cache() -> dict[str, Any]:
    """
    Возвращает или лениво инициализирует кэш для текущего локального контекста потока.

    :return: Контекстный кэш.
    """
    if not hasattr(_local_context, "cache"):
        _local_context.cache = {}
    return _local_context.cache  # type: ignore[no-any-return]


[документация] class resource: """ Высокоуровневый фасад и контекстный менеджер для ленивой загрузки и кэширования тяжелых ресурсов (графов, байт, изображений). Поддерживает работу в режиме менеджера контекста (with) и в режиме прямой фабрики. """ __slots__ = ('_key', '_is_global', '_cache', '_value') def __init__(self, key: str, *, is_global: bool = False) -> None: """ :param key: Текстовый маркер (уникальный ключ) ресурса. :param is_global: Если True, ресурс сохраняется в глобальный статический реестр, иначе — в локальный кэш текущего контекста. """ self._key: str = key self._is_global: bool = is_global self._cache: dict[str, Any] | type[GlobalResourceRegistry] = ( GlobalResourceRegistry if self._is_global else _get_local_cache() ) self._value: Any = None def __enter__(self) -> 'resource': """ Вход в контекст: пытается извлечь готовый ресурс. :return: Объект менеджера. """ if self._is_global: # Работаем со статическим Registry из utils if GlobalResourceRegistry.has_key(self._key): # type: ignore[attr-defined] self._value = GlobalResourceRegistry.get(self._key) logger.debug(f"Resource '{self._key}' successfully retrieved from global cache.") else: # Работаем с локальным словарем контекста if self._key in self._cache: # type: ignore[operator] self._value = self._cache[self._key] # type: ignore[index] logger.debug(f"Resource '{self._key}' successfully retrieved from local cache.") return self @property def value(self) -> Any: """ Возвращает значение удерживаемого ресурса (None, если он еще не создан). :return: Значение ресурса, или None. """ return self._value @value.setter def value(self, new_value: Any) -> None: """ Позволяет программно записать сгенерированный ресурс внутри блока with. :param new_value: Новое значение ресурса. """ self._value = new_value def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> Literal[False]: """ Выход из контекста: если ресурс был создан внутри, автоматически сохраняет его. :param exc_type: Тип исключения. :param exc_val: Объект исключения. :param exc_tb: Трассировка. :return: False - исключения выбрасываются наружу. """ if exc_type is not None: # Если внутри блока with произошла ошибка, не кэшируем поврежденные данные return False if self._value is not None: if self._is_global: # Если ресурса еще нет в глобальном реестре, регистрируем его if not GlobalResourceRegistry.has_key(self._key): # type: ignore[attr-defined] GlobalResourceRegistry.register(self._key, self._value) else: # Пишем в локальный кэш контекста self._cache[self._key] = self._value # type: ignore[index] return False
[документация] @classmethod def remove(cls, key: str, *, is_global: bool = False) -> bool: """ Принудительно и потокобезопасно удаляет ресурс из кэша по его ключу. Полезно для инвалидации кэша, когда данные изменились. :param key: Текстовый маркер ресурса. :param is_global: Где искать ресурс — в глобальном реестре или локальном контексте. :return: True, если ресурс был успешно найден и удален, иначе False. """ if is_global: try: if GlobalResourceRegistry.has_key(key): # type: ignore[attr-defined] GlobalResourceRegistry.unregister(key) # type: ignore[attr-defined] logger.info(f"Resource '{key}' successfully deleted from global resource registry.") return True except Exception as e: logger.error(f"Error deleting global resource '{key}': {e}") return False else: cache = _get_local_cache() if key in cache: cache.pop(key) logger.info(f"Resource '{key}' successfully deleted from local context.") return True return False
[документация] @classmethod def get_or_create( cls, key: str, factory: Callable[..., Any], *args: Any, is_global: bool = False, **kwargs: Any, ) -> Any: """ Функция-обертка: атомарно проверяет кэш, и если ресурса нет — сама запускает фабрику, кэширует результат и возвращает его. :param key: Ключ под которым хранится ресурс. :param factory: Функция, генерирующая ресурс. :param is_global: Какое хранилище использовать. Если True, то глобальное, иначе - локальное. :param args: Аргументы для функции-генератора. :param kwargs: Ключевые аргументы для функции-генератора. :return: Ресурс. """ with cls(key, is_global=is_global) as res: if res.value is None: # Ресурс отсутствует -> запускаем тяжелую генерацию logger.info(f"Resource '{key}' not found. starting generation with {factory.__name__}...") res.value = factory(*args, **kwargs) return res.value