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

immutable-proxies.mddocs/

Immutable Proxy Views

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.

Capabilities

Proxy Construction

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

Read-Only Access

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', []))         # []

Collection Inspection

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'), ...]

Creating Mutable Copies

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 item

Live View Behavior

Proxies 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)         # True

Safe API Exposure

Common 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')  # AttributeError

Case-Insensitive Proxy Operations

CIMultiDictProxy 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-length

Proxy Chains and Nesting

Proxies 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')  # AttributeError

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