Accept header choose() function removed and replaced by a more versatile match().

This commit is contained in:
L. Karkkainen 2023-01-30 00:45:35 +00:00
parent b8ae4285a4
commit ec25581262

View File

@ -112,6 +112,18 @@ class MediaType:
return cls(type_.lstrip(), subtype.rstrip(), **params) return cls(type_.lstrip(), subtype.rstrip(), **params)
class Matched(str):
"""A matching result of a MIME string against a MediaType."""
def __new__(cls, mime: str, m: Optional[MediaType]):
return super().__new__(cls, mime)
def __init__(self, mime: str, m: Optional[MediaType]):
self.m = m
def __repr__(self):
return f"<{self} matched {self.m}>" if self else "<no match>"
class AcceptList(list): class AcceptList(list):
"""A list of media types, as used in the Accept header. """A list of media types, as used in the Accept header.
@ -119,58 +131,40 @@ class AcceptList(list):
with the most preferred. This class is a list of `MediaType` objects, with the most preferred. This class is a list of `MediaType` objects,
that encapsulate also the q value or any other parameters. that encapsulate also the q value or any other parameters.
Three separate methods are provided for searching the list, for Two separate methods are provided for searching the list:
different use cases. The first two match wildcards with anything, - 'match' for finding the most preferred match (wildcards supported)
while `in` and other operators handle wildcards as literal values. - operator 'in' for checking explicit matches (wildcards as literals)
- `choose` for choosing one of its arguments to use in response.
- 'match' for the best MediaType of the accept header, or None.
- operator 'in' for checking explicit matches (wildcards as is).
""" """
def match(self, *media_types: List[str]) -> Optional[MediaType]: def match(self, *mimes: List[str]) -> Matched:
"""Find a media type accepted by the client. """Find a media type accepted by the client.
This method can be used to find which of the media types requested by This method can be used to find which of the media types requested by
the client is most preferred while matching any of the arguments. the client is most preferred against the ones given as arguments.
Wildcards are supported. Most clients include */* as the last item in The ordering of preference is set by:
their Accept header, so this method will always return a match unless 1. The q values on the Accept header, and those being equal,
a custom header is used, but it may return a more specific match if 2. The order of the arguments (first is most preferred), and
the client has requested any suitable types explicitly. 3. The first matching entry on the Accept header.
@param media_types: Any type/subtype strings to find. Wildcards are matched both ways. A match is usually found, as the
@return A matching `MediaType` or `None` if nothing matches. Accept headers typically include `*/*`, in particular if the header
is missing, is not manually set, or if the client is a browser.
Note: the returned object behaves as a string of the mime argument
that matched, and is empty/falsy if no match was found. The matched
header entry `MediaType` or `None` is available as the `m` attribute.
@param mimes: Any MIME types to search for in order of preference.
@return A match object with the mime string and the MediaType object.
""" """
for accepted in self: l = sorted([
if any(accepted.match(mt) for mt in media_types): (-acc.q, i, j, mime, acc) # Sort by -q, i, j
return accepted for j, acc in enumerate(self)
for i, mime in enumerate(mimes)
if acc.match(mime)
def choose(self, *media_types: List[str], omit_wildcard=True) -> str: ])
"""Choose a most suitable media type based on the Accept header. return Matched(*(l[0][3:] if l else ("", None)))
This is the recommended way to choose a response format based on the
Accept header. The q values and the order of the Accept header are
respected, and if due to wildcards multiple arguments match the same
accept header entry, the first one matching is returned.
Should none of the arguments be acceptable, the first argument is
returned with the q value of 0.0 (i.e. the lowest possible).
@param media_types: Any type/subtype strings to find.
@param omit_wildcard: Ignore full wildcard */* in the Accept header.
@return A tuple of one of the arguments and the q value of the match.
"""
# Find the preferred MediaType if any match
for accepted in self:
if omit_wildcard and accepted.is_wildcard:
continue
for mt in media_types:
if accepted.match(mt):
return mt, accepted.q
# Fall back to the first argument
return media_types[0], 0.0
def parse_accept(accept: str) -> AcceptList: def parse_accept(accept: str) -> AcceptList: