codekingpro/portable-devtools
114k
1"""Private module full of compatibility hacks.2 3Primarily this is for downstream redistributions of requests that unvendor4urllib3 without providing a shim.5 6.. warning::7 8 This module is private. If you use it, and something breaks, you were9 warned10"""11import sys12 13import requests14 15try:16 from requests.packages.urllib3 import fields17 from requests.packages.urllib3 import filepost18 from requests.packages.urllib3 import poolmanager19except ImportError:20 from urllib3 import fields21 from urllib3 import filepost22 from urllib3 import poolmanager23 24try:25 from requests.packages.urllib3.connection import HTTPConnection26 from requests.packages.urllib3 import connection27except ImportError:28 try:29 from urllib3.connection import HTTPConnection30 from urllib3 import connection31 except ImportError:32 HTTPConnection = None33 connection = None34 35 36if requests.__build__ < 0x020300:37 timeout = None38else:39 try:40 from requests.packages.urllib3.util import timeout41 except ImportError:42 from urllib3.util import timeout43 44PY3 = sys.version_info > (3, 0)45 46if PY3:47 from collections.abc import Mapping, MutableMapping48 import queue49 from urllib.parse import urlencode, urljoin50else:51 from collections import Mapping, MutableMapping52 import Queue as queue53 from urllib import urlencode54 from urlparse import urljoin55 56try:57 basestring = basestring58except NameError:59 basestring = (str, bytes)60 61 62class HTTPHeaderDict(MutableMapping):63 """64 :param headers:65 An iterable of field-value pairs. Must not contain multiple field names66 when compared case-insensitively.67 68 :param kwargs:69 Additional field-value pairs to pass in to ``dict.update``.70 71 A ``dict`` like container for storing HTTP Headers.72 73 Field names are stored and compared case-insensitively in compliance with74 RFC 7230. Iteration provides the first case-sensitive key seen for each75 case-insensitive pair.76 77 Using ``__setitem__`` syntax overwrites fields that compare equal78 case-insensitively in order to maintain ``dict``'s api. For fields that79 compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add``80 in a loop.81 82 If multiple fields that are equal case-insensitively are passed to the83 constructor or ``.update``, the behavior is undefined and some will be84 lost.85 86 >>> headers = HTTPHeaderDict()87 >>> headers.add('Set-Cookie', 'foo=bar')88 >>> headers.add('set-cookie', 'baz=quxx')89 >>> headers['content-length'] = '7'90 >>> headers['SET-cookie']91 'foo=bar, baz=quxx'92 >>> headers['Content-Length']93 '7'94 """95 96 def __init__(self, headers=None, **kwargs):97 super(HTTPHeaderDict, self).__init__()98 self._container = {}99 if headers is not None:100 if isinstance(headers, HTTPHeaderDict):101 self._copy_from(headers)102 else:103 self.extend(headers)104 if kwargs:105 self.extend(kwargs)106 107 def __setitem__(self, key, val):108 self._container[key.lower()] = (key, val)109 return self._container[key.lower()]110 111 def __getitem__(self, key):112 val = self._container[key.lower()]113 return ', '.join(val[1:])114 115 def __delitem__(self, key):116 del self._container[key.lower()]117 118 def __contains__(self, key):119 return key.lower() in self._container120 121 def __eq__(self, other):122 if not isinstance(other, Mapping) and not hasattr(other, 'keys'):123 return False124 if not isinstance(other, type(self)):125 other = type(self)(other)126 return ({k.lower(): v for k, v in self.itermerged()} ==127 {k.lower(): v for k, v in other.itermerged()})128 129 def __ne__(self, other):130 return not self.__eq__(other)131 132 if not PY3: # Python 2133 iterkeys = MutableMapping.iterkeys134 itervalues = MutableMapping.itervalues135 136 __marker = object()137 138 def __len__(self):139 return len(self._container)140 141 def __iter__(self):142 # Only provide the originally cased names143 for vals in self._container.values():144 yield vals[0]145 146 def pop(self, key, default=__marker):147 """D.pop(k[,d]) -> v, remove specified key and return its value.148 149 If key is not found, d is returned if given, otherwise KeyError is150 raised.151 """152 # Using the MutableMapping function directly fails due to the private153 # marker.154 # Using ordinary dict.pop would expose the internal structures.155 # So let's reinvent the wheel.156 try:157 value = self[key]158 except KeyError:159 if default is self.__marker:160 raise161 return default162 else:163 del self[key]164 return value165 166 def discard(self, key):167 try:168 del self[key]169 except KeyError:170 pass171 172 def add(self, key, val):173 """Adds a (name, value) pair, doesn't overwrite the value if it already174 exists.175 176 >>> headers = HTTPHeaderDict(foo='bar')177 >>> headers.add('Foo', 'baz')178 >>> headers['foo']179 'bar, baz'180 """181 key_lower = key.lower()182 new_vals = key, val183 # Keep the common case aka no item present as fast as possible184 vals = self._container.setdefault(key_lower, new_vals)185 if new_vals is not vals:186 # new_vals was not inserted, as there was a previous one187 if isinstance(vals, list):188 # If already several items got inserted, we have a list189 vals.append(val)190 else:191 # vals should be a tuple then, i.e. only one item so far192 # Need to convert the tuple to list for further extension193 self._container[key_lower] = [vals[0], vals[1], val]194 195 def extend(self, *args, **kwargs):196 """Generic import function for any type of header-like object.197 Adapted version of MutableMapping.update in order to insert items198 with self.add instead of self.__setitem__199 """200 if len(args) > 1:201 raise TypeError("extend() takes at most 1 positional "202 "arguments ({} given)".format(len(args)))203 other = args[0] if len(args) >= 1 else ()204 205 if isinstance(other, HTTPHeaderDict):206 for key, val in other.iteritems():207 self.add(key, val)208 elif isinstance(other, Mapping):209 for key in other:210 self.add(key, other[key])211 elif hasattr(other, "keys"):212 for key in other.keys():213 self.add(key, other[key])214 else:215 for key, value in other:216 self.add(key, value)217 218 for key, value in kwargs.items():219 self.add(key, value)220 221 def getlist(self, key):222 """Returns a list of all the values for the named field. Returns an223 empty list if the key doesn't exist."""224 try:225 vals = self._container[key.lower()]226 except KeyError:227 return []228 else:229 if isinstance(vals, tuple):230 return [vals[1]]231 else:232 return vals[1:]233 234 # Backwards compatibility for httplib235 getheaders = getlist236 getallmatchingheaders = getlist237 iget = getlist238 239 def __repr__(self):240 return "%s(%s)" % (type(self).__name__, dict(self.itermerged()))241 242 def _copy_from(self, other):243 for key in other:244 val = other.getlist(key)245 if isinstance(val, list):246 # Don't need to convert tuples247 val = list(val)248 self._container[key.lower()] = [key] + val249 250 def copy(self):251 clone = type(self)()252 clone._copy_from(self)253 return clone254 255 def iteritems(self):256 """Iterate over all header lines, including duplicate ones."""257 for key in self:258 vals = self._container[key.lower()]259 for val in vals[1:]:260 yield vals[0], val261 262 def itermerged(self):263 """Iterate over all headers, merging duplicate ones together."""264 for key in self:265 val = self._container[key.lower()]266 yield val[0], ', '.join(val[1:])267 268 def items(self):269 return list(self.iteritems())270 271 @classmethod272 def from_httplib(cls, message): # Python 2273 """Read headers from a Python 2 httplib message object."""274 # python2.7 does not expose a proper API for exporting multiheaders275 # efficiently. This function re-reads raw lines from the message276 # object and extracts the multiheaders properly.277 headers = []278 279 for line in message.headers:280 if line.startswith((' ', '\t')):281 key, value = headers[-1]282 headers[-1] = (key, value + '\r\n' + line.rstrip())283 continue284 285 key, value = line.split(':', 1)286 headers.append((key, value.strip()))287 288 return cls(headers)289 290 291__all__ = (292 'basestring',293 'connection',294 'fields',295 'filepost',296 'poolmanager',297 'timeout',298 'HTTPHeaderDict',299 'queue',300 'urlencode',301 'urljoin',302)303 