• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aio-libs / multidict / 37775699769
86%

Build:
DEFAULT BRANCH: master
Ran 08 Oct 2026 12:17PM UTC
Jobs 1
Files 50
Run time 1min
Badge
Embed ▾
README BADGES
x

If you need to use a raster PNG badge, change the '.svg' to '.png' in the link

Markdown

Textile

RDoc

HTML

Rst

08 Oct 2026 12:16PM UTC coverage: 85.978% (+0.08%) from 85.9%
37775699769

push

github

web-flow
Yield each key once from keys() and iteration (#1682)

<!-- Thank you for your contribution! -->

## What do these changes do?

`keys()` and iteration over a multidict yield each key once, as spelled
in its first item, which is also the item `d[key]` returns. `items()`,
`values()` and `len(d)` keep every item. `len(d.keys())`, the keys
view's `repr()`, its set operators and its comparisons follow the
deduplicated view, so `dict(md)` and `{k: md.getall(k) for k in md}` now
give one entry per key. This is the outcome agreed in #1654.

Both backends use the same rule. Inserts take only empty slots and
rebuilds reinsert in order, so the first entry of a key on its hash
chain is its first item; the keys iterator yields an entry only if it is
that first one. To keep the common case cheap, each hash table records
whether it may hold a key twice: an insert sets the flag when its probe
passes an entry of the same hash, a rebuild re-checks a set flag, and a
debug build asserts the flag is never clear while two entries share a
hash. While it is clear, keys iteration skips the check, and `getall()`,
`popall()`, `del`, replacing a key, `update()` and `to_dict()` stop at
the first match instead of walking the whole chain. Callers that have
just looked the key up (`setdefault()`, `d[k] = v`, `update()`,
`merge()`), extend from a multidict without repeats, or fill an empty
case-sensitive multidict from a dict or keyword arguments skip the
insert-side check.

Callgrind, instructions per operation vs. master (`MultiDict` /
`CIMultiDict`, C extension):

| Operation | GIL | free-threaded |
|---|---|---|
| `for k in d`, no repeated keys | +4% / +4% | +4% / +4% |
| `for k in d`, every key twice | +5% / +0% | -39% / -41% |
| `getall()` | -14% / -12% | -14% / -14% |
| `d[k] = v`, existing key | -7% / -7% | -7% / -9% |
| `del d[k]` | -8% / -8% | -8% / -8% |
| `update()` | -5% / -5% | -9% / -5% |
| `d[k] = v`, new key | +3% / +3% | +6% / +5% |
| `setdefault()`, new key | +3% /... (continued)

830 of 1660 branches covered (50.0%)

Branch coverage included in aggregate %.

351 of 364 new or added lines in 8 files covered. (96.43%)

1 existing line in 1 file now uncovered.

9655 of 10535 relevant lines covered (91.65%)

1.83 hits per line

Uncovered Changes

Lines Coverage ∆ File
6
97.37
tests/test_keys_dedup.py
5
71.49
-0.18% multidict/_multidict_py.py
1
85.55
-0.22% tests/test_free_threading.py
1
93.69
0.0% tests/test_multidict.py

Coverage Regressions

Lines Coverage ∆ File
1
71.49
-0.18% multidict/_multidict_py.py
Jobs
ID Job ID Ran Files Coverage
1 MyPy - 37775699769.1 08 Oct 2026 12:17PM UTC 100
85.98
GitHub Action Run
Source Files on build 37775699769
  • Tree
  • List 50
  • Changed 8
  • Source Changed 7
  • Coverage Changed 6
Coverage ∆ File Lines Relevant Covered Missed Hits/Line Branch Hits Branch Misses
  • Back to Repo
  • Github Actions Build #37775699769
  • 408f3513 on github
  • Prev Build on master (#37772235297)
  • Next Build on master (#37778453129)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc