or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

case-insensitive.mdimmutable-proxies.mdindex.mdmutable-multidict.mdstring-types-utilities.md

immutable-proxies.mddocs/

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

```