Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-more-itertools for openSUSE:Factory checked in at 2022-11-01 13:41:10 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-more-itertools (Old) and /work/SRC/openSUSE:Factory/.python-more-itertools.new.2275 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-more-itertools" Tue Nov 1 13:41:10 2022 rev:21 rq:1032501 version:9.0.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-more-itertools/python-more-itertools.changes 2022-09-18 17:32:12.625768784 +0200 +++ /work/SRC/openSUSE:Factory/.python-more-itertools.new.2275/python-more-itertools.changes 2022-11-01 13:41:20.055502214 +0100 @@ -1,0 +2,17 @@ +Fri Oct 28 18:32:55 UTC 2022 - Yogalakshmi Arunachalam <yarunacha...@suse.com> + +- Update to 9.0.0 + * Potentially breaking changes + grouper() no longer accepts an integer as its first argument. Previously this raised a DeprecationWarning. + collate() has been removed. Use the built-in heapq.merge() instead. + windowed() now yields nothing when its iterable is empty. + This library now advertises support for Python 3.7+. + * New functions + constrained_batches() + batched() (from the Python itertools docs) + polynomial_from_roots() (from the Python itertools docs) + sieve() (from the Python itertools docs) + * Other changes + Some documentation issues were fixed (thanks to nanouasyn) + +------------------------------------------------------------------- Old: ---- more-itertools-8.14.0.tar.gz New: ---- more-itertools-9.0.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-more-itertools.spec ++++++ --- /var/tmp/diff_new_pack.sQLD4j/_old 2022-11-01 13:41:20.599505108 +0100 +++ /var/tmp/diff_new_pack.sQLD4j/_new 2022-11-01 13:41:20.603505128 +0100 @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python3-%{**}} %define skip_python2 1 Name: python-more-itertools -Version: 8.14.0 +Version: 9.0.0 Release: 0 Summary: More routines for operating on iterables, beyond itertools License: MIT ++++++ more-itertools-8.14.0.tar.gz -> more-itertools-9.0.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/more-itertools-8.14.0/.github/workflows/python-app.yml new/more-itertools-9.0.0/.github/workflows/python-app.yml --- old/more-itertools-8.14.0/.github/workflows/python-app.yml 2022-08-09 16:00:34.670124000 +0200 +++ new/more-itertools-9.0.0/.github/workflows/python-app.yml 2022-10-18 15:38:19.069835000 +0200 @@ -8,7 +8,7 @@ runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9.0, 3.10.0, pypy3] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11.0-rc.2", "pypy-3.8"] steps: - uses: actions/checkout@v2 @@ -31,28 +31,28 @@ run: | flake8 . - name: Check formatting with black - if: "matrix.python-version == '3.6'" + if: "matrix.python-version == '3.7'" run: | pip install -U black black --check . - name: Check type stubs with mypy - if: "matrix.python-version != 'pypy3'" + if: "matrix.python-version != 'pypy-3.8'" run: | pip install -U mypy stubtest more_itertools.more more_itertools.recipes - name: Build docs with sphinx - if: "matrix.python-version == '3.6'" + if: "matrix.python-version == '3.7'" run: | pip install -U sphinx sphinx_rtd_theme sphinx-build -W -b html docs docs/_build/html - name: Build packages - if: "matrix.python-version == '3.6'" + if: "matrix.python-version == '3.7'" run: | pip install -U flit twine flit build --setup-py twine check dist/* - name: Upload packages - if: "matrix.python-version == '3.6'" + if: "matrix.python-version == '3.7'" uses: actions/upload-artifact@v2 with: name: more-itertools-packages diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/more-itertools-8.14.0/PKG-INFO new/more-itertools-9.0.0/PKG-INFO --- old/more-itertools-8.14.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 +++ new/more-itertools-9.0.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 @@ -1,21 +1,21 @@ Metadata-Version: 2.1 Name: more-itertools -Version: 8.14.0 +Version: 9.0.0 Summary: More routines for operating on iterables, beyond itertools -Keywords: itertools,iterator,iteration,filter,peek,peekable,collate,chunk,chunked +Keywords: itertools,iterator,iteration,filter,peek,peekable,chunk,chunked Author-email: Erik Rose <erikr...@grinchcentral.com> -Requires-Python: >=3.5 +Requires-Python: >=3.7 Description-Content-Type: text/x-rst Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy @@ -39,6 +39,7 @@ | | `ichunked <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.ichunked>`_, | | | `chunked_even <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.chunked_even>`_, | | | `sliced <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sliced>`_, | +| | `constrained_batches <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.constrained_batches>`_, | | | `distribute <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.distribute>`_, | | | `divide <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.divide>`_, | | | `split_at <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.split_at>`_, | @@ -48,6 +49,7 @@ | | `split_when <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.split_when>`_, | | | `bucket <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.bucket>`_, | | | `unzip <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.unzip>`_, | +| | `batched <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.batched>`_, | | | `grouper <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.grouper>`_, | | | `partition <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.partition>`_ | +------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -62,8 +64,8 @@ | | `windowed_complete <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.windowed_complete>`_, | | | `pairwise <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.pairwise>`_, | | | `triplewise <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.triplewise>`_, | -| | `sliding_window <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sliding_window>`_ | -| | `subslices <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.subslices>`_, | +| | `sliding_window <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sliding_window>`_, | +| | `subslices <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.subslices>`_ | +------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Augmenting | `count_cycle <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.count_cycle>`_, | | | `intersperse <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.intersperse>`_, | @@ -103,7 +105,7 @@ | | `all_unique <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.all_unique>`_, | | | `minmax <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.minmax>`_, | | | `first_true <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.first_true>`_, | -| | `quantify <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.quantify>`_ | +| | `quantify <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.quantify>`_, | | | `iequals <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.iequals>`_ | +------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Selecting | `islice_extended <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.islice_extended>`_, | @@ -126,7 +128,7 @@ | | `unique_everseen <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertoo ls.unique_everseen>`_, | | | `unique_justseen <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.unique_justseen>`_, | | | `duplicates_everseen <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.duplicates_everseen>`_, | -| | `duplicates_justseen <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.duplicates_justseen>`_ | +| | `duplicates_justseen <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.duplicates_justseen>`_, | | | `longest_common_prefix <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.longest_common_prefix>`_ | +------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Combinatorics | `distinct_permutations <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.distinct_permutations>`_, | @@ -167,6 +169,8 @@ | | `consume <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.consume>`_, | | | `tabulate <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.tabulate>`_, | | | `repeatfunc <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.repeatfunc>`_ | +| | `polynomial_from_roots <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.polynomial_from_roots>`_ | +| | `sieve <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sieve>`_ | +------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/more-itertools-8.14.0/README.rst new/more-itertools-9.0.0/README.rst --- old/more-itertools-8.14.0/README.rst 2022-08-09 15:50:39.706522700 +0200 +++ new/more-itertools-9.0.0/README.rst 2022-10-18 15:38:19.069835000 +0200 @@ -15,6 +15,7 @@ | | `ichunked <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.ichunked>`_, | | | `chunked_even <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.chunked_even>`_, | | | `sliced <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sliced>`_, | +| | `constrained_batches <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.constrained_batches>`_, | | | `distribute <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.distribute>`_, | | | `divide <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.divide>`_, | | | `split_at <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.split_at>`_, | @@ -24,6 +25,7 @@ | | `split_when <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.split_when>`_, | | | `bucket <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.bucket>`_, | | | `unzip <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.unzip>`_, | +| | `batched <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.batched>`_, | | | `grouper <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.grouper>`_, | | | `partition <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.partition>`_ | +------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -38,8 +40,8 @@ | | `windowed_complete <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.windowed_complete>`_, | | | `pairwise <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.pairwise>`_, | | | `triplewise <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.triplewise>`_, | -| | `sliding_window <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sliding_window>`_ | -| | `subslices <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.subslices>`_, | +| | `sliding_window <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sliding_window>`_, | +| | `subslices <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.subslices>`_ | +------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Augmenting | `count_cycle <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.count_cycle>`_, | | | `intersperse <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.intersperse>`_, | @@ -79,7 +81,7 @@ | | `all_unique <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.all_unique>`_, | | | `minmax <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.minmax>`_, | | | `first_true <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.first_true>`_, | -| | `quantify <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.quantify>`_ | +| | `quantify <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.quantify>`_, | | | `iequals <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.iequals>`_ | +------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Selecting | `islice_extended <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.islice_extended>`_, | @@ -102,7 +104,7 @@ | | `unique_everseen <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertoo ls.unique_everseen>`_, | | | `unique_justseen <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.unique_justseen>`_, | | | `duplicates_everseen <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.duplicates_everseen>`_, | -| | `duplicates_justseen <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.duplicates_justseen>`_ | +| | `duplicates_justseen <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.duplicates_justseen>`_, | | | `longest_common_prefix <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.longest_common_prefix>`_ | +------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Combinatorics | `distinct_permutations <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.distinct_permutations>`_, | @@ -143,6 +145,8 @@ | | `consume <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.consume>`_, | | | `tabulate <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.tabulate>`_, | | | `repeatfunc <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.repeatfunc>`_ | +| | `polynomial_from_roots <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.polynomial_from_roots>`_ | +| | `sieve <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.sieve>`_ | +------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/more-itertools-8.14.0/docs/api.rst new/more-itertools-9.0.0/docs/api.rst --- old/more-itertools-8.14.0/docs/api.rst 2022-08-09 15:43:59.871538400 +0200 +++ new/more-itertools-9.0.0/docs/api.rst 2022-10-18 15:37:02.101069200 +0200 @@ -17,6 +17,7 @@ .. autofunction:: ichunked .. autofunction:: chunked_even .. autofunction:: sliced +.. autofunction:: constrained_batches(iterable, max_size, max_count=None, get_len=len, strict=True) .. autofunction:: distribute .. autofunction:: divide .. autofunction:: split_at @@ -31,6 +32,7 @@ **Itertools recipes** +.. autofunction:: batched .. autofunction:: grouper .. autofunction:: partition @@ -287,3 +289,5 @@ .. autofunction:: consume .. autofunction:: tabulate .. autofunction:: repeatfunc +.. autofunction:: polynomial_from_roots +.. autofunction:: sieve diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/more-itertools-8.14.0/docs/versions.rst new/more-itertools-9.0.0/docs/versions.rst --- old/more-itertools-8.14.0/docs/versions.rst 2022-08-09 15:49:41.910306700 +0200 +++ new/more-itertools-9.0.0/docs/versions.rst 2022-10-18 15:38:19.069835000 +0200 @@ -5,6 +5,24 @@ .. automodule:: more_itertools :noindex: +9.0.0 +------ + +* Potentially breaking changes + * :func:`grouper` no longer accepts an integer as its first argument. Previously this raised a ``DeprecationWarning``. + * :func:`collate` has been removed. Use the built-in :func:`heapq.merge` instead. + * :func:`windowed` now yields nothing when its iterable is empty. + * This library now advertises support for Python 3.7+. + +* New functions + * :func:`constrained_batches` + * :func:`batched` (from the Python itertools docs) + * :func:`polynomial_from_roots` (from the Python itertools docs) + * :func:`sieve` (from the Python itertools docs) + +* Other changes + * Some documentation issues were fixed (thanks to nanouasyn) + 8.14.0 ------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/more-itertools-8.14.0/more_itertools/__init__.py new/more-itertools-9.0.0/more_itertools/__init__.py --- old/more-itertools-8.14.0/more_itertools/__init__.py 2022-08-09 15:53:46.644506700 +0200 +++ new/more-itertools-9.0.0/more_itertools/__init__.py 2022-10-18 15:38:19.069835000 +0200 @@ -3,4 +3,4 @@ from .more import * # noqa from .recipes import * # noqa -__version__ = '8.14.0' +__version__ = '9.0.0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/more-itertools-8.14.0/more_itertools/more.py new/more-itertools-9.0.0/more_itertools/more.py --- old/more-itertools-8.14.0/more_itertools/more.py 2022-08-09 15:43:59.877477000 +0200 +++ new/more-itertools-9.0.0/more_itertools/more.py 2022-10-18 15:38:19.073834700 +0200 @@ -3,7 +3,7 @@ from collections import Counter, defaultdict, deque, abc from collections.abc import Sequence from functools import partial, reduce, wraps -from heapq import merge, heapify, heapreplace, heappop +from heapq import heapify, heapreplace, heappop from itertools import ( chain, compress, @@ -52,9 +52,9 @@ 'chunked_even', 'circular_shifts', 'collapse', - 'collate', 'combination_index', 'consecutive_groups', + 'constrained_batches', 'consumer', 'count_cycle', 'countable', @@ -412,44 +412,6 @@ return self._cache[index] -def collate(*iterables, **kwargs): - """Return a sorted merge of the items from each of several already-sorted - *iterables*. - - >>> list(collate('ACDZ', 'AZ', 'JKL')) - ['A', 'A', 'C', 'D', 'J', 'K', 'L', 'Z', 'Z'] - - Works lazily, keeping only the next value from each iterable in memory. Use - :func:`collate` to, for example, perform a n-way mergesort of items that - don't fit in memory. - - If a *key* function is specified, the iterables will be sorted according - to its result: - - >>> key = lambda s: int(s) # Sort by numeric value, not by string - >>> list(collate(['1', '10'], ['2', '11'], key=key)) - ['1', '2', '10', '11'] - - - If the *iterables* are sorted in descending order, set *reverse* to - ``True``: - - >>> list(collate([5, 3, 1], [4, 2, 0], reverse=True)) - [5, 4, 3, 2, 1, 0] - - If the elements of the passed-in iterables are out of order, you might get - unexpected results. - - On Python 3.5+, this function is an alias for :func:`heapq.merge`. - - """ - warnings.warn( - "collate is no longer part of more_itertools, use heapq.merge", - DeprecationWarning, - ) - return merge(*iterables, **kwargs) - - def consumer(func): """Decorator that automatically advances a PEP-342-style "reverse iterator" to its first yield point so you don't have to call ``next()`` on it @@ -875,7 +837,9 @@ yield tuple(window) size = len(window) - if size < n: + if size == 0: + return + elif size < n: yield tuple(chain(window, repeat(fillvalue, n - size))) elif 0 < i < min(step, n): window += (fillvalue,) * i @@ -4331,3 +4295,53 @@ hi, hi_key = y, y_key return lo, hi + + +def constrained_batches( + iterable, max_size, max_count=None, get_len=len, strict=True +): + """Yield batches of items from *iterable* with a combined size limited by + *max_size*. + + >>> iterable = [b'12345', b'123', b'12345678', b'1', b'1', b'12', b'1'] + >>> list(constrained_batches(iterable, 10)) + [(b'12345', b'123'), (b'12345678', b'1', b'1'), (b'12', b'1')] + + If a *max_count* is supplied, the number of items per batch is also + limited: + + >>> iterable = [b'12345', b'123', b'12345678', b'1', b'1', b'12', b'1'] + >>> list(constrained_batches(iterable, 10, max_count = 2)) + [(b'12345', b'123'), (b'12345678', b'1'), (b'1', b'12'), (b'1',)] + + If a *get_len* function is supplied, use that instead of :func:`len` to + determine item size. + + If *strict* is ``True``, raise ``ValueError`` if any single item is bigger + than *max_size*. Otherwise, allow single items to exceed *max_size*. + """ + if max_size <= 0: + raise ValueError('maximum size must be greater than zero') + + batch = [] + batch_size = 0 + batch_count = 0 + for item in iterable: + item_len = get_len(item) + if strict and item_len > max_size: + raise ValueError('item size exceeds maximum size') + + reached_count = batch_count == max_count + reached_size = item_len + batch_size > max_size + if batch_count and (reached_size or reached_count): + yield tuple(batch) + batch.clear() + batch_size = 0 + batch_count = 0 + + batch.append(item) + batch_size += item_len + batch_count += 1 + + if batch: + yield tuple(batch) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/more-itertools-8.14.0/more_itertools/more.pyi new/more-itertools-9.0.0/more_itertools/more.pyi --- old/more-itertools-8.14.0/more_itertools/more.pyi 2022-08-09 15:43:59.881166500 +0200 +++ new/more-itertools-9.0.0/more_itertools/more.pyi 2022-10-18 15:38:19.073834700 +0200 @@ -72,7 +72,6 @@ @overload def __getitem__(self, index: slice) -> List[_T]: ... -def collate(*iterables: Iterable[_T], **kwargs: Any) -> Iterable[_T]: ... def consumer(func: _GenFn) -> _GenFn: ... def ilen(iterable: Iterable[object]) -> int: ... def iterate(func: Callable[[_T], _T], start: _T) -> Iterator[_T]: ... @@ -179,7 +178,7 @@ iterable: Iterable[_T], *, n: Optional[int] = ..., - next_multiple: bool = ... + next_multiple: bool = ..., ) -> Iterator[Optional[_T]]: ... @overload def padded( @@ -225,7 +224,7 @@ __iter1: Iterable[_T], __iter2: Iterable[_T], __iter3: Iterable[_T], - *iterables: Iterable[_T] + *iterables: Iterable[_T], ) -> Iterator[Tuple[_T, ...]]: ... @overload def zip_offset( @@ -233,7 +232,7 @@ *, offsets: _SizedIterable[int], longest: bool = ..., - fillvalue: None = None + fillvalue: None = None, ) -> Iterator[Tuple[Optional[_T1]]]: ... @overload def zip_offset( @@ -242,7 +241,7 @@ *, offsets: _SizedIterable[int], longest: bool = ..., - fillvalue: None = None + fillvalue: None = None, ) -> Iterator[Tuple[Optional[_T1], Optional[_T2]]]: ... @overload def zip_offset( @@ -252,7 +251,7 @@ *iterables: Iterable[_T], offsets: _SizedIterable[int], longest: bool = ..., - fillvalue: None = None + fillvalue: None = None, ) -> Iterator[Tuple[Optional[_T], ...]]: ... @overload def zip_offset( @@ -420,7 +419,7 @@ iterable: Iterable[_T], func: Callable[[_T, _T], _U] = ..., *, - initial: None = ... + initial: None = ..., ) -> Iterator[Union[_T, _U]]: ... @overload def difference( @@ -529,12 +528,12 @@ def filter_except( validator: Callable[[Any], object], iterable: Iterable[_T], - *exceptions: Type[BaseException] + *exceptions: Type[BaseException], ) -> Iterator[_T]: ... def map_except( function: Callable[[Any], _U], iterable: Iterable[_T], - *exceptions: Type[BaseException] + *exceptions: Type[BaseException], ) -> Iterator[_U]: ... def map_if( iterable: Iterable[Any], @@ -610,7 +609,7 @@ scalar_types: Union[ type, Tuple[Union[type, Tuple[Any, ...]], ...], None ] = ..., - strict: bool = ... + strict: bool = ..., ) -> Iterable[Tuple[_T, ...]]: ... def unique_in_window( iterable: Iterable[_T], n: int, key: Optional[Callable[[_T], _U]] = ... @@ -640,7 +639,7 @@ iterable_or_value: Iterable[_SupportsLessThanT], *, key: None = None, - default: _U + default: _U, ) -> Union[_U, Tuple[_SupportsLessThanT, _SupportsLessThanT]]: ... @overload def minmax( @@ -653,16 +652,23 @@ def minmax( iterable_or_value: _SupportsLessThanT, __other: _SupportsLessThanT, - *others: _SupportsLessThanT + *others: _SupportsLessThanT, ) -> Tuple[_SupportsLessThanT, _SupportsLessThanT]: ... @overload def minmax( iterable_or_value: _T, __other: _T, *others: _T, - key: Callable[[_T], _SupportsLessThan] + key: Callable[[_T], _SupportsLessThan], ) -> Tuple[_T, _T]: ... def longest_common_prefix( iterables: Iterable[Iterable[_T]], ) -> Iterator[_T]: ... def iequals(*iterables: Iterable[object]) -> bool: ... +def constrained_batches( + iterable: Iterable[object], + max_size: int, + max_count: Optional[int] = ..., + get_len: Callable[[_T], object] = ..., + strict: bool = ..., +) -> Iterator[Tuple[_T]]: ... diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/more-itertools-8.14.0/more_itertools/recipes.py new/more-itertools-9.0.0/more_itertools/recipes.py --- old/more-itertools-8.14.0/more_itertools/recipes.py 2022-08-09 15:43:59.884092800 +0200 +++ new/more-itertools-9.0.0/more_itertools/recipes.py 2022-10-18 15:38:19.073834700 +0200 @@ -7,14 +7,16 @@ .. [1] http://docs.python.org/library/itertools.html#recipes """ +import math import operator -import warnings from collections import deque from collections.abc import Sized +from functools import reduce from itertools import ( chain, combinations, + compress, count, cycle, groupby, @@ -28,6 +30,7 @@ __all__ = [ 'all_equal', + 'batched', 'before_and_after', 'consume', 'convolve', @@ -43,6 +46,7 @@ 'pad_none', 'pairwise', 'partition', + 'polynomial_from_roots', 'powerset', 'prepend', 'quantify', @@ -52,6 +56,7 @@ 'random_product', 'repeatfunc', 'roundrobin', + 'sieve', 'sliding_window', 'subslices', 'tabulate', @@ -364,11 +369,6 @@ UnequalIterablesError """ - if isinstance(iterable, int): - warnings.warn( - "grouper expects iterable as first parameter", DeprecationWarning - ) - n, iterable = iterable, n args = [iter(iterable)] * n if incomplete == 'fill': return zip_longest(*args, fillvalue=fillvalue) @@ -738,11 +738,12 @@ transition.append(elem) return - def remainder_iterator(): - yield from transition - yield from it + # Note: this is different from itertools recipes to allow nesting + # before_and_after remainders into before_and_after again. See tests + # for an example. + remainder_iterator = chain(transition, it) - return true_iterator(), remainder_iterator() + return true_iterator(), remainder_iterator def triplewise(iterable): @@ -790,3 +791,51 @@ seq = list(iterable) slices = starmap(slice, combinations(range(len(seq) + 1), 2)) return map(operator.getitem, repeat(seq), slices) + + +def polynomial_from_roots(roots): + """Compute a polynomial's coefficients from its roots. + + >>> roots = [5, -4, 3] # (x - 5) * (x + 4) * (x - 3) + >>> polynomial_from_roots(roots) # x^3 - 4 * x^2 - 17 * x + 60 + [1, -4, -17, 60] + """ + # Use math.prod for Python 3.8+, + prod = getattr(math, 'prod', lambda x: reduce(operator.mul, x, 1)) + roots = list(map(operator.neg, roots)) + return [ + sum(map(prod, combinations(roots, k))) for k in range(len(roots) + 1) + ] + + +def sieve(n): + """Yield the primes less than n. + + >>> list(sieve(30)) + [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] + """ + isqrt = getattr(math, 'isqrt', lambda x: int(math.sqrt(x))) + limit = isqrt(n) + 1 + data = bytearray([1]) * n + data[:2] = 0, 0 + for p in compress(range(limit), data): + data[p + p : n : p] = bytearray(len(range(p + p, n, p))) + + return compress(count(), data) + + +def batched(iterable, n): + """Batch data into lists of length *n*. The last batch may be shorter. + + >>> list(batched('ABCDEFG', 3)) + [['A', 'B', 'C'], ['D', 'E', 'F'], ['G']] + + This recipe is from the ``itertools`` docs. This library also provides + :func:`chunked`, which has a different implementation. + """ + it = iter(iterable) + while True: + batch = list(islice(it, n)) + if not batch: + break + yield batch diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/more-itertools-8.14.0/more_itertools/recipes.pyi new/more-itertools-9.0.0/more_itertools/recipes.pyi --- old/more-itertools-8.14.0/more_itertools/recipes.pyi 2022-04-29 16:09:30.648787500 +0200 +++ new/more-itertools-9.0.0/more_itertools/recipes.pyi 2022-10-18 15:38:19.073834700 +0200 @@ -6,6 +6,7 @@ Iterator, List, Optional, + Sequence, Tuple, TypeVar, Union, @@ -39,20 +40,12 @@ func: Callable[..., _U], times: Optional[int] = ..., *args: Any ) -> Iterator[_U]: ... def pairwise(iterable: Iterable[_T]) -> Iterator[Tuple[_T, _T]]: ... -@overload def grouper( iterable: Iterable[_T], n: int, incomplete: str = ..., fillvalue: _U = ..., ) -> Iterator[Tuple[Union[_T, _U], ...]]: ... -@overload -def grouper( # Deprecated interface - iterable: int, - n: Iterable[_T], - incomplete: str = ..., - fillvalue: _U = ..., -) -> Iterator[Tuple[Union[_T, _U], ...]]: ... def roundrobin(*iterables: Iterable[_T]) -> Iterator[_T]: ... def partition( pred: Optional[Callable[[_T], object]], iterable: Iterable[_T] @@ -109,3 +102,9 @@ iterable: Iterable[_T], n: int ) -> Iterator[Tuple[_T, ...]]: ... def subslices(iterable: Iterable[_T]) -> Iterator[List[_T]]: ... +def polynomial_from_roots(roots: Sequence[int]) -> List[int]: ... +def sieve(n: int) -> Iterator[int]: ... +def batched( + iterable: Iterable[_T], + n: int, +) -> Iterator[List[_T]]: ... diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/more-itertools-8.14.0/pyproject.toml new/more-itertools-9.0.0/pyproject.toml --- old/more-itertools-8.14.0/pyproject.toml 2022-08-09 15:43:59.886106300 +0200 +++ new/more-itertools-9.0.0/pyproject.toml 2022-10-18 15:38:19.073834700 +0200 @@ -6,7 +6,7 @@ name = "more-itertools" authors = [{name = "Erik Rose", email = "erikr...@grinchcentral.com"}] readme = "README.rst" -requires-python = ">=3.5" +requires-python = ">=3.7" license = {file = "LICENSE"} keywords = [ "itertools", @@ -15,7 +15,6 @@ "filter", "peek", "peekable", - "collate", "chunk", "chunked", ] @@ -25,11 +24,11 @@ "Natural Language :: English", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", @@ -45,5 +44,5 @@ [tool.black] line-length = 79 -target-version = ['py35'] +target-version = ['py37'] skip-string-normalization = true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/more-itertools-8.14.0/setup.cfg new/more-itertools-9.0.0/setup.cfg --- old/more-itertools-8.14.0/setup.cfg 2022-08-09 15:53:46.644978300 +0200 +++ new/more-itertools-9.0.0/setup.cfg 2022-10-18 15:38:19.073834700 +0200 @@ -1,5 +1,5 @@ [bumpversion] -current_version = 8.14.0 +current_version = 9.0.0 commit = True tag = False files = more_itertools/__init__.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/more-itertools-8.14.0/tests/test_more.py new/more-itertools-9.0.0/tests/test_more.py --- old/more-itertools-8.14.0/tests/test_more.py 2022-08-09 15:43:59.889434000 +0200 +++ new/more-itertools-9.0.0/tests/test_more.py 2022-10-18 15:38:19.073834700 +0200 @@ -7,7 +7,6 @@ from doctest import DocTestSuite from fractions import Fraction from functools import partial, reduce -from heapq import merge from io import StringIO from itertools import ( accumulate, @@ -41,51 +40,6 @@ return tests -class CollateTests(TestCase): - """Unit tests for ``collate()``""" - - # Also accidentally tests peekable, though that could use its own tests - - def test_default(self): - """Test with the default `key` function.""" - iterables = [range(4), range(7), range(3, 6)] - self.assertEqual( - sorted(reduce(list.__add__, [list(it) for it in iterables])), - list(mi.collate(*iterables)), - ) - - def test_key(self): - """Test using a custom `key` function.""" - iterables = [range(5, 0, -1), range(4, 0, -1)] - actual = sorted( - reduce(list.__add__, [list(it) for it in iterables]), reverse=True - ) - expected = list(mi.collate(*iterables, key=lambda x: -x)) - self.assertEqual(actual, expected) - - def test_empty(self): - """Be nice if passed an empty list of iterables.""" - self.assertEqual([], list(mi.collate())) - - def test_one(self): - """Work when only 1 iterable is passed.""" - self.assertEqual([0, 1], list(mi.collate(range(2)))) - - def test_reverse(self): - """Test the `reverse` kwarg.""" - iterables = [range(4, 0, -1), range(7, 0, -1), range(3, 6, -1)] - - actual = sorted( - reduce(list.__add__, [list(it) for it in iterables]), reverse=True - ) - expected = list(mi.collate(*iterables, reverse=True)) - self.assertEqual(actual, expected) - - def test_alias(self): - self.assertNotEqual(merge.__doc__, mi.collate.__doc__) - self.assertNotEqual(partial.__doc__, mi.collate.__doc__) - - class ChunkedTests(TestCase): """Tests for ``chunked()``""" @@ -289,11 +243,6 @@ class PeekableTests(PeekableMixinTests, TestCase): - """Tests for ``peekable()`` behavior not incidentally covered by testing - ``collate()`` - - """ - cls = mi.peekable def test_indexing(self): @@ -783,64 +732,55 @@ class WindowedTests(TestCase): - """Tests for ``windowed()``""" - def test_basic(self): - actual = list(mi.windowed([1, 2, 3, 4, 5], 3)) - expected = [(1, 2, 3), (2, 3, 4), (3, 4, 5)] - self.assertEqual(actual, expected) - - def test_large_size(self): - """ - When the window size is larger than the iterable, and no fill value is - given,``None`` should be filled in. - """ - actual = list(mi.windowed([1, 2, 3, 4, 5], 6)) - expected = [(1, 2, 3, 4, 5, None)] - self.assertEqual(actual, expected) - - def test_fillvalue(self): - """ - When sizes don't match evenly, the given fill value should be used. - """ iterable = [1, 2, 3, 4, 5] - for n, kwargs, expected in [ - (6, {}, [(1, 2, 3, 4, 5, '!')]), # n > len(iterable) - (3, {'step': 3}, [(1, 2, 3), (4, 5, '!')]), # using ``step`` - ]: - actual = list(mi.windowed(iterable, n, fillvalue='!', **kwargs)) - self.assertEqual(actual, expected) + for n, expected in ( + (6, [(1, 2, 3, 4, 5, None)]), + (5, [(1, 2, 3, 4, 5)]), + (4, [(1, 2, 3, 4), (2, 3, 4, 5)]), + (3, [(1, 2, 3), (2, 3, 4), (3, 4, 5)]), + (2, [(1, 2), (2, 3), (3, 4), (4, 5)]), + (1, [(1,), (2,), (3,), (4,), (5,)]), + (0, [()]), + ): + with self.subTest(n=n): + actual = list(mi.windowed(iterable, n)) + self.assertEqual(actual, expected) - def test_zero(self): - """When the window size is zero, an empty tuple should be emitted.""" - actual = list(mi.windowed([1, 2, 3, 4, 5], 0)) - expected = [tuple()] + def test_fillvalue(self): + actual = list(mi.windowed([1, 2, 3, 4, 5], 6, fillvalue='!')) + expected = [(1, 2, 3, 4, 5, '!')] self.assertEqual(actual, expected) - def test_negative(self): - """When the window size is negative, ValueError should be raised.""" - with self.assertRaises(ValueError): - list(mi.windowed([1, 2, 3, 4, 5], -1)) - def test_step(self): - """The window should advance by the number of steps provided""" iterable = [1, 2, 3, 4, 5, 6, 7] for n, step, expected in [ (3, 2, [(1, 2, 3), (3, 4, 5), (5, 6, 7)]), # n > step (3, 3, [(1, 2, 3), (4, 5, 6), (7, None, None)]), # n == step - (3, 4, [(1, 2, 3), (5, 6, 7)]), # line up nicely + (3, 4, [(1, 2, 3), (5, 6, 7)]), # lines up nicely (3, 5, [(1, 2, 3), (6, 7, None)]), # off by one (3, 6, [(1, 2, 3), (7, None, None)]), # off by two (3, 7, [(1, 2, 3)]), # step past the end (7, 8, [(1, 2, 3, 4, 5, 6, 7)]), # step > len(iterable) ]: - actual = list(mi.windowed(iterable, n, step=step)) - self.assertEqual(actual, expected) + with self.subTest(n=n, step=step): + actual = list(mi.windowed(iterable, n, step=step)) + self.assertEqual(actual, expected) + def test_invalid_step(self): # Step must be greater than or equal to 1 with self.assertRaises(ValueError): - list(mi.windowed(iterable, 3, step=0)) + list(mi.windowed([1, 2, 3, 4, 5], 3, step=0)) + + def test_fillvalue_step(self): + actual = list(mi.windowed([1, 2, 3, 4, 5], 3, fillvalue='!', step=3)) + expected = [(1, 2, 3), (4, 5, '!')] + self.assertEqual(actual, expected) + + def test_negative(self): + with self.assertRaises(ValueError): + list(mi.windowed([1, 2, 3, 4, 5], -1)) class SubstringsTests(TestCase): @@ -5137,3 +5077,100 @@ def test_not_identical_but_equal(self): self.assertTrue([1, True], [1.0, complex(1, 0)]) + + +class ConstrainedBatchesTests(TestCase): + def test_basic(self): + zen = [ + 'Beautiful is better than ugly', + 'Explicit is better than implicit', + 'Simple is better than complex', + 'Complex is better than complicated', + 'Flat is better than nested', + 'Sparse is better than dense', + 'Readability counts', + ] + for size, expected in ( + ( + 34, + [ + (zen[0],), + (zen[1],), + (zen[2],), + (zen[3],), + (zen[4],), + (zen[5],), + (zen[6],), + ], + ), + ( + 61, + [ + (zen[0], zen[1]), + (zen[2],), + (zen[3], zen[4]), + (zen[5], zen[6]), + ], + ), + ( + 90, + [ + (zen[0], zen[1], zen[2]), + (zen[3], zen[4], zen[5]), + (zen[6],), + ], + ), + ( + 124, + [(zen[0], zen[1], zen[2], zen[3]), (zen[4], zen[5], zen[6])], + ), + ( + 150, + [(zen[0], zen[1], zen[2], zen[3], zen[4]), (zen[5], zen[6])], + ), + ( + 177, + [(zen[0], zen[1], zen[2], zen[3], zen[4], zen[5]), (zen[6],)], + ), + ): + with self.subTest(size=size): + actual = list(mi.constrained_batches(iter(zen), size)) + self.assertEqual(actual, expected) + + def test_max_count(self): + iterable = ['1', '1', '12345678', '12345', '12345'] + max_size = 10 + max_count = 2 + actual = list(mi.constrained_batches(iterable, max_size, max_count)) + expected = [('1', '1'), ('12345678',), ('12345', '12345')] + self.assertEqual(actual, expected) + + def test_strict(self): + iterable = ['1', '123456789', '1'] + size = 8 + with self.assertRaises(ValueError): + list(mi.constrained_batches(iterable, size)) + + actual = list(mi.constrained_batches(iterable, size, strict=False)) + expected = [('1',), ('123456789',), ('1',)] + self.assertEqual(actual, expected) + + def test_get_len(self): + class Record(tuple): + def total_size(self): + return sum(len(x) for x in self) + + record_3 = Record(('1', '23')) + record_5 = Record(('1234', '1')) + record_10 = Record(('1', '12345678', '1')) + record_2 = Record(('1', '1')) + iterable = [record_3, record_5, record_10, record_2] + + self.assertEqual( + list( + mi.constrained_batches( + iterable, 10, get_len=lambda x: x.total_size() + ) + ), + [(record_3, record_5), (record_10,), (record_2,)], + ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/more-itertools-8.14.0/tests/test_recipes.py new/more-itertools-9.0.0/tests/test_recipes.py --- old/more-itertools-8.14.0/tests/test_recipes.py 2022-08-09 15:43:59.891580000 +0200 +++ new/more-itertools-9.0.0/tests/test_recipes.py 2022-10-18 15:38:19.073834700 +0200 @@ -1,6 +1,5 @@ -import warnings - from doctest import DocTestSuite +from functools import reduce from itertools import combinations, count, permutations from math import factorial from unittest import TestCase @@ -335,16 +334,6 @@ with self.assertRaises(ValueError): list(mi.grouper(iter(seq), n, incomplete='strict')) - def test_legacy_order(self): - with warnings.catch_warnings(record=True) as caught: - warnings.simplefilter('always') - self.assertEqual( - list(mi.grouper(3, 'ABCDEF')), - [('A', 'B', 'C'), ('D', 'E', 'F')], - ) - - self.assertEqual(caught[0].category, DeprecationWarning) - def test_invalid_incomplete(self): with self.assertRaises(ValueError): list(mi.grouper('ABCD', 3, incomplete='bogus')) @@ -783,6 +772,39 @@ self.assertEqual(list(before), [1, True]) self.assertEqual(list(after), [0, False]) + @staticmethod + def _group_events(events): + events = iter(events) + + while True: + try: + operation = next(events) + except StopIteration: + break + assert operation in ["SUM", "MULTIPLY"] + + # Here, the remainder `events` is passed into `before_and_after` + # again, which would be problematic if the remainder is a + # generator function (as in Python 3.10 itertools recipes), since + # that creates recursion. `itertools.chain` solves this problem. + numbers, events = mi.before_and_after( + lambda e: isinstance(e, int), events + ) + + yield (operation, numbers) + + def test_nested_remainder(self): + events = ["SUM", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] * 1000 + events += ["MULTIPLY", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] * 1000 + + for operation, numbers in self._group_events(events): + if operation == "SUM": + res = sum(numbers) + self.assertEqual(res, 55) + elif operation == "MULTIPLY": + res = reduce(lambda a, b: a * b, numbers) + self.assertEqual(res, 3628800) + class TriplewiseTests(TestCase): def test_basic(self): @@ -843,3 +865,80 @@ with self.subTest(expected=expected): actual = list(mi.subslices(iterable)) self.assertEqual(actual, expected) + + +class PolynomialFromRootsTests(TestCase): + def test_basic(self): + for roots, expected in [ + ((2, 1, -1), [1, -2, -1, 2]), + ((2, 3), [1, -5, 6]), + ((1, 2, 3), [1, -6, 11, -6]), + ((2, 4, 1), [1, -7, 14, -8]), + ]: + with self.subTest(roots=roots): + actual = mi.polynomial_from_roots(roots) + self.assertEqual(actual, expected) + + +class SieveTests(TestCase): + def test_basic(self): + self.assertEqual( + list(mi.sieve(67)), + [ + 2, + 3, + 5, + 7, + 11, + 13, + 17, + 19, + 23, + 29, + 31, + 37, + 41, + 43, + 47, + 53, + 59, + 61, + ], + ) + self.assertEqual(list(mi.sieve(68))[-1], 67) + + def test_prime_counts(self): + for n, expected in ( + (100, 25), + (1_000, 168), + (10_000, 1229), + (100_000, 9592), + (1_000_000, 78498), + ): + with self.subTest(n=n): + self.assertEqual(mi.ilen(mi.sieve(n)), expected) + + def test_small_numbers(self): + with self.assertRaises(ValueError): + list(mi.sieve(-1)) + + for n in (0, 1, 2): + with self.subTest(n=n): + self.assertEqual(list(mi.sieve(n)), []) + + +class BatchedTests(TestCase): + def test_basic(self): + iterable = range(1, 5 + 1) + for n, expected in ( + (0, []), + (1, [[1], [2], [3], [4], [5]]), + (2, [[1, 2], [3, 4], [5]]), + (3, [[1, 2, 3], [4, 5]]), + (4, [[1, 2, 3, 4], [5]]), + (5, [[1, 2, 3, 4, 5]]), + (6, [[1, 2, 3, 4, 5]]), + ): + with self.subTest(n=n): + actual = list(mi.batched(iterable, n)) + self.assertEqual(actual, expected)