""" utils.py — Backend utilities condivise (P9). gather_safe(*coros): wrapper asyncio.gather con return_exceptions=True che filtra eccezioni, le loga, e restituisce solo i risultati validi. """ from __future__ import annotations import asyncio import logging from collections.abc import Awaitable from typing import TypeVar _logger = logging.getLogger("backend.utils") T = TypeVar("T") async def gather_safe(*coros: Awaitable[T], tag: str = "") -> list[T]: """ Esegui le coroutine in parallelo con return_exceptions=True. - Le eccezioni vengono loggate come warning (non propagate). - Restituisce solo i risultati validi (non-eccezione). - tag: stringa opzionale per identificare il callsite nei log. Uso: results = await gather_safe(coro1(), coro2(), coro3(), tag="web_search") """ raw = await asyncio.gather(*coros, return_exceptions=True) results: list[T] = [] for i, item in enumerate(raw): if isinstance(item, BaseException): _logger.warning( "gather_safe[%s] coro[%d] raised %s: %s", tag or "?", i, type(item).__name__, item, ) else: results.append(item) # type: ignore[arg-type] return results