Dictionary-like collection where keys can occur multiple times, optimized for HTTP headers and URL query strings
—
Multidict provides specialized string types and utility functions to support case-insensitive operations and multidict management. These components enable advanced functionality like version tracking and custom string handling.
The istr class provides case-insensitive string functionality for use with multidict keys and general string operations.
class istr(str):
"""
Case-insensitive string subclass.
Behaves like regular strings but compares equal regardless of case.
Used internally by CIMultiDict and CIMultiDictProxy for key handling.
"""
def __new__(cls, val=''):
"""
Create a case-insensitive string.
Parameters:
- val: String value to convert (can be str, bytes, or other objects)
Returns:
New istr instance
"""
# Special attributes for internal use
__is_istr__: bool = True
__istr_identity__: Optional[str] = NoneUsage examples:
from multidict import istr
# Create case-insensitive strings
s1 = istr('Content-Type')
s2 = istr('content-type')
s3 = istr('CONTENT-TYPE')
# All compare equal regardless of case
print(s1 == s2) # True
print(s2 == s3) # True
print(s1 == s3) # True
# But regular string comparison still case-sensitive
print(str(s1) == str(s2)) # False (different case)
# Original case is preserved
print(str(s1)) # 'Content-Type'
print(str(s2)) # 'content-type'
# Can be used with regular strings too
print(s1 == 'content-type') # True
print(s1 == 'CONTENT-TYPE') # TrueCheck if a string is a case-insensitive string type.
from multidict import istr
regular_str = 'Hello'
case_insensitive_str = istr('Hello')
# Check string type
print(hasattr(regular_str, '__is_istr__')) # False
print(hasattr(case_insensitive_str, '__is_istr__')) # True
print(case_insensitive_str.__is_istr__) # True
# Type checking
print(isinstance(case_insensitive_str, str)) # True (is a string)
print(isinstance(case_insensitive_str, istr)) # True (is istr)
print(type(case_insensitive_str).__name__) # 'istr'Using istr for custom case-insensitive operations outside of multidict.
from multidict import istr
# Case-insensitive set operations
headers_seen = set()
for header_name in ['Content-Type', 'content-length', 'ACCEPT']:
istr_header = istr(header_name)
if istr_header not in headers_seen:
headers_seen.add(istr_header)
print(f"New header: {header_name}")
# Case-insensitive dictionary keys (manual approach)
class CaseInsensitiveDict(dict):
def __setitem__(self, key, value):
super().__setitem__(istr(key), value)
def __getitem__(self, key):
return super().__getitem__(istr(key))
def __contains__(self, key):
return super().__contains__(istr(key))
ci_dict = CaseInsensitiveDict()
ci_dict['Content-Type'] = 'text/html'
print(ci_dict['content-type']) # 'text/html'The getversion function provides access to internal version numbers for change detection and optimization.
def getversion(md: Union[MultiDict[object], MultiDictProxy[object]]) -> int:
"""
Get the internal version number of a multidict.
Parameters:
- md: MultiDict or MultiDictProxy instance
Returns:
Integer version number that increments with each modification
Raises:
TypeError if parameter is not a multidict or proxy
The version number is used internally for view invalidation and can be
useful for caching and change detection in applications.
"""Usage examples:
from multidict import MultiDict, MultiDictProxy, getversion
# Track changes with version numbers
headers = MultiDict([('Accept', 'text/html')])
initial_version = getversion(headers)
print(f"Initial version: {initial_version}")
# Version increments with modifications
headers.add('User-Agent', 'MyApp/1.0')
after_add_version = getversion(headers)
print(f"After add: {after_add_version}") # Higher number
headers['Accept'] = 'application/json'
after_set_version = getversion(headers)
print(f"After set: {after_set_version}") # Even higher
# Proxy has same version as underlying multidict
proxy = MultiDictProxy(headers)
print(f"Proxy version: {getversion(proxy)}") # Same as headers
# Use for change detection
cached_version = getversion(headers)
cached_data = headers.copy()
# Later... check if data changed
if getversion(headers) != cached_version:
print("Headers changed, refresh cache")
cached_data = headers.copy()
cached_version = getversion(headers)Common pattern for implementing efficient caching with version tracking.
from multidict import MultiDict, getversion
class CachedHeaderProcessor:
def __init__(self):
self._cache = {}
self._cache_versions = {}
def process_headers(self, headers: MultiDict) -> str:
"""
Process headers with caching based on version tracking.
Parameters:
- headers: MultiDict to process
Returns:
Processed header string
"""
headers_id = id(headers)
current_version = getversion(headers)
# Check if we have cached result for this version
if (headers_id in self._cache and
self._cache_versions.get(headers_id) == current_version):
return self._cache[headers_id]
# Process headers (expensive operation)
result = self._expensive_processing(headers)
# Cache result with version
self._cache[headers_id] = result
self._cache_versions[headers_id] = current_version
return result
def _expensive_processing(self, headers: MultiDict) -> str:
# Simulate expensive processing
processed_items = []
for key in headers:
values = headers.getall(key)
processed_items.append(f"{key}: {', '.join(values)}")
return '\n'.join(processed_items)
# Usage
processor = CachedHeaderProcessor()
headers = MultiDict([('Accept', 'text/html'), ('User-Agent', 'MyApp/1.0')])
# First call - processes and caches
result1 = processor.process_headers(headers) # Expensive processing
# Second call - returns cached result
result2 = processor.process_headers(headers) # Fast cached lookup
# Modify headers - version changes
headers.add('Accept', 'application/json')
# Third call - detects change and reprocesses
result3 = processor.process_headers(headers) # Expensive processing againThe upstr alias is maintained for backward compatibility but is deprecated.
upstr = istr # Deprecated alias for istrUsage guidance:
from multidict import istr, upstr
# Preferred - use istr directly
preferred = istr('Content-Type')
# Deprecated - upstr is an alias for istr
deprecated = upstr('Content-Type') # Same as istr('Content-Type')
# Both work the same way
print(preferred == deprecated) # True
# But use istr in new code
recommended_usage = istr('header-name')istr integrates seamlessly with Python's standard library while maintaining case-insensitive behavior.
from multidict import istr
import re
# Works with regular expressions
pattern = re.compile(r'content-.*', re.IGNORECASE)
header = istr('Content-Type')
# String methods work normally
print(header.lower()) # 'content-type'
print(header.upper()) # 'CONTENT-TYPE'
print(header.title()) # 'Content-Type'
# But comparisons are still case-insensitive
print(header == 'CONTENT-TYPE') # True
# Hashing is case-insensitive
header_set = {istr('Content-Type'), istr('content-type')}
print(len(header_set)) # 1 (treated as same key)
# But regular strings hash differently
mixed_set = {'Content-Type', 'content-type', istr('CONTENT-TYPE')}
print(len(mixed_set)) # 2 (regular strings hash differently)Install with Tessl CLI
npx tessl i tessl/pypi-multidict