CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-multidict

Dictionary-like collection where keys can occur multiple times, optimized for HTTP headers and URL query strings

Pending
Overview
Eval results
Files

string-types-utilities.mddocs/

String Types and Utilities

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.

Capabilities

Case-Insensitive String Type

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] = None

Usage 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')  # True

String Type Detection

Check 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'

Manual Case-Insensitive Operations

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'

Version Tracking Utility

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)

Version-Based Caching Pattern

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 again

Deprecated upstr Alias

The upstr alias is maintained for backward compatibility but is deprecated.

upstr = istr  # Deprecated alias for istr

Usage 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')

Integration with Standard Library

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

docs

case-insensitive.md

immutable-proxies.md

index.md

mutable-multidict.md

string-types-utilities.md

tile.json