async_utils.py 1.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445
  1. import asyncio
  2. import logging
  3. from typing import AsyncGenerator, Generator, TypeVar, Any
  4. T = TypeVar("T")
  5. logger = logging.getLogger(__name__)
  6. async def async_generator_wrapper(gen):
  7. """
  8. Wrap a synchronous generator into an asynchronous one.
  9. Also handles async generators transparently.
  10. """
  11. if hasattr(gen, '__aiter__'):
  12. async for item in gen:
  13. yield item
  14. return
  15. while True:
  16. try:
  17. # We must use run_in_executor because next() on sync generator blocks
  18. # But await asyncio.to_thread(next, sync_gen) is cleaner in Py3.9+
  19. # However, if sync_gen raises StopIteration, to_thread might wrap it in execution error or not propagate correctly
  20. # Let's be explicit
  21. def _next():
  22. try:
  23. return next(gen)
  24. except StopIteration:
  25. return StopIteration
  26. except Exception as e:
  27. return e
  28. chunk = await asyncio.to_thread(_next)
  29. if chunk is StopIteration:
  30. break
  31. if isinstance(chunk, Exception):
  32. logger.error(f"Error in generator: {chunk}")
  33. break
  34. yield chunk
  35. except Exception as e:
  36. logger.error(f"Error in async_wrapper loop: {e}")
  37. break