Changelog

To be included in v7.1.1.dev0 (if present)

7.1.1.dev0 [UNRELEASED DRAFT]

(2026-10-12)

Bug fixes

  • Stopped a huge __length_hint__() from raising MemoryError in extend(), update() and merge(); the C extension capped the room it reserved for the hint at the largest table the constructor allocates up front – by @asvetlov.

    Related issues and pull requests on GitHub: #1767.

  • Fixed a segmentation fault when using a MultiDictProxy or CIMultiDictProxy built by __new__() without __init__(); __new__() started requiring the proxied multidict, the same argument as the constructor – by @asvetlov.

    Related issues and pull requests on GitHub: #1782.

Removals and backward incompatible breaking changes

  • Dropped support for Python 3.10 as it has reached end of life – by @asvetlov.

    Related issues and pull requests on GitHub: #1777.

Packaging updates and notes for downstreams

  • Added the CPython 3.15 trove classifier and started testing the GIL and free-threaded 3.15 builds in CI – by @asvetlov.

    Related issues and pull requests on GitHub: #1766.

Contributor-facing changes

  • Split the release publishing and signing across four parallel jobs, so that each one finishes well within the lifetime of its short-lived PyPI upload token – by @asvetlov.

    Related issues and pull requests on GitHub: #1768.

Miscellaneous internal changes

  • Changed the C extension’s update and merge helpers to take the batch’s state as one structure, which held the function the batch applied to each item, and to pass every item through a visitor; extending got its own helpers, which needed no state. On the GIL build, constructing a multidict from a sequence of pairs needed 8% to 10% fewer instructions – by @asvetlov.

    Related issues and pull requests on GitHub: #1763.

  • Stopped the C extension from computing a key’s identity for add(), getall(), md[key] = value and del md[key] when the key, or an istr’s canonical form, already serves as one, as md[key] did: on the GIL build del md[key] needed 4% to 5% fewer instructions and md[key] = value about 2% fewer – by @asvetlov.

    Related issues and pull requests on GitHub: #1769.

  • Restored the speed of add(), getall(), md[key] = value and del md[key] for a CIMultiDict key holding uppercase letters, which had become 1% to 4% slower when these methods started to borrow the key’s identity; such a key is now checked only once – by @asvetlov.

    Related issues and pull requests on GitHub: #1770.

  • Folded the helpers choosing the kind of a new hash table into their callers – by @asvetlov.

    Related issues and pull requests on GitHub: #1771.

  • Merged the two per-kind checks for a deleted entry in the C extension into a single one – by @asvetlov.

    Related issues and pull requests on GitHub: #1772.

  • Folded the helpers reading the identity and the hash of a compact hash table entry into their callers – by @asvetlov.

    Related issues and pull requests on GitHub: #1773.

  • Changed the C extension to read a table’s size once per probe loop when looking for a free slot, rather than once per probed slot: on the free-threaded build add(), md[key] = value for a new key and setdefault() needed 2% to 3% fewer instructions, and about 1% fewer on the GIL build – by @asvetlov.

    Related issues and pull requests on GitHub: #1774.

  • Sped up len(md.keys()) and iterating over the keys of a MultiDict or CIMultiDict that holds a key more than once by 4% to 13%, by keeping the duplicate check’s probe state out of memory for an entry found in its first slot – by @asvetlov.

    Related issues and pull requests on GitHub: #1775.

  • Stopped taking the multidict’s lock in getall() on the free-threaded build for a multidict without repeated keys, the way get() already avoided it – by @asvetlov.

    Related issues and pull requests on GitHub: #1776.

  • Moved the C extension’s repr() of multidicts, proxies and views out of the hash table header into its own header – by @asvetlov.

    Related issues and pull requests on GitHub: #1778.

  • Folded three C extension helpers that each had a single caller into that caller, and dropped a redundant hash check; on the GIL build pop() needed 1% fewer instructions – by @asvetlov.

    Related issues and pull requests on GitHub: #1779.

  • Sped up md[key], get() and getone() by up to 7% by returning the lookup’s result by value instead of through a pointer to the caller’s local, which had made -fstack-protector-strong add a stack canary to every call – by @asvetlov.

    Related issues and pull requests on GitHub: #1781.


Released versions

7.1.0

(2026-10-08)

7.1.0 is a correctness and housekeeping release. It changes what keys() yields, fixes a long list of crashes and inconsistencies that Python code run in the middle of an operation could trigger, stores the common all-str multidicts in half the space, and deprecates multidict.upstr.

Behaviour changes. keys() and iteration over a multidict yield each key once, as spelled in its first item, so len(d.keys()) counts distinct keys; items(), values() and len(d) still see every item. A str subclass’s own lower() is no longer called to compute the case-insensitive identity of a CIMultiDict key.

Robustness. Finalizers, __eq__(), __repr__(), lower() and argument iterators that read or mutate the multidict in the middle of __init__(), update(), merge(), del d[key], popall(), popitem(), to_dict() or repr() no longer crash the C extension, lose or duplicate pairs, or make the two backends disagree. Two data races against lock-free readers on the free-threaded build are gone too.

Memory. A MultiDict whose keys are all exact str, and a CIMultiDict whose keys are all exact istr, are stored in a compact table without a separate identity and hash per entry, which cuts their size by about 45%.

Bug fixes

  • Fixed building the C extension on platforms where char is unsigned, such as ARM, PowerPC, s390x and RISC-V, and with GCC 9, which flags CPython’s own headers from 3.12 on; both failed on a sign conversion warning since -Wconversion took effect. GCC 9 builds the extension without -Wconversion – by @asvetlov.

    Related issues and pull requests on GitHub: #1759, #1760.

  • Fixed getversion() returning only the low 32 bits of the version on Windows and on 32-bit platforms, where two different versions could compare equal, and fixed a race in the pure-Python implementation on GIL builds that could give two multidicts mutated at the same time from different threads the same version – by @asvetlov.

    Related issues and pull requests on GitHub: #1607, #1610.

  • Fixed in on an items view truncating the length of a non-tuple, non-list operand to 32 bits in the C extension, which let an object reporting 2**32 + 2 items be indexed as a pair and raise TypeError instead of returning False – by @asvetlov.

    Related issues and pull requests on GitHub: #1611.

  • Fixed adding to a full table of the C extension, which compacted it in place whenever it held deleted entries and so regained only as many slots as were deleted: a delete-then-add loop on a full table rebuilt it on every add. The table was resized by the number of live entries instead, as the pure-Python implementation did, which also let it shrink after deletions. Fixed extend(), update() and merge() reserving room by comparing table sizes, which ignored the room taken by deleted entries; the reservation checked the free room instead, in both implementations – by @asvetlov.

    Related issues and pull requests on GitHub: #1615, #1617.

  • Fixed calling __init__() again on a live multidict losing and leaking whatever a finalizer of an old value added to it in the C extension, and miscounting len() in the pure-Python implementation, by installing the new contents and size before releasing the old pairs – by @asvetlov.

    Related issues and pull requests on GitHub: #1619, #1626.

  • Fixed two data races on free-threaded builds against lock-free readers: copy() and __init__() from a multidict of the same kind read the source’s reader count while its readers updated it, and __init__() on a live multidict rewrote the case-insensitivity flag its lookups read – by @asvetlov.

    Related issues and pull requests on GitHub: #1621.

  • Fixed MultiDict.__init__() called on a CIMultiDict making it case-sensitive in the C extension, and CIMultiDict.__init__() rejecting a MultiDict there, by taking the mode from the instance rather than from the class whose __init__() was called, as the pure-Python implementation already did – by @asvetlov.

    Related issues and pull requests on GitHub: #1622.

  • Fixed the C extension dropping the existing items when a multidict was re-initialized from itself, or from a proxy of itself, together with keyword arguments, as in d.__init__(d, key=value); the pure-Python implementation was not affected – by @asvetlov.

    Related issues and pull requests on GitHub: #1666.

  • Fixed a use-after-free in popitem() when building the result ran Python code that mutated the multidict: a str subclass key’s __str__() on CIMultiDict, or on Python 3.10 and 3.11 a garbage collection’s finalizers, by removing the pair first, and fixed the pure-Python popitem() miscounting len() in the same case – by @asvetlov.

    Related issues and pull requests on GitHub: #1625.

  • Fixed del md[key] and popall() also removing, and popall() also returning, the pairs a finalizer of a removed key or value added while the call was still running, by releasing the removed pairs only once every match is gone, and fixed the pure-Python implementation raising IndexError or miscounting len() in the same case, for md[key] = value too – by @asvetlov.

    Related issues and pull requests on GitHub: #1627.

  • Fixed a crash when Python code run between the items of update() (the argument’s own iterator, a key’s lower() or a finalizer) read the multidict, where the pure-Python implementation returned None for the pairs the call was about to remove, and fixed update() and merge() overwriting or losing pairs when that code changed the multidict. An update from another multidict raised RuntimeError when a key’s lower() mutated the source – by @asvetlov.

    Related issues and pull requests on GitHub: #1628, #1744.

  • Fixed a use-after-free in to_dict() on Python 3.10 and 3.11, where allocating a value list can run a garbage collection whose finalizers mutate the multidict, by refusing that mutation with RuntimeError as it is refused from a key’s __hash__() – by @asvetlov.

    Related issues and pull requests on GitHub: #1630.

  • Fixed repr() of a multidict reading freed memory on the GIL build when a key’s or value’s __repr__ called an extend(), update() or merge() that grew the table and then failed, by bumping the version on every resize – by @asvetlov.

    Related issues and pull requests on GitHub: #1658.

  • Fixed CIMultiDict returning a key added as a str subclass with the spelling of the subclass’s own __str__() instead of the key’s, and stopped the C extension from replacing such a key with its istr while reading it, which could run the key’s finalizer inside the read. Documented that CIMultiDict converts keys to istr – by @asvetlov.

    Related issues and pull requests on GitHub: #1640.

  • Fixed the istr keys that extend(), update(), merge() and the MultiDict constructor took from a CIMultiDict: they carried the key’s own spelling as their case-insensitive identity instead of its lower-cased form, so a CIMultiDict keyed by one of them found it under no spelling of the key. Changed the pure-Python implementation to hand over those keys as istr too, as the C extension did – by @asvetlov.

    Related issues and pull requests on GitHub: #1647.

  • Fixed a lock-free lookup on the free-threaded build reading the hash through a key of a compact table that a concurrent delete had already freed, by taking a reference to such a key first unless it is the very object being looked up – by @asvetlov.

    Related issues and pull requests on GitHub: #1653.

  • Changed CIMultiDictProxy.__init__() called without an argument to report the same TypeError message as MultiDictProxy and the pure-Python implementation – by @asvetlov.

    Related issues and pull requests on GitHub: #1677.

Features

  • Made MultiDict in the C extension store a mapping whose keys are all exact str in half the space, keeping no separate identity or hash per entry, and did the same for a CIMultiDict whose keys are all exact istr, taking each identity from the key’s canonical form, which cut its size by about 45%. Copying such a mapping became about 55% cheaper in instructions and clear() about 27%; any other key moves the mapping to the full layout, and clear() lets it start compact again. A lookup also stopped taking a reference to the identity of an exact str or istr key, which made d[key] and key in d up to 7% faster on the GIL build and up to 20% cheaper in instructions on the free-threaded one – by @asvetlov.

    Related issues and pull requests on GitHub: #1644, #1648, #1651.

  • Sped up getall(), del d[key], replacing a key with d[key] = value and update() by 5 to 14 percent, and up to 26 percent in the pure-Python implementation, when no key repeats, which each hash table started to track; the tracking made add() up to 10 percent slower, and building a small multidict from items up to 18 percent, both up to 16 percent in the pure-Python implementation – by @asvetlov.

    Related issues and pull requests on GitHub: #1654.

  • Replaced the set of visited entries that getall(), to_dict() and the items view’s & kept with a check that each match’s entry index is above the previous one’s, since equal keys are first reached in insertion order. A getall() of a key with many values needed up to half fewer instructions – by @asvetlov.

    Related issues and pull requests on GitHub: #1752, #1756.

  • Sped up update() and merge(): they stopped growing the table up front when it could hold the argument even empty, as dict.update() does, stopped holding extra references to the keys and values of a dict argument, and walked the hash chain for each item without a function call. An update of keys already present needed 13% to 29% fewer instructions – by @asvetlov.

    Related issues and pull requests on GitHub: #1747, #1755, #1757.

  • Made d[key] = value in the C extension keep the collector for duplicate keys off the stack until a duplicate turns up, which made replacing a key about 4-6% and adding a new one about 2-3% cheaper in instructions – by @asvetlov.

    Related issues and pull requests on GitHub: #1650.

  • Sped up building an istr from a str in the C extension by copying the string directly instead of going through str.__new__(); reading a str key of a CIMultiDict for the first time and popitem() became about 30% cheaper in instructions – by @asvetlov.

    Related issues and pull requests on GitHub: #1645.

  • Stopped a multidict with no watchers from testing for them on every record made by del d[key], d[key] = value and pop(): each operation checked once and ran a copy without the watcher code, which recovered most of the cost the watchers C API had added to them. Changed free-threaded builds to reserve versions for each multidict in blocks of 256, so a mutation advanced a counter in the object itself instead of looking up a thread-local one, which cut 2 to 5 percent of the instructions of every mutation – by @asvetlov.

    Related issues and pull requests on GitHub: #1604, #1605.

  • Stopped clearing each field of a hash table that is being freed, and read its entry count once instead of after every release, cutting about 8% off tearing down a table of 200 entries in clear() and on deallocation; replaced Py_CLEAR() on success paths with Py_DECREF() or Py_XDECREF(), which made building a multidict from items up to 1.7% and comparing one with another mapping up to 4% faster – by @asvetlov.

    Related issues and pull requests on GitHub: #1639, #1642.

Deprecations (removal in next major release)

  • Started emitting DeprecationWarning on access to multidict.upstr, the alias for istr deprecated since 2.0, and removed it from multidict.__all__ – by @asvetlov.

    Related issues and pull requests on GitHub: #1680.

Removals and backward incompatible breaking changes

  • Changed keys() and iteration over MultiDict, CIMultiDict and their proxies to yield each key once, as spelled in its first item, so that CIMultiDict([('X-Foo', '1'), ('x-foo', '2')]) iterates over 'X-Foo' alone and len(d.keys()) counts distinct keys; items(), values() and len(d) kept every item – by @asvetlov.

    Related issues and pull requests on GitHub: #1654.

  • Stopped calling a str subclass’s own lower() for the case-insensitive identity of a key in CIMultiDict and in IStr_FromUnicode(); the identity came from str.lower() instead, so computing it ran no user code – by @asvetlov.

    Related issues and pull requests on GitHub: #1746.

Packaging updates and notes for downstreams

  • Moved the project metadata from setup.cfg to pyproject.toml per PEP 621, and removed the now-empty setup.cfg along with its obsolete [bdist_wheel] universal flag. Adopted PEP 639 license metadata: declared the license as the SPDX expression Apache-2.0 and moved license-files into the [project] table, which raised the build-time requirement to setuptools >= 77.0. Built distributions started carrying License-Expression in place of the legacy License field – by @aiolibsbot.

    Related issues and pull requests on GitHub: #1646.

  • Added the benchmarks/ scripts to the source distribution, so that the test suite run from an unpacked sdist also covers the benchmark driver – by @asvetlov.

    Related issues and pull requests on GitHub: #1624.

Contributor-facing changes

  • Stopped the wheel jobs that run under QEMU from running the pure-Python half of the tests marked expensive, and the tests marked threaded on GIL builds, which had grown enough since 7.0.0 to take the manylinux riscv64 job past its two-hour timeout; native jobs still run them – by @asvetlov.

    Related issues and pull requests on GitHub: #1762.

  • Added tools/check_inlining.py and an Inlining CI job, which fail when GCC stops inlining a helper that a hot path depends on, or makes an out-of-line copy of an inline function from the CPython headers or from pythoncapi_compat.h such as Py_DECREF(), since each time that happened a benchmark regressed on code the change never touched – by @asvetlov.

    Related issues and pull requests on GitHub: #1612, #1686.

  • Stopped passing -Wno-conversion to the C extension build, which had silently turned -Wconversion off, and added the explicit casts it then required – by @asvetlov.

    Related issues and pull requests on GitHub: #1611.

  • Dropped the macOS and Windows legs of the CI test matrix, since cibuildwheel already ran the same test suite against the very wheels those jobs installed. Moved every artifact the CI test jobs consume into one first stage, the source distribution together with the pure-Python and Linux binary wheels, and switched the remaining test jobs to that source distribution instead of a repository checkout. The wheel builds for the other platforms stopped gating the test matrix, the windows-11-arm wheel build got limited to release tags again, and x86_64 macOS wheels started being tested under Rosetta outside pull requests only – by @asvetlov.

    Related issues and pull requests on GitHub: #1575, #1638.

  • Added CodSpeed benchmarks for mutating a multidict that a watcher is attached to and for re-initializing a populated multidict, started benchmarking the FT build alongside the GIL one, and split each build’s benchmark CI job into two parallel shards of similar length, selected by a new benchmark_shard_2 pytest mark – by @asvetlov.

    Related issues and pull requests on GitHub: #1608, #1620.

  • Changed benchmarks/callgrind_driver.py to run its Callgrind children from a fixed-length staging directory holding only the modules they import, so that two checkouts of one commit at different paths no longer measure up to 3% apart on allocating operations, and pinned mimalloc’s arena address and purge timer, which had moved free-threaded counts between two runs of one tree. Stopped the driver from collecting whole-process counts, which cannot be compared with bracketed ones, unless it is given --whole-process, and made its checks before measuring, including --self-check, cover only the implementations selected with --impl, so a pure-Python install can be measured – by @asvetlov.

    Related issues and pull requests on GitHub: #1623, #1624, #1637.

  • Added a test that runs every mutating method of both multidict classes with keys, values and argument items whose finalizers mutate the multidict, and checks the C implementation against the pure-Python one, along with reusable helpers for writing such tests, and a threaded stress test that re-initializes a shared multidict whose old values each have such a finalizer while other threads run lock-free lookups and iterate it – by @asvetlov.

    Related issues and pull requests on GitHub: #1621, #1631, #1633.

  • Made the ThreadSanitizer run reliable: added tools/tsan_suppressions.txt for the faulthandler watchdog’s traceback dumps, used by the CI job and by the documented local command, which also gained --no-cov; and changed the two pure-Python deadlock tests for reciprocal view operations to fail only when their workers stop making progress for 20 seconds, rather than when they have not finished within 20 seconds; and rewrote the retry loop of the test for reference leaks on memory errors so that it takes the same branches on every Python version – by @asvetlov.

    Related issues and pull requests on GitHub: #1655.

  • Renamed the hypothesis-freethreading CI job to hypothesis-ft (Hypothesis FT), on a par with the GIL one, and removed the update argument of the debug-only ASSERT_CONSISTENT() check, which stopped doing anything once update() kept its doomed entries whole – by @asvetlov.

    Related issues and pull requests on GitHub: #1609, #1629.

  • Moved the sanitizer build recipes and the performance measurement procedure out of AGENTS.md into Claude Code skills under .claude/skills/, and dropped its file layout table, so that less of it is loaded into every agent session – by @asvetlov.

    Related issues and pull requests on GitHub: #1692.

Miscellaneous internal changes

  • Reworked how the C extension spends GCC’s inlining budget, which it sits at as a single translation unit. Dropped the ALWAYS_INLINE macro and inline from almost every helper, leaving the decision to the compiler, and turned the hash table index lookup, store and probe helpers into macros. Moved the table resize and reservation, clear(), popitem(), the equality comparison, the iterator and view constructors, the end of a lock-free read and other rarely run code out of line, and made the proxy methods tail-call the shared ones. Marked the error-raising helpers, __sizeof__, __reduce__() and the proxy constructors’ error paths cold, and kept NOINLINE only for the few helpers GCC re-inlined without it. Moved argument parsing into the method entry points and returned the keyword-bound arguments by value, which dropped a stack protector canary from every get(), getone(), getall(), add() and setdefault() call – by @asvetlov.

    Related issues and pull requests on GitHub: #1613, #1614, #1661, #1664, #1667, #1670, #1671, #1672, #1683, #1690, #1691, #1693, #1694, #1695, #1697, #1698, #1699, #1700, #1701, #1702, #1703, #1704, #1705, #1707, #1709, #1711, #1712, #1713, #1714, #1717, #1718, #1719, #1720, #1722, #1723, #1724, #1726, #1727, #1730, #1732, #1733, #1734, #1735, #1737, #1739, #1740, #1749, #1750, #1751.

  • Shrank the C extension by merging duplicated code: the bodies of __init__(), extend(), update() and merge(), of pop() and popone(), the per-class copies of the entry points, the update loops and the tp_vectorcall slots, which read the class at run time instead, the repeated tails of the table rebuilds and of getall() and popall(), and the module setup, which became two table loops. The set operators and comparisons of the keys and items views were rewritten around shared helpers compiled for size. When they landed, the view rewrite made the compiled extension about 6% smaller and reading the class at run time about 15%, which left room for the compact tables; the view set operators became up to 17% slower and update() and merge() up to 3% slower – by @asvetlov.

    Related issues and pull requests on GitHub: #1669, #1673, #1675, #1677, #1678, #1679, #1706, #1715, #1716, #1725, #1728, #1729, #1731.

  • Reorganized how the C extension’s hash table lays out and reaches its entries: split the entry into a key and value layout that every table kind shares and a layout that adds the identity and its hash, computed the entry size with sizeof, told a deleted entry by its key alone, replaced the md_pos_t cursor and the entry stepping helpers with typed, indexed access, and walked a table in one loop per table kind and, for compact tables, per class. repr(), the views’ | and -, the update from another multidict and the comparison with a plain mapping moved onto the shared walk over all entries – by @asvetlov.

    Related issues and pull requests on GitHub: #1641, #1649, #1656, #1659, #1660, #1662, #1663, #1665, #1674, #1676, #1741, #1742, #1743, #1745, #1748.

  • Merged the C extension’s two ways of dropping a hash table it no longer used, freeing it at once on GIL builds and retiring it on free-threaded ones, into one helper – by @asvetlov.

    Related issues and pull requests on GitHub: #1618.

  • Upgraded pythoncapi_compat, and stopped copying the fast path of PyUnstable_TryIncRef() into the free-threaded C extension, where it read CPython’s private reference count fields, by calling the function itself instead – by @asvetlov.

    Related issues and pull requests on GitHub: #1687, #1688.

  • Removed dead code from the C extension: a self-update branch no caller could reach, version guards that are always true on the supported Python versions, unused helpers and single-use forwarding wrappers. Renamed the header-local helpers to carry a leading underscore, moved istr_canonical() next to the istrobject struct, and folded the pure-Python update() match test back into one condition – by @asvetlov.

    Related issues and pull requests on GitHub: #1635, #1668, #1689, #1753, #1754.

  • Added const qualifiers to internal C helpers that read table, reference list, and watcher metadata – by @anshurajbisoyi98-ctrl.

    Related issues and pull requests on GitHub: #1643.


7.0.0

(2026-09-26)

7.0.0 is a major release. It makes istr final, adds a public C API for third-party extensions, and makes the C extension substantially faster across the board.

Breaking change. istr can no longer be subclassed, on either backend. Code that subclassed it has to wrap or convert instead. The C extension also started rejecting an argument passed both positionally and by name with TypeError, where it used to silently misbehave.

Public C and Cython API. Other C extensions and Cython modules can now create, read and mutate multidicts through a capsule, without going through the Python-level API. The capsule comes with multidict_capi.h, a cimport-able multidict/__init__.pxd and multidict.get_include(). The API includes MultiDict_ForEach() and a watchers API modeled on CPython’s dict watchers. See the C API and Cython API references.

Performance. On CodSpeed’s GIL-build benchmarks against 6.9.1, 82 C-extension benchmarks got faster and none got slower. Rough figures:

  • CIMultiDict keyed by plain str: construction, add(), extend(), update() and item assignment got 2 to 3.8 times faster, and lookups about 2 times faster.

  • CIMultiDict keyed by istr, and case-sensitive MultiDict: lookups, insertion and deletion got 10% to 70% faster.

  • getall() and iterating items() got about 35% faster, and the view set operations 10% to 85% faster.

  • popitem() on a whole mapping went from quadratic to linear time.

  • On the free-threaded build, measured in instructions against 6.9.1, lookups got 24% to 33% cheaper on MultiDict and 4 to 5 times cheaper on CIMultiDict with lowercase str keys, and item assignment 15% to 19% cheaper. Construction and deletion on MultiDict cost 2% to 7% more. With several threads mutating at once, a d[key] = value got about four times faster.

The pure-Python backend is unchanged in speed.

Robustness. This release fixed several crashes and leaks in the C extension, including use-after-free bugs when a key’s lower() or a value’s __eq__ mutated a multidict, a crash at interpreter shutdown, and table and reference leaks on the free-threaded build.

Bug fixes

  • Fixed a segmentation fault in the C extension when a finalizer let the same multidict be resized while del md[key] or popall() was still scanning the table, and a spurious RuntimeError from getall() when a garbage collection triggered while building its result mutated the multidict – by @asvetlov.

    Related issues and pull requests on GitHub: #1492, #1512.

  • Fixed a reference leak of the key and value in the C extension when growing the hash table fails with MemoryError during add(), update(), merge(), setdefault() or item assignment – by @asvetlov.

    Related issues and pull requests on GitHub: #1507.

  • Fixed a data race on the free-threaded build of the C extension where a MultiDict’s own version counter was read by getversion() and written on every mutation with a plain, non-atomic load and store. Both sides switched to a relaxed atomic load/store, matching the pattern already used for the object’s item count – by @asvetlov.

    Related issues and pull requests on GitHub: #1514.

  • Fixed the C extension discarding a MemoryError or KeyboardInterrupt raised by a positional argument’s __len__ while the size of an update was estimated. extend(), update(), merge() and subclass construction started propagating it, matching the pure-Python backend; only the TypeError from an unusable __length_hint__ stayed ignored – by @asvetlov.

    Related issues and pull requests on GitHub: #1517.

  • Ensured that canonical keys in pure-Python MultiDict and CIMultiDict are converted to exact str instances without invoking object.__str__() overrides, matching the C extension – by @agustin18.

    Related issues and pull requests on GitHub: #1535.

  • Fixed the argument counts reported by the C extension when a method is called with too many positional arguments; methods with an optional second argument claimed to take exactly one, and methods requiring two claimed to take a range. A method called with none of its two required arguments now reports both as missing – by @asvetlov.

    Related issues and pull requests on GitHub: #1545.

  • Fixed the C extension accepting an argument passed both positionally and by name instead of raising TypeError. md.add("a", key="k", value="v") stored a wrong value, and md.get("a", "b", key="c") silently ignored the surplus arguments – by @asvetlov.

    Related issues and pull requests on GitHub: #1545.

  • Fixed a memory leak in the free-threaded build of the C extension. clear() hands its hash table to a drain that frees it once no lock-free reader is in flight, and a drain that finds one leaves the table for the next drain to free. Deallocation ran no drain of its own, so a multidict dropped after such a clear, with no operation in between, never freed that table nor released the references its entries still held. Builds with the GIL enabled emit byte-identical code and are unaffected – by @asvetlov.

    Related issues and pull requests on GitHub: #1555, #1562.

  • Fixed a crash during interpreter shutdown in the C extension: a multidict started keeping a reference to its own module, so the module state it reads while being torn down could not be freed first – by @asvetlov.

    Related issues and pull requests on GitHub: #1563.

  • Fixed retired hash tables lingering in the free-threaded build of the C extension. A drain that finds a lock-free reader in flight puts the tables it holds back for the next drain to run, and the reader it saw could already have looked, so nothing was left to free them: they stayed until the multidict was next used, and if it was dropped instead, the references their entries held were never released. Builds with the GIL enabled emit byte-identical code and are unaffected – by @asvetlov.

    Related issues and pull requests on GitHub: #1565.

  • Fixed reference cycles through a multidict going uncollected in the free-threaded build of the C extension. A hash table waiting to be freed still owns the references of the entries left in it, and those were not reported to the garbage collector, which then read the values as reachable from outside the cycle and kept it alive. Builds with the GIL enabled emit byte-identical code and are unaffected – by @asvetlov.

    Related issues and pull requests on GitHub: #1566.

  • Fixed extend(), update() and merge() of the C extension raising SystemError when given an object whose __length_hint__() reported close to sys.maxsize together with keyword arguments, and overflowing the table size computation for such a hint; a hint too large to reserve for is ignored, as list.extend() does – by @asvetlov.

    Related issues and pull requests on GitHub: #1577.

  • Fixed a use-after-free in the C extension’s MultiDict.__eq__ when a value’s __eq__ mutated either multidict; comparing exact str values also became faster – by @asvetlov.

    Related issues and pull requests on GitHub: #1579.

  • Fixed a use-after-free in the C extension when a str subclass key’s lower() mutated the source while a CIMultiDict was built from, extended, updated or merged with a dict or a multidict of the other case sensitivity. The extra references this takes are limited to the keys whose lower() can run Python code, so other updates do not pay for them – by @asvetlov.

    Related issues and pull requests on GitHub: #1581, #1598.

Features

  • Added a public C and Cython API for third-party extensions. Its MultiDict_ForEach() family hands a visitor each entry’s key, value, identity (the canonical key the mapping looks entries up by, lower-cased on a CIMultiDict) and the identity’s hash – by @Vizonex and @asvetlov.

    Related issues and pull requests on GitHub: #1478.

  • Changed the C extension to track the entries that getall(), item assignment, update(), merge() and to_dict() have visited in a bitmap private to the call, instead of temporarily marking entry hashes in the table itself. Concurrent readers on free-threaded builds no longer have to tolerate marked entries. Item assignment, to_dict() and lookups of keys with many values got faster, while update() with many repeated keys got somewhat slower – by @asvetlov.

    Related issues and pull requests on GitHub: #1505.

  • Replaced the C extension’s per-key finder object with a single callback-driven walk in a new _multilib/walk.h header. The entries a walk has already reported were tracked by the walk itself rather than by every caller, and the walk kept its position in registers instead of reloading it from a struct on each step, so getall() and the items and keys view set operations got faster on keys with many values – by @asvetlov.

    Related issues and pull requests on GitHub: #1522.

  • Sped up CIMultiDict operations keyed by a plain str in the C extension. An all-ASCII key was lowered directly instead of through a str.lower() call, and a key that is already lowercase became its own identity, which saved both a copy and a rehash. Looking up a lowercase key got about three times faster and inserting one about twice as fast, keys that do carry uppercase gained 20% to 35%, and bulk operations on already-lowercase keys, such as extend() and construction from a dict, got up to three times faster – by @asvetlov.

    Related issues and pull requests on GitHub: #1527.

  • Sped up building an istr in the C extension. The class gained its own vectorcall, so the one-argument form stopped packing its argument into a tuple for type_call() only to unpack it again with PyArg_ParseTupleAndKeywords(). Building an istr from a str got about 19% faster, and building one from an istr, which hands back the original, about three times faster. In exchange getall() costs nine more instructions per call, since the added code moves where the compiler draws its inlining line – by @asvetlov.

    Related issues and pull requests on GitHub: #1537.

  • Sped up building and dropping multidicts in the C extension by reusing freed hash tables instead of returning them to the allocator. Small tables are kept in a bounded per-size-class pool held in the module state, the way CPython reuses key objects for dict. Only on builds with the GIL: a shared pool needs an atomic exchange to take a table and another to put one back, which measured slower than the per-thread allocator a free-threaded build already has – by @asvetlov.

    Related issues and pull requests on GitHub: #1539.

  • Sped up copy(), constructing a multidict from another one, and growing one past its current size in the C extension, by not zeroing the parts of a hash table that are about to be overwritten. A copied table is overwritten in full, and a resized one has the entries it carries over written straight onto it – by @asvetlov.

    Related issues and pull requests on GitHub: #1540.

  • Sped up items(), keys(), values() and iterating a multidict in the C extension, by reusing the view and iterator objects they allocate instead of returning them to the allocator – by @asvetlov.

    Related issues and pull requests on GitHub: #1541.

  • Sped up constructing a MultiDict, CIMultiDict, MultiDictProxy or CIMultiDictProxy in the C extension, by reusing the object itself as well as its hash table. Subclasses are unaffected and keep allocating as before – by @asvetlov.

    Related issues and pull requests on GitHub: #1542.

  • Sped up getall() in the C extension, by shrinking the scratch buffer the walk carries on the stack from 4 KB to 1 KB. The buffer was large enough to stop the compiler inlining the walk into its callers, which cost more than it saved – by @asvetlov.

    Related issues and pull requests on GitHub: #1543.

  • Sped up lock-free reads on the free-threaded build of the C extension. Every reader that leaves a MultiDict last used to call into the retired-table drain, whose first act is a sequentially consistent read-modify-write, even though the retired list is empty almost every time; a plain load took over that case and the drain itself moved out of line. key in md got about 10% cheaper, md[key] about 8% and a missing md.get(key) about 5%, measured as instruction counts on CPython 3.14. Builds with the GIL enabled emit byte-identical code and are unaffected – by @asvetlov.

    Related issues and pull requests on GitHub: #1546.

  • Added a watchers C API, so another C extension can be notified when a MultiDict or CIMultiDict changes. It follows CPython’s PyDict_AddWatcher() family, with two differences: the callback is passed two context pointers, one fixed per registered callback and one per watched multidict, and events are delivered once the operation that produced them has finished rather than mid-mutation. The events are multi-value aware, and each one that concerns a key carries the identity and its hash alongside it. Up to MULTIDICT_MAX_WATCHERS (32) watchers can be registered, and a failing callback is reported through sys.unraisablehook() with the event and the multidict’s type and address, as CPython does for dict watchers – by @asvetlov.

    Related issues and pull requests on GitHub: #1551.

  • Sped up lookups, insertions and deletions in the C extension by always inlining the hash-table probe’s initializer. Left to itself the compiler emitted it out of line, so every probe opened with a call for five stores. Free-threaded builds already inlined it and are unchanged – by @asvetlov.

    Related issues and pull requests on GitHub: #1554.

  • Sped up every method call on CIMultiDict and CIMultiDictProxy in the C extension by binding their method descriptors to the class itself instead of inheriting them from MultiDict and MultiDictProxy; CPython only specializes a method call when the descriptor belongs to the exact type of the receiver, so every such call was taking the generic path – by @asvetlov.

    Related issues and pull requests on GitHub: #1567.

  • Made popitem() in the C extension drop the trailing deleted entries it walked over, so that popping every item out of a mapping costs linear time rather than quadratic; the pure-Python implementation already did this – by @asvetlov.

    Related issues and pull requests on GitHub: #1568.

  • Sped up iterating over items() in the C extension by handing out the same (key, value) tuple again when the caller has already let go of it, as for key, value in d.items() does on every step; a tuple the caller keeps is left alone and a new one is used from then on – by @asvetlov.

    Related issues and pull requests on GitHub: #1569.

  • Sped up lookups on the free-threaded build by comparing the hash before touching an entry’s key and by matching a key that is the very same object as the one looked up without taking a reference to it, so the common hit costs two fewer atomic operations – by @asvetlov.

    Related issues and pull requests on GitHub: #1570.

  • Sped up mutations on the free-threaded build when several threads mutated multidicts at the same time: each thread reserved version numbers in batches instead of bumping one counter shared by every thread on each mutation, so with six threads a d[key] = value was about four times faster – by @asvetlov.

    Related issues and pull requests on GitHub: #1587, #1599.

  • Sped up the C extension by keeping the table resize on the insert path and the operations behind Python slots out of line, so they stopped crowding the hot paths out of the compiler’s inlining budget. On GIL builds, building a multidict from 200 pairs got about 4% faster in instruction count and inserting a new key about 2%; on free-threaded builds, key lookups got up to 5% faster – by @asvetlov.

    Related issues and pull requests on GitHub: #1600, #1601.

Removals and backward incompatible breaking changes

  • Made istr a final class to disallow subclassing in both the C extension and pure-Python implementations – by @agustin18.

    Related issues and pull requests on GitHub: #1535.

Improved documentation

  • Documented how MultiDict and CIMultiDict compare with dict on both the default and the free-threaded builds, with per-operation measurements, and how to run the benchmarks in a stable environment – by @asvetlov.

    Related issues and pull requests on GitHub: #1532.

  • Documented what istr keys are worth on a CIMultiDict, as instruction counts next to the equivalent plain str keys. The advice to create them once and reuse them was already there; the numbers behind it were not – by @asvetlov.

    Related issues and pull requests on GitHub: #1547.

  • Documented that a few rows of the benchmarking reference are measured on an empty or 20-item mapping rather than the 200-item one the section describes – by @asvetlov.

    Related issues and pull requests on GitHub: #1558.

  • Mentioned in the README and docs index that multidict is optimized for both the GIL and free-threaded builds, and that its performance is typically within 20-30% of dict for common operations, with a link to the benchmarks page for the full breakdown – by @asvetlov.

    Related issues and pull requests on GitHub: #1559.

  • Fixed the C extension’s popone() and pop() docstrings, which claimed the last occurrence of the key was removed; the first one is, as on the pure-Python backend. Added a note on rejecting duplicate values for keys that must carry exactly one, such as security-sensitive HTTP headers – by @asvetlov.

    Related issues and pull requests on GitHub: #1578.

  • Added a C API example of a watcher that invalidates a cache: it unwatches from its own callback, watches again on the next rebuild, and gives up on a multidict that keeps changing – by @asvetlov.

    Related issues and pull requests on GitHub: #1586.

  • Refreshed the tables in the benchmarking reference for the 7.0.0 release, and replaced the range quoted for the pure-Python backend with its average slowdown – by @asvetlov.

    Related issues and pull requests on GitHub: #1602.

Packaging updates and notes for downstreams

  • Removed the C extension code path that existed only for free-threaded Python 3.13, whose support was dropped in v6.8.0. The free-threaded build started requiring CPython 3.14 or newer and failing with a clear compiler error on older free-threaded interpreters; builds with the GIL enabled were unaffected and still go down to Python 3.10 – by @asvetlov.

    Related issues and pull requests on GitHub: #1524.

Contributor-facing changes

  • Added CodSpeed benchmarks for getall() on a large table, update() with duplicate keys, merge(), to_dict() and items view containment with duplicate keys – by @asvetlov.

    Related issues and pull requests on GitHub: #1506.

  • Added CodSpeed benchmarks for getall() on keys with many values, both in a small table and in a table large enough to need a heap allocated bitmap – by @asvetlov.

    Related issues and pull requests on GitHub: #1509.

  • Moved the compiler hint macros used by the C extension out of htkeys.h into compiler.h and dropped the HT_ prefix from their names – by @asvetlov.

    Related issues and pull requests on GitHub: #1510.

  • Moved the debug-only ASSERT_CONSISTENT macro and the _md_check_consistency() and _md_dump() helpers used by the C extension out of hashtable.h into their own debug.h header – by @asvetlov.

    Related issues and pull requests on GitHub: #1513.

  • Added CodSpeed benchmarks for CIMultiDict keyed by plain str instead of istr, covering already-lowercase, mixed-case and non-ASCII keys – by @asvetlov.

    Related issues and pull requests on GitHub: #1521.

  • Documented that the pure-Python test leg needs --no-c-extensions alongside MULTIDICT_NO_EXTENSIONS=1; the environment variable picks the backend that gets imported, but only the flag deselects the C-extension half of the test matrix – by @asvetlov.

    Related issues and pull requests on GitHub: #1523.

  • Documented how to run the Hypothesis property tests, which a default pytest run excludes via the -m "not hypothesis" marker expression in pytest.ini and whose pin make install-dev does not install – by @asvetlov.

    Related issues and pull requests on GitHub: #1525.

  • Added tools/codegen_diff.py, which reports the functions GCC emits differently before and after a change to the C extension. Addresses, RIP-relative displacements and branch offsets inside a symbol are normalized away first, so a single instruction-length change no longer makes every later function look modified, while constants and structure offsets are left alone so that a real change is not folded away. A tree that fails to build is reported by name rather than compared – by @asvetlov.

    Related issues and pull requests on GitHub: #1529.

  • Fixed benchmarks/istr.py to import pyperf under its current name; it had been failing to start since the package was renamed from perf – by @asvetlov.

    Related issues and pull requests on GitHub: #1531.

  • Rewrote the benchmark scripts around a single registry of benchmarked operations, and added a Callgrind driver that records deterministic instruction counts per operation. The measurements of destructive operations such as pop() and clear() stopped counting the cost of rebuilding the mapping – by @asvetlov.

    Related issues and pull requests on GitHub: #1532.

  • Moved the bulk extend(), update() and merge() paths of the C extension out of hashtable.h into their own bulk_update.h header, leaving hashtable.h to the table itself and the single-key operations – by @asvetlov.

    Related issues and pull requests on GitHub: #1533.

  • Moved the two list-reading helpers behind itemsview.__contains__() and the extend()/update()/merge() sequence parser out of hashtable.h and into a new multidict/_multilib/unpack.h, together with a shared unpack_pair() that both call to read a (key, value) pair out of an exact two-element tuple or list. C extension only; the pure Python implementation has no header layout to mirror – by @asvetlov.

    Related issues and pull requests on GitHub: #1534.

  • Added a MULTIDICT_NO_FREELIST=1 build option that stops the C extension reusing freed blocks. A reused block never reaches free(), so AddressSanitizer can neither poison it nor report a use-after-free on it; an instrumented run wants a second pass with this set, as AGENTS.md describes.

    Also added a private _freelist_clear() to the C extension, for tests that inject an allocation failure: a reused block lets an operation run without calling the allocator at all, which would put the recovery path out of reach – by @asvetlov.

    Related issues and pull requests on GitHub: #1539.

  • Added a visitor to the public C API test harnesses that mutates the multidict it is walking, covering the reentrancy guard in MultiDict_ForEach() from both multidict._testcapi and multidict._testcyapi – by @asvetlov.

    Related issues and pull requests on GitHub: #1544.

  • Added benchmarks for the shapes where an allocation is most of the work: an empty mapping, a 20-item one built or copied, a view or an iterator taken on its own, and a proxy. The allocation an operation makes is a fixed cost, so at the existing 200-item size it was divided across 200 entries and all but vanished; d.items() measured 1.7% faster where it is 46% faster – by @asvetlov.

    Related issues and pull requests on GitHub: #1556.

  • Sorted docs/spelling_wordlist.txt case-insensitively, and documented in AGENTS.md that it is to be re-sorted rather than appended to. The list had grown in append order, which made a word hard to find and put every PR adding one on the same last line – by @asvetlov.

    Related issues and pull requests on GitHub: #1557.

  • Stopped asking every performance pull request to regenerate the tables in the benchmarking reference, since each run shifts every row slightly and the churn hides the rows that moved, and moved the refresh to once per release. Added RELEASE.md, which documents the release procedure, including that refresh – by @asvetlov.

    Related issues and pull requests on GitHub: #1564.

  • Renamed the C extension’s internal helpers in the _multilib headers so that a leading underscore marks exactly the functions used only within their own header; type slots and method callbacks keep their names – by @asvetlov.

    Related issues and pull requests on GitHub: #1571.

  • Spelled out in AGENTS.md that every verb in a news fragment is past tense, and added a bad and good example pair plus a matching “Things not to do” entry – by @asvetlov.

    Related issues and pull requests on GitHub: #1588.

  • Changed the release workflow to rewrite the Sphinx roles of the changelog section as Markdown before publishing it as the GitHub Release body, so that class, method and exception references, contributor handles and documentation links render there instead of showing as raw markup – by @asvetlov.

    Related issues and pull requests on GitHub: #1596.

Miscellaneous internal changes

  • Changed the internal htkeys_build_indices() helper in the C extension to return void; it cannot fail, so both callers were checking for an error that never occurred – by @asvetlov.

    Related issues and pull requests on GitHub: #1516.

  • Routed every field of the C extension’s hash table that more than one thread can reach through a named accessor in the new _multilib/freethreading.h header, instead of each call site choosing between an atomic and a plain access behind #ifdef Py_GIL_DISABLED. Each accessor kept the operation and memory ordering its call sites already used, so neither build changed behavior – by @asvetlov.

    Related issues and pull requests on GitHub: #1518.

  • Removed get_mod_state_by_cls(), an unused helper in the C extension – by @asvetlov.

    Related issues and pull requests on GitHub: #1520.

  • Documented why md_clear() keeps clearing entry fields with Py_CLEAR() on the GIL build rather than routing them through _multilib/freethreading.h: Py_CLEAR() skips the store when a field is already NULL, so no accessor there is a substitute for it – by @asvetlov.

    Related issues and pull requests on GitHub: #1526.

  • Collapsed the two #ifdef Py_GIL_DISABLED arms that fill in a freshly inserted hash table entry in the C extension into one. Both builds started writing the entry’s fields in the order the free-threaded build needs, and publishing the value without first loading and releasing the one already there, since a fresh entry holds none – by @asvetlov.

    Related issues and pull requests on GitHub: #1528.

  • Merged the C extension’s deferred-decref accumulator into reflist_t, so one collector of strong references, in the _multilib/reflist.h header, came to serve both the callers that build a result list and the ones that postpone a decref until a mutation finishes. Its storage became an inline array that spills into a chain of heap blocks, which also made getall() a little faster on keys with many values, since the collected values stopped being copied to a bigger buffer as they accumulate – by @asvetlov.

    Related issues and pull requests on GitHub: #1530.

  • Dropped two module state lookups in the C extension that had already been answered elsewhere. MultiDict.__init__() and CIMultiDict.__init__() switched to reading the state pointer that tp_new() stored on the object instead of resolving it from the type a second time, and an istr stopped carrying a state pointer of its own, which was written on every creation and never read. Constructing a subclass of either container got about 1% cheaper, and every istr is eight bytes smaller – by @asvetlov.

    Related issues and pull requests on GitHub: #1536.

  • Narrowed the critical section of the single-key methods (add(), __setitem__(), __delitem__(), setdefault(), pop(), popone(), popall() and getall()): a key is converted to its identity and hashed before the lock is taken, rather than while a free-threaded build holds the multidict locked – by @asvetlov.

    Related issues and pull requests on GitHub: #1538.

  • Changed MultiDict_ForEach() to walk every entry with a linear scan instead of the resumable iterator cursor, which its caller does not need while it holds the critical section for the whole walk – by @asvetlov.

    Related issues and pull requests on GitHub: #1544.

  • Sped up the argument parsing of the C extension. The all-positional form of every method taking key started binding its arguments inline instead of calling into the keyword machinery, and keyword names were matched against interned parameter names by identity before falling back to a string comparison – by @asvetlov.

    Related issues and pull requests on GitHub: #1549.

  • Stopped building the implicit None default of MultiDict.get() on every call in the C extension, and produced it only when the key is actually missing – by @asvetlov.

    Related issues and pull requests on GitHub: #1549.

  • Laid out the common branch in line in the single-key lookup probe loop and in the key to identity conversion: the deleted-slot check and the str subclass check were marked as the rare cases, so the compiler stopped putting the frequent path behind a taken branch – by @asvetlov.

    Related issues and pull requests on GitHub: #1550.

  • Stopped building the implicit None default of MultiDict.setdefault() on every call in the C extension – by @asvetlov.

    Related issues and pull requests on GitHub: #1553.

  • Reordered the fields of the C extension’s multidict object so the ones a lookup reads share a cache line, and dropped its separate module reference in favor of the one the module state already holds, which made every multidict 16 bytes smaller – by @asvetlov.

    Related issues and pull requests on GitHub: #1584.

  • Dropped the C extension’s multidict_tp_traverse() and multidict_tp_clear() wrappers; md_traverse() and md_clear() now serve as the type’s GC slots directly – by @asvetlov.

    Related issues and pull requests on GitHub: #1589.

  • Removed a redundant NULL check and a stray PyErr_Format() argument from the C constructors, and an always true condition from the pure Python getall() – by @asvetlov.

    Related issues and pull requests on GitHub: #1590.

  • Merged the duplicated __init__() bodies of the C MultiDict and CIMultiDict into one helper – by @asvetlov.

    Related issues and pull requests on GitHub: #1591.

  • Replaced the allocate, set state and take the module reference sequence repeated across the C constructors with one helper – by @asvetlov.

    Related issues and pull requests on GitHub: #1592.

  • Renamed internal helpers whose names no longer matched what they did, and fixed misspelled docstring identifiers in the C extension – by @asvetlov.

    Related issues and pull requests on GitHub: #1593.

  • Renamed the C extension’s functions that are used only as type slots after the slot they fill, as <type>_tp_<slot>, <type>_nb_<slot>, <type>_sq_<slot> or <type>_mp_<slot> – by @asvetlov.

    Related issues and pull requests on GitHub: #1594.

  • Replaced the custom tp_new slots that blocked direct instantiation of the C view and iterator types with Py_TPFLAGS_DISALLOW_INSTANTIATION – by @asvetlov.

    Related issues and pull requests on GitHub: #1595.


6.9.1

(2026-09-21)

Bug fixes

  • Fixed the C extension reading freed memory on free-threaded builds when a list handed to update(), extend(), merge() or the MultiDict and CIMultiDict constructors, a [key, value] item inside any iterable handed to them, or a list tested with in against items(), is changed by another thread; a call that catches the list shrinking under it now raises RuntimeError – by @rodrigobnogueira.

    Related issues and pull requests on GitHub: #1437.

  • Fixed a data race on the free-threaded build where a retired hash table’s reader count used relaxed atomics, letting a lock-free get()/getone()/ __getitem__() read race a concurrent free of that table. The reader-exit decrement and the drain’s free check now use release/acquire ordering instead – by @asvetlov.

    Related issues and pull requests on GitHub: #1481.

  • Fixed a free-threaded build bug where two threads calling update(), merge(), or __setitem__() on the same key at the same time could lose the key entirely instead of just racing on which value wins. A decref of the replaced value could transiently suspend the writer’s critical section, letting a second writer for the same key observe the first writer’s in-progress entry as absent and, once both settled, mistake it for a stale duplicate and delete it. Every such decref is now deferred until the writer has released its critical section, so the window can no longer open. setdefault() had an unrelated instance of the same blind spot (it could insert a duplicate rather than recognizing an in-flight key), fixed alongside it – by @asvetlov.

    Related issues and pull requests on GitHub: #1483.

  • Fixed a free-threaded build bug where getall() and the items()/ keys()/values() equality path could raise KeyError or report a present, never-deleted key as missing. A concurrent update()/extend()/ __setitem__() call can have its critical section transiently suspended (a decref triggering a blocking allocator call) while an entry is marked as part of its own bookkeeping; a reader landing in that window used to treat the mark as “not found” instead of “still there, in flight” – by @asvetlov.

    Related issues and pull requests on GitHub: #1484.

  • Fixed a reference leak in the C extension where operand | md.items() and md.items() - operand leaked one key and one value reference per element of operand, letting a large operand grow memory without bound (GHSA-54p9-h82j-f925) – by @asvetlov.

    The issue was reported by @waydeshi.

    Related commits on GitHub: 350b4a0.

  • Fixed a segmentation fault on the standard (non-free-threaded) C extension build when a value type’s __del__ released the GIL (for example by calling time.sleep()) while update(), merge(), __setitem__(), __delitem__(), pop(), popone(), or popall() was dropping a replaced or removed value. Py_BEGIN_CRITICAL_SECTION compiles to a no-op on this build, so nothing else was stopping a second thread from mutating the very same MultiDict concurrently once the GIL was released mid-mutation. Every such decref is now deferred until the mutation has fully finished, the same technique already used to close the analogous free-threaded-build race – by @asvetlov.

    Related issues and pull requests on GitHub: #1489.

  • Fixed the C extension reading freed memory while iterating a CIMultiDict whose keys are plain str. Converting such a key to istr could run Python code (a str subclass’s __str__ or __del__) or, on free-threaded builds, suspend the iterator’s critical section, after which the iterator read the entry again even though a concurrent mutation could already have freed it. As part of the fix, copy() and re-initializing from another multidict now assign a new version in the C extension instead of reusing the source’s, matching the pure Python implementation – by @asvetlov.

    Related issues and pull requests on GitHub: #1496.

  • Fixed a use-after-free on the free-threaded build where a lock-free get(), [] or in could read a hash table that a concurrent resize had just retired and another reader was freeing – by @asvetlov.

    Related issues and pull requests on GitHub: #1497.

Contributor-facing changes

  • Removed a redundant include and a duplicated exclude line from MANIFEST.in; sdist contents are unchanged – by @asvetlov.

    Related issues and pull requests on GitHub: #1478.

  • Added .claudeignore file – by @asvetlov

    Related issues and pull requests on GitHub: #1479.

  • Scaled up the pure-Python pop(), popitem(), __delitem__(), add() and item-insertion benchmarks to do more work per measurement. Repeated CodSpeed runs on the same commit showed these particular benchmarks flagged as dominated by syscalls, understating their real cost and adding noise to the reported values; a larger working set amortizes that overhead – by @asvetlov.

    Related issues and pull requests on GitHub: #1485.

  • Replaced deprecated instrumentation codspeed mode with simulation – by @asvetlov

    Related issues and pull requests on GitHub: #1493.

  • Reorganized the mutating benchmarks (item insertion, update(), add() of the same key, pop(), popitem(), clear(), __delitem__() and __setitem__()) to copy a fresh multidict and apply the operation in a loop, like the add() and extend() benchmarks already do. The insertion, update() and clear() benchmarks previously mutated a single multidict shared across rounds, so only the first round measured the intended operation; the rest did a single copy per round, letting per-round overhead dominate – by @asvetlov.

    Related issues and pull requests on GitHub: #1494.

  • The repr() and view inequality benchmarks were updated to repeat their operation in a loop, like the other benchmarks, and the CodSpeed benchmark job was moved to Python 3.14 – by @asvetlov.

    Related issues and pull requests on GitHub: #1498.

  • Dropped -I from the AddressSanitizer test command in AGENTS.md and in the CI job. It implies -E, which made Python ignore PYTHONMALLOC=malloc, so small hash tables were still served from pymalloc arenas where use-after-free went undetected – by @asvetlov.

    Related issues and pull requests on GitHub: #1499.

  • The CI/CD workflow was updated to stop superseded runs of the same pull request when a new commit is pushed; runs on master, release branches, tags, the merge queue, and the daily schedule are never interrupted – by @asvetlov.

    Related issues and pull requests on GitHub: #1500.

  • The release job was changed to upload distributions and their signatures to the GitHub Release one file at a time, skipping assets that were already attached and retrying after a pause, so that a parallel upload burst no longer tripped the GitHub secondary rate limit – by @asvetlov.

    Related issues and pull requests on GitHub: #1503.

Miscellaneous internal changes

  • Corrected several comments in the free-threaded C extension that attributed critical-section suspension to a blocking PyMem_Malloc() call; allocation alone never suspends an acquired critical section, and the real risk at those sites is a decref running a finalizer or weakref callback. Also dropped a retry loop in md_clone_from_ht() that guarded against the same, non-existent allocation-triggered suspension – by @asvetlov.

    Related issues and pull requests on GitHub: #1486.

  • Changed the free-threaded build’s deferred decref buffer, used by update() and __setitem__(), to a chain of fixed-size blocks with a large inline first block instead of a small inline array that was reallocated on growth – by @asvetlov.

    Related issues and pull requests on GitHub: #1501.


6.9.0

(2026-09-18)

Bug fixes

  • Protected repr() of MultiDict, MultiDictProxy, and their views in the C extension with a critical section, avoiding data races on the free-threaded build of CPython – by @asvetlov.

    Related issues and pull requests on GitHub: #1431.

  • Guarded repr() of MultiDictProxy in the C extension and of KeysView in both the C extension and the pure-Python implementation against infinite recursion on self-referential containers, matching the existing guard on MultiDict, ItemsView, and ValuesView – by @asvetlov.

    Related issues and pull requests on GitHub: #1431.

  • Protected MultiDict.update(), .extend(), .merge(), .clear(), .copy(), and the MultiDict/CIMultiDict constructors in the C extension with a critical section, using the two-object form when a second multidict, multidict proxy, or plain dict instance is involved, avoiding data races and a segmentation fault on the free-threaded build of CPython. .clear() now also publishes the empty table before releasing any entry’s references, so a concurrent caller can never observe a partially-cleared multidict even if releasing a value runs arbitrary Python code that suspends the held critical section – by @asvetlov.

    Related issues and pull requests on GitHub: #1433.

  • Protected MultiDict.add(), __setitem__/__delitem__, get()/getone()/__getitem__, __contains__, getall(), setdefault(), pop()/popone()/popall()/popitem(), __eq__, iteration, and the &/|/-/^/in/ isdisjoint() operations on .keys() and .items() views in the C extension with a critical section, avoiding data races and use-after-free crashes on the free-threaded build of CPython – by @asvetlov.

    Related issues and pull requests on GitHub: #1438.

  • Fixed a data race on the free-threaded build where MultiDictObject.used was written non-atomically while len() read it with a relaxed atomic load – by @asvetlov.

    Related issues and pull requests on GitHub: #1441.

  • Fixed several pre-existing use-after-free races on the free-threaded build: _md_resize() and md_clone_from_ht() could allocate a new hash table, have their critical section transiently suspended during that allocation, and then use a keys-table pointer or size a concurrent operation had already invalidated; _md_del_at() (plain del/pop()) could likewise leave self in an inconsistent state across a suspending decref – by @asvetlov.

    Related issues and pull requests on GitHub: #1441.

  • Fixed a data race on the free-threaded build where md->keys was published with a plain store while lock-free readers loaded it atomically, and a false-negative race in get()/__contains__ where a hash temporarily marked by a concurrent replace could make a present key look absent – by @asvetlov.

    Related issues and pull requests on GitHub: #1441.

  • Fixed several more pre-existing use-after-free and data-corruption races on the free-threaded build, this time in the __setitem__()/update()/ extend()/merge() replace path: a decref suspending the critical section mid-replace could leave a stale table pointer in use, misplace a temporarily-marked entry during a concurrent resize, or let one thread’s duplicate-tracking mark get overwritten by an unrelated key’s entry sharing the same hash bucket – by @asvetlov.

    Related issues and pull requests on GitHub: #1441.

  • Fixed an unbounded memory leak on the free-threaded build where a retired hash table could sit on md->retired for the rest of the object’s lifetime under sustained concurrent read traffic instead of being freed – by @asvetlov.

    Related issues and pull requests on GitHub: #1443.

  • Fixed a data race on the free-threaded build of the C extension where state->global_version, the version counter shared by every multidict instance and used to derive getversion(), was bumped with a plain increment instead of an atomic op. Two threads mutating different instances at the same time could step on each other’s update and hand out a duplicate (or non-monotonic) version number – by @asvetlov.

    Related issues and pull requests on GitHub: #1444.

  • Fixed a data race on the free-threaded build of the C extension where getall()/popall() and update()/extend()/merge() temporarily marked and unmarked a matching entry’s hash with a plain, non-atomic store, while a lock-free __contains__/get() on another thread could load that same field with an atomic operation and no lock at all – by @asvetlov.

    Related issues and pull requests on GitHub: #1448.

  • Fixed a data race in the pure-Python MultiDict/CIMultiDict fallback where concurrent mutating calls (add(), __setitem__, __delitem__, setdefault(), pop()/popone(), popall(), popitem(), update(), extend(), merge(), clear(), and re-__init__) could corrupt the shared hash table, or silently drop a concurrent write, on a regular, GIL-enabled interpreter. Pure-Python bytecode is not atomic under the GIL, so two threads could interleave mid hash-table insert or deletion, leaving the index table pointing at stale or out-of-range entries and causing an infinite probe loop, an AttributeError, or a lost mutation. Each MultiDict/CIMultiDict instance now holds its own lock for the duration of these operations, with two-object operations locking both instances in a fixed order to avoid deadlock. The C extension already serializes these operations with a critical section and was not affected – by @asvetlov.

    Related issues and pull requests on GitHub: #1449.

  • Fixed a data race on the free-threaded build of the C extension where to_dict() cleared every entry’s temporary “seen” mark with a plain, non-atomic store, while a lock-free __contains__/get() on another thread could load that same field with an atomic operation and no lock at all – by @asvetlov.

    Related issues and pull requests on GitHub: #1450.

  • Fixed a data race on the free-threaded build of the C extension where update()/extend()/merge() read self’s module state without holding self’s lock, while a concurrent __init__() on the same, already-published multidict could rewrite that same field under lock. Rather than routing every read through a lock-free lookup, the field is now written only once, at object construction, and never touched again by __init__(), so it is safe to read unlocked anywhere – by @asvetlov.

    Related issues and pull requests on GitHub: #1452.

  • Fixed items() set algebra (&, |, -, in, isdisjoint()) so it no longer corrupts or hangs when a compared value’s __eq__() re-enters the same MultiDict, for example by calling getall() on it. The comparison now runs only after the internal hash-chain walk has been fully materialized and restored, so the callback can no longer observe entries the walk still has marked – by @asvetlov.

    Related issues and pull requests on GitHub: #1453.

  • Fixed a data race on the free-threaded build of the C extension where popall()/popone()/__delitem__/add() rewrote a hash table index slot with a plain, non-atomic store, while a lock-free get()/getone()/__getitem__/__contains__ on another thread walked that same index array with a plain, non-atomic load and no lock at all – by @asvetlov.

    Related issues and pull requests on GitHub: #1454.

  • Stopped folding the case of istr keys in the case-sensitive MultiDict, so both the C and the pure Python implementations now keep such a key exactly as given – by @youdie006.

    Related issues and pull requests on GitHub: #1457.

  • Fixed the C extension’s update() (and merge(), which shares the same code path) silently failing to invalidate an in-progress keys()/ items()/values() iterator when the call only overwrote an already-present key’s value in place. Adding a new key already invalidated iterators correctly; only the in-place overwrite branch was missing the version bump – by @asvetlov.

    Related issues and pull requests on GitHub: #1458.

  • Fixed the pure-Python MultiDict/CIMultiDict iterator guard so that mutating the mapping before the first next() call on an items(), keys(), or values() iterator (or on iter(md)) now reliably raises RuntimeError, matching the C extension. Previously a mutation that happened before the iterator was ever advanced, such as clear() or a del/popone() that removed the only remaining entry, could make the iterator silently raise StopIteration instead – by @asvetlov.

    Related issues and pull requests on GitHub: #1459.

  • Fixed a use-after-free race on the free-threaded build of the C extension where a retired hash table could be freed while a lock-free get()/getone()/__getitem__/__contains__ reader on another thread was still walking it: the coarse “no reader in flight” gate reaching zero did not reliably mean every such reader had also reached its own per-table exit, so a table whose own reader count is still nonzero is now deferred for a later retry instead of freed – by @asvetlov.

    Related issues and pull requests on GitHub: #1461.

Features

  • Added a to_dict() method returning a plain dict that maps every key to the list of all its values. Unlike dict(md), which keeps only the first value per key, and unlike a {k: md.getall(k) for k in md} comprehension, which emits one entry per spelling of a case-insensitive key, it groups by key identity – by @rodrigobnogueira.

    Related issues and pull requests on GitHub: #783.

  • MultiDict, CIMultiDict, MultiDictProxy and CIMultiDictProxy in the C extension implemented the vectorcall calling convention for construction. This sped up MultiDict(...)-style calls by avoiding an intermediate positional-arguments tuple and keyword-arguments dictionary in the common case; the gained burst is ~10. – by @asvetlov.

    Related issues and pull requests on GitHub: #1436.

  • Made MultiDict.__contains__(), .get(), .getone() and __getitem__() lock-free on CPython 3.14+’s free-threaded build, instead of taking a critical section like the rest of the C extension’s API (on 3.13, which lacks the public API these need to safely read an entry concurrently, they still take the critical section, exactly as before). A resize/shrink/clear no longer frees the outgoing hash table immediately; it is deferred until no lock-free reader could still be walking it – by @asvetlov.

    Related issues and pull requests on GitHub: #1441.

  • Made the pure-Python MultiDict/CIMultiDict implementation safe to use from multiple threads under a free-threaded (no-GIL) build. Every method that touches an instance’s hash table now takes that instance’s own lock, two-object operations (update(), extend(), merge(), __eq__(), copying) lock both objects in a fixed, deadlock-safe order, and the version counter shared by every instance is guarded separately. On a regular (GIL-enabled) interpreter this adds no overhead: the locked methods are the exact same function objects as before – by @asvetlov.

    Related issues and pull requests on GitHub: #1447.

  • Sped up adding many values for the same key to a MultiDict or CIMultiDict, each added value no longer gets slower than the one before it – by @bdraco.

    Related issues and pull requests on GitHub: #1466.

Improved documentation

  • Added the plural form “fallbacks” to the docs spell checker’s allowed word list so CHANGES.rst builds cleanly – by @asvetlov.

    Related issues and pull requests on GitHub: #1439.

Contributor-facing changes

  • Added race tests that iterate and extend a MultiDict from several threads on a free-threaded build, so a regression in the C extension’s locking fails a free-threaded CI leg instead of going unnoticed – by @rodrigobnogueira.

    Related issues and pull requests on GitHub: #1317.

  • Added AddressSanitizer/UndefinedBehaviorSanitizer and ThreadSanitizer CI jobs. The former runs the suite under a regular CPython with MULTIDICT_ASAN_BUILD=1; the latter builds a free-threaded CPython instrumented with --with-thread-sanitizer and runs the suite against it via MULTIDICT_TSAN_BUILD=1 – by @asvetlov.

    Related issues and pull requests on GitHub: #1455.

  • Added Hypothesis-based property and stateful fuzz tests covering MultiDict/CIMultiDict semantics, views, iterators, and threaded stress scenarios, run against both the C-extension and pure-Python backends – by @asvetlov.

    Related issues and pull requests on GitHub: #1460.

  • Added core dump collection to the AddressSanitizer and ThreadSanitizer CI jobs: a crash now uploads the core file alongside the crashing interpreter binary and the compiled extension as a downloadable artifact, for offline debugging with a matching symbol-carrying binary instead of raw hex offsets – by @asvetlov.

    Related issues and pull requests on GitHub: #1462.

  • Added benchmarks for adding many values for the same key and for creating a multidict with many items – by @bdraco.

    Related issues and pull requests on GitHub: #1465.

  • Skipped abstractmethods from coverage leaks report, they are empty placeholders that are never executed – by @asvetlov

    Related issues and pull requests on GitHub: #1470.

  • Add benchmarks for __setitem__ – by @asvetlov

    Related issues and pull requests on GitHub: #1471.

  • The AddressSanitizer and ThreadSanitizer CI jobs were changed to run pytest with --capture=no. Previously a sanitizer abort could exit the process before pytest flushed its per-test output buffer, silently dropping the sanitizer report from the job log – by @asvetlov.

    Related issues and pull requests on GitHub: #1472.

  • Moved the hypothesis pin out of requirements/pytest.txt into its own requirements/pytest-hypothesis.txt, installed only by the dedicated Hypothesis CI jobs. hypothesis now ships a Rust extension with no prebuilt wheel for some emulated architectures (e.g. musllinux i686), which broke wheel-testing jobs that never run the Hypothesis-marked tests in the first place – by @asvetlov.

    Related issues and pull requests on GitHub: #1475.

Miscellaneous internal changes

  • Replaced PyIter_Next() calls with the newer PyIter_NextItem() API in the C extension, so the iterator-exhausted and error cases are told apart by the return code instead of an ambiguous NULL result – by @asvetlov.

    Related issues and pull requests on GitHub: #1440.


6.8.0

(2026-09-09)

Bug fixes

  • A segmentation fault that could be triggered when getting an item is now fixed – by @Vizonex.

    Related issues and pull requests on GitHub: #1310.

  • Fixed reference leak in iterators, views and istr – by @Vizonex.

    Related issues and pull requests on GitHub: #1311.

  • Fixed the pure-Python MultiDict constructor and extend(), update(), and merge() methods over-allocating their internal hash table when called with both a positional argument and keyword arguments, because keyword arguments were counted twice in the size estimate – by @aiolibsbot.

    Related issues and pull requests on GitHub: #1338.

  • Fixed __repr__ of MultiDict, CIMultiDict, their proxies, and the keys/items views producing invalid output when keys contained quote characters – keys are now formatted with repr() so the result is a valid Python string literal – by @aiolibsbot.

    Related issues and pull requests on GitHub: #1342.

  • Fixed a segfault when calling add() with only one of its two required arguments supplied by keyword, e.g. d.add(key="k"). Extra keyword arguments passed to the lookup and removal methods are also now rejected with TypeError instead of being silently ignored.

    – by @devdanzin

    Related issues and pull requests on GitHub: #1376.

  • Fixed a segfault when constructing a multidict iterator type directly, e.g. type(iter(md.keys())).__new__(...). Such an iterator had a NULL internal pointer that next() dereferenced. The iterator types now forbid direct instantiation, the same way the view types were fixed in #1163.

    – by @devdanzin

    Related issues and pull requests on GitHub: #1377.

  • Fixed a segfault when using a MultiDict or CIMultiDict created via __new__ without calling __init__ (for example a subclass that does not call super().__init__()). The internal state was left as NULL pointers that the first method call dereferenced. tp_new now initializes the object to a valid empty mapping.

    – by @devdanzin

    Related issues and pull requests on GitHub: #1378.

  • Fixed two crashes in the C extension caused by holding a raw pointer into a hash table across an operation that could reshape it. Updating a multidict from itself (e.g. d.extend(d)) freed the very table being iterated – a use-after-free; extend(self) now doubles the contents and update(self)/merge(self) are no-ops. Building a multidict from a list of pairs whose case-insensitive key .lower() shrinks that list read past the end of the list; the length is now re-checked on every iteration.

    – by @devdanzin

    Related issues and pull requests on GitHub: #1379.

  • Fixed three reference/resource leaks on error paths in the C extension: the items-view __contains__ leaked the first element of a candidate pair when reading the second one raised; __repr__ leaked its PyUnicodeWriter when the multidict was mutated during iteration; and the internal iteration helper leaked the identity reference when key materialization failed under low memory.

    – by @devdanzin

    Related issues and pull requests on GitHub: #1381.

  • Stopped several feature-detection fallbacks in the C extension from swallowing every exception. When probing an argument (arg.items()/arg.keys()) or measuring it (len(other)) fails, the code now clears only the expected TypeError/AttributeError and lets everything else – notably MemoryError and KeyboardInterrupt – propagate, matching the already-correct sites elsewhere in the module.

    – by @devdanzin

    Related issues and pull requests on GitHub: #1382.

  • Fixed a segmentation fault when extending a multidict with itself – by @cananoo and @asvetlov.

    Related issues and pull requests on GitHub: #1398.

  • Fixed a memory leak when MultiDict and CIMultiDict instances are initialized more than once – by @asvetlov.

    Related issues and pull requests on GitHub: #1412.

  • Fixed a reference leak in items-view set operations – by @asvetlov.

    Related issues and pull requests on GitHub: #1413.

  • Fixed a memory leak when destroying C-extension multidict instances and proxies – by @asvetlov.

    Related issues and pull requests on GitHub: #1415.

  • Fixed a reference leak from repeated __init__ calls on MultiDictProxy and CIMultiDictProxy instances – by @asvetlov.

    Related issues and pull requests on GitHub: #1419.

  • Fixed missing decref for None on error in setdefault() – by @asvetlov

    Related issues and pull requests on GitHub: #1421.

Features

  • Added reversed() support to the keys, values, and items views of MultiDict, CIMultiDict, and their proxies in both the C-extension and pure-Python implementations – by @aiolibsbot.

    Related issues and pull requests on GitHub: #448.

  • Optimized key identity comparison for C Extension.

    Now it uses fast path that is equal to PyUnicode_Equal() from Python 3.14+ but without redundant type checks. It gives ~15% speed-up on benchmarks with many key comparisons.

    – by @asvetlov

    Related issues and pull requests on GitHub: #1406.

  • Changed both the pure-Python and C implementations to mark a temporarily removed hash table entry by setting the high bit of its hash instead of overwriting it with a sentinel value, so restoring the entry no longer recomputes the hash; getall() is faster ~10% now in C version – by @asvetlov

    Related issues and pull requests on GitHub: #1426.

Removals and backward incompatible breaking changes

  • Dropped support for Python 3.9 as it has reached end of life.

    Related issues and pull requests on GitHub: #1316.

  • Dropped support for free-threaded Python 3.13 – by @ngoldbaum.

    Related issues and pull requests on GitHub: #1326.

Improved documentation

  • Fixed CIMultiDictProxy documentation to state it inherits from MultiDictProxy (not MultiDict), and fixed missing word in istr section – by @veeceey.

    Related issues and pull requests on GitHub: #1298.

  • Clarified that istr preserves the original casing and compares as a regular str; case-insensitive matching is handled by CIMultiDict – by @gyanu2507.

    Related issues and pull requests on GitHub: #1397.

  • Fixed broken RST markup in items() docstrings – by @veeceey.

    Related issues and pull requests on GitHub: #1299.

Packaging updates and notes for downstreams

  • Dropped support for free-threaded Python 3.13 – by @ngoldbaum.

    Related issues and pull requests on GitHub: #1326.

  • Wheels for iOS and Android (CPython 3.13+) are now published on release-tag builds, so downstreams on those platforms no longer need a local compiler to install multidict; these wheels are build-verified only, since running the test suite on mobile needs a simulator or an emulator – by @aiolibsbot.

    Related issues and pull requests on GitHub: #1337.

  • Added support for building and shipping riscv64 wheels – by @justeph.

    Related issues and pull requests on GitHub: #1293.

  • The setuptools build dependency lower bound has been restored to be >= 47 after an incorrect automated increase in PR #1315 – by @aiolibsbot.

    Related issues and pull requests on GitHub: #1400.

Contributor-facing changes

  • Added support for collecting code coverage of isolated multidict tests – by @Vizonex.

    Related issues and pull requests on GitHub: #1314.

  • Switched to mirrors-clang-format and enabled clang-format in pre-commit.ci – by @bdraco.

    Related issues and pull requests on GitHub: #1318.

  • Added a release-tag-gated CI job that cross-compiles the C extension for iOS and Android through cibuildwheel – by @aiolibsbot.

    Related issues and pull requests on GitHub: #1337.

  • Added an AGENTS.md orientation file at the repository root, covering the pull request template, the CHANGES/ news fragment conventions, the draft-PR / human-review workflow, and the dual pure-Python and C-extension parity rule, so LLM contributors land changes that match project style – by @bdraco.

    Related issues and pull requests on GitHub: #1339.

  • Added init, installable, instantiation, parametrization, parametrized, postfix, and unparseable to the docs spelling list so news fragments and docs can use these words without failing the spell check build – by @pctablet505.

    Related issues and pull requests on GitHub: #1343.

  • Documented three common agent failure modes in AGENTS.md: the docs spell check (make doc-spelling) catches unknown words in news fragments before CI does; coverage runs over the test tree too, so unreachable defensive raise guards and one-sided cleanup branches in tests will surface as uncovered on the codecov patch report; and branch creation is restricted on aio-libs/multidict, so PRs must be pushed from a fork – by @bdraco.

    Related issues and pull requests on GitHub: #1343, #1345.

  • Trimmed the CI test matrix to drop redundant Py_DEBUG jobs on macOS and Windows and to gate the windows-11-arm wheel build to release-time only, cutting CI wall-clock by roughly a third without reducing code coverage – by @aiolibsbot.

    Related issues and pull requests on GitHub: #1346.

  • Added a CLAUDE.md at the repository root that imports AGENTS.md via Claude Code’s @-syntax, so the project’s LLM contributor rules load automatically when working in Claude Code – by @aiolibsbot.

    Related issues and pull requests on GitHub: #1347.

  • Enabled the I (import sorting) rule in [tool.ruff.lint] so ruff check covers import order on top of the existing UP (pyupgrade) group, and updated the ruff-check pre-commit hook to run with --fix --exit-non-zero-on-fix --show-fixes so import-order fixes apply on commit. Sorted imports in four tests/ files to match the new rule – by @bdraco.

    Related issues and pull requests on GitHub: #1348.

  • Enabled the PLC0415 (import-outside-top-level) rule in [tool.ruff.lint]. Function-scoped imports must now opt in with # noqa: PLC0415 plus a comment explaining the reason (typically avoiding a heavy optional dependency at import time). This catches a pattern that LLM contributors frequently introduce. There are currently no opt-outs in the tree, so the rule is enforced everywhere ruff check runs – by @bdraco.

    Related issues and pull requests on GitHub: #1349.

  • Switched the cibuildwheel build frontend to build[uv] so that uv provisions every build and test virtual environment in the wheel matrix. Test-dependency installation in particular drops from a multi-second pip install per ABI to a roughly sub-second uv resolve – by @bdraco.

    Related issues and pull requests on GitHub: #1350.

  • Overrode CIBW_BUILD_FRONTEND=build for the odd-arch wheel matrix; the upstream manylinux/musllinux images for those arches do not ship uv – by @bdraco.

    Related issues and pull requests on GitHub: #1352.

  • Allowed re-running the deploy job after a partial release failure: the Make Release step now skips when the GitHub Release already exists, and the PyPI publish step uses skip-existing so dists that were already uploaded on a prior attempt do not break the retry – by @bdraco.

    Related issues and pull requests on GitHub: #1353.

  • Switched the aarch64 and armv7l wheel builds to GitHub’s native ARM runners. The aarch64 wheels now build without QEMU emulation, and armv7l runs on aarch64 hosts so its 32-bit ARM execution is far cheaper than the previous aarch64-on-x86_64 path – by @bdraco.

    Related issues and pull requests on GitHub: #1354.

  • Documented design principles for pure-Python istr implementation – by @asvetlov

    Related issues and pull requests on GitHub: #1360.

  • Pinned coverage to the ctrace measurement core so the test suite runs on Python 3.14. Coverage picks sysmon there, which cannot record the dynamic contexts pytest-cov switches at runtime, and the resulting warning became an error under the suite’s filterwarnings setting – by @rodrigobnogueira.

    Related issues and pull requests on GitHub: #1393.

  • Dependabot has been restricted to the requirements subdirectory to avoid unintended updates outside dependency requirement files – by @aiolibsbot.

    Related issues and pull requests on GitHub: #1400.

  • Enabled automatic upgrades from ruff after each python version that is dropped – by @Vizonex.

    Related issues and pull requests on GitHub: #1325.

Miscellaneous internal changes

  • Explicitly marked empty_htkeys as const.

    Moved the structure to .rodata linker section.

    Unexpected modification of the structure will lead to segfault instead of corrupting the data and fail fast.

    .rodata is mount as read-only-mapped, it is shared well across multiple threads without cache misses.

    The change is pretty trivial, it is made for the sake of correctness, not for speedup.

    – by @asvetlov.

    Related issues and pull requests on GitHub: #1405.

  • Slightly reorganized multidict creating process.

    1. Removed unnecessary calculations when md_init() creates an empty multidict.

    2. Now tp_alloc slot is used in object cloning instead of bare PyType_GenericNew() call.

    – by @asvetlov

    Related issues and pull requests on GitHub: #1420.

  • Dropped dead code, _md_del_at() and _md_del_at_for_upd() never fails – by @asvetlov

    Related issues and pull requests on GitHub: #1423.

  • Fixed a missing space in the ValueError message raised when constructing a multidict from a sequence – by @veeceey.

    Related issues and pull requests on GitHub: #1295.

  • Renamed the benchmark script from benchmarks/becnhmark.py to benchmarks/benchmark.py so it matches the invocation documented in Benchmarks – by @aiolibsbot.

    Related issues and pull requests on GitHub: #1335.


6.7.1

(2026-01-25)

Bug fixes

  • Fixed slow memory leak caused by identity by adding Py_DECREF to identity value before leaving md_pop_one on success – by @Vizonex.

    Related issues and pull requests on GitHub: #1284.


6.7.0

(2025-10-05)

Contributor-facing changes

  • Updated tests and added CI for CPython 3.14 – by @kumaraditya303.

    Related issues and pull requests on GitHub: #1235.


6.6.4

(2025-08-11)

Bug fixes

  • Fixed MutliDict & CIMultiDict memory leak when deleting values or clearing them – by @Vizonex

    Related issues and pull requests on GitHub: #1233.

Contributor-facing changes

  • The type preciseness coverage report generated by MyPy is now uploaded to Coveralls and will not be included in the Codecov views going forward – by @webknjaz.

    Related issues and pull requests on GitHub: #1122, #1231.

  • Added memory leak test for popping or deleting attributes from a multidict to prevent future issues or bogus claims. – by @Vizonex

    Related issues and pull requests on GitHub: #1233.


6.6.3

(2025-06-30)

Bug fixes

  • Fixed inconsistencies generated by the C implementation of _md_shrink() which might later lead to assertion failures and crash – by @Romain-Geissler-1A.

    Related issues and pull requests on GitHub: #1229.


6.6.2

(2025-06-28)

Bug fixes

  • Fixed a memory corruption issue in the C implementation of _md_shrink() that could lead to segmentation faults and data loss when items were deleted from a MultiDict. The issue was an edge case in the pointer arithmetic during the compaction phase – by @bdraco.

    Related issues and pull requests on GitHub: #1221, #1222.

  • Fixed format string compilation errors in debug builds on 32-bit platforms by using portable %zd format specifiers for Py_ssize_t values instead of %ld – by @bdraco.

    Related issues and pull requests on GitHub: #1225, #1226.

Packaging updates and notes for downstreams

  • Re-enabled 32-bit Linux wheel builds that were disabled by default in cibuildwheel 3.0.0 – by @bdraco.

    Related issues and pull requests on GitHub: #1225, #1227.


6.6.1

(2025-06-28)

Bug fixes

Contributor-facing changes

  • Fixed setuptools deprecation warning about the license specification – by @asvetlov.

    Related issues and pull requests on GitHub: #1216.

  • Fix compiler warnings and convert them to errors – by @asvetlov.

    Related issues and pull requests on GitHub: #1217.


6.6.0

(2025-06-27)

Features

  • Added multidict.MultiDict.merge() which copies all items from arguments if its key not exist in the dictionary – by @asvetlov.

    Related issues and pull requests on GitHub: #292.

  • Stopped reallocating memory for the internal htkeys_t structure when inserting new items if the multidict has deleted items and it could be collapsed in-place. Removal of malloc()/free() improves the performance slightly.

    The change affects C implementation only, pure Python code is not changed.

    Patch by @asvetlov.

    Related issues and pull requests on GitHub: #1200.

  • C implementation of multidict.MultiDict.getall now is slightly faster if it returns nothing – by @asvetlov.

    Related issues and pull requests on GitHub: #1212.

Improved documentation

Contributor-facing changes

  • When building wheels, the source distribution is now passed directly to the cibuildwheel invocation – by @webknjaz.

    Related issues and pull requests on GitHub: #1199.

  • Set up PYTHONHASHSEED for benchmarks execution to make measured times stable – by @asvetlov.

    Related issues and pull requests on GitHub: #1202.


6.5.1

(2025-06-24)

Bug fixes

  • Fixed a bug in C implementation when multidict is resized and it has deleted slots.

    The bug was introduced by multidict 6.5.0 release.

    Patch by @asvetlov.

    Related issues and pull requests on GitHub: #1195.

Contributor-facing changes

  • A pair of code formatters for Python and C have been configured in the pre-commit tool.

    Related issues and pull requests on GitHub: #1123.

  • Shorted fixture parametrization ids.

    For example, test_keys_view_xor[case-insensitive-pure-python-module] becomes test_keys_view_xor[ci-py] – by @asvetlov.

    Related issues and pull requests on GitHub: #1192.

  • The reusable-cibuildwheel.yml workflow has been refactored to be more generic and ci-cd.yml now holds all the configuration toggles – by @webknjaz.

    Related issues and pull requests on GitHub: #1193.


6.5.0

(2025-06-17)

Note

The release was yanked because of #1195, multidict 6.5.1 should be used instead.

Features

  • Replace internal implementation from an array of items to hash table. algorithmic complexity for lookups is switched from O(N) to O(1).

    The hash table is very similar to dict from CPython but it allows keys duplication.

    The benchmark shows 25-50% boost for single lookups, x2-x3 for bulk updates, and x20 for some multidict view operations. The gain is not for free: add and extend are 25-50% slower now. We consider it as acceptable because the lookup is much more common operation that addition for the library domain.

    Related issues and pull requests on GitHub: #1128.

Contributor-facing changes

  • Builds have been added for arm64 Windows wheels and the reusable-build-wheel.yml template has been modified to allow for an os value (windows-11-arm) which does not end with the -latest postfix.

    Related issues and pull requests on GitHub: #1167.


6.4.4

(2025-05-19)

Bug fixes

Miscellaneous internal changes

  • multidict.MultiDictProxy was refactored to rely only on multidict.MultiDict public interface and don’t touch any implementation details.

    Related issues and pull requests on GitHub: #1150.

  • Multidict views were refactored to rely only on multidict.MultiDict API and don’t touch any implementation details.

    Related issues and pull requests on GitHub: #1152.

  • Dropped internal _Impl class from pure Python implementation, both pure Python and C Extension follows the same design internally now.

    Related issues and pull requests on GitHub: #1153.


6.4.3

(2025-04-10)

Bug fixes

  • Fixed building the library in debug mode.

    Related issues and pull requests on GitHub: #1144.

  • Fixed custom PyType_GetModuleByDef() when non-heap type object was passed.

    Related issues and pull requests on GitHub: #1147.

Packaging updates and notes for downstreams


6.4.2

(2025-04-09)

Bug fixes

  • Fixed a segmentation fault when creating subclassed MultiDict objects on Python < 3.11 – by @bdraco.

    The problem first appeared in 6.4.0

    Related issues and pull requests on GitHub: #1141.


6.4.1

(2025-04-09)

No significant changes.


6.4.0

(2025-04-09)

Bug fixes

  • Fixed a memory leak creating new istr objects – by @bdraco.

    The leak was introduced in 6.3.0

    Related issues and pull requests on GitHub: #1133.

  • Fixed reference counting when calling multidict.MultiDict.update() – by @bdraco.

    The leak was introduced in 4.4.0

    Related issues and pull requests on GitHub: #1135.

Features

  • Switched C Extension to use heap types and the module state.

    Related issues and pull requests on GitHub: #1125.

  • Started building armv7l wheels – by @bdraco.

    Related issues and pull requests on GitHub: #1127.


6.3.2

(2025-04-03)

Bug fixes

  • Resolved a memory leak by ensuring proper reference count decrementation – by @asvetlov and @bdraco.

    Related issues and pull requests on GitHub: #1121.


6.3.1

(2025-04-01)

Bug fixes

  • Fixed keys not becoming case-insensitive when multidict.CIMultiDict is created by passing in a multidict.MultiDict – by @bdraco.

    Related issues and pull requests on GitHub: #1112.

  • Fixed the pure Python version mutating the original multidict.MultiDict when creating a new multidict.CIMultiDict from an existing one when keyword arguments are also passed – by @bdraco.

    Related issues and pull requests on GitHub: #1113.

  • Prevented crashing with a segfault when repr() is called for recursive multidicts and their proxies and views.

    Related issues and pull requests on GitHub: #1115.


6.3.0

(2025-03-31)

Bug fixes

  • Set operations for KeysView and ItemsView of case-insensitive multidicts and their proxies are processed in case-insensitive manner.

    Related issues and pull requests on GitHub: #965.

  • Rewrote multidict.CIMultiDict and it proxy to always return multidict.istr keys. istr is derived from str, thus the change is backward compatible.

    The performance boost is about 15% for some operations for C Extension, pure Python implementation have got a visible (15% - 230%) speedup as well.

    Related issues and pull requests on GitHub: #1097.

  • Fixed a crash when extending a multidict from multidict proxy if C Extensions were used.

    Related issues and pull requests on GitHub: #1100.

Features

  • Implemented a custom parser for METH_FASTCALL | METH_KEYWORDS protocol – by @asvetlov.

    The patch re-enables fast call protocol in the multidict C Extension.

    Speedup is about 25%-30% for the library benchmarks for Python 3.12+.

    Related issues and pull requests on GitHub: #1070.

  • The C-extension no longer pre-allocates a Python exception object in lookup-related methods of MultiDict when the passed-in key is not found but default value is provided.

    Namely, this affects MultiDict.getone(), MultiDict.getall(), MultiDict.get(), MultiDict.pop(), MultiDict.popone(), and MultiDict.popall().

    Additionally, the MultiDict comparison with regular dictionaries is now about 60% faster on Python 3.13+ in the fallback-to-default case.

    Related issues and pull requests on GitHub: #1078.

  • Implemented __repr__() for C Extension classes in C.

    The speedup is about 2.5 times.

    Related issues and pull requests on GitHub: #1081.

  • Made C version of multidict.istr pickleable.

    Related issues and pull requests on GitHub: #1098.

  • Optimized multidict creation and extending / updating if C Extensions are used.

    The speedup is between 25% and 70% depending on the usage scenario.

    Related issues and pull requests on GitHub: #1101.

  • multidict.MultiDict.popitem() is changed to remove the latest entry instead of the first.

    It gives O(1) amortized complexity.

    The standard dict.popitem() removes the last entry also.

    Related issues and pull requests on GitHub: #1105.

Contributor-facing changes

  • Started running benchmarks for the pure Python implementation in addition to the C implementation – by @bdraco.

    Related issues and pull requests on GitHub: #1092.

  • The the project-wide Codecov metric is no longer reported via GitHub Checks API. The combined value is not very useful because one of the sources (MyPy) cannot reach 100% with the current state of the ecosystem. We may want to reconsider in the future. Instead, we now have two separate “runtime coverage” metrics for library code and tests. They are to be kept at 100% at all times. And the “type coverage” metric will remain advisory, at a lower threshold.

    The default patch metric check is renamed to “runtime” to better reflect its semantics. This one will also require 100% coverage. Another “typing” patch coverage metric is now reported alongside it. It’s considered advisory, just like its project counterpart.

    When looking at Codecov, one will likely want to look at MyPy and pytest flags separately. It is usually best to avoid looking at the PR pages that sometimes display combined coverage incorrectly.

    The change additionally disables the deprecated GitHub Annotations integration in Codecov.

    Finally, the badge coloring range now starts at 100%.

    Coverage metrics

    – by @webknjaz

    Related issues and pull requests on GitHub: #1093.

Miscellaneous internal changes

  • Synchronized pythoncapi_compat.h with the latest available version.

    Related issues and pull requests on GitHub: #1063.

  • Moved registering ABCs for C Extension classes from C to Python.

    Related issues and pull requests on GitHub: #1083.

  • Refactored the internal pair_list implementation.

    Related issues and pull requests on GitHub: #1084.

  • Implemented views comparison and disjoints in C instead of Python helpers.

    The performance boost is about 40%.

    Related issues and pull requests on GitHub: #1096.


6.2.0

(2025-03-17)

Bug fixes

  • Fixed in checks throwing an exception instead of returning False when testing non-strings.

    Related issues and pull requests on GitHub: #1045.

  • Fixed a leak when the last accessed module in PyInit__multidict() init is not released.

    Related issues and pull requests on GitHub: #1061.

Features

  • Implemented support for the free-threaded build of CPython 3.13 – by @lysnikolaou.

    Related issues and pull requests on GitHub: #1015.

Packaging updates and notes for downstreams

  • Started publishing wheels made for the free-threaded build of CPython 3.13 – by @lysnikolaou.

    Related issues and pull requests on GitHub: #1015.

Miscellaneous internal changes

  • Used stricter typing across the code base, resulting in improved typing accuracy across multidict classes. Funded by an NLnet grant.

    Related issues and pull requests on GitHub: #1046.


6.1.0 (2024-09-09)

Bug fixes

  • Covered the unreachable code path in multidict._multidict_base._abc_itemsview_register() with typing – by @skinnyBat.

    Related issues and pull requests on GitHub: #928.

Features

  • Added support for Python 3.13 – by @bdraco.

    Related issues and pull requests on GitHub: #1002.

Removals and backward incompatible breaking changes

  • Removed Python 3.7 support – by @bdraco.

    Related issues and pull requests on GitHub: #997.

Contributor-facing changes

  • Added tests to have full code coverage of the multidict._multidict_base._viewbaseset_richcmp() function – by @skinnyBat.

    Related issues and pull requests on GitHub: #928.

  • The deprecated ::set-output workflow command has been replaced by the $GITHUB_OUTPUT environment variable in the GitHub Actions CI/CD workflow definition.

    Related issues and pull requests on GitHub: #940.

  • codecov-action has been temporarily downgraded to v3 in the GitHub Actions CI/CD workflow definitions in order to fix uploading coverage to Codecov. See this issue for more details.

    Related issues and pull requests on GitHub: #941.

  • In the GitHub Actions CI/CD workflow definition, the Get pip cache dir step has been fixed for Windows runners by adding shell: bash. See actions/runner#2224 for more details.

    Related issues and pull requests on GitHub: #942.

  • Interpolation of the pip cache keys has been fixed by adding missing $ syntax in the GitHub Actions CI/CD workflow definition.

    Related issues and pull requests on GitHub: #943.


6.0.5 (2024-02-01)

Bug fixes

  • Upgraded the C-API macros that have been deprecated in Python 3.9 and later removed in 3.13 – by @iemelyanov.

    Related issues and pull requests on GitHub: #862, #864, #868, #898.

  • Reverted to using the public argument parsing API PyArg_ParseTupleAndKeywords() under Python 3.12 – by @charles-dyfis-net and @webknjaz.

    The effect is that this change prevents build failures with clang 16.9.6 and gcc-14 reported in #926. It also fixes a segmentation fault crash caused by passing keyword arguments to MultiDict.getall() discovered by @jonaslb and @hroncok while examining the problem.

    Related issues and pull requests on GitHub: #862, #909, #926, #929.

  • Fixed a SystemError: null argument to internal routine error on a MultiDict.items().isdisjoint() call when using C Extensions.

    Related issues and pull requests on GitHub: #927.

Improved documentation

  • On the Contributing docs page, a link to the Towncrier philosophy has been fixed.

    Related issues and pull requests on GitHub: #911.

Packaging updates and notes for downstreams

  • Stopped marking all files as installable package data – by @webknjaz.

    This change helps setuptools understand that C-headers are not to be installed under lib/python3.x/site-packages/.

    Related commits on GitHub: 31e1170.

  • Started publishing pure-python wheels to be installed as a fallback – by @webknjaz.

    Related commits on GitHub: 7ba0e72.

  • Switched from setuptools’ legacy backend (setuptools.build_meta:__legacy__) to the modern one (setuptools.build_meta) by actually specifying the the [build-system] build-backend option in pyproject.toml – by @Jackenmen.

    Related issues and pull requests on GitHub: #802.

  • Declared Python 3.12 supported officially in the distribution package metadata – by @hugovk.

    Related issues and pull requests on GitHub: #877.

Contributor-facing changes

  • The test framework has been refactored. In the previous state, the circular imports reported in #837 caused the C-extension tests to be skipped.

    Now, there is a set of the pytest fixtures that is set up in a parametrized manner allowing to have a consistent way of accessing mirrored multidict implementations across all the tests.

    This change also implemented a pair of CLI flags (--c-extensions / --no-c-extensions) that allow to explicitly request deselecting the tests running against the C-extension.

    – by @webknjaz.

    Related issues and pull requests on GitHub: #98, #837, #915.

  • Updated the test pins lockfile used in the cibuildwheel test stage – by @hoodmane.

    Related issues and pull requests on GitHub: #827.

  • Added an explicit void for arguments in C-function signatures which addresses the following compiler warning:

    warning: a function declaration without a prototype is deprecated in all versions of C [-Wstrict-prototypes]
    

    – by @hoodmane

    Related issues and pull requests on GitHub: #828.

  • An experimental Python 3.13 job now runs in the CI – @webknjaz.

    Related issues and pull requests on GitHub: #920.

  • Added test coverage for the and, or, sub, and xor operators in the multidict/_multidict_base.py module. It also covers NotImplemented and “Iterable-but-not-Set” cases there.

    – by @a5r0n

    Related issues and pull requests on GitHub: #936.

  • The version of pytest is now capped below 8, when running MyPy against Python 3.7. This pytest release dropped support for said runtime.

    Related issues and pull requests on GitHub: #937.


6.0.4 (2022-12-24)

Bugfixes

  • Fixed a type annotations regression introduced in v6.0.2 under Python versions <3.10. It was caused by importing certain types only available in newer versions. (#798)

6.0.3 (2022-12-03)

Features

  • Declared the official support for Python 3.11 — by @mlegner. (#872)

6.0.2 (2022-01-24)

Bugfixes

  • Revert #644, restore type annotations to as-of 5.2.0 version. (#688)

6.0.1 (2022-01-23)

Bugfixes

  • Restored back MultiDict, CIMultiDict, MultiDictProxy, and CIMutiDictProxy generic type arguments; they are parameterized by value type, but the key type is fixed by container class.

    MultiDict[int] means MutableMultiMapping[str, int]. The key type of MultiDict is always str, while all str-like keys are accepted by API and converted to str internally.

    The same is true for CIMultiDict[int] which means MutableMultiMapping[istr, int]. str-like keys are accepted but converted to istr internally. (#682)

6.0.0 (2022-01-22)

Features

  • Use METH_FASTCALL where it makes sense.

    MultiDict.add() is 2.2 times faster now, CIMultiDict.add() is 1.5 times faster. The same boost is applied to get*(), setdefault(), and pop*() methods. (#681)

Bugfixes

  • Fixed type annotations for keys of multidict mapping classes. (#644)

  • Support Multidict[int] for pure-python version. __class_getitem__ is already provided by C Extension, making it work with the pure-extension too. (#678)

Deprecations and Removals

  • Dropped Python 3.6 support (#680)

Misc

5.2.0 (2021-10-03)

Features

    1. Added support Python 3.10

    2. Started shipping platform-specific wheels with the musl tag targeting typical Alpine Linux runtimes.

    3. Started shipping platform-specific arm64 wheels for Apple Silicon. (#629)

Bugfixes

  • Fixed pure-python implementation that used to raise “Dictionary changed during iteration” error when iterated view (.keys(), .values() or .items()) was created before the dictionary’s content change. (#620)

5.1.0 (2020-12-03)

Features

  • Supported GenericAliases (MultiDict[str]) for Python 3.9+ #553

Bugfixes

  • Synchronized the declared supported Python versions in setup.py with actually supported and tested ones. #552


5.0.1 (2020-11-14)

Bugfixes

  • Provided x86 Windows wheels #550


5.0.0 (2020-10-12)

Features

  • Provided wheels for aarch64, i686, ppc64le, s390x architectures on Linux as well as x86_64. #500

  • Provided wheels for Python 3.9. #534

Removal

  • Dropped Python 3.5 support; Python 3.6 is the minimal supported Python version.

Misc


4.7.6 (2020-05-15)

Bugfixes

  • Fixed an issue with some versions of the wheel dist failing because of being unable to detect the license file. #481

4.7.5 (2020-02-21)

Bugfixes

  • Fixed creating and updating of MultiDict from a sequence of pairs and keyword arguments. Previously passing a list argument modified it inplace, and other sequences caused an error. #457

  • Fixed comparing with mapping: an exception raised in the __len__() method caused raising a SyntaxError. #459

  • Fixed comparing with mapping: all exceptions raised in the __getitem__() method were silenced. #460

4.7.4 (2020-01-11)

Bugfixes

  • MultiDict.iter fix memory leak when used iterator over MultiDict instance. #452

Multidict 4.7.3 (2019-12-30)

Features

  • Implement __sizeof__ function to correctly calculate all internal structures size. #444

  • Expose getversion() function. #451

Bugfixes

  • Fix crashes in popone/popall when default is returned. #450

Improved Documentation

  • Corrected the documentation for MultiDict.extend() #446

4.7.2 (2019-12-20)

Bugfixes

4.7.1 (2019-12-12)

Bugfixes

Improved Documentation

  • Mention MULTIDICT_NO_EXTENSIONS environment variable in docs. #393

  • Document the fact that istr preserves the casing of argument untouched but uses internal lower-cased copy for keys comparison. #419

4.7.0 (2019-12-10)

Features

  • Replace Cython optimization with pure C #249

  • Implement __length_hint__() for iterators #310

  • Support the MultiDict[str] generic specialization in the runtime. #392

  • Embed pair_list_t structure into MultiDict Python object #395

  • Embed multidict pairs for small dictionaries to amortize the memory usage. #396

  • Support weak references to C Extension classes. #399

  • Add docstrings to provided classes. #400

  • Merge multidict._istr back with multidict._multidict. #409

Bugfixes

  • Explicitly call tp_free slot on deallocation. #407

  • Return class from __class_getitem__ to simplify subclassing #413

4.6.1 (2019-11-21)

Bugfixes

  • Fix PyPI link for GitHub Issues badge. #391

4.6.0 (2019-11-20)

Bugfixes

  • Fix GC object tracking. #314

  • Preserve the case of istr strings. #374

  • Generate binary wheels for Python 3.8.

4.5.2 (2018-11-28)

  • Fix another memory leak introduced by 4.5.0 release #307

4.5.1 (2018-11-22)

  • Fix a memory leak introduced by 4.5.0 release #306

4.5.0 (2018-11-19)

  • Multidict views ported from Cython to C extension #275

4.4.2 (2018-09-19)

  • Restore Python 3.4 support #289

4.4.1 (2018-09-17)

  • Fix type annotations #283)

  • Allow to install the library on systems without compilation toolset #281

4.4.0 (2018-07-04)

  • Rewrite C implementation to use C pair list.

  • Fix update order when both arg and kwargs are used.

4.3.1 (2018-05-06)

  • Fix a typo in multidict stub file.

4.3.0 (2018-05-06)

  • Polish type hints, make multidict type definitions generic.

4.2.0 (2018-04-15)

  • Publish py.typed flag for type hinting analyzers (mypy etc).

4.1.0 (2018-01-28)

4.0.0 (2018-01-14)

3.3.2 (2017-11-02)

  • Fix tarball (again)

3.3.1 (2017-11-01)

  • Include .c files in tarball #181

3.3.0 (2017-10-15)

  • Introduce abstract base classes #102

  • Publish OSX binary wheels #153

3.2.0 (2017-09-17)

3.1.3 (2017-07-14)

  • Fix build

3.1.2 (2017-07-14)

  • Fix type annotations

3.1.1 (2017-07-09)

  • Remove memory leak in istr implementation #105

3.1.0 (2017-06-25)

  • Raise RuntimeError on dict iterations if the dict was changed (#99)

  • Update __init__.pyi signatures

3.0.0 (2017-06-21)

  • Refactor internal data structures: main dict operations are about 100% faster now.

  • Preserve order on multidict updates

    Updates are md[key] = val and md.update(...) calls.

    Now the last entry is replaced with new key/value pair, all previous occurrences are removed.

    If key is not present in dictionary the pair is added to the end

    #68

  • Force keys to str instances #88

  • Implement .popall(key[, default]) #84

  • .pop() removes only first occurrence, .popone() added #92

  • Implement dict’s version #86

  • Proxies are not pickable anymore #77

2.1.7 (2017-05-29)

  • Fix import warning on Python 3.6 #79

2.1.6 (2017-05-27)

  • Rebuild the library for fixing missing __spec__ attribute #79

2.1.5 (2017-05-13)

  • Build Python 3.6 binary wheels

2.1.4 (2016-12-1)

  • Remove LICENSE filename extension @ MANIFEST.in file #31

2.1.3 (2016-11-26)

  • Add a fastpath for multidict extending by multidict

2.1.2 (2016-09-25)

2.1.1 (2016-09-22)

2.1.0 (2016-09-18)

  • Allow to create proxy from proxy

  • Add type hints (PEP 484)

2.0.1 (2016-08-02)

  • Don’t crash on {} - MultiDict().keys() and similar operations #6

2.0.0 (2016-07-28)

  • Switch from uppercase approach for case-insensitive string to str.title(). #5

  • Deprecate multidict.upstr() class in favor of istr alias.

1.2.2 (2016-08-02)

  • Don’t crash on {} - MultiDict().keys() and similar operations #6

1.2.1 (2016-07-21)

  • Don’t expose multidict.__version__

1.2.0 (2016-07-16)

  • Make upstr(upstr('abc')) much faster

1.1.0 (2016-07-06)

  • Don’t double-iterate during MultiDict initialization #3

  • Fixed CIMultiDict.pop(): it is case insensitive now. #1

  • Provide manylinux wheels as well as Windows ones

1.0.3 (2016-03-24)

  • Add missing MANIFEST.in

1.0.2 (2016-03-24)

  • Fix setup build

1.0.0 (2016-02-19)

  • Initial implementation