Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
_aiter.py383 linesDownload Raw Back to _internal
1"""Adapted.2 3Original source:4https://github.com/maxfischer2781/asyncstdlib/blob/master/asyncstdlib/itertools.py5MIT License6"""7 8from __future__ import annotations9 10import asyncio11import contextvars12import functools13import inspect14from collections import deque15from collections.abc import (16    AsyncGenerator,17    AsyncIterable,18    AsyncIterator,19    Awaitable,20    Coroutine,21    Iterable,22    Iterator,23)24from contextlib import AbstractAsyncContextManager25from typing import (26    Any,27    Callable,28    Generic,29    Optional,30    TypeVar,31    Union,32    cast,33    overload,34)35 36from langsmith._runtime_overrides import get_runtime_overrides37 38T = TypeVar("T")39 40_no_default = object()41 42 43# https://github.com/python/cpython/blob/main/Lib/test/test_asyncgen.py#L5444# before 3.10, the builtin anext() was not available45def py_anext(46    iterator: AsyncIterator[T], default: Union[T, Any] = _no_default47) -> Awaitable[Union[T, None, Any]]:48    """Pure-Python implementation of anext() for testing purposes.49 50    Closely matches the builtin anext() C implementation.51    Can be used to compare the built-in implementation of the inner52    coroutines machinery to C-implementation of __anext__() and send()53    or throw() on the returned generator.54    """55    try:56        __anext__ = cast(57            Callable[[AsyncIterator[T]], Awaitable[T]], type(iterator).__anext__58        )59    except AttributeError:60        raise TypeError(f"{iterator!r} is not an async iterator")61 62    if default is _no_default:63        return __anext__(iterator)64 65    async def anext_impl() -> Union[T, Any]:66        try:67            # The C code is way more low-level than this, as it implements68            # all methods of the iterator protocol. In this implementation69            # we're relying on higher-level coroutine concepts, but that's70            # exactly what we want -- crosstest pure-Python high-level71            # implementation and low-level C anext() iterators.72            return await __anext__(iterator)73        except StopAsyncIteration:74            return default75 76    return anext_impl()77 78 79class NoLock:80    """Dummy lock that provides the proper interface but no protection."""81 82    async def __aenter__(self) -> None:83        pass84 85    async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool:86        return False87 88 89async def tee_peer(90    iterator: AsyncIterator[T],91    # the buffer specific to this peer92    buffer: deque[T],93    # the buffers of all peers, including our own94    peers: list[deque[T]],95    lock: AbstractAsyncContextManager[Any],96) -> AsyncGenerator[T, None]:97    """Iterate over :py:func:`~.tee`."""98    try:99        while True:100            if not buffer:101                async with lock:102                    # Another peer produced an item while we were waiting for the lock.103                    # Proceed with the next loop iteration to yield the item.104                    if buffer:105                        continue106                    try:107                        item = await iterator.__anext__()108                    except StopAsyncIteration:109                        break110                    else:111                        # Append to all buffers, including our own. We'll fetch our112                        # item from the buffer again, instead of yielding it directly.113                        # This ensures the proper item ordering if any of our peers114                        # are fetching items concurrently. They may have buffered their115                        # item already.116                        for peer_buffer in peers:117                            peer_buffer.append(item)118            yield buffer.popleft()119    finally:120        async with lock:121            # this peer is done – remove its buffer122            for idx, peer_buffer in enumerate(peers):  # pragma: no branch123                if peer_buffer is buffer:124                    peers.pop(idx)125                    break126            # if we are the last peer, try and close the iterator127            if not peers and hasattr(iterator, "aclose"):128                await iterator.aclose()129 130 131class Tee(Generic[T]):132    """Create ``n`` separate asynchronous iterators over ``iterable``.133 134    This splits a single ``iterable`` into multiple iterators, each providing135    the same items in the same order.136    All child iterators may advance separately but pare the same items137    from ``iterable`` -- when the most advanced iterator retrieves an item,138    it is buffered until the least advanced iterator has yielded it as well.139    A ``tee`` works lazily and can handle an infinite ``iterable``, provided140    that all iterators advance.141 142    ```python143    async def derivative(sensor_data):144        previous, current = a.tee(sensor_data, n=2)145        await a.anext(previous)  # advance one iterator146        return a.map(operator.sub, previous, current)147    ```148 149    Unlike :py:func:`itertools.tee`, :py:func:`~.tee` returns a custom type instead150    of a :py:class:`tuple`. Like a tuple, it can be indexed, iterated and unpacked151    to get the child iterators. In addition, its :py:meth:`~.tee.aclose` method152    immediately closes all children, and it can be used in an ``async with`` context153    for the same effect.154 155    If ``iterable`` is an iterator and read elsewhere, ``tee`` will *not*156    provide these items. Also, ``tee`` must internally buffer each item until the157    last iterator has yielded it; if the most and least advanced iterator differ158    by most data, using a :py:class:`list` is more efficient (but not lazy).159 160    If the underlying iterable is concurrency safe (``anext`` may be awaited161    concurrently) the resulting iterators are concurrency safe as well. Otherwise,162    the iterators are safe if there is only ever one single "most advanced" iterator.163    To enforce sequential use of ``anext``, provide a ``lock``164    - e.g. an :py:class:`asyncio.Lock` instance in an :py:mod:`asyncio` application -165    and access is automatically synchronised.166    """167 168    def __init__(169        self,170        iterable: AsyncIterator[T],171        n: int = 2,172        *,173        lock: Optional[AbstractAsyncContextManager[Any]] = None,174    ):175        self._iterator = iterable.__aiter__()  # before 3.10 aiter() doesn't exist176        self._buffers: list[deque[T]] = [deque() for _ in range(n)]177        self._children = tuple(178            tee_peer(179                iterator=self._iterator,180                buffer=buffer,181                peers=self._buffers,182                lock=lock if lock is not None else NoLock(),183            )184            for buffer in self._buffers185        )186 187    def __len__(self) -> int:188        return len(self._children)189 190    @overload191    def __getitem__(self, item: int) -> AsyncIterator[T]: ...192 193    @overload194    def __getitem__(self, item: slice) -> tuple[AsyncIterator[T], ...]: ...195 196    def __getitem__(197        self, item: Union[int, slice]198    ) -> Union[AsyncIterator[T], tuple[AsyncIterator[T], ...]]:199        return self._children[item]200 201    def __iter__(self) -> Iterator[AsyncIterator[T]]:202        yield from self._children203 204    async def __aenter__(self) -> Tee[T]:205        return self206 207    async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool:208        await self.aclose()209        return False210 211    async def aclose(self) -> None:212        for child in self._children:213            await child.aclose()214 215 216atee = Tee217 218 219async def async_zip(*async_iterables):220    """Async version of zip."""221    # Before Python 3.10, aiter() was not available222    iterators = [iterable.__aiter__() for iterable in async_iterables]223    while True:224        try:225            items = await asyncio.gather(226                *(py_anext(iterator) for iterator in iterators)227            )228            yield tuple(items)229        except StopAsyncIteration:230            break231 232 233def ensure_async_iterator(234    iterable: Union[Iterable, AsyncIterable],235) -> AsyncIterator:236    if hasattr(iterable, "__anext__"):237        return cast(AsyncIterator, iterable)238    elif hasattr(iterable, "__aiter__"):239        return cast(AsyncIterator, iterable.__aiter__())240    else:241 242        class AsyncIteratorWrapper:243            def __init__(self, iterable: Iterable):244                self._iterator = iter(iterable)245 246            async def __anext__(self):247                try:248                    return next(self._iterator)249                except StopIteration:250                    raise StopAsyncIteration251 252            def __aiter__(self):253                return self254 255        return AsyncIteratorWrapper(iterable)256 257 258def aiter_with_concurrency(259    n: Optional[int],260    generator: AsyncIterator[Coroutine[None, None, T]],261    *,262    _eager_consumption_timeout: float = 0,263) -> AsyncGenerator[T, None]:264    """Process async generator with max parallelism.265 266    Args:267        n: The number of tasks to run concurrently.268        generator: The async generator to process.269        _eager_consumption_timeout: If set, check for completed tasks after270            each iteration and yield their results. This can be used to271            consume the generator eagerly while still respecting the concurrency272            limit.273 274    Yields:275        The processed items yielded by the async generator.276    """277    if n == 0:278 279        async def consume():280            async for item in generator:281                yield await item282 283        return consume()284    semaphore = cast(285        asyncio.Semaphore, asyncio.Semaphore(n) if n is not None else NoLock()286    )287 288    async def process_item(ix: int, item):289        async with semaphore:290            res = await item291            return (ix, res)292 293    async def process_generator():294        tasks = {}295        accepts_context = asyncio_accepts_context()296        ix = 0297        async for item in generator:298            if accepts_context:299                context = contextvars.copy_context()300                task = asyncio.create_task(process_item(ix, item), context=context)301            else:302                task = asyncio.create_task(process_item(ix, item))303            tasks[ix] = task304            ix += 1305            if _eager_consumption_timeout > 0:306                try:307                    for _fut in asyncio.as_completed(308                        tasks.values(),309                        timeout=_eager_consumption_timeout,310                    ):311                        task_idx, res = await _fut312                        yield res313                        del tasks[task_idx]314                except asyncio.TimeoutError:315                    pass316            if n is not None and len(tasks) >= n:317                done, _ = await asyncio.wait(318                    tasks.values(), return_when=asyncio.FIRST_COMPLETED319                )320                for task in done:321                    task_idx, res = task.result()322                    yield res323                    del tasks[task_idx]324 325        for task in asyncio.as_completed(tasks.values()):326            _, res = await task327            yield res328 329    return process_generator()330 331 332def accepts_context(callable: Callable[..., Any]) -> bool:333    """Check if a callable accepts a context argument."""334    try:335        return inspect.signature(callable).parameters.get("context") is not None336    except ValueError:337        return False338 339 340# Ported from Python 3.9+ to support Python 3.8341async def aio_to_thread(342    ctx: contextvars.Context,343    func,344    /,345    *args,346    **kwargs,347):348    """Run ``func`` in a separate thread, inside ``ctx``.349 350    ``ctx`` is the :class:`~contextvars.Context` in which ``func`` is invoked.351    Callers that want default isolation should pass352    ``contextvars.copy_context()``; callers with a specific Context353    (e.g. :func:`trace`) pass it directly so subsequent reads from that354    Context see the mutations.355 356    Return a coroutine that can be awaited to get the eventual result of ``func``.357    """358    overrides = get_runtime_overrides()359    if overrides.aio_to_thread is not None:360        return await overrides.aio_to_thread(361            _default_aio_to_thread, ctx, func, *args, **kwargs362        )363    return await _default_aio_to_thread(ctx, func, *args, **kwargs)364 365 366async def _default_aio_to_thread(367    ctx: contextvars.Context,368    func,369    /,370    *args,371    **kwargs,372):373    """Default implementation of aio_to_thread using run_in_executor."""374    loop = asyncio.get_running_loop()375    func_call = functools.partial(ctx.run, func, *args, **kwargs)376    return await loop.run_in_executor(None, func_call)377 378 379@functools.lru_cache(maxsize=1)380def asyncio_accepts_context():381    """Check if the current asyncio event loop accepts a context argument."""382    return accepts_context(asyncio.create_task)383 
codekingpro/portable-devtools · Team Ai