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