Path: blob/master/venv/Lib/site-packages/urllib3/_collections.py
811 views
from __future__ import absolute_import12try:3from collections.abc import Mapping, MutableMapping4except ImportError:5from collections import Mapping, MutableMapping6try:7from threading import RLock8except ImportError: # Platform-specific: No threads available910class RLock:11def __enter__(self):12pass1314def __exit__(self, exc_type, exc_value, traceback):15pass161718from collections import OrderedDict19from .exceptions import InvalidHeader20from .packages.six import iterkeys, itervalues, PY3212223__all__ = ["RecentlyUsedContainer", "HTTPHeaderDict"]242526_Null = object()272829class RecentlyUsedContainer(MutableMapping):30"""31Provides a thread-safe dict-like container which maintains up to32``maxsize`` keys while throwing away the least-recently-used keys beyond33``maxsize``.3435:param maxsize:36Maximum number of recent elements to retain.3738:param dispose_func:39Every time an item is evicted from the container,40``dispose_func(value)`` is called. Callback which will get called41"""4243ContainerCls = OrderedDict4445def __init__(self, maxsize=10, dispose_func=None):46self._maxsize = maxsize47self.dispose_func = dispose_func4849self._container = self.ContainerCls()50self.lock = RLock()5152def __getitem__(self, key):53# Re-insert the item, moving it to the end of the eviction line.54with self.lock:55item = self._container.pop(key)56self._container[key] = item57return item5859def __setitem__(self, key, value):60evicted_value = _Null61with self.lock:62# Possibly evict the existing value of 'key'63evicted_value = self._container.get(key, _Null)64self._container[key] = value6566# If we didn't evict an existing value, we might have to evict the67# least recently used item from the beginning of the container.68if len(self._container) > self._maxsize:69_key, evicted_value = self._container.popitem(last=False)7071if self.dispose_func and evicted_value is not _Null:72self.dispose_func(evicted_value)7374def __delitem__(self, key):75with self.lock:76value = self._container.pop(key)7778if self.dispose_func:79self.dispose_func(value)8081def __len__(self):82with self.lock:83return len(self._container)8485def __iter__(self):86raise NotImplementedError(87"Iteration over this class is unlikely to be threadsafe."88)8990def clear(self):91with self.lock:92# Copy pointers to all values, then wipe the mapping93values = list(itervalues(self._container))94self._container.clear()9596if self.dispose_func:97for value in values:98self.dispose_func(value)99100def keys(self):101with self.lock:102return list(iterkeys(self._container))103104105class HTTPHeaderDict(MutableMapping):106"""107:param headers:108An iterable of field-value pairs. Must not contain multiple field names109when compared case-insensitively.110111:param kwargs:112Additional field-value pairs to pass in to ``dict.update``.113114A ``dict`` like container for storing HTTP Headers.115116Field names are stored and compared case-insensitively in compliance with117RFC 7230. Iteration provides the first case-sensitive key seen for each118case-insensitive pair.119120Using ``__setitem__`` syntax overwrites fields that compare equal121case-insensitively in order to maintain ``dict``'s api. For fields that122compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add``123in a loop.124125If multiple fields that are equal case-insensitively are passed to the126constructor or ``.update``, the behavior is undefined and some will be127lost.128129>>> headers = HTTPHeaderDict()130>>> headers.add('Set-Cookie', 'foo=bar')131>>> headers.add('set-cookie', 'baz=quxx')132>>> headers['content-length'] = '7'133>>> headers['SET-cookie']134'foo=bar, baz=quxx'135>>> headers['Content-Length']136'7'137"""138139def __init__(self, headers=None, **kwargs):140super(HTTPHeaderDict, self).__init__()141self._container = OrderedDict()142if headers is not None:143if isinstance(headers, HTTPHeaderDict):144self._copy_from(headers)145else:146self.extend(headers)147if kwargs:148self.extend(kwargs)149150def __setitem__(self, key, val):151self._container[key.lower()] = [key, val]152return self._container[key.lower()]153154def __getitem__(self, key):155val = self._container[key.lower()]156return ", ".join(val[1:])157158def __delitem__(self, key):159del self._container[key.lower()]160161def __contains__(self, key):162return key.lower() in self._container163164def __eq__(self, other):165if not isinstance(other, Mapping) and not hasattr(other, "keys"):166return False167if not isinstance(other, type(self)):168other = type(self)(other)169return dict((k.lower(), v) for k, v in self.itermerged()) == dict(170(k.lower(), v) for k, v in other.itermerged()171)172173def __ne__(self, other):174return not self.__eq__(other)175176if not PY3: # Python 2177iterkeys = MutableMapping.iterkeys178itervalues = MutableMapping.itervalues179180__marker = object()181182def __len__(self):183return len(self._container)184185def __iter__(self):186# Only provide the originally cased names187for vals in self._container.values():188yield vals[0]189190def pop(self, key, default=__marker):191"""D.pop(k[,d]) -> v, remove specified key and return the corresponding value.192If key is not found, d is returned if given, otherwise KeyError is raised.193"""194# Using the MutableMapping function directly fails due to the private marker.195# Using ordinary dict.pop would expose the internal structures.196# So let's reinvent the wheel.197try:198value = self[key]199except KeyError:200if default is self.__marker:201raise202return default203else:204del self[key]205return value206207def discard(self, key):208try:209del self[key]210except KeyError:211pass212213def add(self, key, val):214"""Adds a (name, value) pair, doesn't overwrite the value if it already215exists.216217>>> headers = HTTPHeaderDict(foo='bar')218>>> headers.add('Foo', 'baz')219>>> headers['foo']220'bar, baz'221"""222key_lower = key.lower()223new_vals = [key, val]224# Keep the common case aka no item present as fast as possible225vals = self._container.setdefault(key_lower, new_vals)226if new_vals is not vals:227vals.append(val)228229def extend(self, *args, **kwargs):230"""Generic import function for any type of header-like object.231Adapted version of MutableMapping.update in order to insert items232with self.add instead of self.__setitem__233"""234if len(args) > 1:235raise TypeError(236"extend() takes at most 1 positional "237"arguments ({0} given)".format(len(args))238)239other = args[0] if len(args) >= 1 else ()240241if isinstance(other, HTTPHeaderDict):242for key, val in other.iteritems():243self.add(key, val)244elif isinstance(other, Mapping):245for key in other:246self.add(key, other[key])247elif hasattr(other, "keys"):248for key in other.keys():249self.add(key, other[key])250else:251for key, value in other:252self.add(key, value)253254for key, value in kwargs.items():255self.add(key, value)256257def getlist(self, key, default=__marker):258"""Returns a list of all the values for the named field. Returns an259empty list if the key doesn't exist."""260try:261vals = self._container[key.lower()]262except KeyError:263if default is self.__marker:264return []265return default266else:267return vals[1:]268269# Backwards compatibility for httplib270getheaders = getlist271getallmatchingheaders = getlist272iget = getlist273274# Backwards compatibility for http.cookiejar275get_all = getlist276277def __repr__(self):278return "%s(%s)" % (type(self).__name__, dict(self.itermerged()))279280def _copy_from(self, other):281for key in other:282val = other.getlist(key)283if isinstance(val, list):284# Don't need to convert tuples285val = list(val)286self._container[key.lower()] = [key] + val287288def copy(self):289clone = type(self)()290clone._copy_from(self)291return clone292293def iteritems(self):294"""Iterate over all header lines, including duplicate ones."""295for key in self:296vals = self._container[key.lower()]297for val in vals[1:]:298yield vals[0], val299300def itermerged(self):301"""Iterate over all headers, merging duplicate ones together."""302for key in self:303val = self._container[key.lower()]304yield val[0], ", ".join(val[1:])305306def items(self):307return list(self.iteritems())308309@classmethod310def from_httplib(cls, message): # Python 2311"""Read headers from a Python 2 httplib message object."""312# python2.7 does not expose a proper API for exporting multiheaders313# efficiently. This function re-reads raw lines from the message314# object and extracts the multiheaders properly.315obs_fold_continued_leaders = (" ", "\t")316headers = []317318for line in message.headers:319if line.startswith(obs_fold_continued_leaders):320if not headers:321# We received a header line that starts with OWS as described322# in RFC-7230 S3.2.4. This indicates a multiline header, but323# there exists no previous header to which we can attach it.324raise InvalidHeader(325"Header continuation with no previous header: %s" % line326)327else:328key, value = headers[-1]329headers[-1] = (key, value + " " + line.strip())330continue331332key, value = line.split(":", 1)333headers.append((key, value.strip()))334335return cls(headers)336337338