0
# Immutable Proxy Views
1
2
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.
3
4
## Capabilities
5
6
### Proxy Construction
7
8
Create immutable proxy views from existing multidict instances.
9
10
```python { .api }
11
class MultiDictProxy(MultiMapping[_V]):
12
def __init__(self, multidict: Union[MultiDict[_V], MultiDictProxy[_V]]):
13
"""
14
Create an immutable proxy for a MultiDict.
15
16
Parameters:
17
- multidict: The MultiDict instance to wrap
18
19
The proxy reflects changes made to the underlying multidict.
20
"""
21
22
class CIMultiDictProxy(MultiDictProxy[_V]):
23
def __init__(self, ci_multidict: Union[CIMultiDict[_V], CIMultiDictProxy[_V]]):
24
"""
25
Create an immutable proxy for a CIMultiDict.
26
27
Parameters:
28
- ci_multidict: The CIMultiDict instance to wrap
29
30
Provides case-insensitive read-only access.
31
"""
32
```
33
34
Usage examples:
35
36
```python
37
# Create mutable multidict
38
headers = MultiDict([
39
('Accept', 'text/html'),
40
('User-Agent', 'MyApp/1.0')
41
])
42
43
# Create immutable proxy
44
readonly_headers = MultiDictProxy(headers)
45
46
# Case-insensitive version
47
ci_headers = CIMultiDict([('Content-Type', 'text/html')])
48
readonly_ci_headers = CIMultiDictProxy(ci_headers)
49
50
# Proxies reflect changes to underlying multidict
51
headers.add('Accept', 'application/json')
52
print(readonly_headers.getall('Accept')) # ['text/html', 'application/json']
53
```
54
55
### Read-Only Access
56
57
Proxies provide all the read operations of their underlying multidict types without modification methods.
58
59
```python { .api }
60
def __getitem__(self, key: str) -> _V:
61
"""Get the first value for key. Raises KeyError if not found."""
62
63
def get(self, key: str, default: Optional[_T] = None) -> Union[_V, _T, None]:
64
"""Get the first value for key, return default if not found."""
65
66
def getone(self, key: str, default: _T = ...) -> Union[_V, _T]:
67
"""
68
Get the first value for key.
69
70
Parameters:
71
- key: The key to retrieve
72
- default: Value to return if key not found
73
74
Returns:
75
First value for the key, or default if not found
76
77
Raises:
78
KeyError if key not found and no default provided
79
"""
80
81
def getall(self, key: str, default: _T = ...) -> Union[List[_V], _T]:
82
"""
83
Get all values for key as a list.
84
85
Parameters:
86
- key: The key to retrieve
87
- default: Value to return if key not found
88
89
Returns:
90
List of all values for the key, or default if not found
91
92
Raises:
93
KeyError if key not found and no default provided
94
"""
95
```
96
97
Usage examples:
98
99
```python
100
headers = MultiDict([
101
('Accept', 'text/html'),
102
('Accept', 'application/json'),
103
('User-Agent', 'MyApp/1.0')
104
])
105
proxy = MultiDictProxy(headers)
106
107
# All read operations work normally
108
print(proxy['Accept']) # 'text/html'
109
print(proxy.get('Accept')) # 'text/html'
110
print(proxy.getone('Accept')) # 'text/html'
111
print(proxy.getall('Accept')) # ['text/html', 'application/json']
112
113
# Safe access with defaults
114
print(proxy.get('Authorization', 'None')) # 'None'
115
print(proxy.getall('Missing', [])) # []
116
```
117
118
### Collection Inspection
119
120
Proxies support all standard collection inspection methods.
121
122
```python { .api }
123
def __len__(self) -> int:
124
"""Return the number of key-value pairs."""
125
126
def __iter__(self) -> Iterator[str]:
127
"""Iterate over keys in insertion order."""
128
129
def __contains__(self, key: object) -> bool:
130
"""Check if key is present."""
131
132
def keys(self) -> KeysView[str]:
133
"""Return a view of keys."""
134
135
def values(self) -> ValuesView[_V]:
136
"""Return a view of values."""
137
138
def items(self) -> ItemsView[str, _V]:
139
"""Return a view of key-value pairs."""
140
```
141
142
Usage examples:
143
144
```python
145
headers = MultiDict([('Accept', 'text/html'), ('User-Agent', 'MyApp/1.0')])
146
proxy = MultiDictProxy(headers)
147
148
# Check size and contents
149
print(len(proxy)) # 2
150
print('Accept' in proxy) # True
151
print('Missing' in proxy) # False
152
153
# Iterate over proxy
154
for key in proxy:
155
print(f"{key}: {proxy.getall(key)}")
156
157
# Work with views
158
print(list(proxy.keys())) # ['Accept', 'User-Agent']
159
print(list(proxy.values())) # ['text/html', 'MyApp/1.0']
160
print(list(proxy.items())) # [('Accept', 'text/html'), ...]
161
```
162
163
### Creating Mutable Copies
164
165
Proxies can create mutable copies of their underlying data.
166
167
```python { .api }
168
def copy(self) -> MultiDict[_V]:
169
"""
170
Return a mutable copy of the multidict data.
171
172
Returns:
173
New MultiDict instance with the same data
174
175
For CIMultiDictProxy, returns a CIMultiDict instance.
176
"""
177
```
178
179
Usage examples:
180
181
```python
182
# Regular proxy creates MultiDict copy
183
headers = MultiDict([('Accept', 'text/html')])
184
proxy = MultiDictProxy(headers)
185
mutable_copy = proxy.copy() # Returns MultiDict
186
187
# Case-insensitive proxy creates CIMultiDict copy
188
ci_headers = CIMultiDict([('Content-Type', 'text/html')])
189
ci_proxy = CIMultiDictProxy(ci_headers)
190
ci_copy = ci_proxy.copy() # Returns CIMultiDict
191
192
# Copies are independent of original
193
mutable_copy.add('Accept', 'application/json')
194
print(len(proxy)) # Original unchanged
195
print(len(mutable_copy)) # Copy has additional item
196
```
197
198
### Live View Behavior
199
200
Proxies provide live views of their underlying multidict - changes to the original are immediately visible through the proxy.
201
202
```python
203
headers = MultiDict([('Accept', 'text/html')])
204
proxy = MultiDictProxy(headers)
205
206
print(len(proxy)) # 1
207
208
# Modify original multidict
209
headers.add('Accept', 'application/json')
210
headers.add('User-Agent', 'MyApp/1.0')
211
212
# Changes are immediately visible through proxy
213
print(len(proxy)) # 3
214
print(proxy.getall('Accept')) # ['text/html', 'application/json']
215
print('User-Agent' in proxy) # True
216
```
217
218
### Safe API Exposure
219
220
Common pattern for exposing multidict data safely in APIs.
221
222
```python
223
class HTTPRequest:
224
def __init__(self):
225
self._headers = MultiDict()
226
self._query_params = MultiDict()
227
228
def add_header(self, key: str, value: str):
229
"""Internal method for adding headers"""
230
self._headers.add(key, value)
231
232
@property
233
def headers(self) -> MultiDictProxy[str]:
234
"""
235
Read-only access to request headers.
236
237
Returns:
238
Immutable proxy to headers
239
"""
240
return MultiDictProxy(self._headers)
241
242
@property
243
def query_params(self) -> MultiDictProxy[str]:
244
"""
245
Read-only access to query parameters.
246
247
Returns:
248
Immutable proxy to query parameters
249
"""
250
return MultiDictProxy(self._query_params)
251
252
# Usage
253
request = HTTPRequest()
254
request.add_header('Accept', 'text/html')
255
256
# Clients get read-only access
257
headers = request.headers
258
print(headers['Accept']) # Works
259
260
# But cannot modify
261
# headers.add('Accept', 'application/json') # AttributeError
262
```
263
264
### Case-Insensitive Proxy Operations
265
266
`CIMultiDictProxy` provides the same interface with case-insensitive key handling.
267
268
```python
269
ci_headers = CIMultiDict([
270
('Content-Type', 'text/html'),
271
('content-length', '1234')
272
])
273
ci_proxy = CIMultiDictProxy(ci_headers)
274
275
# Case-insensitive access through proxy
276
print(ci_proxy['CONTENT-TYPE']) # 'text/html'
277
print(ci_proxy['Content-Length']) # '1234'
278
print('content-type' in ci_proxy) # True
279
280
# Original case preserved in iteration
281
for key in ci_proxy:
282
print(f"Original case: {key}")
283
# Output: Content-Type, content-length
284
```
285
286
### Proxy Chains and Nesting
287
288
Proxies can wrap other proxies and maintain their immutable guarantees.
289
290
```python
291
headers = MultiDict([('Accept', 'text/html')])
292
proxy1 = MultiDictProxy(headers)
293
proxy2 = MultiDictProxy(proxy1) # Proxy of a proxy
294
295
# Both proxies reflect changes to original
296
headers.add('User-Agent', 'MyApp/1.0')
297
print(len(proxy1)) # 2
298
print(len(proxy2)) # 2
299
300
# But neither can be modified
301
# proxy1.add('Key', 'value') # AttributeError
302
# proxy2.add('Key', 'value') # AttributeError
303
```