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 <dmuel...@suse.com>
+
+- 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)


Reply via email to