@@ -140,7 +140,7 @@ class _MediaRange:
140
140
141
141
__slots__ = ('main_type' , 'subtype' , 'quality' , 'params' )
142
142
143
- _NOT_MATCHING = (- 1 , - 1 , - 1 , 0.0 )
143
+ _NOT_MATCHING = (- 1 , - 1 , - 1 , - 1 , 0.0 )
144
144
145
145
_Q_VALUE_ERROR_MESSAGE = (
146
146
'If provided, the q parameter must be a real number '
@@ -186,7 +186,7 @@ def parse(cls, media_range: str) -> _MediaRange:
186
186
187
187
return cls (main_type , subtype , quality , params )
188
188
189
- def match_score (self , media_type : _MediaType ) -> Tuple [int , int , int , float ]:
189
+ def match_score (self , media_type : _MediaType ) -> Tuple [int , int , int , int , float ]:
190
190
if self .main_type == '*' or media_type .main_type == '*' :
191
191
main_matches = 0
192
192
elif self .main_type != media_type .main_type :
@@ -203,14 +203,15 @@ def match_score(self, media_type: _MediaType) -> Tuple[int, int, int, float]:
203
203
204
204
mr_pnames = frozenset (self .params )
205
205
mt_pnames = frozenset (media_type .params )
206
- param_score = - len (mr_pnames .symmetric_difference (mt_pnames ))
206
+
207
+ exact_match = 0 if mr_pnames .symmetric_difference (mt_pnames ) else 1
208
+
207
209
matching = mr_pnames & mt_pnames
208
210
for pname in matching :
209
211
if self .params [pname ] != media_type .params [pname ]:
210
212
return self ._NOT_MATCHING
211
- param_score += len (matching )
212
213
213
- return (main_matches , sub_matches , param_score , self .quality )
214
+ return (main_matches , sub_matches , exact_match , len ( matching ) , self .quality )
214
215
215
216
def __repr__ (self ) -> str :
216
217
q = self .quality
@@ -235,6 +236,42 @@ def quality(media_type: str, header: str) -> float:
235
236
Media-ranges are parsed from the provided `header` value according to
236
237
RFC 9110, Section 12.5.1 (the ``Accept`` header).
237
238
239
+ The provided `media_type` is matched against each of the parsed media
240
+ ranges, and the fitness of each match is assessed as follows
241
+ (in the decreasing priority list of criteria):
242
+
243
+ 1. Do the main types (as in ``type/subtype``) match?
244
+
245
+ The types must either match exactly, or as wildcard (``*``).
246
+ The matches involving a wildcard are prioritized lower.
247
+
248
+ 2. Do the subtypes (as in ``type/subtype``) match?
249
+
250
+ The subtypes must either match exactly, or as wildcard (``*``).
251
+ The matches involving a wildcard are prioritized lower.
252
+
253
+ 3. Do the parameters match exactly?
254
+
255
+ If all the parameter names and values (if any) between the media range
256
+ and media type match exactly, such match is prioritized higher than
257
+ matches involving extraneous parameters on either side.
258
+
259
+ Note that if parameter names match, the corresponding values must also
260
+ be equal, or the provided media type is considered not to match the
261
+ media range in question at all.
262
+
263
+ 4. The number of matching parameters.
264
+
265
+ 5. Finally, if two or more best media range matches are equally fit
266
+ according to all of the above criteria (1) through (4), the highest
267
+ quality (i.e., the value of the ``q`` parameter) of these is returned.
268
+
269
+ Note:
270
+ With the exception of evaluating the exact parameter match (3), the
271
+ number of extraneous parameters (i.e. where the names are only present
272
+ in the media type, or only in the media range) currently does not
273
+ influence the described specificity sort order.
274
+
238
275
Args:
239
276
media_type: The Internet media type to match against the provided
240
277
HTTP ``Accept`` header value.
@@ -254,7 +291,8 @@ def quality(media_type: str, header: str) -> float:
254
291
255
292
256
293
def best_match (media_types : Iterable [str ], header : str ) -> Optional [str ]:
257
- """Choose media type with the highest quality from a list of candidates.
294
+ """Choose media type with the highest :func:`quality` from a list of
295
+ candidates.
258
296
259
297
Args:
260
298
media_types: An iterable over one or more Internet media types
0 commit comments