Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
iter.py224 linesDownload Raw Back to utils
1"""Utilities for working with iterators."""2 3from collections import deque4from collections.abc import Generator, Iterable, Iterator5from contextlib import AbstractContextManager6from itertools import islice7from types import TracebackType8from typing import (9    Any,10    Generic,11    Literal,12    TypeVar,13    overload,14)15 16T = TypeVar("T")17 18 19class NoLock:20    """Dummy lock that provides the proper interface but no protection."""21 22    def __enter__(self) -> None:23        """Do nothing."""24 25    def __exit__(26        self,27        exc_type: type[BaseException] | None,28        exc_val: BaseException | None,29        exc_tb: TracebackType | None,30    ) -> Literal[False]:31        """Return False (exception not suppressed)."""32        return False33 34 35def tee_peer(36    iterator: Iterator[T],37    # the buffer specific to this peer38    buffer: deque[T],39    # the buffers of all peers, including our own40    peers: list[deque[T]],41    lock: AbstractContextManager[Any],42) -> Generator[T, None, None]:43    """An individual iterator of a `.tee`.44 45    This function is a generator that yields items from the shared iterator `iterator`.46    It buffers items until the least advanced iterator has yielded them as well. The47    buffer is shared with all other peers.48 49    Args:50        iterator: The shared iterator.51        buffer: The buffer for this peer.52        peers: The buffers of all peers.53        lock: The lock to synchronise access to the shared buffers.54 55    Yields:56        The next item from the shared iterator.57    """58    try:59        while True:60            if not buffer:61                with lock:62                    # Another peer produced an item while we were waiting for the lock.63                    # Proceed with the next loop iteration to yield the item.64                    if buffer:65                        continue66                    try:67                        item = next(iterator)68                    except StopIteration:69                        break70                    else:71                        # Append to all buffers, including our own. We'll fetch our72                        # item from the buffer again, instead of yielding it directly.73                        # This ensures the proper item ordering if any of our peers74                        # are fetching items concurrently. They may have buffered their75                        # item already.76                        for peer_buffer in peers:77                            peer_buffer.append(item)78            yield buffer.popleft()79    finally:80        with lock:81            # this peer is done - remove its buffer82            for idx, peer_buffer in enumerate(peers):  # pragma: no branch83                if peer_buffer is buffer:84                    peers.pop(idx)85                    break86            # if we are the last peer, try and close the iterator87            if not peers and hasattr(iterator, "close"):88                iterator.close()89 90 91class Tee(Generic[T]):92    """Create `n` separate asynchronous iterators over `iterable`.93 94    This splits a single `iterable` into multiple iterators, each providing the same95    items in the same order.96 97    All child iterators may advance separately but share the same items from `iterable`98    -- when the most advanced iterator retrieves an item, it is buffered until the least99    advanced iterator has yielded it as well. A `tee` works lazily and can handle an100    infinite `iterable`, provided that all iterators advance.101 102    ```python103    async def derivative(sensor_data):104        previous, current = a.tee(sensor_data, n=2)105        await a.anext(previous)  # advance one iterator106        return a.map(operator.sub, previous, current)107    ```108 109    Unlike `itertools.tee`, `.tee` returns a custom type instead of a `tuple`. Like a110    tuple, it can be indexed, iterated and unpacked to get the child iterators. In111    addition, its `.tee.aclose` method immediately closes all children, and it can be112    used in an `async with` context for the same effect.113 114    If `iterable` is an iterator and read elsewhere, `tee` will *not* provide these115    items. Also, `tee` must internally buffer each item until the last iterator has116    yielded it; if the most and least advanced iterator differ by most data, using a117    `list` is more efficient (but not lazy).118 119    If the underlying iterable is concurrency safe (`anext` may be awaited concurrently)120    the resulting iterators are concurrency safe as well. Otherwise, the iterators are121    safe if there is only ever one single "most advanced" iterator. To enforce122    sequential use of `anext`, provide a `lock`123 124    - e.g., an `asyncio.Lock` instance in an `asyncio` application - and access is125        automatically synchronised.126 127    """128 129    def __init__(130        self,131        iterable: Iterator[T],132        n: int = 2,133        *,134        lock: AbstractContextManager[Any] | None = None,135    ):136        """Create a `tee`.137 138        Args:139            iterable: The iterable to split.140            n: The number of iterators to create.141            lock: The lock to synchronise access to the shared buffers.142 143        """144        self._iterator = iter(iterable)145        self._buffers: list[deque[T]] = [deque() for _ in range(n)]146        self._children = tuple(147            tee_peer(148                iterator=self._iterator,149                buffer=buffer,150                peers=self._buffers,151                lock=lock if lock is not None else NoLock(),152            )153            for buffer in self._buffers154        )155 156    def __len__(self) -> int:157        """Return the number of child iterators."""158        return len(self._children)159 160    @overload161    def __getitem__(self, item: int) -> Iterator[T]: ...162 163    @overload164    def __getitem__(self, item: slice) -> tuple[Iterator[T], ...]: ...165 166    def __getitem__(self, item: int | slice) -> Iterator[T] | tuple[Iterator[T], ...]:167        """Return the child iterator(s) at the given index or slice."""168        return self._children[item]169 170    def __iter__(self) -> Iterator[Iterator[T]]:171        """Return an iterator over the child iterators.172 173        Yields:174            The child iterators.175        """176        yield from self._children177 178    def __enter__(self) -> "Tee[T]":179        """Return `Tee` instance."""180        return self181 182    def __exit__(183        self,184        exc_type: type[BaseException] | None,185        exc_val: BaseException | None,186        exc_tb: TracebackType | None,187    ) -> Literal[False]:188        """Close all child iterators.189 190        Returns:191            `False` (exception not suppressed).192        """193        self.close()194        return False195 196    def close(self) -> None:197        """Close all child iterators."""198        for child in self._children:199            child.close()200 201 202# Why this is needed https://stackoverflow.com/a/44638570203safetee = Tee204 205 206def batch_iterate(size: int | None, iterable: Iterable[T]) -> Iterator[list[T]]:207    """Utility batching function.208 209    Args:210        size: The size of the batch.211 212            If `None`, returns a single batch.213        iterable: The iterable to batch.214 215    Yields:216        The batches of the iterable.217    """218    it = iter(iterable)219    while True:220        chunk = list(islice(it, size))221        if not chunk:222            return223        yield chunk224 
codekingpro/portable-devtools · Team Ai