commit: 07b4f2180a86354c212c11f38e80d408e353ebd9
Author: Brian Harring <ferringb <AT> gmail <DOT> com>
AuthorDate: Mon Oct 27 16:52:50 2025 +0000
Commit: Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
CommitDate: Mon Oct 27 21:28:21 2025 +0000
URL:
https://gitweb.gentoo.org/proj/pkgcore/snakeoil.git/commit/?id=07b4f218
fix: don't suppress TypeError in iter_stable_unique edge case.
If the iterator itself throws the TypeError before we get a value
out of it, 'x' would be unbound, which would then explode for when
the code falls back to the linear list search for unhashable items.
Signed-off-by: Brian Harring <ferringb <AT> gmail.com>
src/snakeoil/sequences.py | 22 +++++++++++++---------
tests/test_sequences.py | 10 +++++++++-
2 files changed, 22 insertions(+), 10 deletions(-)
diff --git a/src/snakeoil/sequences.py b/src/snakeoil/sequences.py
index b80b101..b7e0ba7 100644
--- a/src/snakeoil/sequences.py
+++ b/src/snakeoil/sequences.py
@@ -71,25 +71,29 @@ def iter_stable_unique(iterable):
For performance reasons, only use this if you really do need to preserve
the ordering.
"""
- s = set()
- sadd = s.add
- sl = []
- slappend = sl.append
+ seen = set()
+ unhashable_seen = []
iterable = iter(iterable)
# the reason for this structuring is purely speed- entering try/except
# repeatedly is costly, thus structure it to penalize the unhashables
# instead of penalizing the hashables.
+ singleton = object()
+
while True:
+ x = singleton
try:
for x in iterable:
- if x not in s:
+ if x not in seen:
yield x
- sadd(x)
+ seen.add(x)
except TypeError:
- # unhashable item...
- if x not in sl:
+ # unhashable item pathway
+ if x is singleton:
+ # the iterable itself threw the TypeError
+ raise
+ if x not in unhashable_seen:
yield x
- slappend(x)
+ unhashable_seen.append(x)
continue
break
diff --git a/tests/test_sequences.py b/tests/test_sequences.py
index 0d8c5a6..b558647 100644
--- a/tests/test_sequences.py
+++ b/tests/test_sequences.py
@@ -3,8 +3,9 @@ from itertools import chain
from operator import itemgetter
import pytest
+
from snakeoil import sequences
-from snakeoil.sequences import split_elements, split_negations
+from snakeoil.sequences import iter_stable_unique, split_elements,
split_negations
class UnhashableComplex(complex):
@@ -33,6 +34,13 @@ class TestStableUnique:
l = [1, 2, 3, o, UnhashableComplex(), 4, 3, UnhashableComplex()]
assert list(sequences.iter_stable_unique(l)) == [1, 2, 3, o, 4]
+ def test_TypeError_propagation(self):
+ def iterator():
+ raise TypeError()
+ yield
+
+ pytest.raises(TypeError, lambda: list(iter_stable_unique(iterator())))
+
def _generator(self):
for x in range(5, -1, -1):
yield x