aboutsummaryrefslogtreecommitdiff
path: root/venv/lib/python3.8/site-packages/werkzeug/datastructures/accept.py
diff options
context:
space:
mode:
authorsotech117 <michael_foiani@brown.edu>2025-07-31 17:27:24 -0400
committersotech117 <michael_foiani@brown.edu>2025-07-31 17:27:24 -0400
commit5bf22fc7e3c392c8bd44315ca2d06d7dca7d084e (patch)
tree8dacb0f195df1c0788d36dd0064f6bbaa3143ede /venv/lib/python3.8/site-packages/werkzeug/datastructures/accept.py
parentb832d364da8c2efe09e3f75828caf73c50d01ce3 (diff)
add code for analysis of data
Diffstat (limited to 'venv/lib/python3.8/site-packages/werkzeug/datastructures/accept.py')
-rw-r--r--venv/lib/python3.8/site-packages/werkzeug/datastructures/accept.py326
1 files changed, 326 insertions, 0 deletions
diff --git a/venv/lib/python3.8/site-packages/werkzeug/datastructures/accept.py b/venv/lib/python3.8/site-packages/werkzeug/datastructures/accept.py
new file mode 100644
index 0000000..d80f0bb
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/werkzeug/datastructures/accept.py
@@ -0,0 +1,326 @@
+from __future__ import annotations
+
+import codecs
+import re
+
+from .structures import ImmutableList
+
+
+class Accept(ImmutableList):
+ """An :class:`Accept` object is just a list subclass for lists of
+ ``(value, quality)`` tuples. It is automatically sorted by specificity
+ and quality.
+
+ All :class:`Accept` objects work similar to a list but provide extra
+ functionality for working with the data. Containment checks are
+ normalized to the rules of that header:
+
+ >>> a = CharsetAccept([('ISO-8859-1', 1), ('utf-8', 0.7)])
+ >>> a.best
+ 'ISO-8859-1'
+ >>> 'iso-8859-1' in a
+ True
+ >>> 'UTF8' in a
+ True
+ >>> 'utf7' in a
+ False
+
+ To get the quality for an item you can use normal item lookup:
+
+ >>> print a['utf-8']
+ 0.7
+ >>> a['utf7']
+ 0
+
+ .. versionchanged:: 0.5
+ :class:`Accept` objects are forced immutable now.
+
+ .. versionchanged:: 1.0.0
+ :class:`Accept` internal values are no longer ordered
+ alphabetically for equal quality tags. Instead the initial
+ order is preserved.
+
+ """
+
+ def __init__(self, values=()):
+ if values is None:
+ list.__init__(self)
+ self.provided = False
+ elif isinstance(values, Accept):
+ self.provided = values.provided
+ list.__init__(self, values)
+ else:
+ self.provided = True
+ values = sorted(
+ values, key=lambda x: (self._specificity(x[0]), x[1]), reverse=True
+ )
+ list.__init__(self, values)
+
+ def _specificity(self, value):
+ """Returns a tuple describing the value's specificity."""
+ return (value != "*",)
+
+ def _value_matches(self, value, item):
+ """Check if a value matches a given accept item."""
+ return item == "*" or item.lower() == value.lower()
+
+ def __getitem__(self, key):
+ """Besides index lookup (getting item n) you can also pass it a string
+ to get the quality for the item. If the item is not in the list, the
+ returned quality is ``0``.
+ """
+ if isinstance(key, str):
+ return self.quality(key)
+ return list.__getitem__(self, key)
+
+ def quality(self, key):
+ """Returns the quality of the key.
+
+ .. versionadded:: 0.6
+ In previous versions you had to use the item-lookup syntax
+ (eg: ``obj[key]`` instead of ``obj.quality(key)``)
+ """
+ for item, quality in self:
+ if self._value_matches(key, item):
+ return quality
+ return 0
+
+ def __contains__(self, value):
+ for item, _quality in self:
+ if self._value_matches(value, item):
+ return True
+ return False
+
+ def __repr__(self):
+ pairs_str = ", ".join(f"({x!r}, {y})" for x, y in self)
+ return f"{type(self).__name__}([{pairs_str}])"
+
+ def index(self, key):
+ """Get the position of an entry or raise :exc:`ValueError`.
+
+ :param key: The key to be looked up.
+
+ .. versionchanged:: 0.5
+ This used to raise :exc:`IndexError`, which was inconsistent
+ with the list API.
+ """
+ if isinstance(key, str):
+ for idx, (item, _quality) in enumerate(self):
+ if self._value_matches(key, item):
+ return idx
+ raise ValueError(key)
+ return list.index(self, key)
+
+ def find(self, key):
+ """Get the position of an entry or return -1.
+
+ :param key: The key to be looked up.
+ """
+ try:
+ return self.index(key)
+ except ValueError:
+ return -1
+
+ def values(self):
+ """Iterate over all values."""
+ for item in self:
+ yield item[0]
+
+ def to_header(self):
+ """Convert the header set into an HTTP header string."""
+ result = []
+ for value, quality in self:
+ if quality != 1:
+ value = f"{value};q={quality}"
+ result.append(value)
+ return ",".join(result)
+
+ def __str__(self):
+ return self.to_header()
+
+ def _best_single_match(self, match):
+ for client_item, quality in self:
+ if self._value_matches(match, client_item):
+ # self is sorted by specificity descending, we can exit
+ return client_item, quality
+ return None
+
+ def best_match(self, matches, default=None):
+ """Returns the best match from a list of possible matches based
+ on the specificity and quality of the client. If two items have the
+ same quality and specificity, the one is returned that comes first.
+
+ :param matches: a list of matches to check for
+ :param default: the value that is returned if none match
+ """
+ result = default
+ best_quality = -1
+ best_specificity = (-1,)
+ for server_item in matches:
+ match = self._best_single_match(server_item)
+ if not match:
+ continue
+ client_item, quality = match
+ specificity = self._specificity(client_item)
+ if quality <= 0 or quality < best_quality:
+ continue
+ # better quality or same quality but more specific => better match
+ if quality > best_quality or specificity > best_specificity:
+ result = server_item
+ best_quality = quality
+ best_specificity = specificity
+ return result
+
+ @property
+ def best(self):
+ """The best match as value."""
+ if self:
+ return self[0][0]
+
+
+_mime_split_re = re.compile(r"/|(?:\s*;\s*)")
+
+
+def _normalize_mime(value):
+ return _mime_split_re.split(value.lower())
+
+
+class MIMEAccept(Accept):
+ """Like :class:`Accept` but with special methods and behavior for
+ mimetypes.
+ """
+
+ def _specificity(self, value):
+ return tuple(x != "*" for x in _mime_split_re.split(value))
+
+ def _value_matches(self, value, item):
+ # item comes from the client, can't match if it's invalid.
+ if "/" not in item:
+ return False
+
+ # value comes from the application, tell the developer when it
+ # doesn't look valid.
+ if "/" not in value:
+ raise ValueError(f"invalid mimetype {value!r}")
+
+ # Split the match value into type, subtype, and a sorted list of parameters.
+ normalized_value = _normalize_mime(value)
+ value_type, value_subtype = normalized_value[:2]
+ value_params = sorted(normalized_value[2:])
+
+ # "*/*" is the only valid value that can start with "*".
+ if value_type == "*" and value_subtype != "*":
+ raise ValueError(f"invalid mimetype {value!r}")
+
+ # Split the accept item into type, subtype, and parameters.
+ normalized_item = _normalize_mime(item)
+ item_type, item_subtype = normalized_item[:2]
+ item_params = sorted(normalized_item[2:])
+
+ # "*/not-*" from the client is invalid, can't match.
+ if item_type == "*" and item_subtype != "*":
+ return False
+
+ return (
+ (item_type == "*" and item_subtype == "*")
+ or (value_type == "*" and value_subtype == "*")
+ ) or (
+ item_type == value_type
+ and (
+ item_subtype == "*"
+ or value_subtype == "*"
+ or (item_subtype == value_subtype and item_params == value_params)
+ )
+ )
+
+ @property
+ def accept_html(self):
+ """True if this object accepts HTML."""
+ return (
+ "text/html" in self or "application/xhtml+xml" in self or self.accept_xhtml
+ )
+
+ @property
+ def accept_xhtml(self):
+ """True if this object accepts XHTML."""
+ return "application/xhtml+xml" in self or "application/xml" in self
+
+ @property
+ def accept_json(self):
+ """True if this object accepts JSON."""
+ return "application/json" in self
+
+
+_locale_delim_re = re.compile(r"[_-]")
+
+
+def _normalize_lang(value):
+ """Process a language tag for matching."""
+ return _locale_delim_re.split(value.lower())
+
+
+class LanguageAccept(Accept):
+ """Like :class:`Accept` but with normalization for language tags."""
+
+ def _value_matches(self, value, item):
+ return item == "*" or _normalize_lang(value) == _normalize_lang(item)
+
+ def best_match(self, matches, default=None):
+ """Given a list of supported values, finds the best match from
+ the list of accepted values.
+
+ Language tags are normalized for the purpose of matching, but
+ are returned unchanged.
+
+ If no exact match is found, this will fall back to matching
+ the first subtag (primary language only), first with the
+ accepted values then with the match values. This partial is not
+ applied to any other language subtags.
+
+ The default is returned if no exact or fallback match is found.
+
+ :param matches: A list of supported languages to find a match.
+ :param default: The value that is returned if none match.
+ """
+ # Look for an exact match first. If a client accepts "en-US",
+ # "en-US" is a valid match at this point.
+ result = super().best_match(matches)
+
+ if result is not None:
+ return result
+
+ # Fall back to accepting primary tags. If a client accepts
+ # "en-US", "en" is a valid match at this point. Need to use
+ # re.split to account for 2 or 3 letter codes.
+ fallback = Accept(
+ [(_locale_delim_re.split(item[0], 1)[0], item[1]) for item in self]
+ )
+ result = fallback.best_match(matches)
+
+ if result is not None:
+ return result
+
+ # Fall back to matching primary tags. If the client accepts
+ # "en", "en-US" is a valid match at this point.
+ fallback_matches = [_locale_delim_re.split(item, 1)[0] for item in matches]
+ result = super().best_match(fallback_matches)
+
+ # Return a value from the original match list. Find the first
+ # original value that starts with the matched primary tag.
+ if result is not None:
+ return next(item for item in matches if item.startswith(result))
+
+ return default
+
+
+class CharsetAccept(Accept):
+ """Like :class:`Accept` but with normalization for charsets."""
+
+ def _value_matches(self, value, item):
+ def _normalize(name):
+ try:
+ return codecs.lookup(name).name
+ except LookupError:
+ return name.lower()
+
+ return item == "*" or _normalize(value) == _normalize(item)