Исходный код 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