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 raisingMemoryErrorinextend(),update()andmerge(); 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
MultiDictProxyorCIMultiDictProxybuilt 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¶
Packaging updates and notes for downstreams¶
Contributor-facing changes¶
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] = valueanddel md[key]when the key, or anistr’s canonical form, already serves as one, asmd[key]did: on the GIL builddel md[key]needed 4% to 5% fewer instructions andmd[key] = valueabout 2% fewer – by @asvetlov.Related issues and pull requests on GitHub: #1769.
Restored the speed of
add(),getall(),md[key] = valueanddel md[key]for aCIMultiDictkey 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] = valuefor a new key andsetdefault()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 aMultiDictorCIMultiDictthat 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 wayget()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()andgetone()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-strongadd 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
charis 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-Wconversiontook effect. GCC 9 builds the extension without-Wconversion– by @asvetlov.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.Fixed
inon an items view truncating the length of a non-tuple, non-list operand to 32 bits in the C extension, which let an object reporting2**32 + 2items be indexed as a pair and raiseTypeErrorinstead of returningFalse– 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()andmerge()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.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 miscountinglen()in the pure-Python implementation, by installing the new contents and size before releasing the old pairs – by @asvetlov.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 aCIMultiDictmaking it case-sensitive in the C extension, andCIMultiDict.__init__()rejecting aMultiDictthere, 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: astrsubclass key’s__str__()onCIMultiDict, or on Python 3.10 and 3.11 a garbage collection’s finalizers, by removing the pair first, and fixed the pure-Pythonpopitem()miscountinglen()in the same case – by @asvetlov.Related issues and pull requests on GitHub: #1625.
Fixed
del md[key]andpopall()also removing, andpopall()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 raisingIndexErroror miscountinglen()in the same case, formd[key] = valuetoo – 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’slower()or a finalizer) read the multidict, where the pure-Python implementation returnedNonefor the pairs the call was about to remove, and fixedupdate()andmerge()overwriting or losing pairs when that code changed the multidict. An update from another multidict raisedRuntimeErrorwhen a key’slower()mutated the source – by @asvetlov.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 withRuntimeErroras 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 anextend(),update()ormerge()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
CIMultiDictreturning a key added as astrsubclass 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 itsistrwhile reading it, which could run the key’s finalizer inside the read. Documented thatCIMultiDictconverts keys toistr– by @asvetlov.Related issues and pull requests on GitHub: #1640.
Fixed the
istrkeys thatextend(),update(),merge()and theMultiDictconstructor took from aCIMultiDict: they carried the key’s own spelling as their case-insensitive identity instead of its lower-cased form, so aCIMultiDictkeyed by one of them found it under no spelling of the key. Changed the pure-Python implementation to hand over those keys asistrtoo, 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 sameTypeErrormessage asMultiDictProxyand the pure-Python implementation – by @asvetlov.Related issues and pull requests on GitHub: #1677.
Features¶
Made
MultiDictin the C extension store a mapping whose keys are all exactstrin half the space, keeping no separate identity or hash per entry, and did the same for aCIMultiDictwhose keys are all exactistr, 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 andclear()about 27%; any other key moves the mapping to the full layout, andclear()lets it start compact again. A lookup also stopped taking a reference to the identity of an exactstroristrkey, which maded[key]andkey in dup 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 withd[key] = valueandupdate()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 madeadd()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. Agetall()of a key with many values needed up to half fewer instructions – by @asvetlov.Sped up
update()andmerge(): they stopped growing the table up front when it could hold the argument even empty, asdict.update()does, stopped holding extra references to the keys and values of adictargument, 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] = valuein 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
istrfrom astrin the C extension by copying the string directly instead of going throughstr.__new__(); reading astrkey of aCIMultiDictfor the first time andpopitem()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] = valueandpop(): 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.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; replacedPy_CLEAR()on success paths withPy_DECREF()orPy_XDECREF(), which made building a multidict from items up to 1.7% and comparing one with another mapping up to 4% faster – by @asvetlov.
Deprecations (removal in next major release)¶
Started emitting
DeprecationWarningon access tomultidict.upstr, the alias foristrdeprecated since 2.0, and removed it frommultidict.__all__– by @asvetlov.Related issues and pull requests on GitHub: #1680.
Removals and backward incompatible breaking changes¶
Changed
keys()and iteration overMultiDict,CIMultiDictand their proxies to yield each key once, as spelled in its first item, so thatCIMultiDict([('X-Foo', '1'), ('x-foo', '2')])iterates over'X-Foo'alone andlen(d.keys())counts distinct keys;items(),values()andlen(d)kept every item – by @asvetlov.Related issues and pull requests on GitHub: #1654.
Stopped calling a
strsubclass’s ownlower()for the case-insensitive identity of a key inCIMultiDictand inIStr_FromUnicode(); the identity came fromstr.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.cfgtopyproject.tomlper PEP 621, and removed the now-emptysetup.cfgalong with its obsolete[bdist_wheel] universalflag. Adopted PEP 639 license metadata: declared the license as the SPDX expressionApache-2.0and movedlicense-filesinto the[project]table, which raised the build-time requirement tosetuptools >= 77.0. Built distributions started carryingLicense-Expressionin place of the legacyLicensefield – 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 markedthreadedon 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.pyand anInliningCI 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 frompythoncapi_compat.hsuch asPy_DECREF(), since each time that happened a benchmark regressed on code the change never touched – by @asvetlov.Stopped passing
-Wno-conversionto the C extension build, which had silently turned-Wconversionoff, 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
cibuildwheelalready 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, thewindows-11-armwheel build got limited to release tags again, and x86_64 macOS wheels started being tested under Rosetta outside pull requests only – by @asvetlov.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_2pytest mark – by @asvetlov.Changed
benchmarks/callgrind_driver.pyto 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.txtfor thefaulthandlerwatchdog’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-freethreadingCI job tohypothesis-ft(Hypothesis FT), on a par with the GIL one, and removed theupdateargument of the debug-onlyASSERT_CONSISTENT()check, which stopped doing anything onceupdate()kept its doomed entries whole – by @asvetlov.Moved the sanitizer build recipes and the performance measurement procedure out of
AGENTS.mdinto 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_INLINEmacro andinlinefrom 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 keptNOINLINEonly 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 everyget(),getone(),getall(),add()andsetdefault()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()andmerge(), ofpop()andpopone(), the per-class copies of the entry points, the update loops and thetp_vectorcallslots, which read the class at run time instead, the repeated tails of the table rebuilds and ofgetall()andpopall(), 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 andupdate()andmerge()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 themd_pos_tcursor 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 ofPyUnstable_TryIncRef()into the free-threaded C extension, where it read CPython’s private reference count fields, by calling the function itself instead – by @asvetlov.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 theistrobjectstruct, and folded the pure-Pythonupdate()match test back into one condition – by @asvetlov.Related issues and pull requests on GitHub: #1635, #1668, #1689, #1753, #1754.
Added
constqualifiers 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:
CIMultiDictkeyed by plainstr: construction,add(),extend(),update()and item assignment got 2 to 3.8 times faster, and lookups about 2 times faster.CIMultiDictkeyed byistr, and case-sensitiveMultiDict: lookups, insertion and deletion got 10% to 70% faster.getall()and iteratingitems()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
MultiDictand 4 to 5 times cheaper onCIMultiDictwith lowercasestrkeys, and item assignment 15% to 19% cheaper. Construction and deletion onMultiDictcost 2% to 7% more. With several threads mutating at once, ad[key] = valuegot 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]orpopall()was still scanning the table, and a spuriousRuntimeErrorfromgetall()when a garbage collection triggered while building its result mutated the multidict – by @asvetlov.Fixed a reference leak of the key and value in the C extension when growing the hash table fails with
MemoryErrorduringadd(),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 bygetversion()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
MemoryErrororKeyboardInterruptraised 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 theTypeErrorfrom an unusable__length_hint__stayed ignored – by @asvetlov.Related issues and pull requests on GitHub: #1517.
Ensured that canonical keys in pure-Python
MultiDictandCIMultiDictare converted to exactstrinstances without invokingobject.__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, andmd.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.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()andmerge()of the C extension raisingSystemErrorwhen given an object whose__length_hint__()reported close tosys.maxsizetogether with keyword arguments, and overflowing the table size computation for such a hint; a hint too large to reserve for is ignored, aslist.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 exactstrvalues also became faster – by @asvetlov.Related issues and pull requests on GitHub: #1579.
Fixed a use-after-free in the C extension when a
strsubclass key’slower()mutated the source while aCIMultiDictwas built from, extended, updated or merged with adictor a multidict of the other case sensitivity. The extra references this takes are limited to the keys whoselower()can run Python code, so other updates do not pay for them – by @asvetlov.
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 aCIMultiDict) 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()andto_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, whileupdate()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.hheader. 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, sogetall()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
CIMultiDictoperations keyed by a plainstrin the C extension. An all-ASCII key was lowered directly instead of through astr.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 asextend()and construction from adict, got up to three times faster – by @asvetlov.Related issues and pull requests on GitHub: #1527.
Sped up building an
istrin the C extension. The class gained its own vectorcall, so the one-argument form stopped packing its argument into a tuple fortype_call()only to unpack it again withPyArg_ParseTupleAndKeywords(). Building anistrfrom astrgot about 19% faster, and building one from anistr, which hands back the original, about three times faster. In exchangegetall()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,MultiDictProxyorCIMultiDictProxyin 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
MultiDictlast 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 mdgot about 10% cheaper,md[key]about 8% and a missingmd.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
MultiDictorCIMultiDictchanges. It follows CPython’sPyDict_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 toMULTIDICT_MAX_WATCHERS(32) watchers can be registered, and a failing callback is reported throughsys.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
CIMultiDictandCIMultiDictProxyin the C extension by binding their method descriptors to the class itself instead of inheriting them fromMultiDictandMultiDictProxy; 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, asfor 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] = valuewas about four times faster – by @asvetlov.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.
Removals and backward incompatible breaking changes¶
Made
istra 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
MultiDictandCIMultiDictcompare withdicton 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
istrkeys are worth on aCIMultiDict, as instruction counts next to the equivalent plainstrkeys. 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
multidictis optimized for both the GIL and free-threaded builds, and that its performance is typically within 20-30% ofdictfor 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()andpop()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.hintocompiler.hand dropped theHT_prefix from their names – by @asvetlov.Related issues and pull requests on GitHub: #1510.
Moved the debug-only
ASSERT_CONSISTENTmacro and the_md_check_consistency()and_md_dump()helpers used by the C extension out ofhashtable.hinto their owndebug.hheader – by @asvetlov.Related issues and pull requests on GitHub: #1513.
Added CodSpeed benchmarks for
CIMultiDictkeyed by plainstrinstead ofistr, 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-extensionsalongsideMULTIDICT_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
pytestrun excludes via the-m "not hypothesis"marker expression inpytest.iniand whose pinmake install-devdoes 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.pyto importpyperfunder its current name; it had been failing to start since the package was renamed fromperf– 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()andclear()stopped counting the cost of rebuilding the mapping – by @asvetlov.Related issues and pull requests on GitHub: #1532.
Moved the bulk
extend(),update()andmerge()paths of the C extension out ofhashtable.hinto their ownbulk_update.hheader, leavinghashtable.hto 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 theextend()/update()/merge()sequence parser out ofhashtable.hand into a newmultidict/_multilib/unpack.h, together with a sharedunpack_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=1build option that stops the C extension reusing freed blocks. A reused block never reachesfree(), so AddressSanitizer can neither poison it nor report a use-after-free on it; an instrumented run wants a second pass with this set, asAGENTS.mddescribes.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 bothmultidict._testcapiandmultidict._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.txtcase-insensitively, and documented inAGENTS.mdthat 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
_multilibheaders 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.mdthat 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 returnvoid; 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.hheader, 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 withPy_CLEAR()on the GIL build rather than routing them through_multilib/freethreading.h:Py_CLEAR()skips the store when a field is alreadyNULL, 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_DISABLEDarms 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.hheader, 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 madegetall()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__()andCIMultiDict.__init__()switched to reading the state pointer thattp_new()stored on the object instead of resolving it from the type a second time, and anistrstopped 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 everyistris 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()andgetall()): 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
keystarted 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
Nonedefault ofMultiDict.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
strsubclass 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
Nonedefault ofMultiDict.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()andmultidict_tp_clear()wrappers;md_traverse()andmd_clear()now serve as the type’s GC slots directly – by @asvetlov.Related issues and pull requests on GitHub: #1589.
Removed a redundant
NULLcheck and a strayPyErr_Format()argument from the C constructors, and an always true condition from the pure Pythongetall()– by @asvetlov.Related issues and pull requests on GitHub: #1590.
Merged the duplicated
__init__()bodies of the CMultiDictandCIMultiDictinto 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_newslots that blocked direct instantiation of the C view and iterator types withPy_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 theMultiDictandCIMultiDictconstructors, a[key, value]item inside any iterable handed to them, or a list tested withinagainstitems(), is changed by another thread; a call that catches the list shrinking under it now raisesRuntimeError– 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 theitems()/keys()/values()equality path could raiseKeyErroror report a present, never-deleted key as missing. A concurrentupdate()/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()andmd.items() - operandleaked one key and one value reference per element ofoperand, 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 callingtime.sleep()) whileupdate(),merge(),__setitem__(),__delitem__(),pop(),popone(), orpopall()was dropping a replaced or removed value.Py_BEGIN_CRITICAL_SECTIONcompiles to a no-op on this build, so nothing else was stopping a second thread from mutating the very sameMultiDictconcurrently 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
CIMultiDictwhose keys are plainstr. Converting such a key toistrcould run Python code (astrsubclass’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(),[]orincould 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
includeand a duplicatedexcludeline fromMANIFEST.in; sdist contents are unchanged – by @asvetlov.Related issues and pull requests on GitHub: #1478.
Added
.claudeignorefile – by @asvetlovRelated 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 theadd()andextend()benchmarks already do. The insertion,update()andclear()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
-Ifrom the AddressSanitizer test command inAGENTS.mdand in the CI job. It implies-E, which made Python ignorePYTHONMALLOC=malloc, so small hash tables were still served frompymallocarenas 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 inmd_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()ofMultiDict,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()ofMultiDictProxyin the C extension and ofKeysViewin both the C extension and the pure-Python implementation against infinite recursion on self-referential containers, matching the existing guard onMultiDict,ItemsView, andValuesView– by @asvetlov.Related issues and pull requests on GitHub: #1431.
Protected
MultiDict.update(),.extend(),.merge(),.clear(),.copy(), and theMultiDict/CIMultiDictconstructors in the C extension with a critical section, using the two-object form when a second multidict, multidict proxy, or plaindictinstance 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.usedwas written non-atomically whilelen()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()andmd_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()(plaindel/pop()) could likewise leaveselfin 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->keyswas published with a plain store while lock-free readers loaded it atomically, and a false-negative race inget()/__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->retiredfor 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 derivegetversion(), 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()andupdate()/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/CIMultiDictfallback 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, anAttributeError, or a lost mutation. EachMultiDict/CIMultiDictinstance 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()readself’s module state without holdingself’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 sameMultiDict, for example by callinggetall()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-freeget()/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
istrkeys in the case-sensitiveMultiDict, 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()(andmerge(), which shares the same code path) silently failing to invalidate an in-progresskeys()/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/CIMultiDictiterator guard so that mutating the mapping before the firstnext()call on anitems(),keys(), orvalues()iterator (or oniter(md)) now reliably raisesRuntimeError, matching the C extension. Previously a mutation that happened before the iterator was ever advanced, such asclear()or adel/popone()that removed the only remaining entry, could make the iterator silently raiseStopIterationinstead – 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 plaindictthat maps every key to the list of all its values. Unlikedict(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,MultiDictProxyandCIMultiDictProxyin the C extension implemented the vectorcall calling convention for construction. This sped upMultiDict(...)-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/CIMultiDictimplementation 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
MultiDictorCIMultiDict, each added value no longer gets slower than the one before it – by @bdraco.Related issues and pull requests on GitHub: #1466.
Improved documentation¶
Contributor-facing changes¶
Added race tests that iterate and extend a
MultiDictfrom 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/UndefinedBehaviorSanitizerandThreadSanitizerCI jobs. The former runs the suite under a regular CPython withMULTIDICT_ASAN_BUILD=1; the latter builds a free-threaded CPython instrumented with--with-thread-sanitizerand runs the suite against it viaMULTIDICT_TSAN_BUILD=1– by @asvetlov.Related issues and pull requests on GitHub: #1455.
Added Hypothesis-based property and stateful fuzz tests covering
MultiDict/CIMultiDictsemantics, 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 @asvetlovRelated 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
hypothesispin out ofrequirements/pytest.txtinto its ownrequirements/pytest-hypothesis.txt, installed only by the dedicated Hypothesis CI jobs.hypothesisnow 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¶
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
MultiDictconstructor andextend(),update(), andmerge()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__ofMultiDict,CIMultiDict, their proxies, and the keys/items views producing invalid output when keys contained quote characters – keys are now formatted withrepr()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 withTypeErrorinstead 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 thatnext()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
MultiDictorCIMultiDictcreated via__new__without calling__init__(for example a subclass that does not callsuper().__init__()). The internal state was left as NULL pointers that the first method call dereferenced.tp_newnow 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 andupdate(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 itsPyUnicodeWriterwhen 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 expectedTypeError/AttributeErrorand lets everything else – notablyMemoryErrorandKeyboardInterrupt– 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
MultiDictandCIMultiDictinstances 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 onMultiDictProxyandCIMultiDictProxyinstances – by @asvetlov.Related issues and pull requests on GitHub: #1419.
Fixed missing decref for
Noneon error insetdefault()– by @asvetlovRelated issues and pull requests on GitHub: #1421.
Features¶
Added
reversed()support to the keys, values, and items views ofMultiDict,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 @asvetlovRelated 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
CIMultiDictProxydocumentation to state it inherits fromMultiDictProxy(notMultiDict), and fixed missing word inistrsection – by @veeceey.Related issues and pull requests on GitHub: #1298.
Clarified that
istrpreserves the original casing and compares as a regularstr; case-insensitive matching is handled byCIMultiDict– 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
setuptoolsbuild dependency lower bound has been restored to be>= 47after 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-formatand enabled clang-format inpre-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.mdorientation file at the repository root, covering the pull request template, theCHANGES/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, andunparseableto 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 defensiveraiseguards and one-sided cleanup branches in tests will surface as uncovered on the codecov patch report; and branch creation is restricted onaio-libs/multidict, so PRs must be pushed from a fork – by @bdraco.Trimmed the CI test matrix to drop redundant
Py_DEBUGjobs on macOS and Windows and to gate thewindows-11-armwheel 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.mdat the repository root that importsAGENTS.mdvia 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]soruff checkcovers import order on top of the existingUP(pyupgrade) group, and updated theruff-checkpre-commit hook to run with--fix --exit-non-zero-on-fix --show-fixesso import-order fixes apply on commit. Sorted imports in fourtests/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: PLC0415plus 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 everywhereruff checkruns – by @bdraco.Related issues and pull requests on GitHub: #1349.
Switched the
cibuildwheelbuild frontend tobuild[uv]so thatuvprovisions every build and test virtual environment in the wheel matrix. Test-dependency installation in particular drops from a multi-secondpip installper ABI to a roughly sub-seconduvresolve – by @bdraco.Related issues and pull requests on GitHub: #1350.
Overrode
CIBW_BUILD_FRONTEND=buildfor the odd-arch wheel matrix; the upstreammanylinux/musllinuximages for those arches do not shipuv– by @bdraco.Related issues and pull requests on GitHub: #1352.
Allowed re-running the deploy job after a partial release failure: the
Make Releasestep now skips when the GitHub Release already exists, and the PyPI publish step usesskip-existingso 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
istrimplementation – by @asvetlovRelated issues and pull requests on GitHub: #1360.
Pinned
coverageto thectracemeasurement core so the test suite runs on Python 3.14. Coverage pickssysmonthere, which cannot record the dynamic contextspytest-covswitches at runtime, and the resulting warning became an error under the suite’sfilterwarningssetting – by @rodrigobnogueira.Related issues and pull requests on GitHub: #1393.
Dependabot has been restricted to the
requirementssubdirectory 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_htkeysasconst.Moved the structure to
.rodatalinker section.Unexpected modification of the structure will lead to segfault instead of corrupting the data and fail fast.
.rodatais 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.
Removed unnecessary calculations when
md_init()creates an empty multidict.Now
tp_allocslot is used in object cloning instead of barePyType_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 @asvetlovRelated issues and pull requests on GitHub: #1423.
Fixed a missing space in the
ValueErrormessage 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.pytobenchmarks/benchmark.pyso 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¶
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¶
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.
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 aMultiDict. The issue was an edge case in the pointer arithmetic during the compaction phase – by @bdraco.Fixed format string compilation errors in debug builds on 32-bit platforms by using portable
%zdformat specifiers forPy_ssize_tvalues instead of%ld– by @bdraco.
Packaging updates and notes for downstreams¶
6.6.1¶
(2025-06-28)
Bug fixes¶
If
multidict.MultiDict.extend(),multidict.MultiDict.merge(), ormultidict.MultiDict.update()raises an exception, now the multidict internal state is correctly restored. Patch by @asvetlov.Related issues and pull requests on GitHub: #1215.
Contributor-facing changes¶
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_tstructure when inserting new items if the multidict has deleted items and it could be collapsed in-place. Removal ofmalloc()/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.getallnow is slightly faster if it returns nothing – by @asvetlov.Related issues and pull requests on GitHub: #1212.
Improved documentation¶
Replaced docstring for
multidict.MultiDict.update()to don’t use RST/markdown markup.Related issues and pull requests on GitHub: #1203.
Improved documentation for
multidict.MultiDict.extend()andmultidict.MultiDict.update()– by @asvetlov.Related issues and pull requests on GitHub: #1205.
Contributor-facing changes¶
When building wheels, the source distribution is now passed directly to the
cibuildwheelinvocation – by @webknjaz.Related issues and pull requests on GitHub: #1199.
Set up
PYTHONHASHSEEDfor 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¶
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]becomestest_keys_view_xor[ci-py]– by @asvetlov.Related issues and pull requests on GitHub: #1192.
The
reusable-cibuildwheel.ymlworkflow has been refactored to be more generic andci-cd.ymlnow 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
dictfrom 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:
addandextendare 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.ymltemplate has been modified to allow for an os value (windows-11-arm) which does not end with the-latestpostfix.Related issues and pull requests on GitHub: #1167.
6.4.4¶
(2025-05-19)
Bug fixes¶
Fixed a segmentation fault when calling
multidict.MultiDict.setdefault()with a single argument – by @bdraco.Related issues and pull requests on GitHub: #1160.
Fixed a segmentation fault when attempting to directly instantiate view objects (
multidict._ItemsView,multidict._KeysView,multidict._ValuesView) – by @bdraco.View objects now raise a proper
TypeErrorwith the message “cannot create ‘…’ instances directly” when direct instantiation is attempted.View objects should only be created through the proper methods:
multidict.MultiDict.items(),multidict.MultiDict.keys(), andmultidict.MultiDict.values().Related issues and pull requests on GitHub: #1164.
Miscellaneous internal changes¶
multidict.MultiDictProxywas refactored to rely only onmultidict.MultiDictpublic 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.MultiDictAPI and don’t touch any implementation details.Related issues and pull requests on GitHub: #1152.
Dropped internal
_Implclass 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¶
Packaging updates and notes for downstreams¶
Added the ability to build in debug mode by setting
MULTIDICT_DEBUG_BUILDin the environment – by @bdraco.Related issues and pull requests on GitHub: #1145.
6.4.2¶
(2025-04-09)
Bug fixes¶
6.4.1¶
(2025-04-09)
No significant changes.
6.4.0¶
(2025-04-09)
Bug fixes¶
Fixed a memory leak creating new
istrobjects – 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¶
6.3.2¶
(2025-04-03)
Bug fixes¶
6.3.1¶
(2025-04-01)
Bug fixes¶
Fixed keys not becoming case-insensitive when
multidict.CIMultiDictis created by passing in amultidict.MultiDict– by @bdraco.Related issues and pull requests on GitHub: #1112.
Fixed the pure Python version mutating the original
multidict.MultiDictwhen creating a newmultidict.CIMultiDictfrom 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
KeysViewandItemsViewof case-insensitive multidicts and their proxies are processed in case-insensitive manner.Related issues and pull requests on GitHub: #965.
Rewrote
multidict.CIMultiDictand it proxy to always returnmultidict.istrkeys.istris derived fromstr, 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_KEYWORDSprotocol – by @asvetlov.The patch re-enables fast call protocol in the
multidictC 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
MultiDictwhen 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(), andMultiDict.popall().Additionally, the
MultiDictcomparison with regulardictionaries 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.istrpickleable.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%.
– by @webknjaz
Related issues and pull requests on GitHub: #1093.
Miscellaneous internal changes¶
Synchronized
pythoncapi_compat.hwith 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_listimplementation.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¶
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
NLnetgrant.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¶
Removals and backward incompatible breaking changes¶
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-outputworkflow command has been replaced by the$GITHUB_OUTPUTenvironment variable in the GitHub Actions CI/CD workflow definition.Related issues and pull requests on GitHub: #940.
codecov-action has been temporarily downgraded to
v3in 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 dirstep has been fixed for Windows runners by addingshell: bash. See actions/runner#2224 for more details.Related issues and pull requests on GitHub: #942.
Interpolation of the
pipcache 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 routineerror on aMultiDict.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 philosophyhas 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
setuptoolsunderstand that C-headers are not to be installed underlib/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-backendoption inpyproject.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
pytestfixtures that is set up in a parametrized manner allowing to have a consistent way of accessing mirroredmultidictimplementations 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
cibuildwheeltest stage – by @hoodmane.Related issues and pull requests on GitHub: #827.
Added an explicit
voidfor 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, andxoroperators in themultidict/_multidict_base.pymodule. It also coversNotImplementedand “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¶
6.0.2 (2022-01-24)¶
Bugfixes¶
6.0.1 (2022-01-23)¶
Bugfixes¶
Restored back
MultiDict,CIMultiDict,MultiDictProxy, andCIMutiDictProxygeneric type arguments; they are parameterized by value type, but the key type is fixed by container class.MultiDict[int]meansMutableMultiMapping[str, int]. The key type ofMultiDictis alwaysstr, while all str-like keys are accepted by API and converted tostrinternally.The same is true for
CIMultiDict[int]which meansMutableMultiMapping[istr, int]. str-like keys are accepted but converted toistrinternally. (#682)
6.0.0 (2022-01-22)¶
Features¶
Use
METH_FASTCALLwhere it makes sense.MultiDict.add()is 2.2 times faster now,CIMultiDict.add()is 1.5 times faster. The same boost is applied toget*(),setdefault(), andpop*()methods. (#681)
Bugfixes¶
Deprecations and Removals¶
Dropped Python 3.6 support (#680)
Misc¶
5.2.0 (2021-10-03)¶
Features¶
Added support Python 3.10
Started shipping platform-specific wheels with the
musltag targeting typical Alpine Linux runtimes.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.pywith 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¶
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
wheeldist failing because of being unable to detect the license file. #481
4.7.5 (2020-02-21)¶
Bugfixes¶
Fixed creating and updating of
MultiDictfrom a sequence of pairs and keyword arguments. Previously passing a list argument modified it inplace, and other sequences caused an error. #457Fixed comparing with mapping: an exception raised in the
__len__()method caused raising a SyntaxError. #459Fixed comparing with mapping: all exceptions raised in the
__getitem__()method were silenced. #460
4.7.4 (2020-01-11)¶
Bugfixes¶
Multidict 4.7.3 (2019-12-30)¶
Features¶
Bugfixes¶
Fix crashes in
popone/popallwhen default is returned. #450
Improved Documentation¶
Corrected the documentation for
MultiDict.extend()#446
4.7.2 (2019-12-20)¶
Bugfixes¶
Fixed crashing when multidict is used pyinstaller #432
Fixed typing for
CIMultiDict.copy()#434Fixed a memory leak in
MultiDict.copy()#443
4.7.1 (2019-12-12)¶
Bugfixes¶
CIMultiDictProxy.copy()fixed to return a case-insensitivemultidict._multidict.CIMultiDict()object. #427Made
CIMultiDictsubclassable again. #416Fixed a regression,
multidict.MultiDictcan be constructed from an arbitrary iterable of pairs again. #418CIMultiDict.add()may be called with keyword arguments now. #421
Improved Documentation¶
4.7.0 (2019-12-10)¶
Features¶
Replace Cython optimization with pure C #249
Implement
__length_hint__()for iterators #310Support 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._istrback withmultidict._multidict. #409
Bugfixes¶
4.6.1 (2019-11-21)¶
Bugfixes¶
Fix PyPI link for GitHub Issues badge. #391
4.6.0 (2019-11-20)¶
Bugfixes¶
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)¶
4.4.0 (2018-07-04)¶
Rewrite C implementation to use C pair list.
Fix update order when both
argandkwargsare 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.typedflag for type hinting analyzers (mypyetc).
4.1.0 (2018-01-28)¶
Fix key casing in Pure Python implementation of
CIMultiDict#202
4.0.0 (2018-01-14)¶
Started accepting multiple keys in
MultiDict.update()andCIMultiDict.update(). #199
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)¶
3.2.0 (2017-09-17)¶
Fix pickling #134
Fix equality check when other contains more keys #124
Fix
CIMultiDictcopy #107
3.1.3 (2017-07-14)¶
Fix build
3.1.2 (2017-07-14)¶
Fix type annotations
3.1.1 (2017-07-09)¶
3.1.0 (2017-06-25)¶
Raise
RuntimeErrorondictiterations if the dict was changed (#99)Update
__init__.pyisignatures
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] = valandmd.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
Implement
.popall(key[, default])#84Implement 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
LICENSEfilename extension @MANIFEST.infile #31
2.1.3 (2016-11-26)¶
Add a fastpath for multidict extending by multidict
2.1.2 (2016-09-25)¶
Fixed
CIMultiDict.update()for case of acceptingistr.
2.1.1 (2016-09-22)¶
Fixed the
CIMultiDictconstructor for the case of acceptingistr. #11
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(). #5Deprecate
multidict.upstr()class in favor ofistralias.
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)¶
Fixed
CIMultiDict.pop(): it is case insensitive now. #1Provide 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