Repository navigation
chore(deps): update dependency multidict to v7 - #18617
Open
renovate-bot wants to merge 1 commit into
Open
renovate-bot wants to merge 1 commit into
renovate-bot wants to merge 1 commit into
Conversation
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
==6.9.1→==7.1.0Release Notes
aio-libs/multidict (multidict)
v7.1.0Compare Source
=====
(2026-10-08)
7.1.0 is a correctness and housekeeping release. It changes what
:meth:
~multidict.MultiDict.keysyields, fixes a long list of crashes andinconsistencies that Python code run in the middle of an operation could
trigger, stores the common all-:class:
strmultidicts in half the space, anddeprecates
multidict.upstr.Behaviour changes. :meth:
~multidict.MultiDict.keysand iteration over amultidict yield each key once, as spelled in its first item, so
len(d.keys())counts distinct keys; :meth:~multidict.MultiDict.items,:meth:
~multidict.MultiDict.valuesandlen(d)still see every item. A:class:
strsubclass's ownlower()is no longer called to compute thecase-insensitive identity of a :class:
~multidict.CIMultiDictkey.Robustness. Finalizers,
__eq__(),__repr__(),lower()andargument iterators that read or mutate the multidict in the middle of
__init__(),update(),merge(),del d[key],:meth:
~multidict.MultiDict.popall, :meth:~multidict.MultiDict.popitem,:meth:
~multidict.MultiDict.to_dictor :func:reprno longer crash the Cextension, 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 :class:
~multidict.MultiDictwhose keys are all exact:class:
str, and a :class:~multidict.CIMultiDictwhose keys are all exact:class:
~multidict.istr, are stored in a compact table without a separateidentity 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 extensionwithout
-Wconversion-- by :user:asvetlov.Related issues and pull requests on GitHub:
:issue:
1759, :issue:1760.Fixed :func:
~multidict.getversionreturning only the low 32 bits ofthe 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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1607, :issue:1610.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
reporting
2**32 + 2items be indexed as a pair and raise:exc:
TypeErrorinstead of returningFalse-- by :user:asvetlov.Related issues and pull requests on GitHub:
:issue:
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 :meth:
~multidict.MultiDict.extend,:meth:
~multidict.MultiDict.updateand :meth:~multidict.MultiDict.mergereserving room by comparing table sizes, which ignored the room taken by
deleted entries; the reservation checked the free room instead, in both
implementations -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1615, :issue:1617.Fixed calling
__init__()again on a live multidict losing and leakingwhatever a finalizer of an old value added to it in the C extension, and
miscounting :func:
lenin the pure-Python implementation, by installingthe new contents and size before releasing the old pairs
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1619, :issue:1626.Fixed two data races on free-threaded builds against lock-free readers:
copy()and__init__()from a multidict of the same kind read thesource's reader count while its readers updated it, and
__init__()on a live multidict rewrote the case-insensitivity flag its lookups
read -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1621.Fixed
MultiDict.__init__()called on a :class:~multidict.CIMultiDictmaking itcase-sensitive in the C extension, and
CIMultiDict.__init__()rejecting a :class:
~multidict.MultiDictthere, by taking the mode from theinstance rather than from the class whose
__init__()was called, asthe pure-Python implementation already did -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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-Pythonimplementation was not affected -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1666.Fixed a use-after-free in :meth:
~multidict.MultiDict.popitemwhenbuilding the result ran Python code that mutated the multidict: a
:class:
strsubclass key's__str__()on:class:
~multidict.CIMultiDict, or on Python 3.10 and 3.11 a garbagecollection's finalizers, by removing the pair first, and fixed the
pure-Python
popitem()miscounting :func:lenin the same case-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1625.Fixed
del md[key]and :meth:~multidict.MultiDict.popallalsoremoving, and
popall()also returning, the pairs a finalizer of aremoved key or value added while the call was still running, by
releasing the removed pairs only once every match is gone, and fixed the
pure-Python implementation raising :exc:
IndexErroror miscounting:func:
lenin the same case, formd[key] = valuetoo-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1627.Fixed a crash when Python code run between the items of
:meth:
~multidict.MultiDict.update(the argument's own iterator, a key'slower()or a finalizer) read the multidict, where the pure-Pythonimplementation returned
Nonefor the pairs the call was about toremove, and fixed
update()and :meth:~multidict.MultiDict.mergeoverwriting or losing pairs when that code changed the multidict. An
update from another multidict raised :exc:
RuntimeErrorwhen a key'slower()mutated the source -- by :user:asvetlov.Related issues and pull requests on GitHub:
:issue:
1628, :issue:1744.Fixed a use-after-free in :meth:
~multidict.MultiDict.to_dicton Python3.10 and 3.11, where allocating a value list can run a garbage collection
whose finalizers mutate the multidict, by refusing that mutation with
:exc:
RuntimeErroras it is refused from a key's__hash__()-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1630.Fixed :func:
reprof a multidict reading freed memory on the GIL buildwhen a key's or value's
__repr__called anextend(),update()or
merge()that grew the table and then failed, by bumping the versionon every resize -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1658.Fixed :class:
~multidict.CIMultiDictreturning a key added as a:class:
strsubclass with the spelling of the subclass's own:meth:
~object.__str__instead of the key's, and stopped the C extension fromreplacing such a key with its :class:
~multidict.istrwhile reading it, whichcould run the key's finalizer inside the read. Documented that
:class:
~multidict.CIMultiDictconverts keys to :class:~multidict.istr-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1640.Fixed the :class:
~multidict.istrkeys that:meth:
~multidict.MultiDict.extend, :meth:~multidict.MultiDict.update,:meth:
~multidict.MultiDict.mergeand the :class:~multidict.MultiDictconstructor took from a :class:
~multidict.CIMultiDict: they carried thekey's own spelling as their case-insensitive identity instead of its
lower-cased form, so a :class:
~multidict.CIMultiDictkeyed by one of themfound it under no spelling of the key. Changed the pure-Python
implementation to hand over those keys as :class:
~multidict.istrtoo, asthe C extension did -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1653.Changed
CIMultiDictProxy.__init__()called without an argument toreport the same :exc:
TypeErrormessage asMultiDictProxyand thepure-Python implementation -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1677.Features
Made :class:
~multidict.MultiDictin the C extension store a mapping whosekeys are all exact :class:
strin half the space, keeping no separateidentity or hash per entry, and did the same for a
:class:
~multidict.CIMultiDictwhose keys are all exact:class:
~multidict.istr, taking each identity from the key's canonicalform, which cut its size by about 45%. Copying such a mapping became
about 55% cheaper in instructions and :meth:
~multidict.MultiDict.clearabout 27%; any other key moves the mapping to the full layout, and
:meth:
~multidict.MultiDict.clearlets it start compact again. A lookupalso stopped taking a reference to the identity of an exact :class:
stror :class:
~multidict.istrkey, which maded[key]andkey in dupto 7% faster on the GIL build and up to 20% cheaper in instructions on the
free-threaded one -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1644, :issue:1648, :issue:1651.Sped up :meth:
~multidict.MultiDict.getall,del d[key], replacing akey with
d[key] = valueand :meth:~multidict.MultiDict.updateby 5to 14 percent, and up to 26 percent in the pure-Python implementation,
when no key repeats, which each hash table started to track; the
tracking made :meth:
~multidict.MultiDict.addup 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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1654.Replaced the set of visited entries that :meth:
~multidict.MultiDict.getall,:meth:
~multidict.MultiDict.to_dictand the items view's&kept with acheck that each match's entry index is above the previous one's, since
equal keys are first reached in insertion order. A
:meth:
~multidict.MultiDict.getallof a key with many values needed up tohalf fewer instructions -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1752, :issue:1756.Sped up :meth:
~multidict.MultiDict.updateand:meth:
~multidict.MultiDict.merge: they stopped growing the table up frontwhen it could hold the argument even empty, as :meth:
dict.updatedoes,stopped holding extra references to the keys and values of a :class:
dictargument, 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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1747, :issue:1755, :issue:1757.Made
d[key] = valuein the C extension keep the collector forduplicate 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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1650.Sped up building an :class:
~multidict.istrfrom a :class:strin the Cextension by copying the string directly instead of going through
str.__new__(); reading a :class:strkey of a:class:
~multidict.CIMultiDictfor the first time and:meth:
~multidict.MultiDict.popitembecame about 30% cheaper ininstructions -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1645.Stopped a multidict with no watchers from testing for them on every
record made by
del d[key],d[key] = valueandpop(): eachoperation 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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1604, :issue:1605.Stopped clearing each field of a hash table that is being freed, and read
its entry count once instead of after every release, cutting about 8% off
tearing down a table of 200 entries in
clear()and on deallocation;replaced
Py_CLEAR()on success paths 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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1639, :issue:1642.Deprecations (removal in next major release)
Started emitting :exc:
DeprecationWarningon access tomultidict.upstr,the alias for :class:
~multidict.istrdeprecated since 2.0, and removed itfrom
multidict.__all__-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1680.Removals and backward incompatible breaking changes
Changed :meth:
~multidict.MultiDict.keysand iteration over:class:
~multidict.MultiDict, :class:~multidict.CIMultiDictand theirproxies to yield each key once, as spelled in its first item, so that
CIMultiDict([('X-Foo', '1'), ('x-foo', '2')])iterates over'X-Foo'alone and
len(d.keys())counts distinct keys; :meth:~multidict.MultiDict.items,:meth:
~multidict.MultiDict.valuesandlen(d)kept every item-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1654.Stopped calling a :class:
strsubclass's ownlower()for thecase-insensitive identity of a key in :class:
~multidict.CIMultiDictand in:c:func:
IStr_FromUnicode; the identity came from :meth:str.lowerinstead,so computing it ran no user code -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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:639license metadata:declared the license as the SPDX expression
Apache-2.0and movedlicense-filesinto the[project]table, which raised thebuild-time requirement to
setuptools >= 77.0. Built distributionsstarted carrying
License-Expressionin place of the legacyLicensefield -- by :user:aiolibsbot.Related issues and pull requests on GitHub:
:issue:
1646.Added the
benchmarks/scripts to the source distribution, so that thetest suite run from an unpacked sdist also covers the benchmark driver
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1762.Added
tools/check_inlining.pyand anInliningCI job, whichfail when GCC stops inlining a helper that a hot path depends on, or
makes an out-of-line copy of an inline function from the CPython headers
or from
pythoncapi_compat.hsuch asPy_DECREF(), since each timethat happened a benchmark regressed on code the change never touched
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1612, :issue:1686.Stopped passing
-Wno-conversionto the C extension build, whichhad silently turned
-Wconversionoff, and added the explicit castsit then required -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1611.Dropped the macOS and Windows legs of the CI test matrix, since
cibuildwheelalready ran the same test suite against the very wheelsthose jobs installed. Moved every artifact the CI test jobs consume into
one first stage, the source distribution together with the pure-Python
and Linux binary wheels, and switched the remaining test jobs to that
source distribution instead of a repository checkout. The wheel builds
for the other platforms stopped gating the test matrix, the
windows-11-armwheel build got limited to release tags again, andx86_64 macOS wheels started being tested under Rosetta outside pull
requests only -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1575, :issue:1638.Added CodSpeed benchmarks for mutating a multidict that a watcher is
attached to and for re-initializing a populated multidict, started
benchmarking the FT build alongside the GIL one, and split each build's
benchmark CI job into two parallel shards of similar length, selected by
a new
benchmark_shard_2pytest mark -- by :user:asvetlov.Related issues and pull requests on GitHub:
:issue:
1608, :issue:1620.Changed
benchmarks/callgrind_driver.pyto run its Callgrind children from afixed-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, andmade its checks before measuring, including
--self-check, cover onlythe implementations selected with
--impl, so a pure-Python install canbe measured -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1623, :issue:1624, :issue: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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1621, :issue:1631, :issue:1633.Made the ThreadSanitizer run reliable: added
tools/tsan_suppressions.txtfor thefaulthandlerwatchdog'straceback dumps, used by the CI job and by the documented local command,
which also gained
--no-cov; and changed the two pure-Python deadlocktests 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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1655.Renamed the
hypothesis-freethreadingCI job tohypothesis-ft(Hypothesis FT), on a par with the GIL one, and removed the
updateargument of the debug-only
ASSERT_CONSISTENT()check, which stoppeddoing anything once :meth:
~multidict.MultiDict.updatekept its doomedentries whole -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1609, :issue:1629.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 ofit is loaded into every agent session
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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 thecompiler, 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 viewconstructors, 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 proxyconstructors' error paths cold, and kept
NOINLINEonly for the fewhelpers GCC re-inlined without it. Moved argument parsing into the method
entry points and returned the keyword-bound arguments by value, which
dropped a stack protector canary from every
get(),getone(),getall(),add()andsetdefault()call-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1613, :issue:1614, :issue:1661, :issue:1664, :issue:1667,:issue:
1670, :issue:1671, :issue:1672, :issue:1683, :issue:1690,:issue:
1691, :issue:1693, :issue:1694, :issue:1695, :issue:1697,:issue:
1698, :issue:1699, :issue:1700, :issue:1701, :issue:1702,:issue:
1703, :issue:1704, :issue:1705, :issue:1707, :issue:1709,:issue:
1711, :issue:1712, :issue:1713, :issue:1714, :issue:1717,:issue:
1718, :issue:1719, :issue:1720, :issue:1722, :issue:1723,:issue:
1724, :issue:1726, :issue:1727, :issue:1730, :issue:1732,:issue:
1733, :issue:1734, :issue:1735, :issue:1737, :issue:1739,:issue:
1740, :issue:1749, :issue:1750, :issue:1751.Shrank the C extension by merging duplicated code: the bodies of
__init__(),extend(),update()andmerge(), ofpop()and
popone(), the per-class copies of the entry points, the updateloops and the
tp_vectorcallslots, which read the class at run timeinstead, the repeated tails of the table rebuilds and of
getall()andpopall(), and the module setup, which became two table loops. Theset operators and comparisons of the keys and items views were rewritten
around shared helpers compiled for size. When they landed, the view
rewrite made the compiled extension about 6% smaller and reading the
class at run time about 15%, which left room for the compact tables; the
view set operators became up to 17% slower and
update()andmerge()up to 3% slower -- by :user:asvetlov.Related issues and pull requests on GitHub:
:issue:
1669, :issue:1673, :issue:1675, :issue:1677, :issue:1678,:issue:
1679, :issue:1706, :issue:1715, :issue:1716, :issue:1725,:issue:
1728, :issue:1729, :issue:1731.Reorganized how the C extension's hash table lays out and reaches its
entries: split the entry into a key and value layout that every table
kind shares and a layout that adds the identity and its hash, computed
the entry size with
sizeof, told a deleted entry by its key alone,replaced the
md_pos_tcursor and the entry stepping helpers withtyped, 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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1641, :issue:1649, :issue:1656, :issue:1659, :issue:1660,:issue:
1662, :issue:1663, :issue:1665, :issue:1674, :issue:1676,:issue:
1741, :issue:1742, :issue:1743, :issue:1745, :issue: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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1618.Upgraded
pythoncapi_compat, and stopped copying the fastpath of :c:func:
!PyUnstable_TryIncRefinto the free-threaded Cextension, where it read CPython's private reference count fields, by
calling the function itself instead -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1687, :issue:1688.Removed dead code from the C extension: a self-update branch no caller
could reach, version guards that are always true on the supported Python
versions, unused helpers and single-use forwarding wrappers. Renamed the
header-local helpers to carry a leading underscore, moved
istr_canonical()next to theistrobjectstruct, and folded thepure-Python
update()match test back into one condition-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1635, :issue:1668, :issue:1689, :issue:1753, :issue:1754.Added
constqualifiers to internal C helpers that read table, reference list,and watcher metadata -- by :user:
anshurajbisoyi98-ctrl.Related issues and pull requests on GitHub:
:issue:
1643.v7.0.0Compare Source
=====
(2026-09-26)
7.0.0 is a major release. It makes :class:
~multidict.istrfinal, adds apublic C API for third-party extensions, and makes the C extension
substantially faster across the board.
Breaking change. :class:
~multidict.istrcan 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 :exc:
TypeError, where it used to silentlymisbehave.
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, acimport-ablemultidict/__init__.pxdand:func:
multidict.get_include. The API includesMultiDict_ForEach()anda watchers API modeled on CPython's dict watchers. See the :doc:
C API <capi>and:doc:
Cython API <cyapi>references.Performance. On CodSpeed's GIL-build benchmarks against 6.9.1, 82
C-extension benchmarks got faster and none got slower. Rough figures:
~multidict.CIMultiDictkeyed by plain :class:str: construction,add(),extend(),update()and item assignment got 2 to 3.8times faster, and lookups about 2 times faster.
~multidict.CIMultiDictkeyed by :class:~multidict.istr, andcase-sensitive :class:
~multidict.MultiDict: lookups, insertion anddeletion got 10% to 70% faster.
~multidict.MultiDict.getalland iterating:meth:
~multidict.MultiDict.itemsgot about 35% faster, and the view setoperations 10% to 85% faster.
~multidict.MultiDict.popitemon a whole mapping went fromquadratic to linear time.
lookups got 24% to 33% cheaper on :class:
~multidict.MultiDictand 4 to 5times cheaper on :class:
~multidict.CIMultiDictwith lowercase :class:strkeys, and item assignment 15% to 19% cheaper. Construction and deletion on
:class:
~multidict.MultiDictcost 2% to 7% more. With several threadsmutating at once, a
d[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 avalue's
__eq__mutated a multidict, a crash at interpreter shutdown,and table and reference leaks on the free-threaded build.
Bug fixes
Fixed a segmentation fault in the C extension when a finalizer let the
same multidict be resized while
del md[key]or:meth:
~multidict.MultiDict.popallwas still scanning the table, and aspurious :exc:
RuntimeErrorfrom :meth:~multidict.MultiDict.getallwhen a garbage collection triggered while building its result mutated
the multidict -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1492, :issue:1512.Fixed a reference leak of the key and value in the C extension when
growing the hash table fails with :exc:
MemoryErrorduring:meth:
~multidict.MultiDict.add, :meth:~multidict.MultiDict.update,:meth:
~multidict.MultiDict.merge, :meth:~multidict.MultiDict.setdefaultor item assignment -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1507.Fixed a data race on the free-threaded build of the C extension where a
MultiDict's own version counter was read by:func:
~multidict.getversionand 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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1514.Fixed the C extension discarding a :exc:
MemoryErroror:exc:
KeyboardInterruptraised by a positional argument's__len__while the size of an update was estimated.
extend(),update(),merge()and subclass construction started propagating it, matching thepure-Python backend; only the :exc:
TypeErrorfrom an unusable__length_hint__stayed ignored -- by :user:asvetlov.Related issues and pull requests on GitHub:
:issue:
1517.Ensured that canonical keys in pure-Python :class:
~multidict.MultiDictand:class:
~multidict.CIMultiDictare converted to exact :class:strinstanceswithout invoking :meth:
object.__str__overrides, matching the C extension --by :user:
agustin18.Related issues and pull requests on GitHub:
:issue:
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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1545.Fixed the C extension accepting an argument passed both positionally and
by name instead of raising :exc:
TypeError.md.add("a", key="k", value="v")stored a wrong value, andmd.get("a", "b", key="c")silently ignored the surplus arguments -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1545.Fixed a memory leak in the free-threaded build of the C extension.
:meth:
~multidict.MultiDict.clearhands its hash table to a drain thatfrees 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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1555, :issue:1562.Fixed a crash during interpreter shutdown in the C extension: a
multidict started keeping a reference to its own module, so the module
state it reads while being torn down could not be freed first
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1566.Fixed
extend(),update()andmerge()of the C extensionraising :exc:
SystemErrorwhen given an object whose__length_hint__()reported close to :data:sys.maxsizetogetherwith keyword arguments, and overflowing the table size computation for
such a hint; a hint too large to reserve for is ignored, as
:meth:
list.extenddoes -- by :user:asvetlov.Related issues and pull requests on GitHub:
:issue:
1577.Fixed a use-after-free in the C extension's
MultiDict.__eq__when a value's
__eq__mutated either multidict; comparingexact :class:
strvalues also became faster -- by :user:asvetlov.Related issues and pull requests on GitHub:
:issue:
1579.Fixed a use-after-free in the C extension when a :class:
strsubclasskey's
lower()mutated the source while a:class:
~multidict.CIMultiDictwas built from, extended, updated ormerged with a :class:
dictor a multidict of the other casesensitivity. The extra references this takes are limited to the keys
whose
lower()can run Python code, so other updates do not pay forthem -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1581, :issue:1598.Features
Added a public C and Cython API for third-party extensions. Its
MultiDict_ForEach()family hands a visitor each entry's key, value,identity (the canonical key the mapping looks entries up by, lower-cased
on a :class:
~multidict.CIMultiDict) and the identity's hash-- by :user:
Vizonexand :user:asvetlov.Related issues and pull requests on GitHub:
:issue:
1478.Changed the C extension to track the entries that
:meth:
~multidict.MultiDict.getall, item assignment,:meth:
~multidict.MultiDict.update, :meth:~multidict.MultiDict.mergeand :meth:
~multidict.MultiDict.to_dicthave visited in a bitmap privateto 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,
:meth:
~multidict.MultiDict.to_dictand lookups of keys with many valuesgot faster, while :meth:
~multidict.MultiDict.updatewith many repeatedkeys got somewhat slower
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1505.Replaced the C extension's per-key finder object with a single
callback-driven walk in a new
_multilib/walk.hheader. The entries awalk has already reported were tracked by the walk itself rather than
by every caller, and the walk kept its position in registers instead of
reloading it from a struct on each step, so
:meth:
~multidict.MultiDict.getalland the items and keys view setoperations got faster on keys with many values -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1522.Sped up
CIMultiDictoperations keyed by a plainstrin the Cextension. An all-ASCII key was lowered directly instead of through a
str.lower()call, and a key that is already lowercase became its ownidentity, which saved both a copy and a rehash. Looking up a lowercase key
got about three times faster and inserting one about twice as fast, keys
that do carry uppercase gained 20% to 35%, and bulk operations on
already-lowercase keys, such as :meth:
~multidict.MultiDict.extendandconstruction from a :class:
dict, got up to three times faster-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1527.Sped up building an
istrin the C extension. The class gained its ownvectorcall, so the one-argument form stopped packing its argument into a
tuple for :c:func:
!type_callonly to unpack it again with:c:func:
PyArg_ParseTupleAndKeywords. Building anistrfrom a:class:
strgot about 19% faster, and building one from anistr,which hands back the original, about three times faster. In exchange
:meth:
~multidict.MultiDict.getallcosts nine more instructions percall, since the added code moves where the compiler draws its inlining
line -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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 :class:
dict. Only onbuilds 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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1539.Sped up :meth:
~multidict.MultiDict.copy, constructing a multidict fromanother 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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1540.Sped up :meth:
~multidict.MultiDict.items,:meth:
~multidict.MultiDict.keys, :meth:~multidict.MultiDict.valuesand iterating a multidict in the C extension, by reusing the view and
iterator objects they allocate instead of returning them to the
allocator -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1541.Sped up constructing a :class:
~multidict.MultiDict,:class:
~multidict.CIMultiDict, :class:~multidict.MultiDictProxyor:class:
~multidict.CIMultiDictProxyin the C extension, by reusing theobject itself as well as its hash table. Subclasses are unaffected and
keep allocating as before -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1542.Sped up :meth:
~multidict.MultiDict.getallin the C extension, byshrinking 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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1543.Sped up lock-free reads on the free-threaded build of the C extension.
Every reader that leaves a :class:
~multidict.MultiDictlast used tocall 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 asinstruction counts on CPython 3.14. Builds with the GIL enabled emit
byte-identical code and are unaffected -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1546.Added a watchers C API, so another C extension can be notified when a
:class:
~multidict.MultiDictor :class:~multidict.CIMultiDictchanges.It follows CPython's :c:func:
!PyDict_AddWatcherfamily, with twodifferences: the callback is passed two context pointers, one fixed per
registered callback and one per watched multidict, and events are
delivered once the operation that produced them has finished rather than
mid-mutation. The events are multi-value aware, and each one that concerns
a key carries the identity and its hash alongside it. Up to
:c:macro:
MULTIDICT_MAX_WATCHERS(32) watchers can be registered, and afailing callback is reported through :func:
sys.unraisablehookwith theevent and the multidict's type and address, as CPython does for dict
watchers -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1554.Sped up every method call on :class:
~multidict.CIMultiDictand:class:
~multidict.CIMultiDictProxyin the C extension by bindingtheir method descriptors to the class itself instead of inheriting
them from :class:
~multidict.MultiDictand:class:
~multidict.MultiDictProxy; CPython only specializes amethod call when the descriptor belongs to the exact type of the
receiver, so every such call was taking the generic path
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1567.Made :meth:
~multidict.MultiDict.popitemin the C extension drop thetrailing 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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1568.Sped up iterating over :meth:
~multidict.MultiDict.itemsin the Cextension by handing out the same
(key, value)tuple again whenthe caller has already let go of it, as
for key, value in d.items()does on every step; a tuple the caller keeps is leftalone and a new one is used from then on -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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] = valuewasabout four times faster -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1587, :issue:1599.Sped up the C extension by keeping the table resize on the insert path and
the operations behind Python slots out of line, so they stopped crowding the
hot paths out of the compiler's inlining budget. On GIL builds, building a
multidict from 200 pairs got about 4% faster in instruction count and
inserting a new key about 2%; on free-threaded builds, key lookups got up to
5% faster -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1600, :issue:1601.Removals and backward incompatible breaking changes
Made :class:
~multidict.istra final class to disallow subclassing in boththe C extension and pure-Python implementations -- by :user:
agustin18.Related issues and pull requests on GitHub:
:issue:
1535.Improved documentation
Documented how :class:
~multidict.MultiDictand:class:
~multidict.CIMultiDictcompare with :class:dicton both thedefault and the free-threaded builds, with per-operation measurements,
and how to run the benchmarks in a stable environment
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1532.Documented what :class:
~multidict.istrkeys are worth on a:class:
~multidict.CIMultiDict, as instruction counts next to theequivalent plain :class:
strkeys. The advice to create them once andreuse them was already there; the numbers behind it were not
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1558.Mentioned in the README and docs index that
multidictis optimized forboth the GIL and free-threaded builds, and that its performance is typically
within 20-30% of :class:
dictfor common operations, with a link to thebenchmarks page for the full breakdown -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1559.Fixed the C extension's
popone()andpop()docstrings, whichclaimed 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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1524.Contributor-facing changes
Added CodSpeed benchmarks for
getall()on a large table,update()with duplicate keys,
merge(),to_dict()and items view containmentwith duplicate keys -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1506.Added CodSpeed benchmarks for
getall()on keys with many values, bothin a small table and in a table large enough to need a heap allocated
bitmap -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1509.Moved the compiler hint macros used by the C extension out of
htkeys.hintocompiler.hand dropped theHT_prefix fromtheir names -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1510.Moved the debug-only
ASSERT_CONSISTENTmacro and the_md_check_consistency()and_md_dump()helpers used by the Cextension out of
hashtable.hinto their owndebug.hheader-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1513.Added CodSpeed benchmarks for
CIMultiDictkeyed by plainstrinsteadof
istr, covering already-lowercase, mixed-case and non-ASCII keys-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1521.Documented that the pure-Python test leg needs
--no-c-extensionsalongside
MULTIDICT_NO_EXTENSIONS=1; the environment variable picksthe backend that gets imported, but only the flag deselects the
C-extension half of the test matrix -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1523.Documented how to run the Hypothesis property tests, which a default
pytestrun excludes via the-m "not hypothesis"markerexpression in
pytest.iniand whose pinmake install-devdoesnot install -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1525.Added
tools/codegen_diff.py, which reports the functions GCC emitsdifferently 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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1529.Fixed
benchmarks/istr.pyto importpyperfunder its currentname; it had been failing to start since the package was renamed from
perf-- by :user:asvetlov.Related issues and pull requests on GitHub:
:issue:
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 costof rebuilding the mapping -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1532.Moved the bulk
extend(),update()andmerge()paths of the Cextension out of
hashtable.hinto their ownbulk_update.hheader, leaving
hashtable.hto the table itself and the single-keyoperations -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1533.Moved the two list-reading helpers behind
itemsview.__contains__()andthe
extend()/update()/merge()sequence parser out ofhashtable.hand into a newmultidict/_multilib/unpack.h, together witha shared
unpack_pair()that both call to read a(key, value)pair out of an exact two-element tuple or list. C extension only; the pure
Python implementation has no header layout to mirror
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1534.Added a
MULTIDICT_NO_FREELIST=1build option that stops the Cextension reusing freed blocks. A reused block never reaches
free(),so AddressSanitizer can neither poison it nor report a use-after-free on
it; an instrumented run wants a second pass with this set, as
AGENTS.mddescribes.Also added a private
_freelist_clear()to the C extension, for teststhat 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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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()fromboth
multidict._testcapiandmultidict._testcyapi-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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 whereit is 46% faster -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1556.Sorted
docs/spelling_wordlist.txtcase-insensitively, and documentedin
AGENTS.mdthat it is to be re-sorted rather than appended to. Thelist had grown in append order, which made a word hard to find and put
every PR adding one on the same last line -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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 :file:
RELEASE.md, which documents the release procedure, includingthat refresh -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1564.Renamed the C extension's internal helpers in the
_multilibheadersso that a leading underscore marks exactly the functions used only
within their own header; type slots and method callbacks keep their
names -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1571.Spelled out in
AGENTS.mdthat every verb in a news fragment ispast tense, and added a bad and good example pair plus a matching
"Things not to do" entry -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1596.Miscellaneous internal changes
Changed the internal
htkeys_build_indices()helper in the Cextension to return
void; it cannot fail, so both callers werechecking for an error that never occurred -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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 choosingbetween 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 :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1518.Removed
get_mod_state_by_cls(), an unused helper in the C extension-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
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 afield is already
NULL, so no accessor there is a substitute for it-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1526.Collapsed the two
#ifdef Py_GIL_DISABLEDarms that fill in a freshlyinserted 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 witho
Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.