{"id":279,"library":"multidict","title":"multidict","description":"multidict is a mapping implementation that allows multiple values per key, mirroring the semantics of HTTP headers and query strings. It provides four concrete classes — MultiDict (mutable), MultiDictProxy (immutable view), CIMultiDict (case-insensitive mutable), and CIMultiDictProxy (case-insensitive immutable view) — plus the istr helper for pre-folded string keys. Current version is 6.7.1 (released January 2026). The library ships an optional C extension; release cadence is frequent patch releases with periodic minor releases adding features such as the new merge() method (6.6.0) and a hash-table backend replacing the previous O(N) array (6.5.0).","status":"active","version":"6.7.1","language":"python","source_language":"en","source_url":"https://github.com/aio-libs/multidict","tags":["http","headers","mapping","aiohttp","web","multidict","async","aio-libs"],"install":[{"cmd":"pip install multidict","lang":"bash","label":"Latest (with C extension)"},{"cmd":"MULTIDICT_NO_EXTENSIONS=1 pip install multidict","lang":"bash","label":"Pure-Python fallback (20-50x slower)"}],"dependencies":[],"imports":[{"note":"Never import from the private _multidict (C) or _multidict_py (pure-Python) submodules directly; always import from the top-level multidict package.","wrong":"from multidict._multidict import MultiDict","symbol":"MultiDict","correct":"from multidict import MultiDict"},{"note":"Private submodule imports bypass the C/Python dispatch and are not part of the public API.","wrong":"from multidict._multidict_py import CIMultiDict","symbol":"CIMultiDict","correct":"from multidict import CIMultiDict"},{"note":"MultiDictProxy raises TypeError if the argument is not a MultiDict instance — it cannot wrap a plain dict.","symbol":"MultiDictProxy","correct":"from multidict import MultiDictProxy"},{"note":"CIMultiDictProxy raises TypeError if the argument is not a CIMultiDict instance.","symbol":"CIMultiDictProxy","correct":"from multidict import CIMultiDictProxy"},{"note":"Use istr for pre-folded keys with CIMultiDict to avoid redundant lower() calls in hot paths.","symbol":"istr","correct":"from multidict import istr"},{"note":"d['key'] returns only the FIRST value for a key. Use getall() to retrieve all values for a duplicated key.","wrong":"d['key']","symbol":"getall","correct":"d.getall('key')"}],"quickstart":{"code":"from multidict import MultiDict, MultiDictProxy, CIMultiDict, istr\n\n# --- Mutable multi-valued dict ---\nd = MultiDict([('a', 1), ('b', 2), ('a', 3)])\nassert d['a'] == 1              # returns FIRST value only\nassert d.getall('a') == [1, 3] # all values for key\nd.add('b', 99)                  # append without replacing\nassert len(d) == 4\n\n# --- set() replaces ALL entries for that key ---\nd['a'] = 99\nassert d.getall('a') == [99]\n\n# --- Read-only proxy (dynamic view) ---\nproxy = MultiDictProxy(d)\nassert proxy['b'] == 2\nd.add('b', 42)\nassert proxy.getall('b') == [2, 99, 42]  # proxy reflects changes\n\n# --- Case-insensitive dict ---\nci = CIMultiDict(Content_Type='application/json')\nassert 'content-type' in ci\nassert ci['CONTENT-TYPE'] == 'application/json'\n\n# --- istr for pre-folded keys (performance) ---\nHDR = istr('Content-Type')\nassert ci[HDR] == 'application/json'\n\n# --- merge (6.6+): copies key only if not already present ---\nbase = MultiDict(a=1)\nbase.merge(MultiDict(a=99, b=2))\nassert base['a'] == 1   # not overwritten\nassert base['b'] == 2\n","lang":"python","description":"Demonstrates mutable MultiDict with duplicate keys, getall, add, and a case-insensitive CIMultiDict."},"warnings":[{"fix":"Replace d['key'] with d.getall('key') when multiple values per key are expected.","message":"d['key'] returns only the FIRST value for a duplicated key. Iterating .keys() yields ALL keys including duplicates. Use getall('key') to fetch every value.","severity":"gotcha","affected_versions":"all"},{"fix":"Use d.add(key, value) to append a value while preserving existing entries for the same key.","message":"d['key'] = value replaces ALL existing entries for that key with a single entry, not just the first. Use d.add('key', value) to append without clobbering existing values.","severity":"gotcha","affected_versions":"all"},{"fix":"Upgrade to >=6.5.1 which fixes a resize-with-deleted-slots bug introduced in 6.5.0. Treat key iteration order as insertion-order stable but do not assume slot density.","message":"6.5.0 switched the internal storage from a linear array (O(N) lookups) to a hash table (O(1) lookups). MultiDict.add() and extend() are now 25-50% slower; single lookups are 25-50% faster and bulk operations are 2-3x faster. Code relying on iteration order of keys() after deletions may observe subtly different compaction behaviour.","severity":"breaking","affected_versions":">=6.5.0"},{"fix":"Wrap the source mapping in MultiDict() first: MultiDictProxy(MultiDict(plain_dict)).","message":"MultiDictProxy and CIMultiDictProxy raise TypeError if passed a plain dict or any non-MultiDict mapping. They only accept the matching MultiDict subclass.","severity":"breaking","affected_versions":"all"},{"fix":"Do not set MULTIDICT_NO_EXTENSIONS in production. For Alpine Linux / source builds without a C compiler, the pure-Python wheel is automatically used as a fallback starting with 6.x.","message":"Setting MULTIDICT_NO_EXTENSIONS=1 at runtime forces the pure-Python implementation even when the C extension wheel is installed. The pure-Python path is roughly 20-50x slower. This env var also suppresses C extension compilation at build time if set during pip install.","severity":"gotcha","affected_versions":"all"},{"fix":"Always import from the top-level package: from multidict import MultiDict, CIMultiDict, ...","message":"Directly importing from private submodules multidict._multidict (C extension) or multidict._multidict_py (pure Python) is not part of the public API and can break across patch releases. The internal _Impl class was removed from the pure-Python implementation in 6.4.x.","severity":"deprecated","affected_versions":"all"},{"fix":"Use str(key) if you need a plain str, or compare with == rather than 'is'. istr is a subclass of str so equality checks work normally.","message":"CIMultiDict stores and returns keys as istr (a str subclass with preserved original case), not plain str. Identity or strict type checks against str will pass since istr derives from str, but isinstance checks for exactly str may differ from expectations when iterating keys.","severity":"gotcha","affected_versions":">=6.3.0"},{"fix":"For case-insensitive key access, use `CIMultiDict` instead of `MultiDict`. Ensure the object `ci` is an instance of `CIMultiDict` if you expect `assert 'content-type' in ci` to pass when only 'Content-Type' is present.","message":"`MultiDict` performs case-sensitive key lookups, while `CIMultiDict` performs case-insensitive lookups. If you are expecting case-insensitive behavior (e.g., when working with HTTP headers), ensure you are using `CIMultiDict`.","severity":"gotcha","affected_versions":"all"}],"env_vars":null,"last_verified":"2026-05-12T12:47:33.302Z","next_check":"2026-06-26T00:00:00.000Z","problems":[{"fix":"Install the package using pip: `pip install multidict` or `python -m pip install multidict`.","cause":"The `multidict` package is not installed in the Python environment being used, or there's a mismatch in virtual environments.","error":"ModuleNotFoundError: No module named 'multidict'"},{"fix":"Ensure you have a C compiler (e.g., Build Tools for Visual Studio on Windows, or `build-essential` on Debian/Ubuntu) and Python development headers installed. Alternatively, you can force the installation of the pure-Python version (which will be slower) using the environment variable: `MULTIDICT_NO_EXTENSIONS=1 pip install multidict`.","cause":"This error occurs when `pip` cannot compile the optional C extension for `multidict` during installation. This often happens on systems without a C compiler, required Python development headers, or with newer Python versions (e.g., Python 3.12) for which pre-built wheels might not yet be available.","error":"ERROR: Could not build wheels for multidict, which is required to install pyproject.toml-based projects"},{"fix":"Use the `.get()` method, which returns `None` or a specified default value if the key is not found, or check for key existence with `if 'key' in d:` before accessing. To get all values for a key, use `.getall('key')`.","cause":"Attempting to access a key in a `MultiDict` (or its variants) using bracket notation `d['key']` when that key does not exist in the dictionary.","error":"KeyError: 'some_key'"},{"fix":"Replace `d.iteritems()` with `d.items()`, `d.iterkeys()` with `d.keys()`, and `d.itervalues()` with `d.values()`. These methods return view objects in Python 3, which behave similarly to iterators for looping.","cause":"This error typically occurs when migrating Python 2 code to Python 3. The `iteritems()`, `iterkeys()`, and `itervalues()` methods were removed in Python 3; `MultiDict` (like standard Python dictionaries) provides `items()`, `keys()`, and `values()` which return view objects.","error":"AttributeError: 'MultiDict' object has no attribute 'iteritems'"},{"fix":"If you need to serialize the data, convert the proxy object to a mutable `CIMultiDict` or a standard dictionary first. For example, `import pickle; dct = multidict.CIMultiDict(original_proxy); pickled_dct = pickle.dumps(dct)` or `pickled_dict = pickle.dumps(dict(original_proxy))`.","cause":"`CIMultiDictProxy` (and `MultiDictProxy`) are designed as immutable views (proxies) of underlying mutable multidicts and are explicitly not pickleable by design, mirroring Python's `types.MappingProxyType` behavior.","error":"TypeError: can't pickle multidict._multidict.CIMultiDictProxy objects"}],"ecosystem":"pypi","meta_description":null,"install_score":100,"install_tag":"verified","quickstart_score":0,"quickstart_tag":"stale","pypi_latest":null,"install_checks":{"last_tested":"2026-05-12","tag":"verified","tag_description":"installs cleanly on critical runtimes, fast import, recently tested","results":[{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.03,"mem_mb":1.3,"disk_size":"18.8M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.02,"mem_mb":1.3,"disk_size":"19M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.03,"mem_mb":1,"disk_size":"20.3M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.03,"mem_mb":1,"disk_size":"21M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.02,"mem_mb":0.7,"disk_size":"12.2M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.02,"mem_mb":0.7,"disk_size":"13M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.02,"mem_mb":0.8,"disk_size":"11.8M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.01,"mem_mb":0.6,"disk_size":"13M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.02,"mem_mb":1.2,"disk_size":"18.2M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.02,"mem_mb":1.2,"disk_size":"19M"}]},"quickstart_checks":{"last_tested":"2026-04-23","tag":"stale","tag_description":"widespread failures or data too old to trust","results":[{"runtime":"python:3.10-alpine","exit_code":1},{"runtime":"python:3.10-slim","exit_code":1},{"runtime":"python:3.11-alpine","exit_code":1},{"runtime":"python:3.11-slim","exit_code":1},{"runtime":"python:3.12-alpine","exit_code":1},{"runtime":"python:3.12-slim","exit_code":1},{"runtime":"python:3.13-alpine","exit_code":1},{"runtime":"python:3.13-slim","exit_code":1},{"runtime":"python:3.9-alpine","exit_code":1},{"runtime":"python:3.9-slim","exit_code":1}]}}