pax_global_header00006660000000000000000000000064146652302430014517gustar00rootroot0000000000000052 comment=eeaef4806a23370d0c8685d1ff5c06b9e47d9d3e omnilib-aioitertools-ff59bbd/000077500000000000000000000000001466523024300164465ustar00rootroot00000000000000omnilib-aioitertools-ff59bbd/.flake8000066400000000000000000000002651466523024300176240ustar00rootroot00000000000000[flake8] ignore = # mccabe complexity C901 # covered by black/usort E1 E2 E3 E4 E704 max-line-length = 88 per-file-ignores = __init__.py: F401 omnilib-aioitertools-ff59bbd/.github/000077500000000000000000000000001466523024300200065ustar00rootroot00000000000000omnilib-aioitertools-ff59bbd/.github/dependabot.yml000066400000000000000000000003171466523024300226370ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" - package-ecosystem: "pip" directory: "/" schedule: interval: "monthly" omnilib-aioitertools-ff59bbd/.github/issue_template.md000066400000000000000000000002221466523024300233470ustar00rootroot00000000000000### Description ### Details * OS: * Python version: * aioitertools version: * Can you repro on master? * Can you repro in a clean virtualenv? omnilib-aioitertools-ff59bbd/.github/pull_request_template.md000066400000000000000000000000751466523024300247510ustar00rootroot00000000000000### Description Fixes: # omnilib-aioitertools-ff59bbd/.github/workflows/000077500000000000000000000000001466523024300220435ustar00rootroot00000000000000omnilib-aioitertools-ff59bbd/.github/workflows/ci.yml000066400000000000000000000032371466523024300231660ustar00rootroot00000000000000name: CI on: push: branches: - main tags: - v* pull_request: permissions: contents: read env: UV_SYSTEM_PYTHON: 1 jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] os: [macOS-latest, ubuntu-latest, windows-latest] steps: - name: Checkout uses: actions/checkout@v4 - name: Set Up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} allow-prereleases: true - uses: hynek/setup-cached-uv@v2 with: cache-dependency-path: pyproject.toml - name: Install run: make EXTRAS=dev install - name: Test run: make test - name: Lint run: make lint sdist: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.12' - uses: hynek/setup-cached-uv@v2 with: cache-dependency-path: pyproject.toml - name: Install run: make install - name: Build run: python -m build --sdist - name: Upload uses: actions/upload-artifact@v3 with: name: sdist path: dist publish: needs: sdist runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/v') permissions: id-token: write steps: - uses: actions/download-artifact@v3 with: name: sdist path: dist - uses: pypa/gh-action-pypi-publish@release/v1 omnilib-aioitertools-ff59bbd/.gitignore000066400000000000000000000062001466523024300204340ustar00rootroot00000000000000html/ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control #poetry.lock # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. #pdm.lock # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it # in version control. # https://pdm.fming.dev/latest/usage/project/#working-with-version-control .pdm.toml .pdm-python .pdm-build/ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. .idea/ # Visual Studio Code .vscode/ # Ruff stuff .ruff_cache omnilib-aioitertools-ff59bbd/.mailmap000066400000000000000000000000621466523024300200650ustar00rootroot00000000000000Amethyst Reese omnilib-aioitertools-ff59bbd/.readthedocs.yml000066400000000000000000000003031466523024300215300ustar00rootroot00000000000000version: 2 sphinx: configuration: docs/conf.py build: os: ubuntu-22.04 tools: python: "3.10" python: install: - method: pip path: . extra_requirements: - docs omnilib-aioitertools-ff59bbd/CHANGELOG.md000066400000000000000000000127621466523024300202670ustar00rootroot00000000000000aioitertools ============ [![Generated by attribution][attribution-badge]][attribution-url] v0.12.0 ------- Feature release - New: added `itertools.batched()` from Python 3.13 (#177) - New: added `builtins.tuple()` (#152) - Docs: fixed rendering of shell sections (#183) - Python: added support for 3.12 and 3.13 (#154, #192) - Python: dropped support for 3.6 and 3.7 (#154) - Dev: updated gitignore and contributing guide (#176, #192) - Dev: use `uv` for creating venv and installing deps when available (#192) ```text $ git shortlog -s v0.11.0...v0.12.0 7 Amethyst Reese 1 Julian Gilbey 2 Stanley Kudrow 1 Vlastimil Zíma 17 dependabot[bot] ``` v0.11.0 ------- Feature release - New: `before_and_after` from `more_itertools` (#111) - Removed: `loop` parameter is now removed from asyncio functions (#136) - Docs: fixed docstring for `builtins.max` (#107) - Docs: can now be built without installing package (#135) - Python: Added support for Python 3.11 (#133) - Python: Deprecated support for Python 3.6, to be removed in 0.12.0 ```text $ git shortlog -s v0.10.0...v0.11.0 16 Amethyst Reese 2 Daniel Miranda 1 Minsung.Kim 12 dependabot[bot] ``` v0.10.0 ------- Feature release - New `as_generated()` function for consuming multiple iterables (#99) ```text $ git shortlog -s v0.10.0b1...v0.10.0 1 Amethyst Reese ``` v0.10.0b1 --------- Beta release - New `as_generated()` function for consuming multiple iterables (#99) ```text $ git shortlog -s v0.9.0...v0.10.0b1 3 Amethyst Reese ``` v0.9.0 ------ Feature release - Python 3.10 support (#84, #97) - Fixed: `as_completed()` will now cancel pending tasks when the timeout threshold has been reached. - Fixed: `zip()` will now gather all tasks correctly when exceptions are raised, resulting in no pending tasks being unawaited. - DEPRECATED: `loop` parameters to asyncio functions are ignored, and will be removed entirely in v0.11.0. This is for 3.10 compatibility, as well as to follow common practice and guidance around asyncio. ```text $ git shortlog -s v0.8.0...v0.9.0 17 Amethyst Reese 20 dependabot[bot] 3 pyup.io bot ``` v0.8.0 ------ Feature release: - Added `builtins.any()` and `builtins.all()` (#44) - `builtins.next()` takes an optional `default` parameter (#40, #41) - `asyncio.gather()` now handles cancellation (#64) - Better exception handling in `itertools.tee()` (#47) - Removed dependency on typing_extensions for Python 3.8 and newer (#49) - Improved documentation and formatting ```text $ git shortlog -s v0.7.1...v0.8.0 15 Amethyst Reese 1 Bryan Forbes 2 Jason Fried 1 Kevin Stone 3 Roger Aiudi 3 Sunyeop Lee 1 Yop 10 pyup.io bot ``` v0.7.1 ------ Bugfix release: * Fix groupby() not working with empty iterables (#39) * Tested on Python 3.9 ```text $ git shortlog -s v0.7.0...v0.7.1 8 Amethyst Reese 1 Roger Aiudi 2 pyup-bot ``` v0.7.0 ------ Feature release - Add `min()` and `max()` to builtins ```text $ git shortlog -s v0.6.1...v0.7.0 7 Amethyst Reese ``` v0.6.1 ------ Metadata fix - Corrected description field for PyPI - Switched from setuptools to flit for build/publish ```text $ git shortlog -s v0.6.0...v0.6.1 6 Amethyst Reese 1 Dima Tisnek ``` v0.6.0 ------ Feature release v0.6.0 - First pieces of more_itertools (#18) ```text $ git shortlog -s v0.5.1...v0.6.0 3 Amethyst Reese 1 Zsolt Dollenstein ``` v0.5.1 ------ Documentation Release v0.5.1 - Include changelog, code of conduct, and contributers guide in sdist - Include wheels when building release distributions ```text $ git shortlog -s v0.5.0...v0.5.1 4 Amethyst Reese ``` v0.5.0 ------ Feature Release v0.5.0 - Feature: concurrency-limited implementation of asyncio.gather (#10) - Fix: platform independent encoding in setup.py (#15, #16) - Fix: make zip_longest stop iterating on finished iterators (#13) - Improved documentation - Overhaul package configuration and requirements - Format package using isort - Add coverage testing with codecov.io - Switch to Github Actions for CI - Testing on Python 3.8 ```text $ git shortlog -s v0.4.0...v0.5.0 1 Alexey Simuskov 23 Amethyst Reese 1 Tim Hatch ``` v0.4.0 ------ Feature release v0.4.0 - Provisional module for friendly versions of the asyncio library. Access this via `aioitertools.asyncio`. ```text $ git shortlog -s v0.3.2...v0.4.0 4 Amethyst Reese ``` v0.3.2 ------ Bug fix release: - chain.from_iterable now accepts async generators to provide iterables (#8) ```text $ git shortlog -s v0.3.1...v0.3.2 1 A Connecticut Princess 2 Amethyst Reese ``` v0.3.1 ------ Bug fix release v0.3.1: - Fixes `islice` consuming extra items (#7) ```text $ git shortlog -s v0.3.0...v0.3.1 2 Amethyst Reese 3 Vladimir Solomatin ``` v0.3.0 ------ Feature release v0.3.0: - Accept `start` parameter to `enumerate()` (#2) - Added PEP 561 compliance and py.typed (#1) - Support for functions that return awaitables (#5) ```text $ git shortlog -s v0.2.0...v0.3.0 12 Amethyst Reese 6 Bryan Forbes ``` v0.2.0 ------ Feature release: - Support all of itertools ```text $ git shortlog -s v0.1.0...v0.2.0 8 Amethyst Reese ``` v0.1.0 ------ Initial feature release: - Shadow major builtins for iterables - Unit tests for all builtins ```text $ git shortlog -s v0.1.0 2 Amethyst Reese ``` [attribution-badge]: https://img.shields.io/badge/generated%20by-attribution-informational [attribution-url]: https://attribution.omnilib.dev omnilib-aioitertools-ff59bbd/CODE_OF_CONDUCT.md000066400000000000000000000062201466523024300212450ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@omnilib.dev. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ omnilib-aioitertools-ff59bbd/CONTRIBUTING.md000066400000000000000000000013761466523024300207060ustar00rootroot00000000000000# Contributing to aioitertools ## Preparation You'll need to have Python 3.8 or newer available for testing. I recommend using [pyenv][] for this: ```sh $ pyenv install 3.12 $ pyenv shell 3.12 ``` ## Setup Create a fresh development enviroment, and install the appropriate tools and dependencies: ```sh $ cd $ make venv $ source .venv/bin/activate ``` ## Submitting Before submitting a pull request, please ensure that you have done the following: * Documented changes or features in README.md * Added appropriate license headers to new files * Written or modified tests for new functionality * Used `make format` to format code appropriately * Validated and tested code with `make test lint` [pyenv]: https://github.com/pyenv/pyenv omnilib-aioitertools-ff59bbd/LICENSE000066400000000000000000000020571466523024300174570ustar00rootroot00000000000000MIT License Copyright (c) 2022 Amethyst Reese Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. omnilib-aioitertools-ff59bbd/MANIFEST.in000066400000000000000000000001121466523024300201760ustar00rootroot00000000000000include README.md LICENSE CHANGELOG.md CONTRIBUTING.md CODE_OF_CONDUCT.md omnilib-aioitertools-ff59bbd/README.md000066400000000000000000000050731466523024300177320ustar00rootroot00000000000000aioitertools ============ Implementation of itertools, builtins, and more for AsyncIO and mixed-type iterables. [![documentation](https://readthedocs.org/projects/aioitertools/badge/?version=latest)](https://aioitertools.omnilib.dev) [![version](https://img.shields.io/pypi/v/aioitertools.svg)](https://pypi.org/project/aioitertools) [![changelog](https://img.shields.io/badge/change-log-blue)](https://aioitertools.omnilib.dev/en/latest/changelog.html) [![license](https://img.shields.io/pypi/l/aioitertools.svg)](https://github.com/omnilib/aioitertools/blob/master/LICENSE) Install ------- aioitertools requires Python 3.8 or newer. You can install it from PyPI: ```sh $ pip install aioitertools ``` Usage ----- aioitertools shadows the standard library whenever possible to provide asynchronous version of the modules and functions you already know. It's fully compatible with standard iterators and async iterators alike, giving you one unified, familiar interface for interacting with iterable objects: ```python from aioitertools import iter, next, map, zip something = iter(...) first_item = await next(something) async for item in iter(something): ... async def fetch(url): response = await aiohttp.request(...) return response.json async for value in map(fetch, MANY_URLS): ... async for a, b in zip(something, something_else): ... ``` aioitertools emulates the entire `itertools` module, offering the same function signatures, but as async generators. All functions support standard iterables and async iterables alike, and can take functions or coroutines: ```python from aioitertools import chain, islice async def generator1(...): yield ... async def generator2(...): yield ... async for value in chain(generator1(), generator2()): ... async for value in islice(generator1(), 2, None, 2): ... ``` See [builtins.py][], [itertools.py][], and [more_itertools.py][] for full documentation of functions and abilities. License ------- aioitertools is copyright [Amethyst Reese](https://noswap.com), and licensed under the MIT license. I am providing code in this repository to you under an open source license. This is my personal repository; the license you receive to my code is from me and not from my employer. See the `LICENSE` file for details. [builtins.py]: https://github.com/omnilib/aioitertools/blob/master/aioitertools/builtins.py [itertools.py]: https://github.com/omnilib/aioitertools/blob/master/aioitertools/itertools.py [more_itertools.py]: https://github.com/omnilib/aioitertools/blob/master/aioitertools/more_itertools.py omnilib-aioitertools-ff59bbd/aioitertools/000077500000000000000000000000001466523024300211635ustar00rootroot00000000000000omnilib-aioitertools-ff59bbd/aioitertools/__init__.py000066400000000000000000000012531466523024300232750ustar00rootroot00000000000000# Copyright 2022 Amethyst Reese # Licensed under the MIT license """ itertools and builtins for AsyncIO and mixed iterables """ __author__ = "Amethyst Reese" from . import asyncio from .__version__ import __version__ from .builtins import ( all, any, enumerate, iter, list, map, max, min, next, set, sum, tuple, zip, ) from .itertools import ( accumulate, batched, chain, combinations, combinations_with_replacement, compress, count, cycle, dropwhile, filterfalse, groupby, islice, permutations, product, repeat, starmap, takewhile, tee, zip_longest, ) omnilib-aioitertools-ff59bbd/aioitertools/__version__.py000066400000000000000000000002351466523024300240160ustar00rootroot00000000000000""" This file is automatically generated by attribution. Do not edit manually. Get more info at https://attribution.omnilib.dev """ __version__ = "0.12.0" omnilib-aioitertools-ff59bbd/aioitertools/asyncio.py000066400000000000000000000203451466523024300232060ustar00rootroot00000000000000# Copyright 2022 Amethyst Reese # Licensed under the MIT license """ Friendlier version of asyncio standard library. Provisional library. Must be imported as `aioitertools.asyncio`. """ import asyncio import time from typing import ( Any, AsyncGenerator, AsyncIterable, Awaitable, cast, Dict, Iterable, List, Optional, Set, Tuple, ) from .builtins import iter as aiter, maybe_await from .types import AnyIterable, AsyncIterator, MaybeAwaitable, T async def as_completed( aws: Iterable[Awaitable[T]], *, timeout: Optional[float] = None, ) -> AsyncIterator[T]: """ Run awaitables in `aws` concurrently, and yield results as they complete. Unlike `asyncio.as_completed`, this yields actual results, and does not require awaiting each item in the iterable. Cancels all remaining awaitables if a timeout is given and the timeout threshold is reached. Example:: async for value in as_completed(futures): ... # use value immediately """ done: Set[Awaitable[T]] = set() pending: Set[Awaitable[T]] = {asyncio.ensure_future(a) for a in aws} remaining: Optional[float] = None if timeout and timeout > 0: threshold = time.time() + timeout else: timeout = None while pending: if timeout: remaining = threshold - time.time() if remaining <= 0: for fut in pending: if isinstance(fut, asyncio.Future): fut.cancel() else: # pragma: no cover pass raise asyncio.TimeoutError() # asyncio.Future inherits from typing.Awaitable # asyncio.wait takes Iterable[Union[Future, Generator, Awaitable]], but # returns Tuple[Set[Future], Set[Future]. Because mypy doesn't like assigning # these values to existing Set[Awaitable] or even Set[Union[Awaitable, Future]], # we need to first cast the results to something that we can actually use # asyncio.Future: https://github.com/python/typeshed/blob/72ff7b94e534c610ddf8939bacbc55343e9465d2/stdlib/3/asyncio/futures.pyi#L30 # noqa: E501 # asyncio.wait(): https://github.com/python/typeshed/blob/72ff7b94e534c610ddf8939bacbc55343e9465d2/stdlib/3/asyncio/tasks.pyi#L89 # noqa: E501 done, pending = cast( Tuple[Set[Awaitable[T]], Set[Awaitable[T]]], await asyncio.wait( pending, timeout=remaining, return_when=asyncio.FIRST_COMPLETED, ), ) for item in done: yield await item async def as_generated( iterables: Iterable[AsyncIterable[T]], *, return_exceptions: bool = False, ) -> AsyncIterable[T]: """ Yield results from one or more async iterables, in the order they are produced. Like :func:`as_completed`, but for async iterators or generators instead of futures. Creates a separate task to drain each iterable, and a single queue for results. If ``return_exceptions`` is ``False``, then any exception will be raised, and pending iterables and tasks will be cancelled, and async generators will be closed. If ``return_exceptions`` is ``True``, any exceptions will be yielded as results, and execution will continue until all iterables have been fully consumed. Example:: async def generator(x): for i in range(x): yield i gen1 = generator(10) gen2 = generator(12) async for value in as_generated([gen1, gen2]): ... # intermixed values yielded from gen1 and gen2 """ exc_queue: asyncio.Queue[Exception] = asyncio.Queue() queue: asyncio.Queue[T] = asyncio.Queue() async def tailer(iter: AsyncIterable[T]) -> None: try: async for item in iter: await queue.put(item) except asyncio.CancelledError: if isinstance(iter, AsyncGenerator): # pragma:nocover await iter.aclose() raise except Exception as e: await exc_queue.put(e) tasks = [asyncio.ensure_future(tailer(iter)) for iter in iterables] pending = set(tasks) try: while pending: try: exc = exc_queue.get_nowait() if return_exceptions: yield exc # type: ignore else: raise exc except asyncio.QueueEmpty: pass try: value = queue.get_nowait() yield value except asyncio.QueueEmpty: for task in list(pending): if task.done(): pending.remove(task) await asyncio.sleep(0.001) except (asyncio.CancelledError, GeneratorExit): pass finally: for task in tasks: if not task.done(): task.cancel() for task in tasks: try: await task except asyncio.CancelledError: pass async def gather( *args: Awaitable[T], return_exceptions: bool = False, limit: int = -1, ) -> List[Any]: """ Like asyncio.gather but with a limit on concurrency. Note that all results are buffered. If gather is cancelled all tasks that were internally created and still pending will be cancelled as well. Example:: futures = [some_coro(i) for i in range(10)] results = await gather(*futures, limit=2) """ # For detecting input duplicates and reconciling them at the end input_map: Dict[Awaitable[T], List[int]] = {} # This is keyed on what we'll get back from asyncio.wait pos: Dict[asyncio.Future[T], int] = {} ret: List[Any] = [None] * len(args) pending: Set[asyncio.Future[T]] = set() done: Set[asyncio.Future[T]] = set() next_arg = 0 while True: while next_arg < len(args) and (limit == -1 or len(pending) < limit): # We have to defer the creation of the Task as long as possible # because once we do, it starts executing, regardless of what we # have in the pending set. if args[next_arg] in input_map: input_map[args[next_arg]].append(next_arg) else: # We call ensure_future directly to ensure that we have a Task # because the return value of asyncio.wait will be an implicit # task otherwise, and we won't be able to know which input it # corresponds to. task: asyncio.Future[T] = asyncio.ensure_future(args[next_arg]) pending.add(task) pos[task] = next_arg input_map[args[next_arg]] = [next_arg] next_arg += 1 # pending might be empty if the last items of args were dupes; # asyncio.wait([]) will raise an exception. if pending: try: done, pending = await asyncio.wait( pending, return_when=asyncio.FIRST_COMPLETED ) for x in done: if return_exceptions and x.exception(): ret[pos[x]] = x.exception() else: ret[pos[x]] = x.result() except asyncio.CancelledError: # Since we created these tasks we should cancel them for x in pending: x.cancel() # we insure that all tasks are cancelled before we raise await asyncio.gather(*pending, return_exceptions=True) raise if not pending and next_arg == len(args): break for lst in input_map.values(): for i in range(1, len(lst)): ret[lst[i]] = ret[lst[0]] return ret async def gather_iter( itr: AnyIterable[MaybeAwaitable[T]], return_exceptions: bool = False, limit: int = -1, ) -> List[T]: """ Wrapper around gather to handle gathering an iterable instead of ``*args``. Note that the iterable values don't have to be awaitable. """ return await gather( *[maybe_await(i) async for i in aiter(itr)], return_exceptions=return_exceptions, limit=limit, ) omnilib-aioitertools-ff59bbd/aioitertools/builtins.py000066400000000000000000000226051466523024300233730ustar00rootroot00000000000000# Copyright 2022 Amethyst Reese # Licensed under the MIT license """ Async-compatible versions of builtin functions for iterables. These functions intentionally shadow their builtins counterparts, enabling use with both standard iterables and async iterables, without needing to use if/else clauses or awkward logic. Standard iterables get wrapped in async generators, and all functions are designed for use with `await`, `async for`, etc. """ import asyncio import builtins from enum import Enum from typing import ( Any, AsyncIterable, AsyncIterator, Callable, cast, Iterable, List, Optional, overload, Set, Tuple, Union, ) from . import asyncio as ait_asyncio from .helpers import maybe_await, Orderable from .types import ( AnyIterable, AnyIterator, AnyStop, MaybeAwaitable, R, T, T1, T2, T3, T4, T5, ) class Sentinel(Enum): """ :meta private: """ MISSING = object() async def all(itr: AnyIterable[MaybeAwaitable[Any]]) -> bool: """ Return True if all values are truthy in a mixed iterable, else False. The iterable will be fully consumed and any awaitables will automatically be awaited. Example:: if await all(it): ... """ return builtins.all(await ait_asyncio.gather_iter(itr)) async def any(itr: AnyIterable[MaybeAwaitable[Any]]) -> bool: """ Return True if any value is truthy in a mixed iterable, else False. The iterable will be fully consumed and any awaitables will automatically be awaited. Example:: if await any(it): ... """ return builtins.any(await ait_asyncio.gather_iter(itr)) def iter(itr: AnyIterable[T]) -> AsyncIterator[T]: """ Get an async iterator from any mixed iterable. Async iterators will be returned directly. Async iterables will return an async iterator. Standard iterables will be wrapped in an async generator yielding each item in the iterable in the same order. Examples:: async for value in iter(range(10)): ... """ if isinstance(itr, AsyncIterator): return itr if isinstance(itr, AsyncIterable): return itr.__aiter__() async def gen() -> AsyncIterator[T]: for item in cast(Iterable[T], itr): yield item return gen() @overload async def next(itr: AnyIterator[T]) -> T: # pragma: no cover ... @overload async def next(itr: AnyIterator[T1], default: T2) -> Union[T1, T2]: # pragma: no cover ... async def next( itr: AnyIterator[T1], default: Union[T2, Sentinel] = Sentinel.MISSING ) -> Union[T1, T2]: """ Return the next item of any mixed iterator. Calls builtins.next() on standard iterators, and awaits itr.__anext__() on async iterators. Example:: value = await next(it) """ try: if isinstance(itr, AsyncIterator): return await itr.__anext__() try: return builtins.next(itr) except StopIteration: raise StopAsyncIteration except StopAsyncIteration: if default is Sentinel.MISSING: raise return default async def list(itr: AnyIterable[T]) -> List[T]: """ Consume a mixed iterable and return a list of items in order. Example:: await list(range(5)) -> [0, 1, 2, 3, 4] """ return [item async for item in iter(itr)] async def tuple(itr: AnyIterable[T]) -> Tuple[T, ...]: """ Consume a mixed iterable and return a tuple of items in order. Example:: await tuple(range(5)) -> (0, 1, 2, 3, 4) """ # Suboptimal, but tuple can't be created from AsyncIterable directly. return builtins.tuple(await list(itr)) async def set(itr: AnyIterable[T]) -> Set[T]: """ Consume a mixed iterable and return a set of items. Example:: await set([0, 1, 2, 3, 0, 1, 2, 3]) -> {0, 1, 2, 3} """ return {item async for item in iter(itr)} async def enumerate( itr: AnyIterable[T], start: int = 0 ) -> AsyncIterator[Tuple[int, T]]: """ Consume a mixed iterable and yield the current index and item. Example:: async for index, value in enumerate(...): ... """ index = start async for item in iter(itr): yield index, item index += 1 async def map(fn: Callable[[T], R], itr: AnyIterable[T]) -> AsyncIterator[R]: """ Modify item of a mixed iterable using the given function or coroutine. Example:: async for response in map(func, data): ... """ # todo: queue items eagerly async for item in iter(itr): yield await maybe_await(fn(item)) @overload async def max( itr: AnyIterable[Orderable], *, key: Optional[Callable] = None ) -> Orderable: # pragma: no cover pass @overload async def max( itr: AnyIterable[Orderable], *, default: T, key: Optional[Callable] = None ) -> Union[Orderable, T]: # pragma: no cover pass async def max(itr: AnyIterable[Orderable], **kwargs: Any) -> Any: """ Return the largest item in an iterable or the largest of two or more arguments. Example:: await max(range(5)) -> 4 """ for k in kwargs: if k not in ("key", "default"): raise ValueError(f"kwarg {k} not supported") value: Orderable vkey: Any keyfunc = kwargs.get("key", None) it = iter(itr) try: value = await next(it) if keyfunc: vkey = keyfunc(value) except StopAsyncIteration: if "default" in kwargs: return kwargs["default"] raise ValueError("iterable is empty and no default value given") if keyfunc: async for item in it: ikey = keyfunc(item) if ikey > vkey: value = item vkey = ikey else: async for item in it: if item > value: value = item return value @overload async def min( itr: AnyIterable[Orderable], *, key: Optional[Callable] = None ) -> Orderable: # pragma: no cover pass @overload async def min( itr: AnyIterable[Orderable], *, default: T, key: Optional[Callable] = None ) -> Union[Orderable, T]: # pragma: no cover pass async def min(itr: AnyIterable[Orderable], **kwargs: Any) -> Any: """ Return the smallest item in an iterable or the smallest of two or more arguments. Example:: await min(range(5)) -> 0 """ for k in kwargs: if k not in ("key", "default"): raise ValueError(f"kwarg {k} not supported") value: Orderable vkey: Any keyfunc = kwargs.get("key", None) it = iter(itr) try: value = await next(it) if keyfunc: vkey = keyfunc(value) except StopAsyncIteration: if "default" in kwargs: return kwargs["default"] raise ValueError("iterable is empty and no default value given") if keyfunc: async for item in it: ikey = keyfunc(item) if ikey < vkey: value = item vkey = ikey else: async for item in it: if item < value: value = item return value async def sum(itr: AnyIterable[T], start: Optional[T] = None) -> T: """ Compute the sum of a mixed iterable, adding each value with the start value. Example:: await sum(generator()) -> 1024 """ value: T if start is None: value = cast(T, 0) # emulate stdlib but still type nicely for non-ints else: value = start async for item in iter(itr): value += item # type: ignore # mypy doesn't know T + T return value @overload def zip(__iter1: AnyIterable[T1]) -> AsyncIterator[Tuple[T1]]: # pragma: no cover pass @overload def zip( __iter1: AnyIterable[T1], __iter2: AnyIterable[T2] ) -> AsyncIterator[Tuple[T1, T2]]: # pragma: no cover pass @overload def zip( __iter1: AnyIterable[T1], __iter2: AnyIterable[T2], __iter3: AnyIterable[T3] ) -> AsyncIterator[Tuple[T1, T2, T3]]: # pragma: no cover pass @overload def zip( __iter1: AnyIterable[T1], __iter2: AnyIterable[T2], __iter3: AnyIterable[T3], __iter4: AnyIterable[T4], ) -> AsyncIterator[Tuple[T1, T2, T3, T4]]: # pragma: no cover pass @overload def zip( __iter1: AnyIterable[T1], __iter2: AnyIterable[T2], __iter3: AnyIterable[T3], __iter4: AnyIterable[T4], __iter5: AnyIterable[T5], ) -> AsyncIterator[Tuple[T1, T2, T3, T4, T5]]: # pragma: no cover pass @overload def zip( __iter1: AnyIterable[Any], __iter2: AnyIterable[Any], __iter3: AnyIterable[Any], __iter4: AnyIterable[Any], __iter5: AnyIterable[Any], __iter6: AnyIterable[Any], *__iterables: AnyIterable[Any], ) -> AsyncIterator[Tuple[Any, ...]]: # pragma: no cover pass async def zip(*itrs: AnyIterable[Any]) -> AsyncIterator[Tuple[Any, ...]]: """ Yield a tuple of items from mixed iterables until the shortest is consumed. Example:: async for a, b, c in zip(i, j, k): ... """ its: List[AsyncIterator[Any]] = [iter(itr) for itr in itrs] while True: values = await asyncio.gather( *[it.__anext__() for it in its], return_exceptions=True ) if builtins.any(isinstance(v, AnyStop) for v in values): break yield builtins.tuple(values) omnilib-aioitertools-ff59bbd/aioitertools/helpers.py000066400000000000000000000011221466523024300231730ustar00rootroot00000000000000# Copyright 2022 Amethyst Reese # Licensed under the MIT license import inspect import sys from typing import Awaitable, Union from .types import T if sys.version_info < (3, 8): # pragma: no cover from typing_extensions import Protocol else: # pragma: no cover from typing import Protocol class Orderable(Protocol): # pragma: no cover def __lt__(self, other): ... def __gt__(self, other): ... async def maybe_await(object: Union[Awaitable[T], T]) -> T: if inspect.isawaitable(object): return await object # type: ignore return object # type: ignore omnilib-aioitertools-ff59bbd/aioitertools/itertools.py000066400000000000000000000354611466523024300235720ustar00rootroot00000000000000# Copyright 2022 Amethyst Reese # Licensed under the MIT license """ Async-compatible version of itertools standard library functions. These functions build on top of the async builtins components, enabling use of both standard iterables and async iterables, without needing to use if/else clauses or awkward logic. Standard iterables get wrapped in async generators, and all functions are designed for use with `await`, `async for`, etc. See https://docs.python.org/3/library/itertools.html for reference. """ import asyncio import builtins import itertools import operator from typing import Any, AsyncIterator, List, Optional, overload, Tuple from .builtins import enumerate, iter, list, next, tuple, zip from .helpers import maybe_await from .types import ( Accumulator, AnyFunction, AnyIterable, AnyIterableIterable, AnyStop, KeyFunction, N, Predicate, R, T, ) async def accumulate( itr: AnyIterable[T], func: Accumulator[T] = operator.add ) -> AsyncIterator[T]: """ Yield the running accumulation of an iterable and operator. Accepts both a standard function or a coroutine for accumulation. Example:: data = [1, 2, 3, 4] async def mul(a, b): return a * b async for total in accumulate(data, func=mul): ... # 1, 2, 6, 24 """ itr = iter(itr) try: total: T = await next(itr) except AnyStop: return yield total async for item in itr: total = await maybe_await(func(total, item)) yield total async def batched( iterable: AnyIterable[T], n: int, *, strict: bool = False, ) -> AsyncIterator[Tuple[T, ...]]: """ Yield batches of values from the given iterable. The final batch may be shorter. Example:: async for batch in batched(range(15), 5): ... # (0, 1, 2, 3, 4), (5, 6, 7, 8, 9), (10, 11, 12, 13, 14) """ if n < 1: raise ValueError("n must be at least one") aiterator = iter(iterable) while batch := await tuple(islice(aiterator, n)): if strict and len(batch) != n: raise ValueError("batched: incomplete batch") yield batch class Chain: def __call__(self, *itrs: AnyIterable[T]) -> AsyncIterator[T]: """ Yield values from one or more iterables in series. Consumes the first iterable lazily, in entirety, then the second, and so on. Example:: async for value in chain([1, 2, 3], [7, 8, 9]): ... # 1, 2, 3, 7, 8, 9 """ return self.from_iterable(itrs) async def from_iterable(self, itrs: AnyIterableIterable[T]) -> AsyncIterator[T]: """ Like chain, but takes an iterable of iterables. Alias for chain(*itrs) """ async for itr in iter(itrs): async for item in iter(itr): yield item chain = Chain() async def combinations(itr: AnyIterable[T], r: int) -> AsyncIterator[Tuple[T, ...]]: """ Yield r length subsequences from the given iterable. Simple wrapper around itertools.combinations for asyncio. This will consume the entire iterable before yielding values. Example:: async for value in combinations(range(4), 3): ... # (0, 1, 2), (0, 1, 3), (0, 2, 3), (1, 2, 3) """ pool: List[T] = await list(itr) for value in itertools.combinations(pool, r): yield value async def combinations_with_replacement( itr: AnyIterable[T], r: int ) -> AsyncIterator[Tuple[T, ...]]: """ Yield r length subsequences from the given iterable with replacement. Simple wrapper around itertools.combinations_with_replacement. This will consume the entire iterable before yielding values. Example:: async for value in combinations_with_replacement("ABC", 2): ... # ("A", "A"), ("A", "B"), ("A", "C"), ("B", "B"), ... """ pool: List[T] = await list(itr) for value in itertools.combinations_with_replacement(pool, r): yield value async def compress( itr: AnyIterable[T], selectors: AnyIterable[Any] ) -> AsyncIterator[T]: """ Yield elements only when the corresponding selector evaluates to True. Stops when either the iterable or the selectors have been exhausted. Example:: async for value in compress(range(5), [1, 0, 0, 1, 1]): ... # 0, 3, 4 """ async for value, selector in zip(itr, selectors): if selector: yield value async def count(start: N = 0, step: N = 1) -> AsyncIterator[N]: """ Yield an infinite series, starting at the given value and increasing by step. Example:: async for value in counter(10, -1): ... # 10, 9, 8, 7, ... """ value = start while True: yield value value += step async def cycle(itr: AnyIterable[T]) -> AsyncIterator[T]: """ Yield a repeating series from the given iterable. Lazily consumes the iterable when the next value is needed, and caching the values in memory for future iterations of the series. Example:: async for value in cycle([1, 2]): ... # 1, 2, 1, 2, 1, 2, ... """ items = [] async for item in iter(itr): yield item items.append(item) while True: for item in items: yield item async def dropwhile( predicate: Predicate[T], iterable: AnyIterable[T] ) -> AsyncIterator[T]: """ Drops all items until the predicate evaluates False; yields all items afterwards. Accepts both standard functions and coroutines for the predicate. Example:: def pred(x): return x < 4 async for item in dropwhile(pred, range(6)): ... # 4, 5, 6 """ itr = iter(iterable) async for item in itr: if not await maybe_await(predicate(item)): yield item break async for item in itr: yield item async def filterfalse( predicate: Predicate[T], iterable: AnyIterable[T] ) -> AsyncIterator[T]: """ Yield items from the iterable only when the predicate evaluates to False. Accepts both standard functions and coroutines for the predicate. Example:: def pred(x): return x < 4 async for item in filterfalse(pred, range(6)): ... # 4, 5 """ async for item in iter(iterable): if not await maybe_await(predicate(item)): yield item @overload def groupby(itr: AnyIterable[T]) -> AsyncIterator[Tuple[T, List[T]]]: # pragma: nocover pass @overload def groupby( itr: AnyIterable[T], key: KeyFunction[T, R] ) -> AsyncIterator[Tuple[R, List[T]]]: # pragma: nocover pass async def groupby( itr: AnyIterable[T], key: Optional[KeyFunction[T, R]] = None ) -> AsyncIterator[Tuple[Any, List[T]]]: """ Yield consecutive keys and groupings from the given iterable. Items will be grouped based on the key function, which defaults to the identity of each item. Accepts both standard functions and coroutines for the key function. Suggest sorting by the key function before using groupby. Example:: data = ["A", "a", "b", "c", "C", "c"] async for key, group in groupby(data, key=str.lower): key # "a", "b", "c" group # ["A", "a"], ["b"], ["c", "C", "c"] """ if key is None: key = lambda x: x # noqa: E731 grouping: List[T] = [] it = iter(itr) try: item = await next(it) except StopAsyncIteration: return grouping = [item] j = await maybe_await(key(item)) async for item in it: k = await maybe_await(key(item)) if k != j: yield j, grouping grouping = [item] else: grouping.append(item) j = k yield j, grouping @overload def islice( itr: AnyIterable[T], __stop: Optional[int] ) -> AsyncIterator[T]: # pragma: nocover pass @overload def islice( itr: AnyIterable[T], __start: int, __stop: Optional[int], __step: int = 1 ) -> AsyncIterator[T]: # pragma: nocover pass async def islice(itr: AnyIterable[T], *args: Optional[int]) -> AsyncIterator[T]: """ Yield selected items from the given iterable. islice(iterable, stop) islice(iterable, start, stop[, step]) Starting from the start index (or zero), stopping at the stop index (or until exhausted), skipping items if step > 0. Example:: data = range(10) async for item in islice(data, 5): ... # 0, 1, 2, 3, 4 async for item in islice(data, 2, 5): ... # 2, 3, 4 async for item in islice(data, 1, 7, 2): ... # 1, 3, 5 """ start = 0 step = 1 if not args: raise ValueError("must pass stop index") if len(args) == 1: (stop,) = args elif len(args) == 2: start, stop = args # type: ignore elif len(args) == 3: start, stop, step = args # type: ignore else: raise ValueError("too many arguments given") assert start >= 0 and (stop is None or stop >= 0) and step >= 0 step = max(1, step) if stop == 0: return async for index, item in enumerate(itr): if index >= start and (index - start) % step == 0: yield item if stop is not None and index + 1 >= stop: break async def permutations( itr: AnyIterable[T], r: Optional[int] = None ) -> AsyncIterator[Tuple[T, ...]]: """ Yield r length permutations of elements in the iterable. Simple wrapper around itertools.combinations for asyncio. This will consume the entire iterable before yielding values. Example:: async for value in permutations(range(3)): ... # (0, 1, 2), (0, 2, 1), (1, 0, 2), ... """ pool: List[T] = await list(itr) for value in itertools.permutations(pool, r): yield value async def product( *itrs: AnyIterable[T], repeat: int = 1 ) -> AsyncIterator[Tuple[T, ...]]: """ Yield cartesian products of all iterables. Simple wrapper around itertools.combinations for asyncio. This will consume all iterables before yielding any values. Example:: async for value in product("abc", "xy"): ... # ("a", "x"), ("a", "y"), ("b", "x"), ... async for value in product(range(3), repeat=3): ... # (0, 0, 0), (0, 0, 1), (0, 0, 2), ... """ pools = await asyncio.gather(*[list(itr) for itr in itrs]) for value in itertools.product(*pools, repeat=repeat): yield value async def repeat(elem: T, n: int = -1) -> AsyncIterator[T]: """ Yield the given value repeatedly, forever or up to n times. Example:: async for value in repeat(7): ... # 7, 7, 7, 7, 7, 7, ... """ while True: if n == 0: break yield elem n -= 1 async def starmap( fn: AnyFunction[R], iterable: AnyIterableIterable[Any] ) -> AsyncIterator[R]: """ Yield values from a function using an iterable of iterables for arguments. Each iterable contained within will be unpacked and consumed before executing the function or coroutine. Example:: data = [(1, 1), (1, 1, 1), (2, 2)] async for value in starmap(operator.add, data): ... # 2, 3, 4 """ async for itr in iter(iterable): args = await list(itr) yield await maybe_await(fn(*args)) async def takewhile( predicate: Predicate[T], iterable: AnyIterable[T] ) -> AsyncIterator[T]: """ Yield values from the iterable until the predicate evaluates False. Accepts both standard functions and coroutines for the predicate. Example:: def pred(x): return x < 4 async for value in takewhile(pred, range(8)): ... # 0, 1, 2, 3 """ async for item in iter(iterable): if await maybe_await(predicate(item)): yield item else: break def tee(itr: AnyIterable[T], n: int = 2) -> Tuple[AsyncIterator[T], ...]: """ Return n iterators that each yield items from the given iterable. The first iterator lazily fetches from the original iterable, and then queues the values for the other iterators to yield when needed. Caveat: all iterators are dependent on the first iterator – if it is consumed more slowly than the rest, the other consumers will be blocked until the first iterator continues forward. Similarly, if the first iterator is consumed more quickly than the rest, more memory will be used in keeping values in the queues until the other iterators finish consuming them. Example:: it1, it2 = tee(range(5), n=2) async for value in it1: ... # 0, 1, 2, 3, 4 async for value in it2: ... # 0, 1, 2, 3, 4 """ assert n > 0 sentinel = object() queues: List[asyncio.Queue] = [asyncio.Queue() for k in range(n)] async def gen(k: int, q: asyncio.Queue) -> AsyncIterator[T]: if k == 0: try: async for value in iter(itr): await asyncio.gather( *[queue.put((None, value)) for queue in queues[1:]] ) yield value except Exception as e: await asyncio.gather(*[queue.put((e, None)) for queue in queues[1:]]) raise await asyncio.gather(*[queue.put((None, sentinel)) for queue in queues[1:]]) else: while True: error, value = await q.get() if error is not None: raise error if value is sentinel: break yield value return builtins.tuple(gen(k, q) for k, q in builtins.enumerate(queues)) async def zip_longest( *itrs: AnyIterable[Any], fillvalue: Any = None ) -> AsyncIterator[Tuple[Any, ...]]: """ Yield a tuple of items from mixed iterables until all are consumed. If shorter iterables are exhausted, the default value will be used until all iterables are exhausted. Example:: a = range(3) b = range(5) async for a, b in zip_longest(a, b, fillvalue=-1): a # 0, 1, 2, -1, -1 b # 0, 1, 2, 3, 4 """ its: List[AsyncIterator[Any]] = [iter(itr) for itr in itrs] itr_count = len(its) finished = 0 while True: values = await asyncio.gather( *[it.__anext__() for it in its], return_exceptions=True ) for idx, value in builtins.enumerate(values): if isinstance(value, AnyStop): finished += 1 values[idx] = fillvalue its[idx] = repeat(fillvalue) elif isinstance(value, BaseException): raise value if finished >= itr_count: break yield builtins.tuple(values) omnilib-aioitertools-ff59bbd/aioitertools/more_itertools.py000066400000000000000000000047511466523024300246120ustar00rootroot00000000000000# Copyright 2022 Amethyst Reese # Licensed under the MIT license import asyncio from typing import AsyncIterable, List, Tuple, TypeVar from aioitertools.helpers import maybe_await from .builtins import iter from .itertools import islice from .types import AnyIterable, Predicate T = TypeVar("T") async def take(n: int, iterable: AnyIterable[T]) -> List[T]: """ Return the first n items of iterable as a list. If there are too few items in iterable, all of them are returned. n needs to be at least 0. If it is 0, an empty list is returned. Example:: first_two = await take(2, [1, 2, 3, 4, 5]) """ if n < 0: raise ValueError("take's first parameter can't be negative") return [item async for item in islice(iterable, n)] async def chunked(iterable: AnyIterable[T], n: int) -> AsyncIterable[List[T]]: """ Break iterable into chunks of length n. The last chunk will be shorter if the total number of items is not divisible by n. Example:: async for chunk in chunked([1, 2, 3, 4, 5], n=2): ... # first iteration: chunk == [1, 2]; last one: chunk == [5] """ it = iter(iterable) chunk = await take(n, it) while chunk != []: yield chunk chunk = await take(n, it) async def before_and_after( predicate: Predicate[T], iterable: AnyIterable[T] ) -> Tuple[AsyncIterable[T], AsyncIterable[T]]: """ A variant of :func:`aioitertools.takewhile` that allows complete access to the remainder of the iterator. >>> it = iter('ABCdEfGhI') >>> all_upper, remainder = await before_and_after(str.isupper, it) >>> ''.join([char async for char in all_upper]) 'ABC' >>> ''.join([char async for char in remainder]) 'dEfGhI' Note that the first iterator must be fully consumed before the second iterator can generate valid results. """ it = iter(iterable) transition = asyncio.get_event_loop().create_future() async def true_iterator(): async for elem in it: if await maybe_await(predicate(elem)): yield elem else: transition.set_result(elem) return transition.set_exception(StopAsyncIteration) async def remainder_iterator(): try: yield (await transition) except StopAsyncIteration: return async for elm in it: yield elm return true_iterator(), remainder_iterator() omnilib-aioitertools-ff59bbd/aioitertools/py.typed000066400000000000000000000000001466523024300226500ustar00rootroot00000000000000omnilib-aioitertools-ff59bbd/aioitertools/tests/000077500000000000000000000000001466523024300223255ustar00rootroot00000000000000omnilib-aioitertools-ff59bbd/aioitertools/tests/__init__.py000066400000000000000000000003721466523024300244400ustar00rootroot00000000000000# Copyright 2022 Amethyst Reese # Licensed under the MIT license from .asyncio import AsyncioTest from .builtins import BuiltinsTest from .helpers import HelpersTest from .itertools import ItertoolsTest from .more_itertools import MoreItertoolsTest omnilib-aioitertools-ff59bbd/aioitertools/tests/__main__.py000066400000000000000000000002761466523024300244240ustar00rootroot00000000000000# Copyright 2022 Amethyst Reese # Licensed under the MIT license import unittest if __name__ == "__main__": # pragma: no cover unittest.main(module="aioitertools.tests", verbosity=2) omnilib-aioitertools-ff59bbd/aioitertools/tests/asyncio.py000066400000000000000000000171101466523024300243440ustar00rootroot00000000000000# Copyright 2022 Amethyst Reese # Licensed under the MIT license import asyncio from unittest import TestCase import aioitertools as ait import aioitertools.asyncio as aio from .helpers import async_test slist = ["A", "B", "C"] srange = range(3) class AsyncioTest(TestCase): def test_import(self): self.assertEqual(ait.asyncio, aio) @async_test async def test_as_completed(self): async def sleepy(number, duration): await asyncio.sleep(duration) return number pairs = [(1, 0.3), (2, 0.1), (3, 0.5)] expected = [2, 1, 3] futures = [sleepy(*pair) for pair in pairs] results = await ait.list(aio.as_completed(futures)) self.assertEqual(results, expected) futures = [sleepy(*pair) for pair in pairs] results = [] async for value in aio.as_completed(futures): results.append(value) self.assertEqual(results, expected) @async_test async def test_as_completed_timeout(self): calls = [(1.0,), (0.1,)] futures = [asyncio.sleep(*args) for args in calls] with self.assertRaises(asyncio.TimeoutError): await ait.list(aio.as_completed(futures, timeout=0.5)) futures = [asyncio.sleep(*args) for args in calls] results = 0 with self.assertRaises(asyncio.TimeoutError): async for _ in aio.as_completed(futures, timeout=0.5): results += 1 self.assertEqual(results, 1) @async_test async def test_as_generated(self): async def gen(): for i in range(10): yield i await asyncio.sleep(0) gens = [gen(), gen(), gen()] expected = list(range(10)) * 3 results = [] async for value in aio.as_generated(gens): results.append(value) self.assertEqual(30, len(results)) self.assertListEqual(sorted(expected), sorted(results)) @async_test async def test_as_generated_exception(self): async def gen1(): for i in range(3): yield i await asyncio.sleep(0) raise Exception("fake") async def gen2(): for i in range(10): yield i await asyncio.sleep(0) gens = [gen1(), gen2()] results = [] with self.assertRaisesRegex(Exception, "fake"): async for value in aio.as_generated(gens): results.append(value) self.assertNotIn(10, results) @async_test async def test_as_generated_return_exception(self): async def gen1(): for i in range(3): yield i await asyncio.sleep(0) raise Exception("fake") async def gen2(): for i in range(10): yield i await asyncio.sleep(0) gens = [gen1(), gen2()] expected = list(range(3)) + list(range(10)) errors = [] results = [] async for value in aio.as_generated(gens, return_exceptions=True): if isinstance(value, Exception): errors.append(value) else: results.append(value) self.assertListEqual(sorted(expected), sorted(results)) self.assertEqual(1, len(errors)) self.assertIsInstance(errors[0], Exception) @async_test async def test_as_generated_task_cancelled(self): async def gen(max: int = 10): for i in range(5): if i > max: raise asyncio.CancelledError yield i await asyncio.sleep(0) gens = [gen(2), gen()] expected = list(range(3)) + list(range(5)) results = [] async for value in aio.as_generated(gens): results.append(value) self.assertListEqual(sorted(expected), sorted(results)) @async_test async def test_as_generated_cancelled(self): async def gen(): for i in range(5): yield i await asyncio.sleep(0.1) expected = [0, 0, 1, 1] results = [] async def foo(): gens = [gen(), gen()] async for value in aio.as_generated(gens): results.append(value) return results task = asyncio.ensure_future(foo()) await asyncio.sleep(0.15) task.cancel() await task self.assertListEqual(sorted(expected), sorted(results)) @async_test async def test_gather_input_types(self): async def fn(arg): await asyncio.sleep(0.001) return arg fns = [fn(1), asyncio.ensure_future(fn(2))] if hasattr(asyncio, "create_task"): # 3.7 only fns.append(asyncio.create_task(fn(3))) else: fns.append(fn(3)) result = await aio.gather(*fns) self.assertEqual([1, 2, 3], result) @async_test async def test_gather_limited(self): max_counter = 0 counter = 0 async def fn(arg): nonlocal counter, max_counter counter += 1 max_counter = max(max_counter, counter) await asyncio.sleep(0.001) counter -= 1 return arg # Limit of 2 result = await aio.gather(*[fn(i) for i in range(10)], limit=2) self.assertEqual(2, max_counter) self.assertEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], result) # No limit result = await aio.gather(*[fn(i) for i in range(10)]) self.assertEqual( 10, max_counter ) # TODO: on a loaded machine this might be less? self.assertEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], result) @async_test async def test_gather_limited_dupes(self): async def fn(arg): await asyncio.sleep(0.001) return arg f = fn(1) g = fn(2) result = await aio.gather(f, f, f, g, f, g, limit=2) self.assertEqual([1, 1, 1, 2, 1, 2], result) f = fn(1) g = fn(2) result = await aio.gather(f, f, f, g, f, g) self.assertEqual([1, 1, 1, 2, 1, 2], result) @async_test async def test_gather_with_exceptions(self): class MyException(Exception): pass async def fn(arg, fail=False): await asyncio.sleep(arg) if fail: raise MyException(arg) return arg with self.assertRaises(MyException): await aio.gather(fn(0.002, fail=True), fn(0.001)) result = await aio.gather( fn(0.002, fail=True), fn(0.001), return_exceptions=True ) self.assertEqual(result[1], 0.001) self.assertIsInstance(result[0], MyException) @async_test async def test_gather_cancel(self): cancelled = False started = False async def _fn(): nonlocal started, cancelled try: started = True await asyncio.sleep(10) # might as well be forever except asyncio.CancelledError: nonlocal cancelled cancelled = True raise async def _gather(): await aio.gather(_fn()) if hasattr(asyncio, "create_task"): # 3.7+ only task = asyncio.create_task(_gather()) else: task = asyncio.ensure_future(_gather()) # to insure the gather actually runs await asyncio.sleep(0) task.cancel() with self.assertRaises(asyncio.CancelledError): await task self.assertTrue(started) self.assertTrue(cancelled) omnilib-aioitertools-ff59bbd/aioitertools/tests/builtins.py000066400000000000000000000246751466523024300245460ustar00rootroot00000000000000# Copyright 2022 Amethyst Reese # Licensed under the MIT license import asyncio from typing import AsyncIterator from unittest import TestCase import aioitertools as ait from .helpers import async_test slist = ["A", "B", "C"] srange = range(3) srange1 = range(1, 4) srange0 = range(1) class BuiltinsTest(TestCase): # aioitertools.all() @async_test async def test_all_list(self): self.assertTrue(await ait.all([True, 1, "string"])) self.assertFalse(await ait.all([True, 0, "string"])) @async_test async def test_all_range(self): self.assertTrue(await ait.all(srange1)) self.assertFalse(await ait.all(srange)) @async_test async def test_all_generator(self): self.assertTrue(await ait.all(x for x in srange1)) self.assertFalse(await ait.all(x for x in srange)) @async_test async def test_all_async_generator(self): self.assertTrue(await ait.all(ait.iter(srange1))) self.assertFalse(await ait.all(ait.iter(srange))) # aioitertools.any() @async_test async def test_any_list(self): self.assertTrue(await ait.any([False, 1, ""])) self.assertFalse(await ait.any([False, 0, ""])) @async_test async def test_any_range(self): self.assertTrue(await ait.any(srange)) self.assertTrue(await ait.any(srange1)) self.assertFalse(await ait.any(srange0)) @async_test async def test_any_generator(self): self.assertTrue(await ait.any(x for x in srange)) self.assertTrue(await ait.any(x for x in srange1)) self.assertFalse(await ait.any(x for x in srange0)) @async_test async def test_any_async_generator(self): self.assertTrue(await ait.any(ait.iter(srange))) self.assertTrue(await ait.any(ait.iter(srange1))) self.assertFalse(await ait.any(ait.iter(srange0))) # aioitertools.iter() @async_test async def test_iter_list(self): it = ait.iter(slist) self.assertIsInstance(it, AsyncIterator) idx = 0 async for item in it: self.assertEqual(item, slist[idx]) idx += 1 @async_test async def test_iter_range(self): it = ait.iter(srange) self.assertIsInstance(it, AsyncIterator) idx = 0 async for item in it: self.assertEqual(item, srange[idx]) idx += 1 @async_test async def test_iter_iterable(self): sentinel = object() class async_iterable: def __aiter__(self): return sentinel aiter = async_iterable() self.assertEqual(ait.iter(aiter), sentinel) @async_test async def test_iter_iterator(self): sentinel = object() class async_iterator: def __aiter__(self): return sentinel def __anext__(self): return sentinel aiter = async_iterator() self.assertEqual(ait.iter(aiter), aiter) @async_test async def test_iter_async_generator(self): async def async_gen(): yield 1 yield 2 agen = async_gen() self.assertEqual(ait.iter(agen), agen) # aioitertools.next() @async_test async def test_next_list(self): it = ait.iter(slist) self.assertEqual(await ait.next(it), "A") self.assertEqual(await ait.next(it), "B") self.assertEqual(await ait.next(it), "C") with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_next_range(self): it = ait.iter(srange) self.assertEqual(await ait.next(it), 0) self.assertEqual(await ait.next(it), 1) self.assertEqual(await ait.next(it), 2) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_next_iterable(self): class async_iter: def __init__(self): self.index = 0 def __aiter__(self): return self def __anext__(self): if self.index > 2: raise StopAsyncIteration() return self.fake_next() async def fake_next(self): value = slist[self.index] self.index += 1 return value it = ait.iter(async_iter()) self.assertEqual(await ait.next(it), "A") self.assertEqual(await ait.next(it), "B") self.assertEqual(await ait.next(it), "C") with self.assertRaises(StopAsyncIteration): await ait.next(it) it = iter(slist) self.assertEqual(await ait.next(it), "A") self.assertEqual(await ait.next(it), "B") self.assertEqual(await ait.next(it), "C") with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_next_async_generator(self): async def async_gen(): for item in slist: yield item it = ait.iter(async_gen()) self.assertEqual(await ait.next(it), "A") self.assertEqual(await ait.next(it), "B") self.assertEqual(await ait.next(it), "C") with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_next_default_iterable(self): it = iter(["A"]) self.assertEqual(await ait.next(it, "?"), "A") # End of iteration self.assertEqual(await ait.next(it, "?"), "?") @async_test async def test_next_default_async_iterable(self): it = ait.iter(["A"]) self.assertEqual(await ait.next(it, "?"), "A") # End of iteration self.assertEqual(await ait.next(it, "?"), "?") # aioitertools.list() @async_test async def test_list(self): self.assertEqual(await ait.list(ait.iter(slist)), slist) @async_test async def test_tuple(self): self.assertEqual(await ait.tuple(ait.iter(slist)), tuple(slist)) # aioitertools.set() @async_test async def test_set(self): self.assertEqual(await ait.set(ait.iter(slist)), set(slist)) # aioitertools.enumerate() @async_test async def test_enumerate(self): async for index, value in ait.enumerate(slist): self.assertEqual(value, slist[index]) @async_test async def test_enumerate_start(self): async for index, value in ait.enumerate(slist, 4): self.assertEqual(value, slist[index - 4]) # aioitertools.map() @async_test async def test_map_function_list(self): idx = 0 async for value in ait.map(str.lower, slist): self.assertEqual(value, slist[idx].lower()) idx += 1 @async_test async def test_map_function_async_generator(self): async def gen(): for item in slist: yield item idx = 0 async for value in ait.map(str.lower, gen()): self.assertEqual(value, slist[idx].lower()) idx += 1 @async_test async def test_map_coroutine_list(self): async def double(x): await asyncio.sleep(0.0001) return x * 2 idx = 0 async for value in ait.map(double, slist): self.assertEqual(value, slist[idx] * 2) idx += 1 @async_test async def test_map_coroutine_generator(self): async def gen(): for item in slist: yield item async def double(x): await asyncio.sleep(0.0001) return x * 2 idx = 0 async for value in ait.map(double, gen()): self.assertEqual(value, slist[idx] * 2) idx += 1 # aioitertools.max() @async_test async def test_max_basic(self): async def gen(): for item in slist: yield item self.assertEqual(await ait.max(gen()), "C") self.assertEqual(await ait.max(range(4)), 3) with self.assertRaisesRegex(ValueError, "iterable is empty"): await ait.max([]) with self.assertRaisesRegex(ValueError, "kwarg .+ not supported"): await ait.max(None, foo="foo") @async_test async def test_max_default(self): self.assertEqual(await ait.max(range(2), default="x"), 1) self.assertEqual(await ait.max([], default="x"), "x") self.assertEqual(await ait.max([], default=None), None) @async_test async def test_max_key(self): words = ["star", "buzz", "guard"] def reverse(s): return s[::-1] self.assertEqual(reverse("python"), "nohtyp") self.assertEqual(await ait.max(words), "star") self.assertEqual(await ait.max(words, key=reverse), "buzz") # aioitertools.min() @async_test async def test_min_basic(self): async def gen(): for item in slist: yield item self.assertEqual(await ait.min(gen()), "A") self.assertEqual(await ait.min(range(4)), 0) with self.assertRaisesRegex(ValueError, "iterable is empty"): await ait.min([]) with self.assertRaisesRegex(ValueError, "kwarg .+ not supported"): await ait.min(None, foo="foo") @async_test async def test_min_default(self): self.assertEqual(await ait.min(range(2), default="x"), 0) self.assertEqual(await ait.min([], default="x"), "x") self.assertEqual(await ait.min([], default=None), None) @async_test async def test_min_key(self): words = ["star", "buzz", "guard"] def reverse(s): return s[::-1] self.assertEqual(reverse("python"), "nohtyp") self.assertEqual(await ait.min(words), "buzz") self.assertEqual(await ait.min(words, key=reverse), "guard") # aioitertools.sum() @async_test async def test_sum_range_default(self): self.assertEqual(await ait.sum(srange), sum(srange)) @async_test async def test_sum_list_string(self): self.assertEqual(await ait.sum(slist, "foo"), "fooABC") # aioitertools.zip() @async_test async def test_zip_equal(self): idx = 0 async for a, b in ait.zip(slist, srange): self.assertEqual(a, slist[idx]) self.assertEqual(b, srange[idx]) idx += 1 @async_test async def test_zip_shortest(self): short = ["a", "b", "c"] long = [0, 1, 2, 3, 5] result = await ait.list(ait.zip(short, long)) expected = [("a", 0), ("b", 1), ("c", 2)] self.assertListEqual(expected, result) omnilib-aioitertools-ff59bbd/aioitertools/tests/helpers.py000066400000000000000000000026611466523024300243460ustar00rootroot00000000000000# Copyright 2022 Amethyst Reese # Licensed under the MIT license import asyncio import functools import sys from unittest import skipIf, TestCase from aioitertools.helpers import maybe_await def async_test(fn): def wrapped(*args, **kwargs): try: loop = asyncio.new_event_loop() loop.set_debug(False) result = loop.run_until_complete(fn(*args, **kwargs)) return result finally: loop.close() return wrapped class HelpersTest(TestCase): # aioitertools.helpers.maybe_await() @async_test async def test_maybe_await(self): self.assertEqual(await maybe_await(42), 42) @async_test async def test_maybe_await_async_def(self): async def forty_two(): await asyncio.sleep(0.0001) return 42 self.assertEqual(await maybe_await(forty_two()), 42) @skipIf(sys.version_info >= (3, 11), "@asyncio.coroutine removed") @async_test async def test_maybe_await_coroutine(self): @asyncio.coroutine def forty_two(): yield from asyncio.sleep(0.0001) return 42 self.assertEqual(await maybe_await(forty_two()), 42) @async_test async def test_maybe_await_partial(self): async def multiply(a, b): await asyncio.sleep(0.0001) return a * b self.assertEqual(await maybe_await(functools.partial(multiply, 6)(7)), 42) omnilib-aioitertools-ff59bbd/aioitertools/tests/itertools.py000066400000000000000000000547571466523024300247450ustar00rootroot00000000000000# Copyright 2022 Amethyst Reese # Licensed under the MIT license import asyncio import operator from unittest import TestCase import aioitertools as ait from .helpers import async_test slist = ["A", "B", "C"] srange = range(1, 4) class ItertoolsTest(TestCase): @async_test async def test_accumulate_range_default(self): it = ait.accumulate(srange) for k in [1, 3, 6]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_accumulate_range_function(self): it = ait.accumulate(srange, func=operator.mul) for k in [1, 2, 6]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_accumulate_range_coroutine(self): async def mul(a, b): return a * b it = ait.accumulate(srange, func=mul) for k in [1, 2, 6]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_accumulate_gen_function(self): async def gen(): yield 1 yield 2 yield 4 it = ait.accumulate(gen(), func=operator.mul) for k in [1, 2, 8]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_accumulate_gen_coroutine(self): async def mul(a, b): return a * b async def gen(): yield 1 yield 2 yield 4 it = ait.accumulate(gen(), func=mul) for k in [1, 2, 8]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_accumulate_empty(self): values = [] async for value in ait.accumulate([]): values.append(value) self.assertEqual(values, []) @async_test async def test_batched(self): test_matrix = [ ([], 1, []), ([1, 2, 3], 1, [(1,), (2,), (3,)]), ([2, 3, 4], 2, [(2, 3), (4,)]), ([5, 6], 3, [(5, 6)]), (ait.iter([-2, -1, 0, 1, 2]), 2, [(-2, -1), (0, 1), (2,)]), ] for iterable, batch_size, answer in test_matrix: result = [batch async for batch in ait.batched(iterable, batch_size)] self.assertEqual(result, answer) @async_test async def test_batched_errors(self): with self.assertRaisesRegex(ValueError, "n must be at least one"): [batch async for batch in ait.batched([1], 0)] with self.assertRaisesRegex(ValueError, "incomplete batch"): [batch async for batch in ait.batched([1, 2, 3], 2, strict=True)] @async_test async def test_chain_lists(self): it = ait.chain(slist, srange) for k in ["A", "B", "C", 1, 2, 3]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_chain_list_gens(self): async def gen(): for k in range(2, 9, 2): yield k it = ait.chain(slist, gen()) for k in ["A", "B", "C", 2, 4, 6, 8]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_chain_from_iterable(self): async def gen(): for k in range(2, 9, 2): yield k it = ait.chain.from_iterable([slist, gen()]) for k in ["A", "B", "C", 2, 4, 6, 8]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_chain_from_iterable_parameter_expansion_gen(self): async def gen(): for k in range(2, 9, 2): yield k async def parameters_gen(): yield slist yield gen() it = ait.chain.from_iterable(parameters_gen()) for k in ["A", "B", "C", 2, 4, 6, 8]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_combinations(self): it = ait.combinations(range(4), 3) for k in [(0, 1, 2), (0, 1, 3), (0, 2, 3), (1, 2, 3)]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_combinations_with_replacement(self): it = ait.combinations_with_replacement(slist, 2) for k in [ ("A", "A"), ("A", "B"), ("A", "C"), ("B", "B"), ("B", "C"), ("C", "C"), ]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_compress_list(self): data = range(10) selectors = [0, 1, 1, 0, 0, 0, 1, 0, 1, 0] it = ait.compress(data, selectors) for k in [1, 2, 6, 8]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_compress_gen(self): data = "abcdefghijkl" selectors = ait.cycle([1, 0, 0]) it = ait.compress(data, selectors) for k in ["a", "d", "g", "j"]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_count_bare(self): it = ait.count() for k in [0, 1, 2, 3]: self.assertEqual(await ait.next(it), k) @async_test async def test_count_start(self): it = ait.count(42) for k in [42, 43, 44, 45]: self.assertEqual(await ait.next(it), k) @async_test async def test_count_start_step(self): it = ait.count(42, 3) for k in [42, 45, 48, 51]: self.assertEqual(await ait.next(it), k) @async_test async def test_count_negative(self): it = ait.count(step=-2) for k in [0, -2, -4, -6]: self.assertEqual(await ait.next(it), k) @async_test async def test_cycle_list(self): it = ait.cycle(slist) for k in ["A", "B", "C", "A", "B", "C", "A", "B"]: self.assertEqual(await ait.next(it), k) @async_test async def test_cycle_gen(self): async def gen(): yield 1 yield 2 yield 42 it = ait.cycle(gen()) for k in [1, 2, 42, 1, 2, 42, 1, 2]: self.assertEqual(await ait.next(it), k) @async_test async def test_dropwhile_empty(self): def pred(x): return x < 2 result = await ait.list(ait.dropwhile(pred, [])) self.assertEqual(result, []) @async_test async def test_dropwhile_function_list(self): def pred(x): return x < 2 it = ait.dropwhile(pred, srange) for k in [2, 3]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_dropwhile_function_gen(self): def pred(x): return x < 2 async def gen(): yield 1 yield 2 yield 42 it = ait.dropwhile(pred, gen()) for k in [2, 42]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_dropwhile_coroutine_list(self): async def pred(x): return x < 2 it = ait.dropwhile(pred, srange) for k in [2, 3]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_dropwhile_coroutine_gen(self): async def pred(x): return x < 2 async def gen(): yield 1 yield 2 yield 42 it = ait.dropwhile(pred, gen()) for k in [2, 42]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_filterfalse_function_list(self): def pred(x): return x % 2 == 0 it = ait.filterfalse(pred, srange) for k in [1, 3]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_filterfalse_coroutine_list(self): async def pred(x): return x % 2 == 0 it = ait.filterfalse(pred, srange) for k in [1, 3]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_groupby_list(self): data = "aaabba" it = ait.groupby(data) for k in [("a", ["a", "a", "a"]), ("b", ["b", "b"]), ("a", ["a"])]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_groupby_list_key(self): data = "aAabBA" it = ait.groupby(data, key=str.lower) for k in [("a", ["a", "A", "a"]), ("b", ["b", "B"]), ("a", ["A"])]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_groupby_gen(self): async def gen(): for c in "aaabba": yield c it = ait.groupby(gen()) for k in [("a", ["a", "a", "a"]), ("b", ["b", "b"]), ("a", ["a"])]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_groupby_gen_key(self): async def gen(): for c in "aAabBA": yield c it = ait.groupby(gen(), key=str.lower) for k in [("a", ["a", "A", "a"]), ("b", ["b", "B"]), ("a", ["A"])]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_groupby_empty(self): async def gen(): for _ in range(0): yield # Force generator with no actual iteration async for _ in ait.groupby(gen()): self.fail("No iteration should have happened") @async_test async def test_islice_bad_range(self): with self.assertRaisesRegex(ValueError, "must pass stop index"): async for _ in ait.islice([1, 2]): pass with self.assertRaisesRegex(ValueError, "too many arguments"): async for _ in ait.islice([1, 2], 1, 2, 3, 4): pass @async_test async def test_islice_stop_zero(self): values = [] async for value in ait.islice(range(5), 0): values.append(value) self.assertEqual(values, []) @async_test async def test_islice_range_stop(self): it = ait.islice(srange, 2) for k in [1, 2]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_islice_range_start_step(self): it = ait.islice(srange, 0, None, 2) for k in [1, 3]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_islice_range_start_stop(self): it = ait.islice(srange, 1, 3) for k in [2, 3]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_islice_range_start_stop_step(self): it = ait.islice(srange, 1, 3, 2) for k in [2]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_islice_gen_stop(self): async def gen(): yield 1 yield 2 yield 3 yield 4 gen_it = gen() it = ait.islice(gen_it, 2) for k in [1, 2]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) assert await ait.list(gen_it) == [3, 4] @async_test async def test_islice_gen_start_step(self): async def gen(): yield 1 yield 2 yield 3 yield 4 it = ait.islice(gen(), 1, None, 2) for k in [2, 4]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_islice_gen_start_stop(self): async def gen(): yield 1 yield 2 yield 3 yield 4 it = ait.islice(gen(), 1, 3) for k in [2, 3]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_islice_gen_start_stop_step(self): async def gen(): yield 1 yield 2 yield 3 yield 4 gen_it = gen() it = ait.islice(gen_it, 1, 3, 2) for k in [2]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) assert await ait.list(gen_it) == [4] @async_test async def test_permutations_list(self): it = ait.permutations(srange, r=2) for k in [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_permutations_gen(self): async def gen(): yield 1 yield 2 yield 3 it = ait.permutations(gen(), r=2) for k in [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_product_list(self): it = ait.product([1, 2], [6, 7]) for k in [(1, 6), (1, 7), (2, 6), (2, 7)]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_product_gen(self): async def gen(x): yield x yield x + 1 it = ait.product(gen(1), gen(6)) for k in [(1, 6), (1, 7), (2, 6), (2, 7)]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_repeat(self): it = ait.repeat(42) for k in [42] * 10: self.assertEqual(await ait.next(it), k) @async_test async def test_repeat_limit(self): it = ait.repeat(42, 5) for k in [42] * 5: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_starmap_function_list(self): data = [slist[:2], slist[1:], slist] def concat(*args): return "".join(args) it = ait.starmap(concat, data) for k in ["AB", "BC", "ABC"]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_starmap_function_gen(self): def gen(): yield slist[:2] yield slist[1:] yield slist def concat(*args): return "".join(args) it = ait.starmap(concat, gen()) for k in ["AB", "BC", "ABC"]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_starmap_coroutine_list(self): data = [slist[:2], slist[1:], slist] async def concat(*args): return "".join(args) it = ait.starmap(concat, data) for k in ["AB", "BC", "ABC"]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_starmap_coroutine_gen(self): async def gen(): yield slist[:2] yield slist[1:] yield slist async def concat(*args): return "".join(args) it = ait.starmap(concat, gen()) for k in ["AB", "BC", "ABC"]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_takewhile_empty(self): def pred(x): return x < 3 values = await ait.list(ait.takewhile(pred, [])) self.assertEqual(values, []) @async_test async def test_takewhile_function_list(self): def pred(x): return x < 3 it = ait.takewhile(pred, srange) for k in [1, 2]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_takewhile_function_gen(self): async def gen(): yield 1 yield 2 yield 3 def pred(x): return x < 3 it = ait.takewhile(pred, gen()) for k in [1, 2]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_takewhile_coroutine_list(self): async def pred(x): return x < 3 it = ait.takewhile(pred, srange) for k in [1, 2]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_takewhile_coroutine_gen(self): def gen(): yield 1 yield 2 yield 3 async def pred(x): return x < 3 it = ait.takewhile(pred, gen()) for k in [1, 2]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_tee_list_two(self): it1, it2 = ait.tee(slist * 2) for k in slist * 2: a, b = await asyncio.gather(ait.next(it1), ait.next(it2)) self.assertEqual(a, b) self.assertEqual(a, k) self.assertEqual(b, k) for it in [it1, it2]: with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_tee_list_six(self): itrs = ait.tee(slist * 2, n=6) for k in slist * 2: values = await asyncio.gather(*[ait.next(it) for it in itrs]) for value in values: self.assertEqual(value, k) for it in itrs: with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_tee_gen_two(self): async def gen(): yield 1 yield 4 yield 9 yield 16 it1, it2 = ait.tee(gen()) for k in [1, 4, 9, 16]: a, b = await asyncio.gather(ait.next(it1), ait.next(it2)) self.assertEqual(a, b) self.assertEqual(a, k) self.assertEqual(b, k) for it in [it1, it2]: with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_tee_gen_six(self): async def gen(): yield 1 yield 4 yield 9 yield 16 itrs = ait.tee(gen(), n=6) for k in [1, 4, 9, 16]: values = await asyncio.gather(*[ait.next(it) for it in itrs]) for value in values: self.assertEqual(value, k) for it in itrs: with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_tee_propagate_exception(self): class MyError(Exception): pass async def gen(): yield 1 yield 2 raise MyError async def consumer(it): result = 0 async for item in it: result += item return result it1, it2 = ait.tee(gen()) values = await asyncio.gather( consumer(it1), consumer(it2), return_exceptions=True, ) for value in values: self.assertIsInstance(value, MyError) @async_test async def test_zip_longest_range(self): a = range(3) b = range(5) it = ait.zip_longest(a, b) for k in [(0, 0), (1, 1), (2, 2), (None, 3), (None, 4)]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_zip_longest_fillvalue(self): async def gen(): yield 1 yield 4 yield 9 yield 16 a = gen() b = range(5) it = ait.zip_longest(a, b, fillvalue=42) for k in [(1, 0), (4, 1), (9, 2), (16, 3), (42, 4)]: self.assertEqual(await ait.next(it), k) with self.assertRaises(StopAsyncIteration): await ait.next(it) @async_test async def test_zip_longest_exception(self): async def gen(): yield 1 yield 2 raise Exception("fake error") a = gen() b = ait.repeat(5) it = ait.zip_longest(a, b) for k in [(1, 5), (2, 5)]: self.assertEqual(await ait.next(it), k) with self.assertRaisesRegex(Exception, "fake error"): await ait.next(it) omnilib-aioitertools-ff59bbd/aioitertools/tests/more_itertools.py000066400000000000000000000062461466523024300257550ustar00rootroot00000000000000# Copyright 2022 Amethyst Reese # Licensed under the MIT license from typing import AsyncIterable from unittest import TestCase import aioitertools.more_itertools as mit from .helpers import async_test async def _gen() -> AsyncIterable[int]: for i in range(5): yield i async def _empty() -> AsyncIterable[int]: return yield 0 class MoreItertoolsTest(TestCase): @async_test async def test_take(self) -> None: self.assertEqual(await mit.take(2, _gen()), [0, 1]) self.assertEqual(await mit.take(2, range(5)), [0, 1]) @async_test async def test_take_zero(self) -> None: self.assertEqual(await mit.take(0, _gen()), []) @async_test async def test_take_negative(self) -> None: with self.assertRaises(ValueError): await mit.take(-1, _gen()) @async_test async def test_take_more_than_iterable(self) -> None: self.assertEqual(await mit.take(10, _gen()), list(range(5))) @async_test async def test_take_empty(self) -> None: it = _gen() self.assertEqual(len(await mit.take(5, it)), 5) self.assertEqual(await mit.take(1, it), []) self.assertEqual(await mit.take(1, _empty()), []) @async_test async def test_chunked(self) -> None: self.assertEqual( [chunk async for chunk in mit.chunked(_gen(), 2)], [[0, 1], [2, 3], [4]] ) self.assertEqual( [chunk async for chunk in mit.chunked(range(5), 2)], [[0, 1], [2, 3], [4]] ) @async_test async def test_chunked_empty(self) -> None: self.assertEqual([], [chunk async for chunk in mit.chunked(_empty(), 2)]) @async_test async def test_before_and_after_split(self) -> None: it = _gen() before, after = await mit.before_and_after(lambda i: i <= 2, it) self.assertEqual([elm async for elm in before], [0, 1, 2]) self.assertEqual([elm async for elm in after], [3, 4]) @async_test async def test_before_and_after_before_only(self) -> None: it = _gen() before, after = await mit.before_and_after(lambda i: True, it) self.assertEqual([elm async for elm in before], [0, 1, 2, 3, 4]) self.assertEqual([elm async for elm in after], []) @async_test async def test_before_and_after_after_only(self) -> None: it = _gen() before, after = await mit.before_and_after(lambda i: False, it) self.assertEqual([elm async for elm in before], []) self.assertEqual([elm async for elm in after], [0, 1, 2, 3, 4]) @async_test async def test_before_and_after_async_predicate(self) -> None: async def predicate(elm: int) -> bool: return elm <= 2 it = _gen() before, after = await mit.before_and_after(predicate, it) self.assertEqual([elm async for elm in before], [0, 1, 2]) self.assertEqual([elm async for elm in after], [3, 4]) @async_test async def test_before_and_after_empty(self) -> None: it = _empty() before, after = await mit.before_and_after(lambda i: True, it) self.assertEqual([elm async for elm in before], []) self.assertEqual([elm async for elm in after], []) omnilib-aioitertools-ff59bbd/aioitertools/types.py000066400000000000000000000021261466523024300227020ustar00rootroot00000000000000# Copyright 2022 Amethyst Reese # Licensed under the MIT license import sys from typing import ( AsyncIterable, AsyncIterator, Awaitable, Callable, Iterable, Iterator, TypeVar, Union, ) if sys.version_info < (3, 10): # pragma: no cover from typing_extensions import ParamSpec else: # pragma: no cover from typing import ParamSpec P = ParamSpec("P") R = TypeVar("R") T = TypeVar("T") T1 = TypeVar("T1") T2 = TypeVar("T2") T3 = TypeVar("T3") T4 = TypeVar("T4") T5 = TypeVar("T5") N = TypeVar("N", int, float) AnyFunction = Union[Callable[..., R], Callable[..., Awaitable[R]]] AnyIterable = Union[Iterable[T], AsyncIterable[T]] AnyIterableIterable = Union[Iterable[AnyIterable[T]], AsyncIterable[AnyIterable[T]]] AnyIterator = Union[Iterator[T], AsyncIterator[T]] AnyStop = (StopIteration, StopAsyncIteration) Accumulator = Union[Callable[[T, T], T], Callable[[T, T], Awaitable[T]]] KeyFunction = Union[Callable[[T], R], Callable[[T], Awaitable[R]]] Predicate = Union[Callable[[T], object], Callable[[T], Awaitable[object]]] MaybeAwaitable = Union[T, Awaitable[T]] omnilib-aioitertools-ff59bbd/docs/000077500000000000000000000000001466523024300173765ustar00rootroot00000000000000omnilib-aioitertools-ff59bbd/docs/_static/000077500000000000000000000000001466523024300210245ustar00rootroot00000000000000omnilib-aioitertools-ff59bbd/docs/_static/custom.css000066400000000000000000000005371466523024300230550ustar00rootroot00000000000000div.omnilib { margin-top: 24px; } div.omnilib-badges { margin-top: 12px; margin-bottom: 10px; } div.omnilib-badges a { color: #bbb; text-decoration: none; font-size: 24px; border: none; } div.omnilib-badges a:hover { color: #777; border: none; } dl.class, dl.function, dl.attribute { margin-bottom: 15px; } omnilib-aioitertools-ff59bbd/docs/_templates/000077500000000000000000000000001466523024300215335ustar00rootroot00000000000000omnilib-aioitertools-ff59bbd/docs/_templates/badges.html000066400000000000000000000005261466523024300236510ustar00rootroot00000000000000 omnilib-aioitertools-ff59bbd/docs/_templates/omnilib.html000066400000000000000000000016461466523024300240610ustar00rootroot00000000000000

The Omnilib Project

Omnilib is a group of MIT licensed software libraries developed under a common, inclusive Code of Conduct. We are committed to providing a welcoming and open space for all contributors who adhere to these rules.
   
omnilib-aioitertools-ff59bbd/docs/api.rst000066400000000000000000000005201466523024300206760ustar00rootroot00000000000000API Reference ============= builtins -------- .. automodule:: aioitertools.builtins :members: itertools --------- .. automodule:: aioitertools.itertools :members: more_itertools -------------- .. automodule:: aioitertools.more_itertools :members: asyncio ------- .. automodule:: aioitertools.asyncio :members:omnilib-aioitertools-ff59bbd/docs/changelog.rst000066400000000000000000000001061466523024300220540ustar00rootroot00000000000000Changelog ========= .. mdinclude:: ../CHANGELOG.md :start-line: 2omnilib-aioitertools-ff59bbd/docs/conf.py000066400000000000000000000051721466523024300207020ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Project information ----------------------------------------------------- import datetime import os import pathlib import sys root = pathlib.Path(__file__).parent.parent sys.path.insert(0, root.as_posix()) project = "aioitertools" copyright = f"{datetime.date.today().year}, Amethyst Reese" author = "Amethyst Reese" # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinx_mdinclude", ] autodoc_default_options = { "show-inheritance": True, "members": True, "inherited-members": True, } autodoc_member_order = "bysource" autodoc_typehints = "description" # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] highlight_language = "python3" intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} master_doc = "index" # Set canonical URL from the Read the Docs Domain html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "") # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "alabaster" html_theme_options = { "description": "itertools and more for AsyncIO", "fixed_sidebar": True, "badge_branch": "master", "github_button": False, "github_user": "omnilib", "github_repo": "aioitertools", "show_powered_by": False, "sidebar_collapse": False, "extra_nav_links": { "Report Issues": "https://github.com/omnilib/aioitertools/issues", }, } html_sidebars = { "**": [ "about.html", "badges.html", "navigation.html", "relations.html", "searchbox.html", "omnilib.html", ], } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] omnilib-aioitertools-ff59bbd/docs/contributing.rst000066400000000000000000000001171466523024300226360ustar00rootroot00000000000000Contributing ============ .. mdinclude:: ../CONTRIBUTING.md :start-line: 2omnilib-aioitertools-ff59bbd/docs/index.rst000066400000000000000000000004151466523024300212370ustar00rootroot00000000000000aioitertools: itertools and more for AsyncIO ============================================ .. mdinclude:: ../README.md :start-line: 2 .. toctree:: :hidden: :maxdepth: 2 api .. toctree:: :hidden: :maxdepth: 1 changelog contributing omnilib-aioitertools-ff59bbd/makefile000066400000000000000000000013761466523024300201550ustar00rootroot00000000000000PKG:=aioitertools EXTRAS:=dev,docs UV:=$(shell uv --version) ifdef UV VENV:=uv venv PIP:=uv pip else VENV:=python -m venv PIP:=python -m pip endif .venv: $(VENV) .venv venv: .venv source .venv/bin/activate && make install echo 'run `source .venv/bin/activate` to use virtualenv' install: $(PIP) install -Ue .[$(EXTRAS)] release: lint test clean flit publish format: python -m ufmt format $(PKG) lint: python -m flake8 $(PKG) python -m ufmt check $(PKG) test: python -m coverage run -m $(PKG).tests python -m coverage report python -m mypy -p $(PKG) html: .venv README.md docs/* source .venv/bin/activate && sphinx-build -b html docs html clean: rm -rf .mypy_cache build dist html README MANIFEST *.egg-info distclean: clean rm -rf .venv omnilib-aioitertools-ff59bbd/pyproject.toml000066400000000000000000000026131466523024300213640ustar00rootroot00000000000000[build-system] requires = ["flit_core >=3.8,<4"] build-backend = "flit_core.buildapi" [project] name = "aioitertools" readme = "README.md" authors = [{ name = "Amethyst Reese", email = "amethyst@n7.gg" }] license = { file = "LICENSE" } dynamic = ["version", "description"] classifiers = [ "Development Status :: 4 - Beta", "Framework :: AsyncIO", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Topic :: Software Development :: Libraries", ] requires-python = ">=3.8" dependencies = ["typing_extensions>=4.0; python_version < '3.10'"] [project.optional-dependencies] dev = [ "attribution==1.8.0", "black==24.8.0", "build>=1.2", "coverage==7.6.1", "flake8==7.1.1", "flit==3.9.0", "mypy==1.11.2", "usort==1.0.8.post1", "ufmt==2.7.1", ] docs = [ "sphinx==8.0.2", "sphinx-mdinclude==0.6.2", ] [project.urls] Documentation = "https://aioitertools.omnilib.dev" Github = "https://github.com/omnilib/aioitertools" [tool.flit.sdist] exclude = [".github/"] [tool.attribution] name = "aioitertools" package = "aioitertools" version_file = true ignored_authors = ["dependabot[bot]"] [tool.coverage.run] branch = true include = ["aioitertools/*"] omit = ["aioitertools/tests/*"] [tool.coverage.report] fail_under = 97 precision = 1 show_missing = true skip_covered = true [tool.mypy] # strict = true ignore_missing_imports = true