0
# MIME Type Parsing and Content Negotiation
1
2
The mimeparse module provides utilities for parsing MIME types, media ranges, and performing content negotiation. It helps applications determine the best content type to serve based on client preferences and server capabilities.
3
4
## Capabilities
5
6
### MIME Type Parsing
7
8
Parse MIME type strings into structured components for analysis and comparison.
9
10
```python { .api }
11
def parse_mime_type(mime_type):
12
"""
13
Parse a MIME type string into its components.
14
15
Args:
16
mime_type (str): MIME type string (e.g., 'text/html; charset=utf-8')
17
18
Returns:
19
tuple: (type, subtype, params) where:
20
- type (str): Main type (e.g., 'text')
21
- subtype (str): Subtype (e.g., 'html')
22
- params (dict): Parameters (e.g., {'charset': 'utf-8'})
23
"""
24
25
def parse_media_range(range):
26
"""
27
Parse a media range string into components with quality factor.
28
29
Args:
30
range (str): Media range string (e.g., 'text/html;q=0.9')
31
32
Returns:
33
tuple: (type, subtype, params, quality) where:
34
- type (str): Main type
35
- subtype (str): Subtype
36
- params (dict): Parameters excluding quality
37
- quality (float): Quality factor (0.0 to 1.0)
38
"""
39
```
40
41
### Content Negotiation
42
43
Determine the best content type match based on client preferences and server capabilities.
44
45
```python { .api }
46
def quality_parsed(mime_type_list, ranges):
47
"""
48
Calculate quality for parsed MIME types against client ranges.
49
50
Args:
51
mime_type_list (list): List of (type, subtype, params) tuples
52
ranges (list): List of parsed media ranges with quality factors
53
54
Returns:
55
float: Quality factor (0.0 to 1.0) for the best match
56
"""
57
58
def quality(mime_type, ranges):
59
"""
60
Calculate quality of a MIME type against client Accept header ranges.
61
62
Args:
63
mime_type (str): MIME type to evaluate (e.g., 'application/json')
64
ranges (str): Accept header value with media ranges and quality factors
65
66
Returns:
67
float: Quality factor (0.0 to 1.0) indicating preference level
68
"""
69
70
def best_match(supported, header):
71
"""
72
Find the best matching MIME type from supported types.
73
74
Args:
75
supported (list): List of MIME types supported by the server
76
header (str): Client Accept header value
77
78
Returns:
79
str: Best matching MIME type from supported list, or None if no match
80
"""
81
```
82
83
## Usage Examples
84
85
### Basic MIME Type Parsing
86
87
```python
88
from googleapiclient.mimeparse import parse_mime_type, parse_media_range
89
90
# Parse a simple MIME type
91
mime_type = "text/html"
92
type_, subtype, params = parse_mime_type(mime_type)
93
print(f"Type: {type_}, Subtype: {subtype}, Params: {params}")
94
# Output: Type: text, Subtype: html, Params: {}
95
96
# Parse MIME type with parameters
97
mime_type_with_params = "text/html; charset=utf-8; boundary=something"
98
type_, subtype, params = parse_mime_type(mime_type_with_params)
99
print(f"Type: {type_}, Subtype: {subtype}, Params: {params}")
100
# Output: Type: text, Subtype: html, Params: {'charset': 'utf-8', 'boundary': 'something'}
101
102
# Parse media range with quality factor
103
media_range = "application/json;q=0.8"
104
type_, subtype, params, quality = parse_media_range(media_range)
105
print(f"Quality: {quality}")
106
# Output: Quality: 0.8
107
```
108
109
### Content Negotiation
110
111
```python
112
from googleapiclient.mimeparse import best_match, quality
113
114
# Server-supported MIME types
115
supported_types = [
116
'application/json',
117
'application/xml',
118
'text/html',
119
'text/plain'
120
]
121
122
# Client Accept headers (examples)
123
accept_headers = [
124
'application/json, application/xml;q=0.9, */*;q=0.1',
125
'text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8',
126
'application/xml, application/json;q=0.8, text/plain;q=0.5',
127
'*/*'
128
]
129
130
for accept_header in accept_headers:
131
best = best_match(supported_types, accept_header)
132
print(f"Accept: {accept_header}")
133
print(f"Best match: {best}")
134
print()
135
136
# Check quality of specific MIME type
137
mime_type = 'application/json'
138
accept_header = 'application/json;q=0.9, application/xml;q=0.8, */*;q=0.1'
139
quality_score = quality(mime_type, accept_header)
140
print(f"Quality of {mime_type}: {quality_score}")
141
```
142
143
### API Response Content Negotiation
144
145
```python
146
from googleapiclient.mimeparse import best_match
147
from googleapiclient import discovery
148
from flask import Flask, request
149
150
app = Flask(__name__)
151
152
class ContentNegotiator:
153
"""Handle content negotiation for API responses."""
154
155
def __init__(self):
156
self.supported_formats = {
157
'application/json': self._format_json,
158
'application/xml': self._format_xml,
159
'text/csv': self._format_csv,
160
'text/plain': self._format_plain
161
}
162
163
def negotiate_response_format(self, data, accept_header):
164
"""
165
Negotiate the best response format based on Accept header.
166
167
Args:
168
data: Response data to format
169
accept_header (str): Client Accept header
170
171
Returns:
172
tuple: (formatted_data, content_type)
173
"""
174
supported_types = list(self.supported_formats.keys())
175
best_type = best_match(supported_types, accept_header)
176
177
if not best_type:
178
# Default to JSON if no match
179
best_type = 'application/json'
180
181
formatter = self.supported_formats[best_type]
182
formatted_data = formatter(data)
183
184
return formatted_data, best_type
185
186
def _format_json(self, data):
187
"""Format data as JSON."""
188
import json
189
return json.dumps(data, indent=2)
190
191
def _format_xml(self, data):
192
"""Format data as XML."""
193
# Simplified XML formatting
194
def dict_to_xml(d, root_name='root'):
195
xml = f'<{root_name}>'
196
for key, value in d.items():
197
if isinstance(value, dict):
198
xml += dict_to_xml(value, key)
199
elif isinstance(value, list):
200
for item in value:
201
xml += dict_to_xml(item, key[:-1] if key.endswith('s') else key)
202
else:
203
xml += f'<{key}>{value}</{key}>'
204
xml += f'</{root_name}>'
205
return xml
206
207
return dict_to_xml(data, 'response')
208
209
def _format_csv(self, data):
210
"""Format data as CSV."""
211
import csv
212
import io
213
214
output = io.StringIO()
215
if isinstance(data, list) and data:
216
# Assume list of dictionaries
217
fieldnames = data[0].keys()
218
writer = csv.DictWriter(output, fieldnames=fieldnames)
219
writer.writeheader()
220
writer.writerows(data)
221
elif isinstance(data, dict):
222
# Single dictionary
223
writer = csv.DictWriter(output, fieldnames=data.keys())
224
writer.writeheader()
225
writer.writerow(data)
226
227
return output.getvalue()
228
229
def _format_plain(self, data):
230
"""Format data as plain text."""
231
return str(data)
232
233
# Flask route with content negotiation
234
negotiator = ContentNegotiator()
235
236
@app.route('/api/messages')
237
def get_messages():
238
# Get messages from Gmail API
239
service = discovery.build('gmail', 'v1', credentials=credentials)
240
try:
241
messages_result = service.users().messages().list(
242
userId='me',
243
maxResults=10
244
).execute()
245
246
messages = messages_result.get('messages', [])
247
248
# Negotiate response format
249
accept_header = request.headers.get('Accept', 'application/json')
250
formatted_data, content_type = negotiator.negotiate_response_format(
251
messages,
252
accept_header
253
)
254
255
return formatted_data, 200, {'Content-Type': content_type}
256
257
except Exception as e:
258
error_data = {'error': str(e)}
259
accept_header = request.headers.get('Accept', 'application/json')
260
formatted_error, content_type = negotiator.negotiate_response_format(
261
error_data,
262
accept_header
263
)
264
return formatted_error, 500, {'Content-Type': content_type}
265
266
if __name__ == '__main__':
267
app.run(debug=True)
268
```
269
270
### MIME Type Validation
271
272
```python
273
from googleapiclient.mimeparse import parse_mime_type
274
275
class MimeTypeValidator:
276
"""Validate and analyze MIME types."""
277
278
def __init__(self):
279
self.allowed_types = {
280
'text': ['plain', 'html', 'csv', 'css', 'javascript'],
281
'application': ['json', 'xml', 'pdf', 'zip', 'octet-stream'],
282
'image': ['jpeg', 'png', 'gif', 'svg+xml'],
283
'audio': ['mpeg', 'wav', 'ogg'],
284
'video': ['mp4', 'mpeg', 'quicktime']
285
}
286
287
def is_valid_mime_type(self, mime_type):
288
"""
289
Check if a MIME type is valid and allowed.
290
291
Args:
292
mime_type (str): MIME type to validate
293
294
Returns:
295
tuple: (is_valid, reason)
296
"""
297
try:
298
type_, subtype, params = parse_mime_type(mime_type)
299
except Exception as e:
300
return False, f"Invalid MIME type format: {e}"
301
302
if type_ not in self.allowed_types:
303
return False, f"Type '{type_}' not allowed"
304
305
if subtype not in self.allowed_types[type_]:
306
return False, f"Subtype '{subtype}' not allowed for type '{type_}'"
307
308
return True, "Valid MIME type"
309
310
def get_file_categories(self, mime_types):
311
"""Categorize MIME types by file type."""
312
categories = {
313
'documents': [],
314
'images': [],
315
'media': [],
316
'data': [],
317
'other': []
318
}
319
320
for mime_type in mime_types:
321
try:
322
type_, subtype, _ = parse_mime_type(mime_type)
323
324
if type_ == 'text' or (type_ == 'application' and subtype in ['pdf', 'msword']):
325
categories['documents'].append(mime_type)
326
elif type_ == 'image':
327
categories['images'].append(mime_type)
328
elif type_ in ['audio', 'video']:
329
categories['media'].append(mime_type)
330
elif type_ == 'application' and subtype in ['json', 'xml', 'csv']:
331
categories['data'].append(mime_type)
332
else:
333
categories['other'].append(mime_type)
334
335
except Exception:
336
categories['other'].append(mime_type)
337
338
return categories
339
340
# Usage
341
validator = MimeTypeValidator()
342
343
test_mime_types = [
344
'text/plain',
345
'application/json',
346
'image/jpeg',
347
'invalid/mime/type',
348
'application/unknown'
349
]
350
351
for mime_type in test_mime_types:
352
is_valid, reason = validator.is_valid_mime_type(mime_type)
353
print(f"{mime_type}: {'✓' if is_valid else '✗'} - {reason}")
354
355
# Categorize file types
356
file_mime_types = [
357
'text/plain', 'application/pdf', 'image/jpeg',
358
'audio/mpeg', 'application/json', 'text/html'
359
]
360
361
categories = validator.get_file_categories(file_mime_types)
362
for category, types in categories.items():
363
if types:
364
print(f"{category.title()}: {', '.join(types)}")
365
```
366
367
### Advanced Content Negotiation
368
369
```python
370
from googleapiclient.mimeparse import quality_parsed, parse_media_range
371
372
class AdvancedContentNegotiator:
373
"""Advanced content negotiation with custom scoring."""
374
375
def __init__(self):
376
self.type_preferences = {
377
'application/json': 1.0,
378
'application/xml': 0.8,
379
'text/html': 0.6,
380
'text/plain': 0.4
381
}
382
383
def negotiate_with_scoring(self, supported_types, accept_header):
384
"""
385
Negotiate content type with custom server preferences.
386
387
Args:
388
supported_types (list): MIME types supported by server
389
accept_header (str): Client Accept header
390
391
Returns:
392
tuple: (best_type, combined_score)
393
"""
394
# Parse client preferences
395
client_ranges = []
396
for range_str in accept_header.split(','):
397
range_str = range_str.strip()
398
try:
399
type_, subtype, params, quality = parse_media_range(range_str)
400
client_ranges.append((type_, subtype, params, quality))
401
except Exception:
402
continue
403
404
best_match = None
405
best_score = 0.0
406
407
for mime_type in supported_types:
408
# Parse server MIME type
409
try:
410
from googleapiclient.mimeparse import parse_mime_type
411
type_, subtype, params = parse_mime_type(mime_type)
412
server_parsed = [(type_, subtype, params)]
413
414
# Calculate client quality
415
client_quality = quality_parsed(server_parsed, client_ranges)
416
417
# Apply server preference
418
server_preference = self.type_preferences.get(mime_type, 0.5)
419
420
# Combined score
421
combined_score = client_quality * server_preference
422
423
if combined_score > best_score:
424
best_score = combined_score
425
best_match = mime_type
426
427
except Exception:
428
continue
429
430
return best_match, best_score
431
432
def get_negotiation_details(self, supported_types, accept_header):
433
"""Get detailed negotiation information."""
434
details = []
435
436
for mime_type in supported_types:
437
try:
438
from googleapiclient.mimeparse import quality
439
client_quality = quality(mime_type, accept_header)
440
server_preference = self.type_preferences.get(mime_type, 0.5)
441
combined_score = client_quality * server_preference
442
443
details.append({
444
'mime_type': mime_type,
445
'client_quality': client_quality,
446
'server_preference': server_preference,
447
'combined_score': combined_score
448
})
449
except Exception:
450
continue
451
452
# Sort by combined score
453
details.sort(key=lambda x: x['combined_score'], reverse=True)
454
return details
455
456
# Usage
457
negotiator = AdvancedContentNegotiator()
458
459
supported = ['application/json', 'application/xml', 'text/html', 'text/plain']
460
accept_header = 'text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8'
461
462
best_type, score = negotiator.negotiate_with_scoring(supported, accept_header)
463
print(f"Best match: {best_type} (score: {score:.3f})")
464
465
# Get detailed breakdown
466
details = negotiator.get_negotiation_details(supported, accept_header)
467
print("\nNegotiation details:")
468
for detail in details:
469
print(f" {detail['mime_type']}: "
470
f"client={detail['client_quality']:.2f}, "
471
f"server={detail['server_preference']:.2f}, "
472
f"combined={detail['combined_score']:.3f}")
473
```
474
475
### MIME Type Utilities
476
477
```python
478
from googleapiclient.mimeparse import parse_mime_type, best_match
479
480
class MimeTypeUtils:
481
"""Utility functions for working with MIME types."""
482
483
@staticmethod
484
def is_text_type(mime_type):
485
"""Check if MIME type is a text type."""
486
try:
487
type_, _, _ = parse_mime_type(mime_type)
488
return type_ == 'text'
489
except Exception:
490
return False
491
492
@staticmethod
493
def is_binary_type(mime_type):
494
"""Check if MIME type is a binary type."""
495
try:
496
type_, subtype, _ = parse_mime_type(mime_type)
497
# Common binary types
498
binary_types = {
499
'image': True,
500
'audio': True,
501
'video': True,
502
'application': subtype not in ['json', 'xml', 'javascript', 'css']
503
}
504
return binary_types.get(type_, False)
505
except Exception:
506
return False
507
508
@staticmethod
509
def get_preferred_extension(mime_type):
510
"""Get preferred file extension for MIME type."""
511
extensions = {
512
'text/plain': '.txt',
513
'text/html': '.html',
514
'text/css': '.css',
515
'application/json': '.json',
516
'application/xml': '.xml',
517
'application/pdf': '.pdf',
518
'image/jpeg': '.jpg',
519
'image/png': '.png',
520
'image/gif': '.gif',
521
'audio/mpeg': '.mp3',
522
'video/mp4': '.mp4'
523
}
524
return extensions.get(mime_type, '')
525
526
@staticmethod
527
def select_encoding(mime_type):
528
"""Select appropriate encoding for MIME type."""
529
if MimeTypeUtils.is_text_type(mime_type):
530
return 'utf-8'
531
else:
532
return 'binary'
533
534
# Usage examples
535
utils = MimeTypeUtils()
536
537
test_types = [
538
'text/plain',
539
'application/json',
540
'image/jpeg',
541
'application/pdf',
542
'audio/mpeg'
543
]
544
545
for mime_type in test_types:
546
is_text = utils.is_text_type(mime_type)
547
is_binary = utils.is_binary_type(mime_type)
548
extension = utils.get_preferred_extension(mime_type)
549
encoding = utils.select_encoding(mime_type)
550
551
print(f"{mime_type}:")
552
print(f" Text: {is_text}, Binary: {is_binary}")
553
print(f" Extension: {extension}, Encoding: {encoding}")
554
print()
555
```