1
+ from __future__ import annotations
2
+
1
3
__version__ = '2.0.0'
2
4
__author__ = 'Joe Gregorio'
3
5
4
6
__license__ = 'MIT License'
5
7
__credits__ = ''
6
8
9
+ from typing import Dict , Generator , Iterable , Tuple , Union
10
+
7
11
8
12
class MimeTypeParseException (ValueError ):
9
13
pass
10
14
11
15
12
16
# Vendored version of cgi._parseparam from Python 3.11 (deprecated and slated
13
17
# for removal in 3.13)
14
- def _parseparam (s ) :
18
+ def _parseparam (s : str ) -> Generator [ str , None , None ] :
15
19
while s [:1 ] == ';' :
16
20
s = s [1 :]
17
21
end = s .find (';' )
@@ -26,7 +30,7 @@ def _parseparam(s):
26
30
27
31
# Vendored version of cgi.parse_header from Python 3.11 (deprecated and slated
28
32
# for removal in 3.13)
29
- def _parse_header (line ) :
33
+ def _parse_header (line : str ) -> Tuple [ str , Dict [ str , str ]] :
30
34
"""Parse a Content-type like header.
31
35
32
36
Return the main content-type and a dictionary of options.
@@ -47,7 +51,7 @@ def _parse_header(line):
47
51
return key , pdict
48
52
49
53
50
- def parse_mime_type (mime_type ) :
54
+ def parse_mime_type (mime_type : str ) -> Tuple [ str , str , Dict [ str , str ]] :
51
55
"""Parses a mime-type into its component parts.
52
56
53
57
Carves up a mime-type and returns a tuple of the (type, subtype, params)
@@ -56,8 +60,6 @@ def parse_mime_type(mime_type):
56
60
into:
57
61
58
62
('application', 'xhtml', {'q', '0.5'})
59
-
60
- :rtype: (str,str,dict)
61
63
"""
62
64
full_type , params = _parse_header (mime_type )
63
65
# Java URLConnection class sends an Accept header that includes a
@@ -75,7 +77,7 @@ def parse_mime_type(mime_type):
75
77
return (type .strip (), subtype .strip (), params )
76
78
77
79
78
- def parse_media_range (range ) :
80
+ def parse_media_range (range : str ) -> Tuple [ str , str , Dict [ str , str ]] :
79
81
"""Parse a media-range into its component parts.
80
82
81
83
Carves up a media range and returns a tuple of the (type, subtype,
@@ -88,11 +90,9 @@ def parse_media_range(range):
88
90
In addition this function also guarantees that there is a value for 'q'
89
91
in the params dictionary, filling it in with a proper default if
90
92
necessary.
91
-
92
- :rtype: (str,str,dict)
93
93
"""
94
94
(type , subtype , params ) = parse_mime_type (range )
95
- params .setdefault ('q' , params .pop ('Q' , None )) # q is case insensitive
95
+ params .setdefault ('q' , params .pop ('Q' , '1' )) # q is case insensitive
96
96
try :
97
97
if not params ['q' ] or not 0 <= float (params ['q' ]) <= 1 :
98
98
params ['q' ] = '1'
@@ -102,19 +102,20 @@ def parse_media_range(range):
102
102
return (type , subtype , params )
103
103
104
104
105
- def quality_and_fitness_parsed (mime_type , parsed_ranges ):
105
+ def quality_and_fitness_parsed (
106
+ mime_type : str ,
107
+ parsed_ranges : Iterable [Tuple [str , str , Dict [str , str ]]],
108
+ ) -> Tuple [float , float ]:
106
109
"""Find the best match for a mime-type amongst parsed media-ranges.
107
110
108
111
Find the best match for a given mime-type against a list of media_ranges
109
112
that have already been parsed by parse_media_range(). Returns a tuple of
110
113
the fitness value and the value of the 'q' quality parameter of the best
111
114
match, or (-1, 0) if no match was found. Just as for quality_parsed(),
112
115
'parsed_ranges' must be a list of parsed media ranges.
113
-
114
- :rtype: (float,int)
115
116
"""
116
- best_fitness = - 1
117
- best_fit_q = 0
117
+ best_fitness = - 1.
118
+ best_fit_q : Union [ float , str ] = 0.
118
119
(target_type , target_subtype , target_params ) = \
119
120
parse_media_range (mime_type )
120
121
@@ -129,10 +130,10 @@ def quality_and_fitness_parsed(mime_type, parsed_ranges):
129
130
if type_match and subtype_match :
130
131
131
132
# 100 points if the type matches w/o a wildcard
132
- fitness = type == target_type and 100 or 0
133
+ fitness = type == target_type and 100. or 0.
133
134
134
135
# 10 points if the subtype matches w/o a wildcard
135
- fitness += subtype == target_subtype and 10 or 0
136
+ fitness += subtype == target_subtype and 10. or 0.
136
137
137
138
# 1 bonus point for each matching param besides "q"
138
139
param_matches = sum ([
@@ -151,39 +152,35 @@ def quality_and_fitness_parsed(mime_type, parsed_ranges):
151
152
return float (best_fit_q ), best_fitness
152
153
153
154
154
- def quality_parsed (mime_type , parsed_ranges ) :
155
+ def quality_parsed (mime_type : str , parsed_ranges : Iterable [ Tuple [ str , str , Dict [ str , str ]]]) -> float :
155
156
"""Find the best match for a mime-type amongst parsed media-ranges.
156
157
157
158
Find the best match for a given mime-type against a list of media_ranges
158
159
that have already been parsed by parse_media_range(). Returns the 'q'
159
160
quality parameter of the best match, 0 if no match was found. This function
160
161
behaves the same as quality() except that 'parsed_ranges' must be a list of
161
162
parsed media ranges.
162
-
163
- :rtype: float
164
163
"""
165
164
166
165
return quality_and_fitness_parsed (mime_type , parsed_ranges )[0 ]
167
166
168
167
169
- def quality (mime_type , ranges ) :
168
+ def quality (mime_type : str , ranges : str ) -> float :
170
169
"""Return the quality ('q') of a mime-type against a list of media-ranges.
171
170
172
171
Returns the quality 'q' of a mime-type when compared against the
173
172
media-ranges in ranges. For example:
174
173
175
- >>> quality('text/html','text/*;q=0.3, text/html;q=0.7,
174
+ >>> quality('text/html','text/*;q=0.3, text/html;q=0.7' ,
176
175
text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5')
177
176
0.7
178
-
179
- :rtype: float
180
177
"""
181
178
parsed_ranges = [parse_media_range (r ) for r in ranges .split (',' )]
182
179
183
180
return quality_parsed (mime_type , parsed_ranges )
184
181
185
182
186
- def best_match (supported , header ) :
183
+ def best_match (supported : Iterable [ str ] , header : str ) -> str :
187
184
"""Return mime-type with the highest quality ('q') from list of candidates.
188
185
189
186
Takes a list of supported mime-types and finds the best match for all the
@@ -196,8 +193,6 @@ def best_match(supported, header):
196
193
>>> best_match(['application/xbel+xml', 'text/xml'],
197
194
'text/*;q=0.5,*/*; q=0.1')
198
195
'text/xml'
199
-
200
- :rtype: str
201
196
"""
202
197
split_header = _filter_blank (header .split (',' ))
203
198
parsed_header = [parse_media_range (r ) for r in split_header ]
@@ -215,7 +210,7 @@ def best_match(supported, header):
215
210
return weighted_matches [- 1 ][0 ][0 ] and weighted_matches [- 1 ][2 ] or ''
216
211
217
212
218
- def _filter_blank (i ) :
213
+ def _filter_blank (i : Iterable [ str ]) -> Generator [ str , None , None ] :
219
214
"""Return all non-empty items in the list."""
220
215
for s in i :
221
216
if s .strip ():
0 commit comments