Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
accept.py327 linesDownload Raw Back to datastructures
1from __future__ import annotations2 3import codecs4import re5 6from .structures import ImmutableList7 8 9class Accept(ImmutableList):10    """An :class:`Accept` object is just a list subclass for lists of11    ``(value, quality)`` tuples.  It is automatically sorted by specificity12    and quality.13 14    All :class:`Accept` objects work similar to a list but provide extra15    functionality for working with the data.  Containment checks are16    normalized to the rules of that header:17 18    >>> a = CharsetAccept([('ISO-8859-1', 1), ('utf-8', 0.7)])19    >>> a.best20    'ISO-8859-1'21    >>> 'iso-8859-1' in a22    True23    >>> 'UTF8' in a24    True25    >>> 'utf7' in a26    False27 28    To get the quality for an item you can use normal item lookup:29 30    >>> print a['utf-8']31    0.732    >>> a['utf7']33    034 35    .. versionchanged:: 0.536       :class:`Accept` objects are forced immutable now.37 38    .. versionchanged:: 1.0.039       :class:`Accept` internal values are no longer ordered40       alphabetically for equal quality tags. Instead the initial41       order is preserved.42 43    """44 45    def __init__(self, values=()):46        if values is None:47            list.__init__(self)48            self.provided = False49        elif isinstance(values, Accept):50            self.provided = values.provided51            list.__init__(self, values)52        else:53            self.provided = True54            values = sorted(55                values, key=lambda x: (self._specificity(x[0]), x[1]), reverse=True56            )57            list.__init__(self, values)58 59    def _specificity(self, value):60        """Returns a tuple describing the value's specificity."""61        return (value != "*",)62 63    def _value_matches(self, value, item):64        """Check if a value matches a given accept item."""65        return item == "*" or item.lower() == value.lower()66 67    def __getitem__(self, key):68        """Besides index lookup (getting item n) you can also pass it a string69        to get the quality for the item.  If the item is not in the list, the70        returned quality is ``0``.71        """72        if isinstance(key, str):73            return self.quality(key)74        return list.__getitem__(self, key)75 76    def quality(self, key):77        """Returns the quality of the key.78 79        .. versionadded:: 0.680           In previous versions you had to use the item-lookup syntax81           (eg: ``obj[key]`` instead of ``obj.quality(key)``)82        """83        for item, quality in self:84            if self._value_matches(key, item):85                return quality86        return 087 88    def __contains__(self, value):89        for item, _quality in self:90            if self._value_matches(value, item):91                return True92        return False93 94    def __repr__(self):95        pairs_str = ", ".join(f"({x!r}, {y})" for x, y in self)96        return f"{type(self).__name__}([{pairs_str}])"97 98    def index(self, key):99        """Get the position of an entry or raise :exc:`ValueError`.100 101        :param key: The key to be looked up.102 103        .. versionchanged:: 0.5104           This used to raise :exc:`IndexError`, which was inconsistent105           with the list API.106        """107        if isinstance(key, str):108            for idx, (item, _quality) in enumerate(self):109                if self._value_matches(key, item):110                    return idx111            raise ValueError(key)112        return list.index(self, key)113 114    def find(self, key):115        """Get the position of an entry or return -1.116 117        :param key: The key to be looked up.118        """119        try:120            return self.index(key)121        except ValueError:122            return -1123 124    def values(self):125        """Iterate over all values."""126        for item in self:127            yield item[0]128 129    def to_header(self):130        """Convert the header set into an HTTP header string."""131        result = []132        for value, quality in self:133            if quality != 1:134                value = f"{value};q={quality}"135            result.append(value)136        return ",".join(result)137 138    def __str__(self):139        return self.to_header()140 141    def _best_single_match(self, match):142        for client_item, quality in self:143            if self._value_matches(match, client_item):144                # self is sorted by specificity descending, we can exit145                return client_item, quality146        return None147 148    def best_match(self, matches, default=None):149        """Returns the best match from a list of possible matches based150        on the specificity and quality of the client. If two items have the151        same quality and specificity, the one is returned that comes first.152 153        :param matches: a list of matches to check for154        :param default: the value that is returned if none match155        """156        result = default157        best_quality = -1158        best_specificity = (-1,)159        for server_item in matches:160            match = self._best_single_match(server_item)161            if not match:162                continue163            client_item, quality = match164            specificity = self._specificity(client_item)165            if quality <= 0 or quality < best_quality:166                continue167            # better quality or same quality but more specific => better match168            if quality > best_quality or specificity > best_specificity:169                result = server_item170                best_quality = quality171                best_specificity = specificity172        return result173 174    @property175    def best(self):176        """The best match as value."""177        if self:178            return self[0][0]179 180 181_mime_split_re = re.compile(r"/|(?:\s*;\s*)")182 183 184def _normalize_mime(value):185    return _mime_split_re.split(value.lower())186 187 188class MIMEAccept(Accept):189    """Like :class:`Accept` but with special methods and behavior for190    mimetypes.191    """192 193    def _specificity(self, value):194        return tuple(x != "*" for x in _mime_split_re.split(value))195 196    def _value_matches(self, value, item):197        # item comes from the client, can't match if it's invalid.198        if "/" not in item:199            return False200 201        # value comes from the application, tell the developer when it202        # doesn't look valid.203        if "/" not in value:204            raise ValueError(f"invalid mimetype {value!r}")205 206        # Split the match value into type, subtype, and a sorted list of parameters.207        normalized_value = _normalize_mime(value)208        value_type, value_subtype = normalized_value[:2]209        value_params = sorted(normalized_value[2:])210 211        # "*/*" is the only valid value that can start with "*".212        if value_type == "*" and value_subtype != "*":213            raise ValueError(f"invalid mimetype {value!r}")214 215        # Split the accept item into type, subtype, and parameters.216        normalized_item = _normalize_mime(item)217        item_type, item_subtype = normalized_item[:2]218        item_params = sorted(normalized_item[2:])219 220        # "*/not-*" from the client is invalid, can't match.221        if item_type == "*" and item_subtype != "*":222            return False223 224        return (225            (item_type == "*" and item_subtype == "*")226            or (value_type == "*" and value_subtype == "*")227        ) or (228            item_type == value_type229            and (230                item_subtype == "*"231                or value_subtype == "*"232                or (item_subtype == value_subtype and item_params == value_params)233            )234        )235 236    @property237    def accept_html(self):238        """True if this object accepts HTML."""239        return (240            "text/html" in self or "application/xhtml+xml" in self or self.accept_xhtml241        )242 243    @property244    def accept_xhtml(self):245        """True if this object accepts XHTML."""246        return "application/xhtml+xml" in self or "application/xml" in self247 248    @property249    def accept_json(self):250        """True if this object accepts JSON."""251        return "application/json" in self252 253 254_locale_delim_re = re.compile(r"[_-]")255 256 257def _normalize_lang(value):258    """Process a language tag for matching."""259    return _locale_delim_re.split(value.lower())260 261 262class LanguageAccept(Accept):263    """Like :class:`Accept` but with normalization for language tags."""264 265    def _value_matches(self, value, item):266        return item == "*" or _normalize_lang(value) == _normalize_lang(item)267 268    def best_match(self, matches, default=None):269        """Given a list of supported values, finds the best match from270        the list of accepted values.271 272        Language tags are normalized for the purpose of matching, but273        are returned unchanged.274 275        If no exact match is found, this will fall back to matching276        the first subtag (primary language only), first with the277        accepted values then with the match values. This partial is not278        applied to any other language subtags.279 280        The default is returned if no exact or fallback match is found.281 282        :param matches: A list of supported languages to find a match.283        :param default: The value that is returned if none match.284        """285        # Look for an exact match first. If a client accepts "en-US",286        # "en-US" is a valid match at this point.287        result = super().best_match(matches)288 289        if result is not None:290            return result291 292        # Fall back to accepting primary tags. If a client accepts293        # "en-US", "en" is a valid match at this point. Need to use294        # re.split to account for 2 or 3 letter codes.295        fallback = Accept(296            [(_locale_delim_re.split(item[0], 1)[0], item[1]) for item in self]297        )298        result = fallback.best_match(matches)299 300        if result is not None:301            return result302 303        # Fall back to matching primary tags. If the client accepts304        # "en", "en-US" is a valid match at this point.305        fallback_matches = [_locale_delim_re.split(item, 1)[0] for item in matches]306        result = super().best_match(fallback_matches)307 308        # Return a value from the original match list. Find the first309        # original value that starts with the matched primary tag.310        if result is not None:311            return next(item for item in matches if item.startswith(result))312 313        return default314 315 316class CharsetAccept(Accept):317    """Like :class:`Accept` but with normalization for charsets."""318 319    def _value_matches(self, value, item):320        def _normalize(name):321            try:322                return codecs.lookup(name).name323            except LookupError:324                return name.lower()325 326        return item == "*" or _normalize(value) == _normalize(item)327 
codekingpro/portable-devtools · Team Ai