Dictionary-like collection where keys can occur multiple times, optimized for HTTP headers and URL query strings
—
The MultiDictProxy and CIMultiDictProxy classes provide read-only access to multidict data. These immutable proxies are useful for exposing multidict data safely without allowing modifications, commonly used in APIs and shared data structures.
Create immutable proxy views from existing multidict instances.
class MultiDictProxy(MultiMapping[_V]):
def __init__(self, multidict: Union[MultiDict[_V], MultiDictProxy[_V]]):
"""
Create an immutable proxy for a MultiDict.
Parameters:
- multidict: The MultiDict instance to wrap
The proxy reflects changes made to the underlying multidict.
"""
class CIMultiDictProxy(MultiDictProxy[_V]):
def __init__(self, ci_multidict: Union[CIMultiDict[_V], CIMultiDictProxy[_V]]):
"""
Create an immutable proxy for a CIMultiDict.
Parameters:
- ci_multidict: The CIMultiDict instance to wrap
Provides case-insensitive read-only access.
"""Usage examples:
# Create mutable multidict
headers = MultiDict([
('Accept', 'text/html'),
('User-Agent', 'MyApp/1.0')
])
# Create immutable proxy
readonly_headers = MultiDictProxy(headers)
# Case-insensitive version
ci_headers = CIMultiDict([('Content-Type', 'text/html')])
readonly_ci_headers = CIMultiDictProxy(ci_headers)
# Proxies reflect changes to underlying multidict
headers.add('Accept', 'application/json')
print(readonly_headers.getall('Accept')) # ['text/html', 'application/json']Proxies provide all the read operations of their underlying multidict types without modification methods.
def __getitem__(self, key: str) -> _V:
"""Get the first value for key. Raises KeyError if not found."""
def get(self, key: str, default: Optional[_T] = None) -> Union[_V, _T, None]:
"""Get the first value for key, return default if not found."""
def getone(self, key: str, default: _T = ...) -> Union[_V, _T]:
"""
Get the first value for key.
Parameters:
- key: The key to retrieve
- default: Value to return if key not found
Returns:
First value for the key, or default if not found
Raises:
KeyError if key not found and no default provided
"""
def getall(self, key: str, default: _T = ...) -> Union[List[_V], _T]:
"""
Get all values for key as a list.
Parameters:
- key: The key to retrieve
- default: Value to return if key not found
Returns:
List of all values for the key, or default if not found
Raises:
KeyError if key not found and no default provided
"""Usage examples:
headers = MultiDict([
('Accept', 'text/html'),
('Accept', 'application/json'),
('User-Agent', 'MyApp/1.0')
])
proxy = MultiDictProxy(headers)
# All read operations work normally
print(proxy['Accept']) # 'text/html'
print(proxy.get('Accept')) # 'text/html'
print(proxy.getone('Accept')) # 'text/html'
print(proxy.getall('Accept')) # ['text/html', 'application/json']
# Safe access with defaults
print(proxy.get('Authorization', 'None')) # 'None'
print(proxy.getall('Missing', [])) # []Proxies support all standard collection inspection methods.
def __len__(self) -> int:
"""Return the number of key-value pairs."""
def __iter__(self) -> Iterator[str]:
"""Iterate over keys in insertion order."""
def __contains__(self, key: object) -> bool:
"""Check if key is present."""
def keys(self) -> KeysView[str]:
"""Return a view of keys."""
def values(self) -> ValuesView[_V]:
"""Return a view of values."""
def items(self) -> ItemsView[str, _V]:
"""Return a view of key-value pairs."""Usage examples:
headers = MultiDict([('Accept', 'text/html'), ('User-Agent', 'MyApp/1.0')])
proxy = MultiDictProxy(headers)
# Check size and contents
print(len(proxy)) # 2
print('Accept' in proxy) # True
print('Missing' in proxy) # False
# Iterate over proxy
for key in proxy:
print(f"{key}: {proxy.getall(key)}")
# Work with views
print(list(proxy.keys())) # ['Accept', 'User-Agent']
print(list(proxy.values())) # ['text/html', 'MyApp/1.0']
print(list(proxy.items())) # [('Accept', 'text/html'), ...]Proxies can create mutable copies of their underlying data.
def copy(self) -> MultiDict[_V]:
"""
Return a mutable copy of the multidict data.
Returns:
New MultiDict instance with the same data
For CIMultiDictProxy, returns a CIMultiDict instance.
"""Usage examples:
# Regular proxy creates MultiDict copy
headers = MultiDict([('Accept', 'text/html')])
proxy = MultiDictProxy(headers)
mutable_copy = proxy.copy() # Returns MultiDict
# Case-insensitive proxy creates CIMultiDict copy
ci_headers = CIMultiDict([('Content-Type', 'text/html')])
ci_proxy = CIMultiDictProxy(ci_headers)
ci_copy = ci_proxy.copy() # Returns CIMultiDict
# Copies are independent of original
mutable_copy.add('Accept', 'application/json')
print(len(proxy)) # Original unchanged
print(len(mutable_copy)) # Copy has additional itemProxies provide live views of their underlying multidict - changes to the original are immediately visible through the proxy.
headers = MultiDict([('Accept', 'text/html')])
proxy = MultiDictProxy(headers)
print(len(proxy)) # 1
# Modify original multidict
headers.add('Accept', 'application/json')
headers.add('User-Agent', 'MyApp/1.0')
# Changes are immediately visible through proxy
print(len(proxy)) # 3
print(proxy.getall('Accept')) # ['text/html', 'application/json']
print('User-Agent' in proxy) # TrueCommon pattern for exposing multidict data safely in APIs.
class HTTPRequest:
def __init__(self):
self._headers = MultiDict()
self._query_params = MultiDict()
def add_header(self, key: str, value: str):
"""Internal method for adding headers"""
self._headers.add(key, value)
@property
def headers(self) -> MultiDictProxy[str]:
"""
Read-only access to request headers.
Returns:
Immutable proxy to headers
"""
return MultiDictProxy(self._headers)
@property
def query_params(self) -> MultiDictProxy[str]:
"""
Read-only access to query parameters.
Returns:
Immutable proxy to query parameters
"""
return MultiDictProxy(self._query_params)
# Usage
request = HTTPRequest()
request.add_header('Accept', 'text/html')
# Clients get read-only access
headers = request.headers
print(headers['Accept']) # Works
# But cannot modify
# headers.add('Accept', 'application/json') # AttributeErrorCIMultiDictProxy provides the same interface with case-insensitive key handling.
ci_headers = CIMultiDict([
('Content-Type', 'text/html'),
('content-length', '1234')
])
ci_proxy = CIMultiDictProxy(ci_headers)
# Case-insensitive access through proxy
print(ci_proxy['CONTENT-TYPE']) # 'text/html'
print(ci_proxy['Content-Length']) # '1234'
print('content-type' in ci_proxy) # True
# Original case preserved in iteration
for key in ci_proxy:
print(f"Original case: {key}")
# Output: Content-Type, content-lengthProxies can wrap other proxies and maintain their immutable guarantees.
headers = MultiDict([('Accept', 'text/html')])
proxy1 = MultiDictProxy(headers)
proxy2 = MultiDictProxy(proxy1) # Proxy of a proxy
# Both proxies reflect changes to original
headers.add('User-Agent', 'MyApp/1.0')
print(len(proxy1)) # 2
print(len(proxy2)) # 2
# But neither can be modified
# proxy1.add('Key', 'value') # AttributeError
# proxy2.add('Key', 'value') # AttributeErrorInstall with Tessl CLI
npx tessl i tessl/pypi-multidict