Hello community,
here is the log from the commit of package python-more-itertools for
openSUSE:Factory checked in at 2020-10-25 18:08:22
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-more-itertools (Old)
and /work/SRC/openSUSE:Factory/.python-more-itertools.new.3463 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-more-itertools"
Sun Oct 25 18:08:22 2020 rev:12 rq:834878 version:8.5.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-more-itertools/python-more-itertools.changes
2020-08-13 10:15:47.766698836 +0200
+++
/work/SRC/openSUSE:Factory/.python-more-itertools.new.3463/python-more-itertools.changes
2020-10-25 18:08:29.907451950 +0100
@@ -1,0 +2,12 @@
+Wed Sep 16 11:13:30 UTC 2020 - Dirk Mueller <[email protected]>
+
+- update to 8.5.0:
+ * windowed_complete() (thanks to MarcinKonowalczyk)
+ Changes to existing itertools:
+ * The is_sorted() implementation was improved (thanks to cool-RR)
+ * The groupby_transform() now accepts a reducefunc parameter.
+ * The last() implementation was improved (thanks to brianmaissy)
+ * Various documentation fixes (thanks to craigrosie, samuelstjean, PiCT0)
+ * The tests for distinct_combinations() were improved (thanks to Minabsapi)
+
+-------------------------------------------------------------------
Old:
----
more-itertools-8.4.0.tar.gz
New:
----
more-itertools-8.5.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-more-itertools.spec ++++++
--- /var/tmp/diff_new_pack.GgeXDS/_old 2020-10-25 18:08:31.571453524 +0100
+++ /var/tmp/diff_new_pack.GgeXDS/_new 2020-10-25 18:08:31.575453528 +0100
@@ -19,7 +19,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
%define skip_python2 1
Name: python-more-itertools
-Version: 8.4.0
+Version: 8.5.0
Release: 0
Summary: More routines for operating on iterables, beyond itertools
License: MIT
++++++ more-itertools-8.4.0.tar.gz -> more-itertools-8.5.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.4.0/PKG-INFO
new/more-itertools-8.5.0/PKG-INFO
--- old/more-itertools-8.4.0/PKG-INFO 2020-06-14 13:31:51.462923300 +0200
+++ new/more-itertools-8.5.0/PKG-INFO 2020-08-28 20:32:44.358470200 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: more-itertools
-Version: 8.4.0
+Version: 8.5.0
Summary: More routines for operating on iterables, beyond itertools
Home-page: https://github.com/more-itertools/more-itertools
Author: Erik Rose
@@ -13,9 +13,6 @@
.. image::
https://readthedocs.org/projects/more-itertools/badge/?version=latest
:target: https://more-itertools.readthedocs.io/en/stable/
- .. image::
https://coveralls.io/repos/github/more-itertools/more-itertools/badge.svg?branch=master
- :target:
https://coveralls.io/github/more-itertools/more-itertools?branch=master
-
Python's ``itertools`` library is a gem - you can compose elegant
solutions
for a variety of problems with the functions it provides. In
``more-itertools``
we collect additional building blocks, recipes, and routines for
working with
@@ -177,7 +174,7 @@
Blog posts about ``more-itertools``:
- * `Yo, I heard you like decorators
<https://bbayles.com/index/decorator_factory>`__
+ * `Yo, I heard you like decorators
<https://www.bbayles.com/index/decorator_factory>`__
* `Tour of Python Itertools <https://martinheinz.dev/blog/16>`__
@@ -196,6 +193,31 @@
:noindex:
+ 8.5.0
+ -----
+
+
+ * New itertools
+ * windowed_complete (thanks to MarcinKonowalczyk)
+
+ * Changes to existing itertools:
+ * The is_sorted implementation was improved (thanks to cool-RR)
+ * The groupby_transform now accepts a ``reducefunc`` parameter.
+ * The last implementation was improved (thanks to brianmaissy)
+
+ * Other changes
+ * Various documentation fixes (thanks to craigrosie, samuelstjean,
PiCT0)
+ * The tests for distinct_combinations were improved (thanks to
Minabsapi)
+ * Automated tests now run on GitHub Actions. All commits now check:
+ * That unit tests pass
+ * That the examples in docstrings work
+ * That test coverage remains high (using `coverage`)
+ * For linting errors (using `flake8`)
+ * For consistent style (using `black`)
+ * That the type stubs work (using `mypy`)
+ * That the docs build correctly (using `sphinx`)
+ * That packages build correctly (using `twine`)
+
8.4.0
-----
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.4.0/README.rst
new/more-itertools-8.5.0/README.rst
--- old/more-itertools-8.4.0/README.rst 2020-06-14 13:31:22.000000000 +0200
+++ new/more-itertools-8.5.0/README.rst 2020-08-28 20:08:58.000000000 +0200
@@ -5,9 +5,6 @@
.. image::
https://readthedocs.org/projects/more-itertools/badge/?version=latest
:target: https://more-itertools.readthedocs.io/en/stable/
-.. image::
https://coveralls.io/repos/github/more-itertools/more-itertools/badge.svg?branch=master
- :target:
https://coveralls.io/github/more-itertools/more-itertools?branch=master
-
Python's ``itertools`` library is a gem - you can compose elegant solutions
for a variety of problems with the functions it provides. In ``more-itertools``
we collect additional building blocks, recipes, and routines for working with
@@ -169,7 +166,7 @@
Blog posts about ``more-itertools``:
-* `Yo, I heard you like decorators
<https://bbayles.com/index/decorator_factory>`__
+* `Yo, I heard you like decorators
<https://www.bbayles.com/index/decorator_factory>`__
* `Tour of Python Itertools <https://martinheinz.dev/blog/16>`__
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.4.0/docs/api.rst
new/more-itertools-8.5.0/docs/api.rst
--- old/more-itertools-8.4.0/docs/api.rst 2020-06-14 13:31:22.000000000
+0200
+++ new/more-itertools-8.5.0/docs/api.rst 2020-08-28 20:08:58.000000000
+0200
@@ -62,6 +62,7 @@
.. autofunction:: substrings
.. autofunction:: substrings_indexes
.. autofunction:: stagger
+.. autofunction:: windowed_complete
----
@@ -224,6 +225,7 @@
.. autofunction:: always_reversible
.. autofunction:: consumer
.. autofunction:: with_iter
+.. autoclass:: callback_iter
----
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.4.0/docs/versions.rst
new/more-itertools-8.5.0/docs/versions.rst
--- old/more-itertools-8.4.0/docs/versions.rst 2020-06-14 13:31:22.000000000
+0200
+++ new/more-itertools-8.5.0/docs/versions.rst 2020-08-28 20:22:12.000000000
+0200
@@ -5,6 +5,31 @@
.. automodule:: more_itertools
:noindex:
+8.5.0
+-----
+
+
+* New itertools
+ * :func:`windowed_complete` (thanks to MarcinKonowalczyk)
+
+* Changes to existing itertools:
+ * The :func:`is_sorted` implementation was improved (thanks to cool-RR)
+ * The :func:`groupby_transform` now accepts a ``reducefunc`` parameter.
+ * The :func:`last` implementation was improved (thanks to brianmaissy)
+
+* Other changes
+ * Various documentation fixes (thanks to craigrosie, samuelstjean, PiCT0)
+ * The tests for :func:`distinct_combinations` were improved (thanks to
Minabsapi)
+ * Automated tests now run on GitHub Actions. All commits now check:
+ * That unit tests pass
+ * That the examples in docstrings work
+ * That test coverage remains high (using `coverage`)
+ * For linting errors (using `flake8`)
+ * For consistent style (using `black`)
+ * That the type stubs work (using `mypy`)
+ * That the docs build correctly (using `sphinx`)
+ * That packages build correctly (using `twine`)
+
8.4.0
-----
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.4.0/more_itertools/__init__.py
new/more-itertools-8.5.0/more_itertools/__init__.py
--- old/more-itertools-8.4.0/more_itertools/__init__.py 2020-06-14
13:31:22.000000000 +0200
+++ new/more-itertools-8.5.0/more_itertools/__init__.py 2020-08-28
20:22:42.000000000 +0200
@@ -1,4 +1,4 @@
from .more import * # noqa
from .recipes import * # noqa
-__version__ = '8.4.0'
+__version__ = '8.5.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.4.0/more_itertools/more.py
new/more-itertools-8.5.0/more_itertools/more.py
--- old/more-itertools-8.4.0/more_itertools/more.py 2020-06-14
13:31:22.000000000 +0200
+++ new/more-itertools-8.5.0/more_itertools/more.py 2020-08-28
20:29:13.000000000 +0200
@@ -1,6 +1,8 @@
import warnings
+
from collections import Counter, defaultdict, deque, abc
from collections.abc import Sequence
+from concurrent.futures import ThreadPoolExecutor
from functools import partial, wraps
from heapq import merge, heapify, heapreplace, heappop
from itertools import (
@@ -18,9 +20,10 @@
zip_longest,
)
from math import exp, floor, log
+from queue import Empty, Queue
from random import random, randrange, uniform
from operator import itemgetter, sub, gt, lt
-from sys import maxsize
+from sys import hexversion, maxsize
from time import monotonic
from .recipes import (
@@ -33,10 +36,12 @@
)
__all__ = [
+ 'AbortThread',
'adjacent',
'always_iterable',
'always_reversible',
'bucket',
+ 'callback_iter',
'chunked',
'circular_shifts',
'collapse',
@@ -105,6 +110,7 @@
'UnequalIterablesError',
'zip_equal',
'zip_offset',
+ 'windowed_complete',
]
_marker = object()
@@ -175,18 +181,19 @@
raise ``ValueError``.
"""
try:
- try:
- # Try to access the last item directly
+ if isinstance(iterable, Sequence):
return iterable[-1]
- except (TypeError, AttributeError, KeyError):
- # If not slice-able, iterate entirely using length-1 deque
- return deque(iterable, maxlen=1)[0]
- except IndexError as e: # If the iterable was empty
+ # Work around https://bugs.python.org/issue38525
+ elif hasattr(iterable, '__reversed__') and (hexversion != 0x030800F0):
+ return next(reversed(iterable))
+ else:
+ return deque(iterable, maxlen=1)[-1]
+ except (IndexError, TypeError, StopIteration):
if default is _marker:
raise ValueError(
- 'last() was called on an empty iterable, and no '
- 'default value was provided.'
- ) from e
+ 'last() was called on an empty iterable, and no default was '
+ 'provided.'
+ )
return default
@@ -260,7 +267,7 @@
>>> if p: # peekable has items
... list(p)
['a', 'b']
- >>> if not p: # peekable is exhaused
+ >>> if not p: # peekable is exhausted
... list(p)
[]
@@ -462,9 +469,9 @@
def iterate(func, start):
"""Return ``start``, ``func(start)``, ``func(func(start))``, ...
- >>> from itertools import islice
- >>> list(islice(iterate(lambda x: 2*x, 1), 10))
- [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
+ >>> from itertools import islice
+ >>> list(islice(iterate(lambda x: 2*x, 1), 10))
+ [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
"""
while True:
@@ -572,8 +579,10 @@
If *r* is given, only the *r*-length permutations are yielded.
- >>> sorted(distinct_permutations([1, 0, 1], r=3))
- [(0, 1, 1), (1, 0, 1), (1, 1, 0)]
+ >>> sorted(distinct_permutations([1, 0, 1], r=2))
+ [(0, 1), (1, 0), (1, 1)]
+ >>> sorted(distinct_permutations(range(3), r=2))
+ [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
"""
# Algorithm: https://w.wiki/Qai
@@ -1761,21 +1770,23 @@
return zip(adjacent_to_selected, i2)
-def groupby_transform(iterable, keyfunc=None, valuefunc=None):
- """An extension of :func:`itertools.groupby` that transforms the values of
- *iterable* after grouping them.
- *keyfunc* is a function used to compute a grouping key for each item.
- *valuefunc* is a function for transforming the items after grouping.
+def groupby_transform(iterable, keyfunc=None, valuefunc=None, reducefunc=None):
+ """An extension of :func:`itertools.groupby` that can apply transformations
+ to the grouped data.
+
+ * *keyfunc* is a function computing a key value for each item in *iterable*
+ * *valuefunc* is a function that transforms the individual items from
+ *iterable* after grouping
+ * *reducefunc* is a function that transforms each group of items
+
+ >>> iterable = 'aAAbBBcCC'
+ >>> keyfunc = lambda k: k.upper()
+ >>> valuefunc = lambda v: v.lower()
+ >>> reducefunc = lambda g: ''.join(g)
+ >>> list(groupby_transform(iterable, keyfunc, valuefunc, reducefunc))
+ [('A', 'aaa'), ('B', 'bbb'), ('C', 'ccc')]
- >>> iterable = 'AaaABbBCcA'
- >>> keyfunc = lambda x: x.upper()
- >>> valuefunc = lambda x: x.lower()
- >>> grouper = groupby_transform(iterable, keyfunc, valuefunc)
- >>> [(k, ''.join(g)) for k, g in grouper]
- [('A', 'aaaa'), ('B', 'bbb'), ('C', 'cc'), ('A', 'a')]
-
- *keyfunc* and *valuefunc* default to identity functions if they are not
- specified.
+ Each optional argument defaults to an identity function if not specified.
:func:`groupby_transform` is useful when grouping elements of an iterable
using a separate iterable as the key. To do this, :func:`zip` the iterables
@@ -1795,8 +1806,13 @@
duplicate groups, you should sort the iterable by the key function.
"""
- res = groupby(iterable, keyfunc)
- return ((k, map(valuefunc, g)) for k, g in res) if valuefunc else res
+ ret = groupby(iterable, keyfunc)
+ if valuefunc:
+ ret = ((k, map(valuefunc, g)) for k, g in ret)
+ if reducefunc:
+ ret = ((k, reducefunc(g)) for k, g in ret)
+
+ return ret
class numeric_range(abc.Sequence, abc.Hashable):
@@ -2632,8 +2648,8 @@
def circular_shifts(iterable):
"""Return a list of circular shifts of *iterable*.
- >>> circular_shifts(range(4))
- [(0, 1, 2, 3), (1, 2, 3, 0), (2, 3, 0, 1), (3, 0, 1, 2)]
+ >>> circular_shifts(range(4))
+ [(0, 1, 2, 3), (1, 2, 3, 0), (2, 3, 0, 1), (3, 0, 1, 2)]
"""
lst = list(iterable)
return take(len(lst), windowed(cycle(lst), len(lst)))
@@ -2876,7 +2892,7 @@
def partitions(iterable):
- """Yield all possible order-perserving partitions of *iterable*.
+ """Yield all possible order-preserving partitions of *iterable*.
>>> iterable = 'abc'
>>> for part in partitions(iterable):
@@ -3239,8 +3255,171 @@
compare = lt if reverse else gt
it = iterable if (key is None) else map(key, iterable)
- for a, b in pairwise(it):
- if compare(a, b):
+ return not any(starmap(compare, pairwise(it)))
+
+
+class AbortThread(BaseException):
+ pass
+
+
+class callback_iter:
+ """Convert a function that uses callbacks to an iterator.
+
+ Let *func* be a function that takes a `callback` keyword argument.
+ For example:
+
+ >>> def func(callback=None):
+ ... for i, c in [(1, 'a'), (2, 'b'), (3, 'c')]:
+ ... if callback:
+ ... callback(i, c)
+ ... return 4
+
+
+ Use ``with callback_iter(func)`` to get an iterator over the parameters
+ that are delivered to the callback.
+
+ >>> with callback_iter(func) as it:
+ ... for args, kwargs in it:
+ ... print(args)
+ (1, 'a')
+ (2, 'b')
+ (3, 'c')
+
+ The function will be called in a background thread. The ``done`` property
+ indicates whether it has completed execution.
+
+ >>> it.done
+ True
+
+ If it completes successfully, its return value will be available
+ in the ``result`` property.
+
+ >>> it.result
+ 4
+
+ Notes:
+
+ * If the function uses some keyword argument besides ``callback``, supply
+ *callback_kwd*.
+ * If it finished executing, but raised an exception, accessing the
+ ``result`` property will raise the same exception.
+ * If it hasn't finished executing, accessing the ``result``
+ property from within the ``with`` block will raise ``RuntimeError``.
+ * If it hasn't finished executing, accessing the ``result`` property from
+ outside the ``with`` block will raise a
+ ``more_itertools.AbortThread`` exception.
+ * Provide *wait_seconds* to adjust how frequently the it is polled for
+ output.
+
+ """
+
+ def __init__(self, func, callback_kwd='callback', wait_seconds=0.1):
+ self._func = func
+ self._callback_kwd = callback_kwd
+ self._aborted = False
+ self._future = None
+ self._wait_seconds = wait_seconds
+ self._executor = ThreadPoolExecutor(max_workers=1)
+ self._iterator = self._reader()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self._aborted = True
+ self._executor.shutdown()
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ return next(self._iterator)
+
+ @property
+ def done(self):
+ if self._future is None:
return False
+ return self._future.done()
+
+ @property
+ def result(self):
+ if not self.done:
+ raise RuntimeError('Function has not yet completed')
+
+ return self._future.result()
+
+ def _reader(self):
+ q = Queue()
+
+ def callback(*args, **kwargs):
+ if self._aborted:
+ raise AbortThread('canceled by user')
+
+ q.put((args, kwargs))
+
+ self._future = self._executor.submit(
+ self._func, **{self._callback_kwd: callback}
+ )
+
+ while True:
+ try:
+ item = q.get(timeout=self._wait_seconds)
+ except Empty:
+ pass
+ else:
+ q.task_done()
+ yield item
+
+ if self._future.done():
+ break
+
+ remaining = []
+ while True:
+ try:
+ item = q.get_nowait()
+ except Empty:
+ break
+ else:
+ q.task_done()
+ remaining.append(item)
+ q.join()
+ yield from remaining
+
+
+def windowed_complete(iterable, n):
+ """
+ Yield ``(beginning, middle, end)`` tuples, where:
+
+ * Each ``middle`` has *n* items from *iterable*
+ * Each ``beginning`` has the items before the ones in ``middle``
+ * Each ``end`` has the items after the ones in ``middle``
+
+ >>> iterable = range(7)
+ >>> n = 3
+ >>> for beginning, middle, end in windowed_complete(iterable, n):
+ ... print(beginning, middle, end)
+ () (0, 1, 2) (3, 4, 5, 6)
+ (0,) (1, 2, 3) (4, 5, 6)
+ (0, 1) (2, 3, 4) (5, 6)
+ (0, 1, 2) (3, 4, 5) (6,)
+ (0, 1, 2, 3) (4, 5, 6) ()
+
+ Notes:
+ * *n* must be at least 0 and most equal to the length of *iterable*.
+ * This function will exhaust *iterable* and store all its items.
+
+ """
+ if n < 0:
+ raise ValueError('n must be >= 0')
+
+ seq = tuple(iterable)
+ size = len(seq)
+
+ if n > size:
+ raise ValueError('n must be <= len(seq)')
- return True
+ for i in range(size - n + 1):
+ beginning = seq[:i]
+ middle = seq[i : i + n]
+ end = seq[i + n :]
+ yield beginning, middle, end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.4.0/more_itertools/recipes.py
new/more-itertools-8.5.0/more_itertools/recipes.py
--- old/more-itertools-8.4.0/more_itertools/recipes.py 2020-05-18
04:01:59.000000000 +0200
+++ new/more-itertools-8.5.0/more_itertools/recipes.py 2020-08-28
20:29:12.000000000 +0200
@@ -92,9 +92,9 @@
def tail(n, iterable):
"""Return an iterator over the last *n* items of *iterable*.
- >>> t = tail(3, 'ABCDEFG')
- >>> list(t)
- ['E', 'F', 'G']
+ >>> t = tail(3, 'ABCDEFG')
+ >>> list(t)
+ ['E', 'F', 'G']
"""
return iter(deque(iterable, maxlen=n))
@@ -143,11 +143,11 @@
def nth(iterable, n, default=None):
"""Returns the nth item or a default value.
- >>> l = range(10)
- >>> nth(l, 3)
- 3
- >>> nth(l, 20, "zebra")
- 'zebra'
+ >>> l = range(10)
+ >>> nth(l, 3)
+ 3
+ >>> nth(l, 20, "zebra")
+ 'zebra'
"""
return next(islice(iterable, n, None), default)
@@ -170,8 +170,8 @@
def quantify(iterable, pred=bool):
"""Return the how many times the predicate is true.
- >>> quantify([True, False, True])
- 2
+ >>> quantify([True, False, True])
+ 2
"""
return sum(map(pred, iterable))
@@ -194,8 +194,8 @@
def ncycles(iterable, n):
"""Returns the sequence elements *n* times
- >>> list(ncycles(["a", "b"], 3))
- ['a', 'b', 'a', 'b', 'a', 'b']
+ >>> list(ncycles(["a", "b"], 3))
+ ['a', 'b', 'a', 'b', 'a', 'b']
"""
return chain.from_iterable(repeat(tuple(iterable), n))
@@ -204,8 +204,8 @@
def dotproduct(vec1, vec2):
"""Returns the dot product of the two iterables.
- >>> dotproduct([10, 10], [20, 20])
- 400
+ >>> dotproduct([10, 10], [20, 20])
+ 400
"""
return sum(map(operator.mul, vec1, vec2))
@@ -253,8 +253,8 @@
def pairwise(iterable):
"""Returns an iterator of paired items, overlapping, from the original
- >>> take(4, pairwise(count()))
- [(0, 1), (1, 2), (2, 3), (3, 4)]
+ >>> take(4, pairwise(count()))
+ [(0, 1), (1, 2), (2, 3), (3, 4)]
"""
a, b = tee(iterable)
@@ -265,8 +265,8 @@
def grouper(iterable, n, fillvalue=None):
"""Collect data into fixed-length chunks or blocks.
- >>> list(grouper('ABCDEFG', 3, 'x'))
- [('A', 'B', 'C'), ('D', 'E', 'F'), ('G', 'x', 'x')]
+ >>> list(grouper('ABCDEFG', 3, 'x'))
+ [('A', 'B', 'C'), ('D', 'E', 'F'), ('G', 'x', 'x')]
"""
if isinstance(iterable, int):
@@ -401,10 +401,10 @@
def unique_justseen(iterable, key=None):
"""Yields elements in order, ignoring serial duplicates
- >>> list(unique_justseen('AAAABBBCCDAABBB'))
- ['A', 'B', 'C', 'D', 'A', 'B']
- >>> list(unique_justseen('ABBCcAD', str.lower))
- ['A', 'B', 'C', 'A', 'D']
+ >>> list(unique_justseen('AAAABBBCCDAABBB'))
+ ['A', 'B', 'C', 'D', 'A', 'B']
+ >>> list(unique_justseen('ABBCcAD', str.lower))
+ ['A', 'B', 'C', 'A', 'D']
"""
return map(next, map(operator.itemgetter(1), groupby(iterable, key)))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/more-itertools-8.4.0/more_itertools.egg-info/PKG-INFO
new/more-itertools-8.5.0/more_itertools.egg-info/PKG-INFO
--- old/more-itertools-8.4.0/more_itertools.egg-info/PKG-INFO 2020-06-14
13:31:51.000000000 +0200
+++ new/more-itertools-8.5.0/more_itertools.egg-info/PKG-INFO 2020-08-28
20:32:44.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: more-itertools
-Version: 8.4.0
+Version: 8.5.0
Summary: More routines for operating on iterables, beyond itertools
Home-page: https://github.com/more-itertools/more-itertools
Author: Erik Rose
@@ -13,9 +13,6 @@
.. image::
https://readthedocs.org/projects/more-itertools/badge/?version=latest
:target: https://more-itertools.readthedocs.io/en/stable/
- .. image::
https://coveralls.io/repos/github/more-itertools/more-itertools/badge.svg?branch=master
- :target:
https://coveralls.io/github/more-itertools/more-itertools?branch=master
-
Python's ``itertools`` library is a gem - you can compose elegant
solutions
for a variety of problems with the functions it provides. In
``more-itertools``
we collect additional building blocks, recipes, and routines for
working with
@@ -177,7 +174,7 @@
Blog posts about ``more-itertools``:
- * `Yo, I heard you like decorators
<https://bbayles.com/index/decorator_factory>`__
+ * `Yo, I heard you like decorators
<https://www.bbayles.com/index/decorator_factory>`__
* `Tour of Python Itertools <https://martinheinz.dev/blog/16>`__
@@ -196,6 +193,31 @@
:noindex:
+ 8.5.0
+ -----
+
+
+ * New itertools
+ * windowed_complete (thanks to MarcinKonowalczyk)
+
+ * Changes to existing itertools:
+ * The is_sorted implementation was improved (thanks to cool-RR)
+ * The groupby_transform now accepts a ``reducefunc`` parameter.
+ * The last implementation was improved (thanks to brianmaissy)
+
+ * Other changes
+ * Various documentation fixes (thanks to craigrosie, samuelstjean,
PiCT0)
+ * The tests for distinct_combinations were improved (thanks to
Minabsapi)
+ * Automated tests now run on GitHub Actions. All commits now check:
+ * That unit tests pass
+ * That the examples in docstrings work
+ * That test coverage remains high (using `coverage`)
+ * For linting errors (using `flake8`)
+ * For consistent style (using `black`)
+ * That the type stubs work (using `mypy`)
+ * That the docs build correctly (using `sphinx`)
+ * That packages build correctly (using `twine`)
+
8.4.0
-----
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.4.0/setup.cfg
new/more-itertools-8.5.0/setup.cfg
--- old/more-itertools-8.4.0/setup.cfg 2020-06-14 13:31:51.462923300 +0200
+++ new/more-itertools-8.5.0/setup.cfg 2020-08-28 20:32:44.358470200 +0200
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 8.4.0
+current_version = 8.5.0
commit = True
tag = False
files = more_itertools/__init__.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.4.0/tests/test_more.py
new/more-itertools-8.5.0/tests/test_more.py
--- old/more-itertools-8.4.0/tests/test_more.py 2020-06-14 13:31:22.000000000
+0200
+++ new/more-itertools-8.5.0/tests/test_more.py 2020-08-28 20:29:16.000000000
+0200
@@ -150,13 +150,19 @@
class LastTests(TestCase):
def test_basic(self):
- for iterable, expected in [
+ cases = [
(range(4), 3),
(iter(range(4)), 3),
(range(1), 0),
(iter(range(1)), 0),
(IterOnlyRange(5), 4),
- ]:
+ ({n: str(n) for n in range(5)}, 4),
+ ]
+ # Versions below 3.6.0 don't have ordered dicts
+ if version_info >= (3, 6, 0):
+ cases.append(({0: '0', -1: '-1', 2: '-2'}, 2))
+
+ for iterable, expected in cases:
with self.subTest(iterable=iterable):
self.assertEqual(mi.last(iterable), expected)
@@ -164,6 +170,7 @@
for iterable, default, expected in [
(range(1), None, 0),
([], None, None),
+ ({}, None, None),
(iter([]), None, None),
]:
with self.subTest(args=(iterable, default)):
@@ -172,17 +179,8 @@
def test_empty(self):
for iterable in ([], iter(range(0))):
with self.subTest(iterable=iterable):
- try:
+ with self.assertRaises(ValueError):
mi.last(iterable)
- except ValueError:
- formatted_exc = format_exc()
- self.assertIn('IndexError', formatted_exc)
- self.assertIn(
- 'The above exception was the direct cause',
- formatted_exc,
- )
- else:
- self.fail()
class NthOrLastTests(TestCase):
@@ -2150,7 +2148,6 @@
class GroupByTransformTests(TestCase):
def assertAllGroupsEqual(self, groupby1, groupby2):
- """Compare two groupby objects for equality, both keys and groups."""
for a, b in zip(groupby1, groupby2):
key1, group1 = a
key2, group2 = b
@@ -2160,7 +2157,6 @@
self.assertRaises(StopIteration, lambda: next(groupby2))
def test_default_funcs(self):
- """Test that groupby_transform() with default args mimics groupby()"""
iterable = [(x // 5, x) for x in range(1000)]
actual = mi.groupby_transform(iterable)
expected = groupby(iterable)
@@ -2207,6 +2203,22 @@
expected = groupby(iterable, key)
self.assertAllGroupsEqual(actual, expected)
+ def test_reducefunc(self):
+ iterable = range(50)
+ keyfunc = lambda k: 10 * (k // 10)
+ valuefunc = lambda v: v + 1
+ reducefunc = sum
+ actual = list(
+ mi.groupby_transform(
+ iterable,
+ keyfunc=keyfunc,
+ valuefunc=valuefunc,
+ reducefunc=reducefunc,
+ )
+ )
+ expected = [(0, 55), (10, 155), (20, 255), (30, 355), (40, 455)]
+ self.assertEqual(actual, expected)
+
class NumericRangeTests(TestCase):
def test_basic(self):
@@ -3709,20 +3721,20 @@
class DistinctCombinationsTests(TestCase):
def test_basic(self):
- iterable = (1, 2, 2, 3, 3, 3)
- for r in range(len(iterable)):
- with self.subTest(r=r):
- actual = sorted(mi.distinct_combinations(iterable, r))
- expected = sorted(set(combinations(iterable, r)))
- self.assertEqual(actual, expected)
-
- def test_distinct(self):
- iterable = list(range(6))
- for r in range(len(iterable)):
- with self.subTest(r=r):
- actual = list(mi.distinct_combinations(iterable, r))
- expected = list(combinations(iterable, r))
- self.assertEqual(actual, expected)
+ for iterable in [
+ (1, 2, 2, 3, 3, 3), # In order
+ range(6), # All distinct
+ 'abbccc', # Not numbers
+ 'cccbba', # Backward
+ 'mississippi', # No particular order
+ ]:
+ for r in range(len(iterable)):
+ with self.subTest(iterable=iterable, r=r):
+ actual = list(mi.distinct_combinations(iterable, r))
+ expected = list(
+ mi.unique_everseen(combinations(iterable, r))
+ )
+ self.assertEqual(actual, expected)
def test_negative(self):
with self.assertRaises(ValueError):
@@ -3901,3 +3913,136 @@
py_result = iterable == sorted(iterable, **kwargs)
self.assertEqual(mi_result, expected)
self.assertEqual(mi_result, py_result)
+
+
+class CallbackIterTests(TestCase):
+ def _target(self, cb=None, exc=None, wait=0):
+ total = 0
+ for i, c in enumerate('abc', 1):
+ total += i
+ if wait:
+ sleep(wait)
+ if cb:
+ cb(i, c, intermediate_total=total)
+ if exc:
+ raise exc('error in target')
+
+ return total
+
+ def test_basic(self):
+ func = lambda callback=None: self._target(cb=callback, wait=0.02)
+ with mi.callback_iter(func, wait_seconds=0.01) as it:
+ # Execution doesn't start until we begin iterating
+ self.assertFalse(it.done)
+
+ # Consume everything
+ self.assertEqual(
+ list(it),
+ [
+ ((1, 'a'), {'intermediate_total': 1}),
+ ((2, 'b'), {'intermediate_total': 3}),
+ ((3, 'c'), {'intermediate_total': 6}),
+ ],
+ )
+
+ # After consuming everything the future is done and the
+ # result is available.
+ self.assertTrue(it.done)
+ self.assertEqual(it.result, 6)
+
+ # This examines the internal state of the ThreadPoolExecutor. This
+ # isn't documented, so may break in future Python versions.
+ self.assertTrue(it._executor._shutdown)
+
+ def test_callback_kwd(self):
+ with mi.callback_iter(self._target, callback_kwd='cb') as it:
+ self.assertEqual(
+ list(it),
+ [
+ ((1, 'a'), {'intermediate_total': 1}),
+ ((2, 'b'), {'intermediate_total': 3}),
+ ((3, 'c'), {'intermediate_total': 6}),
+ ],
+ )
+
+ def test_partial_consumption(self):
+ func = lambda callback=None: self._target(cb=callback)
+ with mi.callback_iter(func) as it:
+ self.assertEqual(next(it), ((1, 'a'), {'intermediate_total': 1}))
+
+ self.assertTrue(it._executor._shutdown)
+
+ def test_abort(self):
+ func = lambda callback=None: self._target(cb=callback, wait=0.1)
+ with mi.callback_iter(func) as it:
+ self.assertEqual(next(it), ((1, 'a'), {'intermediate_total': 1}))
+
+ with self.assertRaises(mi.AbortThread):
+ it.result
+
+ def test_no_result(self):
+ func = lambda callback=None: self._target(cb=callback)
+ with mi.callback_iter(func) as it:
+ with self.assertRaises(RuntimeError):
+ it.result
+
+ def test_exception(self):
+ func = lambda callback=None: self._target(cb=callback, exc=ValueError)
+ with mi.callback_iter(func) as it:
+ self.assertEqual(
+ next(it),
+ ((1, 'a'), {'intermediate_total': 1}),
+ )
+
+ with self.assertRaises(ValueError):
+ it.result
+
+
+class WindowedCompleteTests(TestCase):
+ """Tests for ``windowed_complete()``"""
+
+ def test_basic(self):
+ actual = list(mi.windowed_complete([1, 2, 3, 4, 5], 3))
+ expected = [
+ ((), (1, 2, 3), (4, 5)),
+ ((1,), (2, 3, 4), (5,)),
+ ((1, 2), (3, 4, 5), ()),
+ ]
+ self.assertEqual(actual, expected)
+
+ def test_zero_length(self):
+ actual = list(mi.windowed_complete([1, 2, 3], 0))
+ expected = [
+ ((), (), (1, 2, 3)),
+ ((1,), (), (2, 3)),
+ ((1, 2), (), (3,)),
+ ((1, 2, 3), (), ()),
+ ]
+ self.assertEqual(actual, expected)
+
+ def test_wrong_length(self):
+ seq = [1, 2, 3, 4, 5]
+ for n in (-10, -1, len(seq) + 1, len(seq) + 10):
+ with self.subTest(n=n):
+ with self.assertRaises(ValueError):
+ list(mi.windowed_complete(seq, n))
+
+ def test_every_partition(self):
+ every_partition = lambda seq: chain(
+ *map(partial(mi.windowed_complete, seq), range(len(seq)))
+ )
+
+ seq = 'ABC'
+ actual = list(every_partition(seq))
+ expected = [
+ ((), (), ('A', 'B', 'C')),
+ (('A',), (), ('B', 'C')),
+ (('A', 'B'), (), ('C',)),
+ (('A', 'B', 'C'), (), ()),
+ ((), ('A',), ('B', 'C')),
+ (('A',), ('B',), ('C',)),
+ (('A', 'B'), ('C',), ()),
+ ((), ('A', 'B'), ('C',)),
+ (('A',), ('B', 'C'), ()),
+ ]
+ self.assertEqual(actual, expected)