Hello community,
here is the log from the commit of package python-more-itertools for
openSUSE:Factory checked in at 2020-03-08 22:21:32
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-more-itertools (Old)
and /work/SRC/openSUSE:Factory/.python-more-itertools.new.26092 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-more-itertools"
Sun Mar 8 22:21:32 2020 rev:9 rq:780384 version:8.2.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-more-itertools/python-more-itertools.changes
2020-01-24 13:09:52.989402235 +0100
+++
/work/SRC/openSUSE:Factory/.python-more-itertools.new.26092/python-more-itertools.changes
2020-03-08 22:21:40.179986101 +0100
@@ -1,0 +2,10 @@
+Fri Feb 28 21:37:22 UTC 2020 - Dirk Mueller <[email protected]>
+
+- update to 8.2.0:
+ * The .pyi files for typing were updated. (thanks to blueyed and
ilai-deutel)
+ * :func:`numeric_range` now behaves more like the built-in :func:`range`.
(thanks to jferard)
+ * :func:`bucket` now allows for enumerating keys. (thanks to alexchandel)
+ * :func:`sliced` now should now work for numpy arrays. (thanks to sswingle)
+ * :func:`seekable` now has a ``maxlen`` parameter.
+
+-------------------------------------------------------------------
Old:
----
more-itertools-8.1.0.tar.gz
New:
----
more-itertools-8.2.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-more-itertools.spec ++++++
--- /var/tmp/diff_new_pack.AhbIVF/_old 2020-03-08 22:21:40.695986419 +0100
+++ /var/tmp/diff_new_pack.AhbIVF/_new 2020-03-08 22:21:40.699986422 +0100
@@ -19,7 +19,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
%define skip_python2 1
Name: python-more-itertools
-Version: 8.1.0
+Version: 8.2.0
Release: 0
Summary: More routines for operating on iterables, beyond itertools
License: MIT
++++++ more-itertools-8.1.0.tar.gz -> more-itertools-8.2.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.1.0/PKG-INFO
new/more-itertools-8.2.0/PKG-INFO
--- old/more-itertools-8.1.0/PKG-INFO 2020-01-11 19:59:22.000000000 +0100
+++ new/more-itertools-8.2.0/PKG-INFO 2020-01-29 13:52:26.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 1.2
Name: more-itertools
-Version: 8.1.0
+Version: 8.2.0
Summary: More routines for operating on iterables, beyond itertools
Home-page: https://github.com/erikrose/more-itertools
Author: Erik Rose
@@ -182,6 +182,18 @@
:noindex:
+ 8.2.0
+ -----
+
+ * Bug fixes
+ * The .pyi files for typing were updated. (thanks to blueyed and
ilai-deutel)
+
+ * Changes to existing itertools:
+ * numeric_range now behaves more like the built-in range. (thanks
to jferard)
+ * bucket now allows for enumerating keys. (thanks to alexchandel)
+ * sliced now should now work for numpy arrays. (thanks to sswingle)
+ * seekable now has a ``maxlen`` parameter.
+
8.1.0
-----
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.1.0/docs/versions.rst
new/more-itertools-8.2.0/docs/versions.rst
--- old/more-itertools-8.1.0/docs/versions.rst 2020-01-11 19:59:09.000000000
+0100
+++ new/more-itertools-8.2.0/docs/versions.rst 2020-01-29 13:49:15.000000000
+0100
@@ -5,6 +5,18 @@
.. automodule:: more_itertools
:noindex:
+8.2.0
+-----
+
+* Bug fixes
+ * The .pyi files for typing were updated. (thanks to blueyed and
ilai-deutel)
+
+* Changes to existing itertools:
+ * :func:`numeric_range` now behaves more like the built-in :func:`range`.
(thanks to jferard)
+ * :func:`bucket` now allows for enumerating keys. (thanks to alexchandel)
+ * :func:`sliced` now should now work for numpy arrays. (thanks to sswingle)
+ * :func:`seekable` now has a ``maxlen`` parameter.
+
8.1.0
-----
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.1.0/more_itertools/__init__.py
new/more-itertools-8.2.0/more_itertools/__init__.py
--- old/more-itertools-8.1.0/more_itertools/__init__.py 2020-01-11
19:59:09.000000000 +0100
+++ new/more-itertools-8.2.0/more_itertools/__init__.py 2020-01-29
13:51:50.000000000 +0100
@@ -1,4 +1,4 @@
from .more import * # noqa
from .recipes import * # noqa
-__version__ = '8.1.0'
+__version__ = '8.2.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.1.0/more_itertools/more.py
new/more-itertools-8.2.0/more_itertools/more.py
--- old/more-itertools-8.1.0/more_itertools/more.py 2020-01-11
19:59:09.000000000 +0100
+++ new/more-itertools-8.2.0/more_itertools/more.py 2020-01-29
13:49:15.000000000 +0100
@@ -1,5 +1,5 @@
import warnings
-from collections import Counter, defaultdict, deque
+from collections import Counter, defaultdict, deque, abc
from collections.abc import Sequence
from functools import partial, wraps
from heapq import merge, heapify, heapreplace, heappop
@@ -18,8 +18,8 @@
zip_longest,
)
from math import exp, floor, log
-from operator import gt, itemgetter, lt, sub
from random import random, randrange, uniform
+from operator import itemgetter, sub, gt, lt
from sys import maxsize
from time import monotonic
@@ -772,7 +772,9 @@
child iterables based on a *key* function.
>>> iterable = ['a1', 'b1', 'c1', 'a2', 'b2', 'c2', 'b3']
- >>> s = bucket(iterable, key=lambda x: x[0])
+ >>> s = bucket(iterable, key=lambda x: x[0]) # Bucket by 1st character
+ >>> sorted(list(s)) # Get the keys
+ ['a', 'b', 'c']
>>> a_iterable = s['a']
>>> next(a_iterable)
'a1'
@@ -846,6 +848,14 @@
elif self._validator(item_value):
self._cache[item_value].append(item)
+ def __iter__(self):
+ for item in self._it:
+ item_value = self._key(item)
+ if self._validator(item_value):
+ self._cache[item_value].append(item)
+
+ yield from self._cache.keys()
+
def __getitem__(self, value):
if not self._validator(value):
return iter(())
@@ -1051,7 +1061,7 @@
For non-sliceable iterables, see :func:`chunked`.
"""
- return takewhile(bool, (seq[i : i + n] for i in count(0, n)))
+ return takewhile(len, (seq[i : i + n] for i in count(0, n)))
def split_at(iterable, pred):
@@ -1610,7 +1620,7 @@
return ((k, map(valuefunc, g)) for k, g in res) if valuefunc else res
-def numeric_range(*args):
+class numeric_range(abc.Sequence, abc.Hashable):
"""An extension of the built-in ``range()`` function whose arguments can
be any orderable numeric type.
@@ -1654,35 +1664,165 @@
>>> start = datetime.datetime(2019, 1, 1)
>>> stop = datetime.datetime(2019, 1, 3)
>>> step = datetime.timedelta(days=1)
- >>> items = numeric_range(start, stop, step)
+ >>> items = iter(numeric_range(start, stop, step))
>>> next(items)
datetime.datetime(2019, 1, 1, 0, 0)
>>> next(items)
datetime.datetime(2019, 1, 2, 0, 0)
"""
- argc = len(args)
- if argc == 1:
- stop, = args
- start = type(stop)(0)
- step = 1
- elif argc == 2:
- start, stop = args
- step = 1
- elif argc == 3:
- start, stop, step = args
- else:
- err_msg = 'numeric_range takes at most 3 arguments, got {}'
- raise TypeError(err_msg.format(argc))
+ _EMPTY_HASH = hash(range(0, 0))
- values = (start + (step * n) for n in count())
- zero = type(step)(0)
- if step > zero:
- return takewhile(partial(gt, stop), values)
- elif step < zero:
- return takewhile(partial(lt, stop), values)
- else:
- raise ValueError('numeric_range arg 3 must not be zero')
+ def __init__(self, *args):
+ argc = len(args)
+ if argc == 1:
+ self._stop, = args
+ self._start = type(self._stop)(0)
+ self._step = type(self._stop - self._start)(1)
+ elif argc == 2:
+ self._start, self._stop = args
+ self._step = type(self._stop - self._start)(1)
+ elif argc == 3:
+ self._start, self._stop, self._step = args
+ elif argc == 0:
+ raise TypeError('numeric_range expected at least '
+ '1 argument, got {}'.format(argc))
+ else:
+ raise TypeError('numeric_range expected at most '
+ '3 arguments, got {}'.format(argc))
+
+ self._zero = type(self._step)(0)
+ if self._step == self._zero:
+ raise ValueError('numeric_range() arg 3 must not be zero')
+ self._growing = self._step > self._zero
+ self._init_len()
+
+ def __bool__(self):
+ if self._growing:
+ return self._start < self._stop
+ else:
+ return self._start > self._stop
+
+ def __contains__(self, elem):
+ if self._growing:
+ if self._start <= elem < self._stop:
+ return (elem - self._start) % self._step == self._zero
+ else:
+ if self._start >= elem > self._stop:
+ return (self._start - elem) % (-self._step) == self._zero
+
+ return False
+
+ def __eq__(self, other):
+ if isinstance(other, numeric_range):
+ empty_self = not bool(self)
+ empty_other = not bool(other)
+ if empty_self or empty_other:
+ return empty_self and empty_other # True if both empty
+ else:
+ return (self._start == other._start
+ and self._step == other._step
+ and self._get_by_index(-1) == other._get_by_index(-1))
+ else:
+ return False
+
+ def __getitem__(self, key):
+ if isinstance(key, int):
+ return self._get_by_index(key)
+ elif isinstance(key, slice):
+ step = self._step if key.step is None else key.step * self._step
+
+ if key.start is None or key.start <= -self._len:
+ start = self._start
+ elif key.start >= self._len:
+ start = self._stop
+ else: # -self._len < key.start < self._len
+ start = self._get_by_index(key.start)
+
+ if key.stop is None or key.stop >= self._len:
+ stop = self._stop
+ elif key.stop <= -self._len:
+ stop = self._start
+ else: # -self._len < key.stop < self._len
+ stop = self._get_by_index(key.stop)
+
+ return numeric_range(start, stop, step)
+ else:
+ raise TypeError(
+ 'numeric range indices must be '
+ 'integers or slices, not {}'.format(type(key).__name__))
+
+ def __hash__(self):
+ if self:
+ return hash((self._start, self._get_by_index(-1), self._step))
+ else:
+ return self._EMPTY_HASH
+
+ def __iter__(self):
+ values = (self._start + (n * self._step) for n in count())
+ if self._growing:
+ return takewhile(partial(gt, self._stop), values)
+ else:
+ return takewhile(partial(lt, self._stop), values)
+
+ def __len__(self):
+ return self._len
+
+ def _init_len(self):
+ if self._growing:
+ start = self._start
+ stop = self._stop
+ step = self._step
+ else:
+ start = self._stop
+ stop = self._start
+ step = -self._step
+ distance = stop - start
+ if distance <= self._zero:
+ self._len = 0
+ else: # distance > 0 and step > 0: regular euclidean division
+ q, r = divmod(distance, step)
+ self._len = int(q) + int(r != self._zero)
+
+ def __reduce__(self):
+ return numeric_range, (self._start, self._stop, self._step)
+
+ def __repr__(self):
+ if self._step == 1:
+ return "numeric_range({}, {})".format(repr(self._start),
+ repr(self._stop))
+ else:
+ return "numeric_range({}, {}, {})".format(repr(self._start),
+ repr(self._stop),
+ repr(self._step))
+
+ def __reversed__(self):
+ return iter(numeric_range(self._get_by_index(-1),
+ self._start - self._step, -self._step))
+
+ def count(self, value):
+ return int(value in self)
+
+ def index(self, value):
+ if self._growing:
+ if self._start <= value < self._stop:
+ q, r = divmod(value - self._start, self._step)
+ if r == self._zero:
+ return int(q)
+ else:
+ if self._start >= value > self._stop:
+ q, r = divmod(self._start - value, -self._step)
+ if r == self._zero:
+ return int(q)
+
+ raise ValueError("{} is not in numeric range".format(value))
+
+ def _get_by_index(self, i):
+ if i < 0:
+ i += self._len
+ if i < 0 or i >= self._len:
+ raise IndexError("numeric range object index out of range")
+ return self._start + i * self._step
def count_cycle(iterable, n=None):
@@ -2121,9 +2261,6 @@
>>> next(it), next(it), next(it)
('0', '1', '2')
- The cache grows as the source iterable progresses, so beware of wrapping
- very large or infinite iterables.
-
You may view the contents of the cache with the :meth:`elements` method.
That returns a :class:`SequenceView`, a view that updates automatically:
@@ -2138,11 +2275,30 @@
>>> elements
SequenceView(['0', '1', '2', '3'])
+ By default, the cache grows as the source iterable progresses, so beware of
+ wrapping very large or infinite iterables. Supply *maxlen* to limit the
+ size of the cache (this of course limits how far back you can seek).
+
+ >>> from itertools import count
+ >>> it = seekable((str(n) for n in count()), maxlen=2)
+ >>> next(it), next(it), next(it), next(it)
+ ('0', '1', '2', '3')
+ >>> list(it.elements())
+ ['2', '3']
+ >>> it.seek(0)
+ >>> next(it), next(it), next(it), next(it)
+ ('2', '3', '4', '5')
+ >>> next(it)
+ '6'
+
"""
- def __init__(self, iterable):
+ def __init__(self, iterable, maxlen=None):
self._source = iter(iterable)
- self._cache = []
+ if maxlen is None:
+ self._cache = []
+ else:
+ self._cache = deque([], maxlen)
self._index = None
def __iter__(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/more-itertools-8.1.0/more_itertools.egg-info/PKG-INFO
new/more-itertools-8.2.0/more_itertools.egg-info/PKG-INFO
--- old/more-itertools-8.1.0/more_itertools.egg-info/PKG-INFO 2020-01-11
19:59:22.000000000 +0100
+++ new/more-itertools-8.2.0/more_itertools.egg-info/PKG-INFO 2020-01-29
13:52:26.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 1.2
Name: more-itertools
-Version: 8.1.0
+Version: 8.2.0
Summary: More routines for operating on iterables, beyond itertools
Home-page: https://github.com/erikrose/more-itertools
Author: Erik Rose
@@ -182,6 +182,18 @@
:noindex:
+ 8.2.0
+ -----
+
+ * Bug fixes
+ * The .pyi files for typing were updated. (thanks to blueyed and
ilai-deutel)
+
+ * Changes to existing itertools:
+ * numeric_range now behaves more like the built-in range. (thanks
to jferard)
+ * bucket now allows for enumerating keys. (thanks to alexchandel)
+ * sliced now should now work for numpy arrays. (thanks to sswingle)
+ * seekable now has a ``maxlen`` parameter.
+
8.1.0
-----
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.1.0/setup.cfg
new/more-itertools-8.2.0/setup.cfg
--- old/more-itertools-8.1.0/setup.cfg 2020-01-11 19:59:22.000000000 +0100
+++ new/more-itertools-8.2.0/setup.cfg 2020-01-29 13:52:26.000000000 +0100
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 8.1.0
+current_version = 8.2.0
commit = True
tag = False
files = more_itertools/__init__.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.1.0/tests/test_more.py
new/more-itertools-8.2.0/tests/test_more.py
--- old/more-itertools-8.1.0/tests/test_more.py 2020-01-11 19:59:09.000000000
+0100
+++ new/more-itertools-8.2.0/tests/test_more.py 2020-01-29 13:49:15.000000000
+0100
@@ -1,4 +1,4 @@
-from collections import OrderedDict, Counter
+from collections import OrderedDict, Counter, abc
from collections.abc import Set
from datetime import datetime, timedelta
from decimal import Decimal
@@ -19,6 +19,7 @@
repeat,
)
from operator import add, mul, itemgetter
+from pickle import loads, dumps
from random import seed
from statistics import mean
from sys import version_info
@@ -821,8 +822,6 @@
class BucketTests(TestCase):
- """Tests for ``bucket()``"""
-
def test_basic(self):
iterable = [10, 20, 30, 11, 21, 31, 12, 22, 23, 33]
D = mi.bucket(iterable, key=lambda x: 10 * (x // 10))
@@ -858,6 +857,24 @@
self.assertNotIn(0, D._cache) # Don't store non-valid entries
self.assertEqual(list(D[0]), [])
+ def test_list(self):
+ iterable = [10, 20, 30, 11, 21, 31, 12, 22, 23, 33]
+ D = mi.bucket(iterable, key=lambda x: 10 * (x // 10))
+ self.assertEqual(list(D[10]), [10, 11, 12])
+ self.assertEqual(list(D[20]), [20, 21, 22, 23])
+ self.assertEqual(list(D[30]), [30, 31, 33])
+ self.assertEqual(set(D), {10, 20, 30})
+
+ def test_list_validator(self):
+ iterable = [10, 20, 30, 11, 21, 31, 12, 22, 23, 33]
+ key = lambda x: 10 * (x // 10)
+ validator = lambda x: x != 20
+ D = mi.bucket(iterable, key, validator=validator)
+ self.assertEqual(set(D), {10, 30})
+ self.assertEqual(list(D[10]), [10, 11, 12])
+ self.assertEqual(list(D[20]), [])
+ self.assertEqual(list(D[30]), [30, 31, 33])
+
class SpyTests(TestCase):
"""Tests for ``spy()``"""
@@ -1059,6 +1076,26 @@
with self.assertRaises(TypeError):
list(mi.sliced(seq, 3))
+ def test_numpy_like_array(self):
+ # Numpy arrays don't behave like Python lists - calling bool()
+ # on them doesn't return False for empty lists and True for non-empty
+ # ones. Emulate that behavior.
+ class FalseList(list):
+ def __getitem__(self, key):
+ ret = super().__getitem__(key)
+ if isinstance(key, slice):
+ return FalseList(ret)
+
+ return ret
+
+ def __bool__(self):
+ return False
+
+ seq = FalseList(range(9))
+ actual = list(mi.sliced(seq, 3))
+ expected = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
+ self.assertEqual(actual, expected)
+
class SplitAtTests(TestCase):
"""Tests for ``split()``"""
@@ -1724,6 +1761,12 @@
str_expected = list(str_obj)
self.assertEqual(str_actual, str_expected)
+ # base_type handles nested tuple (via isinstance).
+ base_type = ((dict,),)
+ custom_actual = list(mi.always_iterable(dict_obj, base_type=base_type))
+ custom_expected = [dict_obj]
+ self.assertEqual(custom_actual, custom_expected)
+
def test_iterables(self):
self.assertEqual(list(mi.always_iterable([0, 1])), [0, 1])
self.assertEqual(
@@ -1948,7 +1991,7 @@
((4,), [0, 1, 2, 3]),
((4.0,), [0.0, 1.0, 2.0, 3.0]),
((1.0, 4), [1.0, 2.0, 3.0]),
- ((1, 4.0), [1, 2, 3]),
+ ((1, 4.0), [1.0, 2.0, 3.0]),
((1.0, 5), [1.0, 2.0, 3.0, 4.0]),
((0, 20, 5), [0, 5, 10, 15]),
((0, 20, 5.0), [0.0, 5.0, 10.0, 15.0]),
@@ -1961,6 +2004,12 @@
((0.0,), []),
((1, 0), []),
((1.0, 0.0), []),
+ ((0.1, 0.30000000000000001, 0.2), [0.1]), # IEE 754 !
+ ((Decimal("0.1"), Decimal("0.30000000000000001"), Decimal("0.2")),
+ [Decimal("0.1"), Decimal("0.3")]), # okay with Decimal
+ ((Fraction(1, 10), Fraction(30000000000000001, 100000000000000000),
+ Fraction(2, 10)),
+ [Fraction(1, 10), Fraction(3, 10)]), # okay with Fraction
((Fraction(2, 1),), [Fraction(0, 1), Fraction(1, 1)]),
((Decimal('2.0'),), [Decimal('0.0'), Decimal('1.0')]),
(
@@ -1977,16 +2026,18 @@
),
]:
actual = list(mi.numeric_range(*args))
- self.assertEqual(actual, expected)
+ self.assertEqual(expected, actual)
self.assertTrue(
all(type(a) == type(e) for a, e in zip(actual, expected))
)
def test_arg_count(self):
- self.assertRaises(TypeError, lambda: list(mi.numeric_range()))
- self.assertRaises(
- TypeError, lambda: list(mi.numeric_range(0, 1, 2, 3))
- )
+ for args, message in [
+ ((), 'numeric_range expected at least 1 argument, got 0'),
+ ((0, 1, 2, 3), 'numeric_range expected at most 3 arguments, got 4')
+ ]:
+ with self.assertRaisesRegex(TypeError, message):
+ mi.numeric_range(*args)
def test_zero_step(self):
for args in [
@@ -1996,10 +2047,322 @@
datetime(2019, 3, 29, 12, 37, 55),
timedelta(minutes=0),
),
+ (1.0, 2.0, 0.0),
+ (Decimal("1.0"), Decimal("2.0"), Decimal("0.0")),
+ (Fraction(2, 2), Fraction(4, 2), Fraction(0, 2)),
]:
with self.assertRaises(ValueError):
list(mi.numeric_range(*args))
+ def test_bool(self):
+ for args, expected in [
+ ((1.0, 3.0, 1.5), True),
+ ((1.0, 2.0, 1.5), True),
+ ((1.0, 1.0, 1.5), False),
+ ((1.0, 0.0, 1.5), False),
+ ((3.0, 1.0, -1.5), True),
+ ((2.0, 1.0, -1.5), True),
+ ((1.0, 1.0, -1.5), False),
+ ((0.0, 1.0, -1.5), False),
+ ((Decimal("1.0"), Decimal("2.0"), Decimal("1.5")), True),
+ ((Decimal("1.0"), Decimal("0.0"), Decimal("1.5")), False),
+ ((Fraction(2, 2), Fraction(4, 2), Fraction(3, 2)), True),
+ ((Fraction(2, 2), Fraction(0, 2), Fraction(3, 2)), False),
+ ((datetime(2019, 3, 29), datetime(2019, 3, 30),
+ timedelta(hours=1)), True),
+ ((datetime(2019, 3, 29), datetime(2019, 3, 28),
+ timedelta(hours=1)), False),
+ ]:
+ self.assertEqual(expected, bool(mi.numeric_range(*args)))
+
+ def test_contains(self):
+ for args, expected_in, expected_not_in in [
+ ((10,), range(10), (0.5,)),
+ ((1.0, 9.9, 1.5), (1.0, 2.5, 4.0, 5.5, 7.0, 8.5), (0.9,)),
+ ((9.0, 1.0, -1.5), (1.5, 3.0, 4.5, 6.0, 7.5, 9.0), (0.0, 0.9)),
+ ((Decimal("1.0"), Decimal("9.9"), Decimal("1.5")),
+ (Decimal("1.0"), Decimal("2.5"), Decimal("4.0"), Decimal("5.5"),
+ Decimal("7.0"), Decimal("8.5"),),
+ (Decimal("0.9"),)),
+ ((Fraction(0, 1), Fraction(5, 1), Fraction(1, 2)),
+ (Fraction(0, 1), Fraction(1, 2), Fraction(9, 2)),
+ (Fraction(10, 2),)),
+ ((datetime(2019, 3, 29), datetime(2019, 3, 30),
+ timedelta(hours=1)),
+ (datetime(2019, 3, 29, 15),), (datetime(2019, 3, 29, 15, 30),))
+ ]:
+ r = mi.numeric_range(*args)
+ for v in expected_in:
+ self.assertTrue(v in r)
+ self.assertFalse(v not in r)
+
+ for v in expected_not_in:
+ self.assertFalse(v in r)
+ self.assertTrue(v not in r)
+
+ def test_eq(self):
+ for args1, args2 in [
+ ((0, 5, 2), (0, 6, 2)),
+ ((1.0, 9.9, 1.5), (1.0, 8.6, 1.5)),
+ ((8.5, 0.0, -1.5), (8.5, 0.7, -1.5)),
+ ((7.0, 0.0, 1.0), (17.0, 7.0, 0.5)),
+ ((Decimal("1.0"), Decimal("9.9"), Decimal("1.5")),
+ (Decimal("1.0"), Decimal("8.6"), Decimal("1.5"))),
+ ((Fraction(1, 1), Fraction(10, 1), Fraction(3, 2)),
+ (Fraction(1, 1), Fraction(9, 1), Fraction(3, 2))),
+ ((datetime(2019, 3, 29), datetime(2019, 3, 30),
+ timedelta(hours=10)),
+ (datetime(2019, 3, 29), datetime(2019, 3, 30, 1),
+ timedelta(hours=10)))
+ ]:
+ self.assertEqual(mi.numeric_range(*args1),
+ mi.numeric_range(*args2))
+
+ for args1, args2 in [
+ ((0, 5, 2), (0, 7, 2)),
+ ((1.0, 9.9, 1.5), (1.2, 9.9, 1.5)),
+ ((1.0, 9.9, 1.5), (1.0, 10.3, 1.5)),
+ ((1.0, 9.9, 1.5), (1.0, 9.9, 1.4)),
+ ((8.5, 0.0, -1.5), (8.4, 0.0, -1.5)),
+ ((8.5, 0.0, -1.5), (8.5, -0.7, -1.5)),
+ ((8.5, 0.0, -1.5), (8.5, 0.0, -1.4)),
+ ((0.0, 7.0, 1.0), (7.0, 0.0, 1.0)),
+ ((Decimal("1.0"), Decimal("10.0"), Decimal("1.5")),
+ (Decimal("1.0"), Decimal("10.5"), Decimal("1.5"))),
+ ((Fraction(1, 1), Fraction(10, 1), Fraction(3, 2)),
+ (Fraction(1, 1), Fraction(21, 2), Fraction(3, 2))),
+ ((datetime(2019, 3, 29), datetime(2019, 3, 30),
+ timedelta(hours=10)),
+ (datetime(2019, 3, 29), datetime(2019, 3, 30, 15),
+ timedelta(hours=10)))
+ ]:
+ self.assertNotEqual(mi.numeric_range(*args1),
+ mi.numeric_range(*args2))
+
+ self.assertNotEqual(mi.numeric_range(7.0), 1)
+ self.assertNotEqual(mi.numeric_range(7.0), "abc")
+
+ def test_get_item_by_index(self):
+ for args, index, expected in [
+ ((1, 6), 2, 3),
+ ((1.0, 6.0, 1.5), 0, 1.0),
+ ((1.0, 6.0, 1.5), 1, 2.5),
+ ((1.0, 6.0, 1.5), 2, 4.0),
+ ((1.0, 6.0, 1.5), 3, 5.5),
+ ((1.0, 6.0, 1.5), -1, 5.5),
+ ((1.0, 6.0, 1.5), -2, 4.0),
+ ((Decimal("1.0"), Decimal("9.0"), Decimal("1.5")),
+ -1, Decimal("8.5")),
+ ((Fraction(1, 1), Fraction(10, 1), Fraction(3, 2)),
+ 2, Fraction(4, 1)),
+ ((datetime(2019, 3, 29), datetime(2019, 3, 30),
+ timedelta(hours=10)),
+ 1, datetime(2019, 3, 29, 10))
+ ]:
+ self.assertEqual(expected, mi.numeric_range(*args)[index])
+
+ for args, index in [
+ ((1.0, 6.0, 1.5), 4),
+ ((1.0, 6.0, 1.5), -5),
+ ((6.0, 1.0, 1.5), 0),
+ ((6.0, 1.0, 1.5), -1),
+ ((Decimal("1.0"), Decimal("9.0"), Decimal("-1.5")), -1),
+ ((Fraction(1, 1), Fraction(2, 1), Fraction(3, 2)), 2),
+ ((datetime(2019, 3, 29), datetime(2019, 3, 30),
+ timedelta(hours=10)), 8)
+ ]:
+ with self.assertRaises(IndexError):
+ mi.numeric_range(*args)[index]
+
+ def test_get_item_by_slice(self):
+ for args, sl, expected_args in [
+ ((1.0, 9.0, 1.5), slice(None, None, None), (1.0, 9.0, 1.5)),
+ ((1.0, 9.0, 1.5), slice(None, 1, None), (1.0, 2.5, 1.5)),
+ ((1.0, 9.0, 1.5), slice(None, None, 2), (1.0, 9.0, 3.0)),
+ ((1.0, 9.0, 1.5), slice(None, 2, None), (1.0, 4.0, 1.5)),
+ ((1.0, 9.0, 1.5), slice(1, 2, None), (2.5, 4.0, 1.5)),
+ ((1.0, 9.0, 1.5), slice(1, -1, None), (2.5, 8.5, 1.5)),
+ ((1.0, 9.0, 1.5), slice(10, None, 3), (9.0, 9.0, 4.5)),
+ ((1.0, 9.0, 1.5), slice(-10, None, 3), (1.0, 9.0, 4.5)),
+ ((1.0, 9.0, 1.5), slice(None, -10, 3), (1.0, 1.0, 4.5)),
+ ((1.0, 9.0, 1.5), slice(None, 10, 3), (1.0, 9.0, 4.5)),
+ ((Decimal("1.0"), Decimal("9.0"), Decimal("1.5")),
+ slice(1, -1, None),
+ (Decimal("2.5"), Decimal("8.5"), Decimal("1.5"))),
+ ((Fraction(1, 1), Fraction(5, 1), Fraction(3, 2)),
+ slice(1, -1, None),
+ (Fraction(5, 2), Fraction(4, 1), Fraction(3, 2))),
+ ((datetime(2019, 3, 29), datetime(2019, 3, 30),
+ timedelta(hours=10)),
+ slice(1, -1, None),
+ (datetime(2019, 3, 29, 10), datetime(2019, 3, 29, 20),
+ timedelta(hours=10)))
+ ]:
+ self.assertEqual(mi.numeric_range(*expected_args),
+ mi.numeric_range(*args)[sl])
+
+ def test_hash(self):
+ for args, expected in [
+ ((1.0, 6.0, 1.5), hash((1.0, 5.5, 1.5))),
+ ((1.0, 7.0, 1.5), hash((1.0, 5.5, 1.5))),
+ ((1.0, 7.5, 1.5), hash((1.0, 7.0, 1.5))),
+ ((1.0, 1.5, 1.5), hash((1.0, 1.0, 1.5))),
+ ((1.5, 1.0, 1.5), hash(range(0, 0))),
+ ((1.5, 1.5, 1.5), hash(range(0, 0))),
+ ((Decimal("1.0"), Decimal("9.0"), Decimal("1.5")),
+ hash((Decimal("1.0"), Decimal("8.5"), Decimal("1.5")))),
+ ((Fraction(1, 1), Fraction(5, 1), Fraction(3, 2)),
+ hash((Fraction(1, 1), Fraction(4, 1), Fraction(3, 2)))),
+ ((datetime(2019, 3, 29), datetime(2019, 3, 30),
+ timedelta(hours=10)),
+ hash((datetime(2019, 3, 29), datetime(2019, 3, 29, 20),
+ timedelta(hours=10))))
+
+ ]:
+ self.assertEqual(expected, hash(mi.numeric_range(*args)))
+
+ def test_iter_twice(self):
+ r1 = mi.numeric_range(1.0, 9.9, 1.5)
+ r2 = mi.numeric_range(8.5, 0.0, -1.5)
+ self.assertEqual([1.0, 2.5, 4.0, 5.5, 7.0, 8.5], list(r1))
+ self.assertEqual([1.0, 2.5, 4.0, 5.5, 7.0, 8.5], list(r1))
+ self.assertEqual([8.5, 7.0, 5.5, 4.0, 2.5, 1.0], list(r2))
+ self.assertEqual([8.5, 7.0, 5.5, 4.0, 2.5, 1.0], list(r2))
+
+ def test_len(self):
+ for args, expected in [
+ ((1.0, 7.0, 1.5), 4),
+ ((1.0, 7.01, 1.5), 5),
+ ((7.0, 1.0, -1.5), 4),
+ ((7.01, 1.0, -1.5), 5),
+ ((0.1, 0.30000000000000001, 0.2), 1), # IEE 754 !
+ ((Decimal("0.1"), Decimal("0.30000000000000001"),
+ Decimal("0.2")), 2), # works with Decimal
+ ((Decimal("1.0"), Decimal("9.0"), Decimal("1.5")), 6),
+ ((Fraction(1, 1), Fraction(5, 1), Fraction(3, 2)), 3),
+ ((datetime(2019, 3, 29), datetime(2019, 3, 30),
+ timedelta(hours=10)), 3)
+ ]:
+ self.assertEqual(expected, len(mi.numeric_range(*args)))
+
+ def test_repr(self):
+ for args, *expected in [
+ ((7.0,), "numeric_range(0.0, 7.0)"),
+ ((1.0, 7.0), "numeric_range(1.0, 7.0)"),
+ ((7.0, 1.0, -1.5), "numeric_range(7.0, 1.0, -1.5)"),
+ ((Decimal("1.0"), Decimal("9.0"), Decimal("1.5")),
+ "numeric_range(Decimal('1.0'), Decimal('9.0'), Decimal('1.5'))"),
+ ((Fraction(7, 7), Fraction(10, 2), Fraction(3, 2)),
+ "numeric_range(Fraction(1, 1), Fraction(5, 1), Fraction(3, 2))"),
+ ((datetime(2019, 3, 29), datetime(2019, 3, 30),
+ timedelta(hours=10)),
+ "numeric_range(datetime.datetime(2019, 3, 29, 0, 0), "
+ "datetime.datetime(2019, 3, 30, 0, 0), "
+ "datetime.timedelta(seconds=36000))",
+ "numeric_range(datetime.datetime(2019, 3, 29, 0, 0), "
+ "datetime.datetime(2019, 3, 30, 0, 0), "
+ "datetime.timedelta(0, 36000))")
+ ]:
+ self.assertIn(repr(mi.numeric_range(*args)), expected)
+
+ def test_reversed(self):
+ for args, expected in [
+ ((7.0,), [6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0]),
+ ((1.0, 7.0), [6.0, 5.0, 4.0, 3.0, 2.0, 1.0]),
+ ((7.0, 1.0, -1.5), [2.5, 4.0, 5.5, 7.0]),
+ ((7.0, 0.9, -1.5), [1.0, 2.5, 4.0, 5.5, 7.0]),
+ ((Decimal("1.0"), Decimal("5.0"), Decimal("1.5")),
+ [Decimal('4.0'), Decimal('2.5'), Decimal('1.0')]),
+ ((Fraction(1, 1), Fraction(5, 1), Fraction(3, 2)),
+ [Fraction(4, 1), Fraction(5, 2), Fraction(1, 1)]),
+ ((datetime(2019, 3, 29), datetime(2019, 3, 30),
+ timedelta(hours=10)), [datetime(2019, 3, 29, 20),
+ datetime(2019, 3, 29, 10), datetime(2019, 3, 29)]),
+ ]:
+ self.assertEqual(expected, list(reversed(mi.numeric_range(*args))))
+
+ def test_count(self):
+ for args, v, c in [
+ ((7.0,), 0.0, 1),
+ ((7.0,), 0.5, 0),
+ ((7.0,), 6.0, 1),
+ ((7.0,), 7.0, 0),
+ ((7.0,), 10.0, 0),
+ ((Decimal("1.0"), Decimal("5.0"), Decimal("1.5")),
+ Decimal('4.0'), 1),
+ ((Fraction(1, 1), Fraction(5, 1), Fraction(3, 2)),
+ Fraction(5, 2), 1),
+ ((datetime(2019, 3, 29), datetime(2019, 3, 30),
+ timedelta(hours=10)), datetime(2019, 3, 29, 20), 1),
+ ]:
+ self.assertEqual(c, mi.numeric_range(*args).count(v))
+
+ def test_index(self):
+ for args, v, i in [
+ ((7.0,), 0.0, 0),
+ ((7.0,), 6.0, 6),
+ ((7.0, 0.0, -1.0), 7.0, 0),
+ ((7.0, 0.0, -1.0), 1.0, 6),
+ ((Decimal("1.0"), Decimal("5.0"), Decimal("1.5")),
+ Decimal('4.0'), 2),
+ ((Fraction(1, 1), Fraction(5, 1), Fraction(3, 2)),
+ Fraction(5, 2), 1),
+ ((datetime(2019, 3, 29), datetime(2019, 3, 30),
+ timedelta(hours=10)), datetime(2019, 3, 29, 20), 2),
+ ]:
+ self.assertEqual(i, mi.numeric_range(*args).index(v))
+
+ for args, v in [
+ ((0.7,), 0.5),
+ ((0.7,), 7.0),
+ ((0.7,), 10.0),
+ ((7.0, 0.0, -1.0), 0.5),
+ ((7.0, 0.0, -1.0), 0.0),
+ ((7.0, 0.0, -1.0), 10.0),
+ ((7.0, 0.0), 5.0),
+ ((Decimal("1.0"), Decimal("5.0"), Decimal("1.5")),
+ Decimal('4.5')),
+ ((Fraction(1, 1), Fraction(5, 1), Fraction(3, 2)),
+ Fraction(5, 3)),
+ ((datetime(2019, 3, 29), datetime(2019, 3, 30),
+ timedelta(hours=10)), datetime(2019, 3, 30)),
+ ]:
+ with self.assertRaises(ValueError):
+ mi.numeric_range(*args).index(v)
+
+ def test_parent_classes(self):
+ r = mi.numeric_range(7.0)
+ self.assertTrue(isinstance(r, abc.Iterable))
+ self.assertFalse(isinstance(r, abc.Iterator))
+ self.assertTrue(isinstance(r, abc.Sequence))
+ self.assertTrue(isinstance(r, abc.Hashable))
+
+ def test_bad_key(self):
+ r = mi.numeric_range(7.0)
+ for arg, message in [
+ ('a', 'numeric range indices must be integers or slices, not str'),
+ ((),
+ 'numeric range indices must be integers or slices, not tuple'),
+ ]:
+ with self.assertRaisesRegex(TypeError, message):
+ r[arg]
+
+ def test_pickle(self):
+ for args in [
+ (7.0,),
+ (5.0, 7.0),
+ (5.0, 7.0, 3.0),
+ (7.0, 5.0),
+ (7.0, 5.0, 4.0),
+ (7.0, 5.0, -1.0),
+ (Decimal("1.0"), Decimal("5.0"), Decimal("1.5")),
+ (Fraction(1, 1), Fraction(5, 1), Fraction(3, 2)),
+ (datetime(2019, 3, 29), datetime(2019, 3, 30)),
+ ]:
+ r = mi.numeric_range(*args)
+ self.assertTrue(dumps(r)) # assert not empty
+ self.assertEqual(r, loads(dumps(r)))
+
class CountCycleTests(TestCase):
def test_basic(self):
@@ -2244,6 +2607,23 @@
mi.take(10, s)
self.assertEqual(list(elements), [str(n) for n in range(20)])
+ def test_maxlen(self):
+ iterable = map(str, count())
+
+ s = mi.seekable(iterable, maxlen=4)
+ self.assertEqual(mi.take(10, s), [str(n) for n in range(10)])
+ self.assertEqual(list(s.elements()), ['6', '7', '8', '9'])
+
+ s.seek(0)
+ self.assertEqual(mi.take(14, s), [str(n) for n in range(6, 20)])
+ self.assertEqual(list(s.elements()), ['16', '17', '18', '19'])
+
+ def test_maxlen_zero(self):
+ iterable = [str(x) for x in range(5)]
+ s = mi.seekable(iterable, maxlen=0)
+ self.assertEqual(list(s), iterable)
+ self.assertEqual(list(s.elements()), [])
+
class SequenceViewTests(TestCase):
def test_init(self):