0
# String Types and Utilities
1
2
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.
3
4
## Capabilities
5
6
### Case-Insensitive String Type
7
8
The `istr` class provides case-insensitive string functionality for use with multidict keys and general string operations.
9
10
```python { .api }
11
class istr(str):
12
"""
13
Case-insensitive string subclass.
14
15
Behaves like regular strings but compares equal regardless of case.
16
Used internally by CIMultiDict and CIMultiDictProxy for key handling.
17
"""
18
19
def __new__(cls, val=''):
20
"""
21
Create a case-insensitive string.
22
23
Parameters:
24
- val: String value to convert (can be str, bytes, or other objects)
25
26
Returns:
27
New istr instance
28
"""
29
30
# Special attributes for internal use
31
__is_istr__: bool = True
32
__istr_identity__: Optional[str] = None
33
```
34
35
Usage examples:
36
37
```python
38
from multidict import istr
39
40
# Create case-insensitive strings
41
s1 = istr('Content-Type')
42
s2 = istr('content-type')
43
s3 = istr('CONTENT-TYPE')
44
45
# All compare equal regardless of case
46
print(s1 == s2) # True
47
print(s2 == s3) # True
48
print(s1 == s3) # True
49
50
# But regular string comparison still case-sensitive
51
print(str(s1) == str(s2)) # False (different case)
52
53
# Original case is preserved
54
print(str(s1)) # 'Content-Type'
55
print(str(s2)) # 'content-type'
56
57
# Can be used with regular strings too
58
print(s1 == 'content-type') # True
59
print(s1 == 'CONTENT-TYPE') # True
60
```
61
62
### String Type Detection
63
64
Check if a string is a case-insensitive string type.
65
66
```python
67
from multidict import istr
68
69
regular_str = 'Hello'
70
case_insensitive_str = istr('Hello')
71
72
# Check string type
73
print(hasattr(regular_str, '__is_istr__')) # False
74
print(hasattr(case_insensitive_str, '__is_istr__')) # True
75
print(case_insensitive_str.__is_istr__) # True
76
77
# Type checking
78
print(isinstance(case_insensitive_str, str)) # True (is a string)
79
print(isinstance(case_insensitive_str, istr)) # True (is istr)
80
print(type(case_insensitive_str).__name__) # 'istr'
81
```
82
83
### Manual Case-Insensitive Operations
84
85
Using `istr` for custom case-insensitive operations outside of multidict.
86
87
```python
88
from multidict import istr
89
90
# Case-insensitive set operations
91
headers_seen = set()
92
for header_name in ['Content-Type', 'content-length', 'ACCEPT']:
93
istr_header = istr(header_name)
94
if istr_header not in headers_seen:
95
headers_seen.add(istr_header)
96
print(f"New header: {header_name}")
97
98
# Case-insensitive dictionary keys (manual approach)
99
class CaseInsensitiveDict(dict):
100
def __setitem__(self, key, value):
101
super().__setitem__(istr(key), value)
102
103
def __getitem__(self, key):
104
return super().__getitem__(istr(key))
105
106
def __contains__(self, key):
107
return super().__contains__(istr(key))
108
109
ci_dict = CaseInsensitiveDict()
110
ci_dict['Content-Type'] = 'text/html'
111
print(ci_dict['content-type']) # 'text/html'
112
```
113
114
### Version Tracking Utility
115
116
The `getversion` function provides access to internal version numbers for change detection and optimization.
117
118
```python { .api }
119
def getversion(md: Union[MultiDict[object], MultiDictProxy[object]]) -> int:
120
"""
121
Get the internal version number of a multidict.
122
123
Parameters:
124
- md: MultiDict or MultiDictProxy instance
125
126
Returns:
127
Integer version number that increments with each modification
128
129
Raises:
130
TypeError if parameter is not a multidict or proxy
131
132
The version number is used internally for view invalidation and can be
133
useful for caching and change detection in applications.
134
"""
135
```
136
137
Usage examples:
138
139
```python
140
from multidict import MultiDict, MultiDictProxy, getversion
141
142
# Track changes with version numbers
143
headers = MultiDict([('Accept', 'text/html')])
144
initial_version = getversion(headers)
145
print(f"Initial version: {initial_version}")
146
147
# Version increments with modifications
148
headers.add('User-Agent', 'MyApp/1.0')
149
after_add_version = getversion(headers)
150
print(f"After add: {after_add_version}") # Higher number
151
152
headers['Accept'] = 'application/json'
153
after_set_version = getversion(headers)
154
print(f"After set: {after_set_version}") # Even higher
155
156
# Proxy has same version as underlying multidict
157
proxy = MultiDictProxy(headers)
158
print(f"Proxy version: {getversion(proxy)}") # Same as headers
159
160
# Use for change detection
161
cached_version = getversion(headers)
162
cached_data = headers.copy()
163
164
# Later... check if data changed
165
if getversion(headers) != cached_version:
166
print("Headers changed, refresh cache")
167
cached_data = headers.copy()
168
cached_version = getversion(headers)
169
```
170
171
### Version-Based Caching Pattern
172
173
Common pattern for implementing efficient caching with version tracking.
174
175
```python
176
from multidict import MultiDict, getversion
177
178
class CachedHeaderProcessor:
179
def __init__(self):
180
self._cache = {}
181
self._cache_versions = {}
182
183
def process_headers(self, headers: MultiDict) -> str:
184
"""
185
Process headers with caching based on version tracking.
186
187
Parameters:
188
- headers: MultiDict to process
189
190
Returns:
191
Processed header string
192
"""
193
headers_id = id(headers)
194
current_version = getversion(headers)
195
196
# Check if we have cached result for this version
197
if (headers_id in self._cache and
198
self._cache_versions.get(headers_id) == current_version):
199
return self._cache[headers_id]
200
201
# Process headers (expensive operation)
202
result = self._expensive_processing(headers)
203
204
# Cache result with version
205
self._cache[headers_id] = result
206
self._cache_versions[headers_id] = current_version
207
208
return result
209
210
def _expensive_processing(self, headers: MultiDict) -> str:
211
# Simulate expensive processing
212
processed_items = []
213
for key in headers:
214
values = headers.getall(key)
215
processed_items.append(f"{key}: {', '.join(values)}")
216
return '\n'.join(processed_items)
217
218
# Usage
219
processor = CachedHeaderProcessor()
220
headers = MultiDict([('Accept', 'text/html'), ('User-Agent', 'MyApp/1.0')])
221
222
# First call - processes and caches
223
result1 = processor.process_headers(headers) # Expensive processing
224
225
# Second call - returns cached result
226
result2 = processor.process_headers(headers) # Fast cached lookup
227
228
# Modify headers - version changes
229
headers.add('Accept', 'application/json')
230
231
# Third call - detects change and reprocesses
232
result3 = processor.process_headers(headers) # Expensive processing again
233
```
234
235
### Deprecated upstr Alias
236
237
The `upstr` alias is maintained for backward compatibility but is deprecated.
238
239
```python { .api }
240
upstr = istr # Deprecated alias for istr
241
```
242
243
Usage guidance:
244
245
```python
246
from multidict import istr, upstr
247
248
# Preferred - use istr directly
249
preferred = istr('Content-Type')
250
251
# Deprecated - upstr is an alias for istr
252
deprecated = upstr('Content-Type') # Same as istr('Content-Type')
253
254
# Both work the same way
255
print(preferred == deprecated) # True
256
257
# But use istr in new code
258
recommended_usage = istr('header-name')
259
```
260
261
### Integration with Standard Library
262
263
`istr` integrates seamlessly with Python's standard library while maintaining case-insensitive behavior.
264
265
```python
266
from multidict import istr
267
import re
268
269
# Works with regular expressions
270
pattern = re.compile(r'content-.*', re.IGNORECASE)
271
header = istr('Content-Type')
272
273
# String methods work normally
274
print(header.lower()) # 'content-type'
275
print(header.upper()) # 'CONTENT-TYPE'
276
print(header.title()) # 'Content-Type'
277
278
# But comparisons are still case-insensitive
279
print(header == 'CONTENT-TYPE') # True
280
281
# Hashing is case-insensitive
282
header_set = {istr('Content-Type'), istr('content-type')}
283
print(len(header_set)) # 1 (treated as same key)
284
285
# But regular strings hash differently
286
mixed_set = {'Content-Type', 'content-type', istr('CONTENT-TYPE')}
287
print(len(mixed_set)) # 2 (regular strings hash differently)
288
```