Skip to content

fix: support negative indices in OrderedSet.__getitem__#633

Merged
fgmacedo merged 2 commits into
fgmacedo:developfrom
gaoflow:fix/orderedset-negative-index
Jun 26, 2026
Merged

fix: support negative indices in OrderedSet.__getitem__#633
fgmacedo merged 2 commits into
fgmacedo:developfrom
gaoflow:fix/orderedset-negative-index

Conversation

@gaoflow

@gaoflow gaoflow commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

What

OrderedSet.__getitem__ raised ValueError (from itertools.islice) when called with a negative index, instead of the IndexError callers expect — or, better, returning the element counting from the end, as all Python sequences do.

s = OrderedSet([1, 2, 3])
s[-1]  # ValueError: Indices for islice() must be None or an integer: 0 <= x <= sys.maxsize.

Why

itertools.islice rejects negative start/stop values with ValueError. Since __getitem__ is documented and exported as a sequence-style accessor (positive indices already work, positive out-of-range already raises IndexError), negative indices should follow the same contract as Python's built-in sequences.

Fix

Normalise the index before delegating to islice:

  • if index < 0, add len(self) to convert it to the equivalent positive offset
  • if still negative after normalisation, raise IndexError with the original index in the message (not the normalised value)
s[-1]  # 3  ✓
s[-3]  # 1  ✓
s[-4]  # IndexError: index -4 out of range  ✓

Two new unit tests cover the happy path and the out-of-range case. Doctests in orderedset.py are updated with examples of s[-1] and s[-3].

This pull request was prepared with the assistance of AI, under my direction and review.

Negative index access (e.g. ``s[-1]``) previously raised ``ValueError``
from ``itertools.islice``, which forbids negative start values.  This is
surprising because Python sequences uniformly accept negative indices
(``s[-1]`` returns the last element) and the custom ``__getitem__`` on
``OrderedSet`` already raises ``IndexError`` for positive out-of-range
indices.

Normalise the index before passing it to ``islice``: add ``len(self)``
when ``index < 0``, and raise ``IndexError`` (not ``ValueError``) when
the normalised index is still negative.  The original index is preserved
in the error message so the reported value matches what the caller passed.
@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (a2457a5) to head (d1f5d9b).

Additional details and impacted files
@@            Coverage Diff            @@
##           develop      #633   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           52        52           
  Lines         5494      5499    +5     
  Branches       865       867    +2     
=========================================
+ Hits          5494      5499    +5     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@gaoflow

gaoflow commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

The previous CI run failed on a timing-sensitive flaky test unrelated to this PR's changes:

`FAILED tests/test_contrib_timeout.py::TestTimeoutComposition::test_timeout_fires_before_slow_invoke[async]`

The assertion `assert 'stuck' in OrderedSet(['loading'])` failed because the 50ms timeout didn't fire within the CI window. The test passes consistently locally. I've pushed a minor fixup commit (removing extra blank lines in the docstring) to re-trigger CI. Could you approve the workflow run so CI can run again?

@fgmacedo

Copy link
Copy Markdown
Owner

Hi @gaoflow, thanks for the careful fix and tests! Verified locally: tests and doctests pass, and the new branches in __getitem__ are fully covered.

@fgmacedo fgmacedo merged commit 2c1aa0c into fgmacedo:develop Jun 26, 2026
10 checks passed
@fgmacedo fgmacedo self-assigned this Jun 26, 2026
@fgmacedo fgmacedo added the bug label Jun 26, 2026
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants