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