From c61b35e3f1d8db18edde0630ed1daf52eaceed77 Mon Sep 17 00:00:00 2001 From: Ryan Grout Date: Fri, 18 Oct 2019 17:44:23 -0500 Subject: [PATCH 01/10] Convert StopIteration into RuntimeError. --- toolz/itertoolz.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/toolz/itertoolz.py b/toolz/itertoolz.py index e71f1eee..c674abc5 100644 --- a/toolz/itertoolz.py +++ b/toolz/itertoolz.py @@ -373,7 +373,9 @@ def first(seq): >>> first('ABC') 'A' """ - return next(iter(seq)) + for rv in seq: + return rv + raise RuntimeError def second(seq): @@ -382,9 +384,9 @@ def second(seq): >>> second('ABC') 'B' """ - seq = iter(seq) - next(seq) - return next(seq) + for rv in itertools.islice(seq, 1, None): + return rv + raise RuntimeError def nth(n, seq): @@ -396,7 +398,7 @@ def nth(n, seq): if isinstance(seq, (tuple, list, Sequence)): return seq[n] else: - return next(itertools.islice(seq, n, None)) + return first(itertools.islice(seq, n, None)) def last(seq): @@ -531,8 +533,12 @@ def interpose(el, seq): [1, 'a', 2, 'a', 3] """ inposed = concat(zip(itertools.repeat(el), seq)) - next(inposed) - return inposed + + try: + next(inposed) + return inposed + except StopIteration: + raise RuntimeError def frequencies(seq): @@ -722,13 +728,16 @@ def partition_all(n, seq): """ args = [iter(seq)] * n it = zip_longest(*args, fillvalue=no_pad) + try: prev = next(it) except StopIteration: return + for item in it: yield prev prev = item + if prev[-1] is no_pad: try: # If seq defines __len__, then @@ -997,8 +1006,11 @@ def peek(seq): [0, 1, 2, 3, 4] """ iterator = iter(seq) - item = next(iterator) - return item, itertools.chain((item,), iterator) + try: + item = next(iterator) + return item, itertools.chain((item,), iterator) + except StopIteration: + raise RuntimeError def peekn(n, seq): From 111f5ca6b9ef2c1fe44da14bebf79cabd4a80482 Mon Sep 17 00:00:00 2001 From: Ryan Grout Date: Fri, 18 Oct 2019 17:56:01 -0500 Subject: [PATCH 02/10] Update tests. --- toolz/tests/test_itertoolz.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/toolz/tests/test_itertoolz.py b/toolz/tests/test_itertoolz.py index 3444e4b1..daa91207 100644 --- a/toolz/tests/test_itertoolz.py +++ b/toolz/tests/test_itertoolz.py @@ -17,7 +17,6 @@ from toolz.compatibility import range, filter from operator import add, mul - # is comparison will fail between this and no_default no_default2 = loads(dumps('__no__default__')) @@ -128,7 +127,7 @@ def test_nth(): assert nth(2, iter('ABCDE')) == 'C' assert nth(1, (3, 2, 1)) == 2 assert nth(0, {'foo': 'bar'}) == 'foo' - assert raises(StopIteration, lambda: nth(10, {10: 'foo'})) + assert raises(RuntimeError, lambda: nth(10, {10: 'foo'})) assert nth(-2, 'ABCDE') == 'D' assert raises(ValueError, lambda: nth(-2, iter('ABCDE'))) @@ -137,12 +136,14 @@ def test_first(): assert first('ABCDE') == 'A' assert first((3, 2, 1)) == 3 assert isinstance(first({0: 'zero', 1: 'one'}), int) + assert raises(RuntimeError, lambda: first([])) def test_second(): assert second('ABCDE') == 'B' assert second((3, 2, 1)) == 2 assert isinstance(second({0: 'zero', 1: 'one'}), int) + assert raises(RuntimeError, lambda: second([1])) def test_last(): @@ -229,6 +230,7 @@ def test_interpose(): assert "tXaXrXzXaXn" == "".join(interpose("X", "tarzan")) assert list(interpose(0, itertools.repeat(1, 4))) == [1, 0, 1, 0, 1, 0, 1] assert list(interpose('.', ['a', 'b', 'c'])) == ['a', '.', 'b', '.', 'c'] + assert raises(RuntimeError, lambda: interpose('a', [])) def test_frequencies(): @@ -511,8 +513,7 @@ def test_peek(): element, blist = peek(alist) assert element == alist[0] assert list(blist) == alist - - assert raises(StopIteration, lambda: peek([])) + assert raises(RuntimeError, lambda: peek([])) def test_peekn(): From 5a5c5cae43f88e770991d94d3bb5e27ded491542 Mon Sep 17 00:00:00 2001 From: Ryan Grout Date: Mon, 4 Nov 2019 12:42:50 -0600 Subject: [PATCH 03/10] Add IterationError exception. --- toolz/exceptions.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 toolz/exceptions.py diff --git a/toolz/exceptions.py b/toolz/exceptions.py new file mode 100644 index 00000000..71fd5208 --- /dev/null +++ b/toolz/exceptions.py @@ -0,0 +1,5 @@ + +__all__ = ('IterationError',) + +class IterationError(RuntimeError): + pass From 40b133a95b3c6934cb33f2c3caa00e24d76916a8 Mon Sep 17 00:00:00 2001 From: Ryan Grout Date: Mon, 4 Nov 2019 12:44:29 -0600 Subject: [PATCH 04/10] Use IterationError instead of RuntimeError. --- toolz/itertoolz.py | 12 +++++------- toolz/tests/test_itertoolz.py | 11 ++++++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/toolz/itertoolz.py b/toolz/itertoolz.py index c674abc5..96268460 100644 --- a/toolz/itertoolz.py +++ b/toolz/itertoolz.py @@ -7,6 +7,7 @@ from toolz.compatibility import (map, filterfalse, zip, zip_longest, iteritems, filter, Sequence) from toolz.utils import no_default +from toolz.exceptions import IterationError __all__ = ('remove', 'accumulate', 'groupby', 'merge_sorted', 'interleave', @@ -375,7 +376,7 @@ def first(seq): """ for rv in seq: return rv - raise RuntimeError + raise IterationError("Received empty sequence") def second(seq): @@ -384,9 +385,7 @@ def second(seq): >>> second('ABC') 'B' """ - for rv in itertools.islice(seq, 1, None): - return rv - raise RuntimeError + return first(itertools.islice(seq, 1, None)) def nth(n, seq): @@ -533,12 +532,11 @@ def interpose(el, seq): [1, 'a', 2, 'a', 3] """ inposed = concat(zip(itertools.repeat(el), seq)) - try: next(inposed) return inposed except StopIteration: - raise RuntimeError + raise IterationError("Received empty sequence") def frequencies(seq): @@ -1010,7 +1008,7 @@ def peek(seq): item = next(iterator) return item, itertools.chain((item,), iterator) except StopIteration: - raise RuntimeError + raise IterationError("Received empty sequence") def peekn(n, seq): diff --git a/toolz/tests/test_itertoolz.py b/toolz/tests/test_itertoolz.py index daa91207..f5135892 100644 --- a/toolz/tests/test_itertoolz.py +++ b/toolz/tests/test_itertoolz.py @@ -14,6 +14,7 @@ sliding_window, count, partition, partition_all, take_nth, pluck, join, diff, topk, peek, peekn, random_sample) +from toolz.exceptions import IterationError from toolz.compatibility import range, filter from operator import add, mul @@ -127,7 +128,7 @@ def test_nth(): assert nth(2, iter('ABCDE')) == 'C' assert nth(1, (3, 2, 1)) == 2 assert nth(0, {'foo': 'bar'}) == 'foo' - assert raises(RuntimeError, lambda: nth(10, {10: 'foo'})) + assert raises(IterationError, lambda: nth(10, {10: 'foo'})) assert nth(-2, 'ABCDE') == 'D' assert raises(ValueError, lambda: nth(-2, iter('ABCDE'))) @@ -136,14 +137,14 @@ def test_first(): assert first('ABCDE') == 'A' assert first((3, 2, 1)) == 3 assert isinstance(first({0: 'zero', 1: 'one'}), int) - assert raises(RuntimeError, lambda: first([])) + assert raises(IterationError, lambda: first([])) def test_second(): assert second('ABCDE') == 'B' assert second((3, 2, 1)) == 2 assert isinstance(second({0: 'zero', 1: 'one'}), int) - assert raises(RuntimeError, lambda: second([1])) + assert raises(IterationError, lambda: second([1])) def test_last(): @@ -230,7 +231,7 @@ def test_interpose(): assert "tXaXrXzXaXn" == "".join(interpose("X", "tarzan")) assert list(interpose(0, itertools.repeat(1, 4))) == [1, 0, 1, 0, 1, 0, 1] assert list(interpose('.', ['a', 'b', 'c'])) == ['a', '.', 'b', '.', 'c'] - assert raises(RuntimeError, lambda: interpose('a', [])) + assert raises(IterationError, lambda: interpose('a', [])) def test_frequencies(): @@ -513,7 +514,7 @@ def test_peek(): element, blist = peek(alist) assert element == alist[0] assert list(blist) == alist - assert raises(RuntimeError, lambda: peek([])) + assert raises(IterationError, lambda: peek([])) def test_peekn(): From 7df34c8da58a444f5d0392a2e8b6ab5d6291a82a Mon Sep 17 00:00:00 2001 From: Ryan Grout Date: Mon, 4 Nov 2019 13:07:04 -0600 Subject: [PATCH 05/10] Satisfy pycodestyle. --- toolz/exceptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/toolz/exceptions.py b/toolz/exceptions.py index 71fd5208..aba77133 100644 --- a/toolz/exceptions.py +++ b/toolz/exceptions.py @@ -1,5 +1,6 @@ __all__ = ('IterationError',) + class IterationError(RuntimeError): pass From 01d00aedfc17963491341587f97fb8368c4ce2e0 Mon Sep 17 00:00:00 2001 From: Ryan Grout Date: Fri, 17 Jan 2020 11:41:44 -0600 Subject: [PATCH 06/10] Produce better exception messages. Catches the IterationError from first and produces a more meaningful exception message. --- toolz/itertoolz.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/toolz/itertoolz.py b/toolz/itertoolz.py index 96268460..091b7fb2 100644 --- a/toolz/itertoolz.py +++ b/toolz/itertoolz.py @@ -385,7 +385,10 @@ def second(seq): >>> second('ABC') 'B' """ - return first(itertools.islice(seq, 1, None)) + try: + return first(itertools.islice(seq, 1, None)) + except IterationError: + raise IterationError("Lenth of seq is < 2") def nth(n, seq): @@ -397,7 +400,10 @@ def nth(n, seq): if isinstance(seq, (tuple, list, Sequence)): return seq[n] else: - return first(itertools.islice(seq, n, None)) + try: + return first(itertools.islice(seq, n, None)) + except IterationError: + raise IterationError("Length of seq is < %d" % n) def last(seq): From 030b2233426e6055817d7bd87965d53a365ddc09 Mon Sep 17 00:00:00 2001 From: Ryan Grout Date: Tue, 17 Mar 2020 12:51:42 -0500 Subject: [PATCH 07/10] Raise from IterationError. --- toolz/itertoolz.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/toolz/itertoolz.py b/toolz/itertoolz.py index 61b7ef76..4985f8db 100644 --- a/toolz/itertoolz.py +++ b/toolz/itertoolz.py @@ -387,8 +387,8 @@ def second(seq): """ try: return first(itertools.islice(seq, 1, None)) - except IterationError: - raise IterationError("Lenth of seq is < 2") + except IterationError as exc: + raise IterationError("Lenth of seq is < 2") from exc def nth(n, seq): @@ -402,8 +402,8 @@ def nth(n, seq): else: try: return first(itertools.islice(seq, n, None)) - except IterationError: - raise IterationError("Length of seq is < %d" % n) + except IterationError as exc: + raise IterationError("Length of seq is < %d" % n) from exc def last(seq): From 3fefff5814613b619b4e6e238db97faaf4ded1d5 Mon Sep 17 00:00:00 2001 From: Ryan Grout Date: Tue, 17 Mar 2020 19:06:05 -0500 Subject: [PATCH 08/10] Raise IterationError where necessary. --- toolz/itertoolz.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/toolz/itertoolz.py b/toolz/itertoolz.py index 4985f8db..bc15a633 100644 --- a/toolz/itertoolz.py +++ b/toolz/itertoolz.py @@ -385,10 +385,15 @@ def second(seq): >>> second('ABC') 'B' """ - try: - return first(itertools.islice(seq, 1, None)) - except IterationError as exc: - raise IterationError("Lenth of seq is < 2") from exc + seq = iter(seq) + for item in seq: + break + else: + raise IterationError("Received empty sequence") + for item in seq: + return item + else: + raise IterationError("Length of sequence is < 2") def nth(n, seq): @@ -400,10 +405,9 @@ def nth(n, seq): if isinstance(seq, (tuple, list, Sequence)): return seq[n] else: - try: - return first(itertools.islice(seq, n, None)) - except IterationError as exc: - raise IterationError("Length of seq is < %d" % n) from exc + for rv in itertools.islice(seq, n, None): + return rv + raise IterationError("Length of seq is < %d" % n) def last(seq): @@ -538,11 +542,9 @@ def interpose(el, seq): [1, 'a', 2, 'a', 3] """ inposed = concat(zip(itertools.repeat(el), seq)) - try: - next(inposed) + for _ in inposed: return inposed - except StopIteration: - raise IterationError("Received empty sequence") + raise IterationError("Received empty sequence") def frequencies(seq): @@ -1010,11 +1012,11 @@ def peek(seq): [0, 1, 2, 3, 4] """ iterator = iter(seq) - try: - item = next(iterator) - return item, itertools.chain((item,), iterator) - except StopIteration: + for peeked in iterator: + break + else: raise IterationError("Received empty sequence") + return peeked, itertools.chain((peeked,), iterator) def peekn(n, seq): @@ -1032,7 +1034,7 @@ def peekn(n, seq): """ iterator = iter(seq) peeked = tuple(take(n, iterator)) - return peeked, itertools.chain(iter(peeked), iterator) + return peeked, itertools.chain(peeked, iterator) def random_sample(prob, seq, random_state=None): From 6435b089a41cd547ffcc8029f746fdda877a6f46 Mon Sep 17 00:00:00 2001 From: Ryan Grout Date: Tue, 17 Mar 2020 19:11:25 -0500 Subject: [PATCH 09/10] Add test for empty sequence in second. --- toolz/tests/test_itertoolz.py | 1 + 1 file changed, 1 insertion(+) diff --git a/toolz/tests/test_itertoolz.py b/toolz/tests/test_itertoolz.py index d7bcf937..eb5e7a4b 100644 --- a/toolz/tests/test_itertoolz.py +++ b/toolz/tests/test_itertoolz.py @@ -146,6 +146,7 @@ def test_second(): assert second((3, 2, 1)) == 2 assert isinstance(second({0: 'zero', 1: 'one'}), int) assert raises(IterationError, lambda: second([1])) + assert raises(IterationError, lambda: second([])) def test_last(): From 994820a59e1820adec6adfca2fb1331ca5018e98 Mon Sep 17 00:00:00 2001 From: Ryan Grout Date: Thu, 19 Mar 2020 10:57:27 -0500 Subject: [PATCH 10/10] Add exceptions module to toolz namespace. --- toolz/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolz/__init__.py b/toolz/__init__.py index 8e3b0b9a..c7bfa05c 100644 --- a/toolz/__init__.py +++ b/toolz/__init__.py @@ -17,7 +17,7 @@ # Aliases comp = compose -from . import curried, sandbox +from . import curried, exceptions, sandbox functoolz._sigs.create_signature_registry()