_compat.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. """Private module full of compatibility hacks.
  2. Primarily this is for downstream redistributions of requests that unvendor
  3. urllib3 without providing a shim.
  4. .. warning::
  5. This module is private. If you use it, and something breaks, you were
  6. warned
  7. """
  8. import sys
  9. import requests
  10. try:
  11. from requests.packages.urllib3 import fields
  12. from requests.packages.urllib3 import filepost
  13. from requests.packages.urllib3 import poolmanager
  14. except ImportError:
  15. from urllib3 import fields
  16. from urllib3 import filepost
  17. from urllib3 import poolmanager
  18. try:
  19. from requests.packages.urllib3.connection import HTTPConnection
  20. from requests.packages.urllib3 import connection
  21. except ImportError:
  22. try:
  23. from urllib3.connection import HTTPConnection
  24. from urllib3 import connection
  25. except ImportError:
  26. HTTPConnection = None
  27. connection = None
  28. if requests.__build__ < 0x020300:
  29. timeout = None
  30. else:
  31. try:
  32. from requests.packages.urllib3.util import timeout
  33. except ImportError:
  34. from urllib3.util import timeout
  35. if requests.__build__ < 0x021000:
  36. gaecontrib = None
  37. else:
  38. try:
  39. from requests.packages.urllib3.contrib import appengine as gaecontrib
  40. except ImportError:
  41. from urllib3.contrib import appengine as gaecontrib
  42. if requests.__build__ < 0x021200:
  43. PyOpenSSLContext = None
  44. else:
  45. try:
  46. from requests.packages.urllib3.contrib.pyopenssl \
  47. import PyOpenSSLContext
  48. except ImportError:
  49. try:
  50. from urllib3.contrib.pyopenssl import PyOpenSSLContext
  51. except ImportError:
  52. PyOpenSSLContext = None
  53. PY3 = sys.version_info > (3, 0)
  54. if PY3:
  55. from collections.abc import Mapping, MutableMapping
  56. import queue
  57. from urllib.parse import urlencode, urljoin
  58. else:
  59. from collections import Mapping, MutableMapping
  60. import Queue as queue
  61. from urllib import urlencode
  62. from urlparse import urljoin
  63. try:
  64. basestring = basestring
  65. except NameError:
  66. basestring = (str, bytes)
  67. class HTTPHeaderDict(MutableMapping):
  68. """
  69. :param headers:
  70. An iterable of field-value pairs. Must not contain multiple field names
  71. when compared case-insensitively.
  72. :param kwargs:
  73. Additional field-value pairs to pass in to ``dict.update``.
  74. A ``dict`` like container for storing HTTP Headers.
  75. Field names are stored and compared case-insensitively in compliance with
  76. RFC 7230. Iteration provides the first case-sensitive key seen for each
  77. case-insensitive pair.
  78. Using ``__setitem__`` syntax overwrites fields that compare equal
  79. case-insensitively in order to maintain ``dict``'s api. For fields that
  80. compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add``
  81. in a loop.
  82. If multiple fields that are equal case-insensitively are passed to the
  83. constructor or ``.update``, the behavior is undefined and some will be
  84. lost.
  85. >>> headers = HTTPHeaderDict()
  86. >>> headers.add('Set-Cookie', 'foo=bar')
  87. >>> headers.add('set-cookie', 'baz=quxx')
  88. >>> headers['content-length'] = '7'
  89. >>> headers['SET-cookie']
  90. 'foo=bar, baz=quxx'
  91. >>> headers['Content-Length']
  92. '7'
  93. """
  94. def __init__(self, headers=None, **kwargs):
  95. super(HTTPHeaderDict, self).__init__()
  96. self._container = {}
  97. if headers is not None:
  98. if isinstance(headers, HTTPHeaderDict):
  99. self._copy_from(headers)
  100. else:
  101. self.extend(headers)
  102. if kwargs:
  103. self.extend(kwargs)
  104. def __setitem__(self, key, val):
  105. self._container[key.lower()] = (key, val)
  106. return self._container[key.lower()]
  107. def __getitem__(self, key):
  108. val = self._container[key.lower()]
  109. return ', '.join(val[1:])
  110. def __delitem__(self, key):
  111. del self._container[key.lower()]
  112. def __contains__(self, key):
  113. return key.lower() in self._container
  114. def __eq__(self, other):
  115. if not isinstance(other, Mapping) and not hasattr(other, 'keys'):
  116. return False
  117. if not isinstance(other, type(self)):
  118. other = type(self)(other)
  119. return (dict((k.lower(), v) for k, v in self.itermerged()) ==
  120. dict((k.lower(), v) for k, v in other.itermerged()))
  121. def __ne__(self, other):
  122. return not self.__eq__(other)
  123. if not PY3: # Python 2
  124. iterkeys = MutableMapping.iterkeys
  125. itervalues = MutableMapping.itervalues
  126. __marker = object()
  127. def __len__(self):
  128. return len(self._container)
  129. def __iter__(self):
  130. # Only provide the originally cased names
  131. for vals in self._container.values():
  132. yield vals[0]
  133. def pop(self, key, default=__marker):
  134. """D.pop(k[,d]) -> v, remove specified key and return its value.
  135. If key is not found, d is returned if given, otherwise KeyError is
  136. raised.
  137. """
  138. # Using the MutableMapping function directly fails due to the private
  139. # marker.
  140. # Using ordinary dict.pop would expose the internal structures.
  141. # So let's reinvent the wheel.
  142. try:
  143. value = self[key]
  144. except KeyError:
  145. if default is self.__marker:
  146. raise
  147. return default
  148. else:
  149. del self[key]
  150. return value
  151. def discard(self, key):
  152. try:
  153. del self[key]
  154. except KeyError:
  155. pass
  156. def add(self, key, val):
  157. """Adds a (name, value) pair, doesn't overwrite the value if it already
  158. exists.
  159. >>> headers = HTTPHeaderDict(foo='bar')
  160. >>> headers.add('Foo', 'baz')
  161. >>> headers['foo']
  162. 'bar, baz'
  163. """
  164. key_lower = key.lower()
  165. new_vals = key, val
  166. # Keep the common case aka no item present as fast as possible
  167. vals = self._container.setdefault(key_lower, new_vals)
  168. if new_vals is not vals:
  169. # new_vals was not inserted, as there was a previous one
  170. if isinstance(vals, list):
  171. # If already several items got inserted, we have a list
  172. vals.append(val)
  173. else:
  174. # vals should be a tuple then, i.e. only one item so far
  175. # Need to convert the tuple to list for further extension
  176. self._container[key_lower] = [vals[0], vals[1], val]
  177. def extend(self, *args, **kwargs):
  178. """Generic import function for any type of header-like object.
  179. Adapted version of MutableMapping.update in order to insert items
  180. with self.add instead of self.__setitem__
  181. """
  182. if len(args) > 1:
  183. raise TypeError("extend() takes at most 1 positional "
  184. "arguments ({} given)".format(len(args)))
  185. other = args[0] if len(args) >= 1 else ()
  186. if isinstance(other, HTTPHeaderDict):
  187. for key, val in other.iteritems():
  188. self.add(key, val)
  189. elif isinstance(other, Mapping):
  190. for key in other:
  191. self.add(key, other[key])
  192. elif hasattr(other, "keys"):
  193. for key in other.keys():
  194. self.add(key, other[key])
  195. else:
  196. for key, value in other:
  197. self.add(key, value)
  198. for key, value in kwargs.items():
  199. self.add(key, value)
  200. def getlist(self, key):
  201. """Returns a list of all the values for the named field. Returns an
  202. empty list if the key doesn't exist."""
  203. try:
  204. vals = self._container[key.lower()]
  205. except KeyError:
  206. return []
  207. else:
  208. if isinstance(vals, tuple):
  209. return [vals[1]]
  210. else:
  211. return vals[1:]
  212. # Backwards compatibility for httplib
  213. getheaders = getlist
  214. getallmatchingheaders = getlist
  215. iget = getlist
  216. def __repr__(self):
  217. return "%s(%s)" % (type(self).__name__, dict(self.itermerged()))
  218. def _copy_from(self, other):
  219. for key in other:
  220. val = other.getlist(key)
  221. if isinstance(val, list):
  222. # Don't need to convert tuples
  223. val = list(val)
  224. self._container[key.lower()] = [key] + val
  225. def copy(self):
  226. clone = type(self)()
  227. clone._copy_from(self)
  228. return clone
  229. def iteritems(self):
  230. """Iterate over all header lines, including duplicate ones."""
  231. for key in self:
  232. vals = self._container[key.lower()]
  233. for val in vals[1:]:
  234. yield vals[0], val
  235. def itermerged(self):
  236. """Iterate over all headers, merging duplicate ones together."""
  237. for key in self:
  238. val = self._container[key.lower()]
  239. yield val[0], ', '.join(val[1:])
  240. def items(self):
  241. return list(self.iteritems())
  242. @classmethod
  243. def from_httplib(cls, message): # Python 2
  244. """Read headers from a Python 2 httplib message object."""
  245. # python2.7 does not expose a proper API for exporting multiheaders
  246. # efficiently. This function re-reads raw lines from the message
  247. # object and extracts the multiheaders properly.
  248. headers = []
  249. for line in message.headers:
  250. if line.startswith((' ', '\t')):
  251. key, value = headers[-1]
  252. headers[-1] = (key, value + '\r\n' + line.rstrip())
  253. continue
  254. key, value = line.split(':', 1)
  255. headers.append((key, value.strip()))
  256. return cls(headers)
  257. __all__ = (
  258. 'basestring',
  259. 'connection',
  260. 'fields',
  261. 'filepost',
  262. 'poolmanager',
  263. 'timeout',
  264. 'HTTPHeaderDict',
  265. 'queue',
  266. 'urlencode',
  267. 'gaecontrib',
  268. 'urljoin',
  269. 'PyOpenSSLContext',
  270. )