Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(88)

Side by Side Diff: third_party/requests/sessions.py

Issue 24076010: Add 'requests' library to third_party. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/swarm_client
Patch Set: Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # -*- coding: utf-8 -*-
2
3 """
4 requests.session
5 ~~~~~~~~~~~~~~~~
6
7 This module provides a Session object to manage and persist settings across
8 requests (cookies, auth, proxies).
9
10 """
11 import os
12 from collections import Mapping
13 from datetime import datetime
14
15 from .compat import cookielib, OrderedDict, urljoin, urlparse
16 from .cookies import cookiejar_from_dict, extract_cookies_to_jar, RequestsCookie Jar
17 from .models import Request, PreparedRequest
18 from .hooks import default_hooks, dispatch_hook
19 from .utils import to_key_val_list, default_headers
20 from .exceptions import TooManyRedirects, InvalidSchema
21 from .structures import CaseInsensitiveDict
22
23 from .adapters import HTTPAdapter
24
25 from .utils import requote_uri, get_environ_proxies, get_netrc_auth
26
27 from .status_codes import codes
28 REDIRECT_STATI = (
29 codes.moved, # 301
30 codes.found, # 302
31 codes.other, # 303
32 codes.temporary_moved, # 307
33 )
34 DEFAULT_REDIRECT_LIMIT = 30
35
36
37 def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
38 """
39 Determines appropriate setting for a given request, taking into account the
40 explicit setting on that request, and the setting in the session. If a
41 setting is a dictionary, they will be merged together using `dict_class`
42 """
43
44 if session_setting is None:
45 return request_setting
46
47 if request_setting is None:
48 return session_setting
49
50 # Bypass if not a dictionary (e.g. verify)
51 if not (
52 isinstance(session_setting, Mapping) and
53 isinstance(request_setting, Mapping)
54 ):
55 return request_setting
56
57 merged_setting = dict_class(to_key_val_list(session_setting))
58 merged_setting.update(to_key_val_list(request_setting))
59
60 # Remove keys that are set to None.
61 for (k, v) in request_setting.items():
62 if v is None:
63 del merged_setting[k]
64
65 return merged_setting
66
67
68 class SessionRedirectMixin(object):
69 def resolve_redirects(self, resp, req, stream=False, timeout=None,
70 verify=True, cert=None, proxies=None):
71 """Receives a Response. Returns a generator of Responses."""
72
73 i = 0
74 prepared_request = PreparedRequest()
75 prepared_request.body = req.body
76 prepared_request.headers = req.headers.copy()
77 prepared_request.hooks = req.hooks
78 prepared_request.method = req.method
79 prepared_request.url = req.url
80
81 # ((resp.status_code is codes.see_other))
82 while (('location' in resp.headers and resp.status_code in REDIRECT_STAT I)):
83
84 resp.content # Consume socket so it can be released
85
86 if i >= self.max_redirects:
87 raise TooManyRedirects('Exceeded %s redirects.' % self.max_redir ects)
88
89 # Release the connection back into the pool.
90 resp.close()
91
92 url = resp.headers['location']
93 method = prepared_request.method
94
95 # Handle redirection without scheme (see: RFC 1808 Section 4)
96 if url.startswith('//'):
97 parsed_rurl = urlparse(resp.url)
98 url = '%s:%s' % (parsed_rurl.scheme, url)
99
100 # Facilitate non-RFC2616-compliant 'location' headers
101 # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/re source')
102 # Compliant with RFC3986, we percent encode the url.
103 if not urlparse(url).netloc:
104 url = urljoin(resp.url, requote_uri(url))
105 else:
106 url = requote_uri(url)
107
108 prepared_request.url = url
109
110 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4
111 if (resp.status_code == codes.see_other and
112 prepared_request.method != 'HEAD'):
113 method = 'GET'
114
115 # Do what the browsers do, despite standards...
116 if (resp.status_code in (codes.moved, codes.found) and
117 prepared_request.method not in ('GET', 'HEAD')):
118 method = 'GET'
119
120 prepared_request.method = method
121
122 # https://github.com/kennethreitz/requests/issues/1084
123 if resp.status_code not in (codes.temporary, codes.resume):
124 if 'Content-Length' in prepared_request.headers:
125 del prepared_request.headers['Content-Length']
126
127 prepared_request.body = None
128
129 headers = prepared_request.headers
130 try:
131 del headers['Cookie']
132 except KeyError:
133 pass
134
135 prepared_request.prepare_cookies(self.cookies)
136
137 resp = self.send(
138 prepared_request,
139 stream=stream,
140 timeout=timeout,
141 verify=verify,
142 cert=cert,
143 proxies=proxies,
144 allow_redirects=False,
145 )
146
147 extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
148
149 i += 1
150 yield resp
151
152
153 class Session(SessionRedirectMixin):
154 """A Requests session.
155
156 Provides cookie persistience, connection-pooling, and configuration.
157
158 Basic Usage::
159
160 >>> import requests
161 >>> s = requests.Session()
162 >>> s.get('http://httpbin.org/get')
163 200
164 """
165
166 __attrs__ = [
167 'headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks',
168 'params', 'verify', 'cert', 'prefetch', 'adapters', 'stream',
169 'trust_env', 'max_redirects']
170
171 def __init__(self):
172
173 #: A case-insensitive dictionary of headers to be sent on each
174 #: :class:`Request <Request>` sent from this
175 #: :class:`Session <Session>`.
176 self.headers = default_headers()
177
178 #: Default Authentication tuple or object to attach to
179 #: :class:`Request <Request>`.
180 self.auth = None
181
182 #: Dictionary mapping protocol to the URL of the proxy (e.g.
183 #: {'http': 'foo.bar:3128'}) to be used on each
184 #: :class:`Request <Request>`.
185 self.proxies = {}
186
187 #: Event-handling hooks.
188 self.hooks = default_hooks()
189
190 #: Dictionary of querystring data to attach to each
191 #: :class:`Request <Request>`. The dictionary values may be lists for
192 #: representing multivalued query parameters.
193 self.params = {}
194
195 #: Stream response content default.
196 self.stream = False
197
198 #: SSL Verification default.
199 self.verify = True
200
201 #: SSL certificate default.
202 self.cert = None
203
204 #: Maximum number of redirects allowed. If the request exceeds this
205 #: limit, a :class:`TooManyRedirects` exception is raised.
206 self.max_redirects = DEFAULT_REDIRECT_LIMIT
207
208 #: Should we trust the environment?
209 self.trust_env = True
210
211 # Set up a CookieJar to be used by default
212 self.cookies = cookiejar_from_dict({})
213
214 # Default connection adapters.
215 self.adapters = OrderedDict()
216 self.mount('https://', HTTPAdapter())
217 self.mount('http://', HTTPAdapter())
218
219 def __enter__(self):
220 return self
221
222 def __exit__(self, *args):
223 self.close()
224
225 def request(self, method, url,
226 params=None,
227 data=None,
228 headers=None,
229 cookies=None,
230 files=None,
231 auth=None,
232 timeout=None,
233 allow_redirects=True,
234 proxies=None,
235 hooks=None,
236 stream=None,
237 verify=None,
238 cert=None):
239 """Constructs a :class:`Request <Request>`, prepares it and sends it.
240 Returns :class:`Response <Response>` object.
241
242 :param method: method for the new :class:`Request` object.
243 :param url: URL for the new :class:`Request` object.
244 :param params: (optional) Dictionary or bytes to be sent in the query
245 string for the :class:`Request`.
246 :param data: (optional) Dictionary or bytes to send in the body of the
247 :class:`Request`.
248 :param headers: (optional) Dictionary of HTTP Headers to send with the
249 :class:`Request`.
250 :param cookies: (optional) Dict or CookieJar object to send with the
251 :class:`Request`.
252 :param files: (optional) Dictionary of 'filename': file-like-objects
253 for multipart encoding upload.
254 :param auth: (optional) Auth tuple or callable to enable
255 Basic/Digest/Custom HTTP Auth.
256 :param timeout: (optional) Float describing the timeout of the
257 request.
258 :param allow_redirects: (optional) Boolean. Set to True by default.
259 :param proxies: (optional) Dictionary mapping protocol to the URL of
260 the proxy.
261 :param stream: (optional) whether to immediately download the response
262 content. Defaults to ``False``.
263 :param verify: (optional) if ``True``, the SSL cert will be verified.
264 A CA_BUNDLE path can also be provided.
265 :param cert: (optional) if String, path to ssl client cert file (.pem).
266 If Tuple, ('cert', 'key') pair.
267 """
268
269 cookies = cookies or {}
270 proxies = proxies or {}
271
272 # Bootstrap CookieJar.
273 if not isinstance(cookies, cookielib.CookieJar):
274 cookies = cookiejar_from_dict(cookies)
275
276 # Merge with session cookies
277 merged_cookies = RequestsCookieJar()
278 merged_cookies.update(self.cookies)
279 merged_cookies.update(cookies)
280 cookies = merged_cookies
281
282 # Gather clues from the surrounding environment.
283 if self.trust_env:
284 # Set environment's proxies.
285 env_proxies = get_environ_proxies(url) or {}
286 for (k, v) in env_proxies.items():
287 proxies.setdefault(k, v)
288
289 # Set environment's basic authentication.
290 if not auth:
291 auth = get_netrc_auth(url)
292
293 # Look for configuration.
294 if not verify and verify is not False:
295 verify = os.environ.get('REQUESTS_CA_BUNDLE')
296
297 # Curl compatibility.
298 if not verify and verify is not False:
299 verify = os.environ.get('CURL_CA_BUNDLE')
300
301 # Merge all the kwargs.
302 params = merge_setting(params, self.params)
303 headers = merge_setting(headers, self.headers, dict_class=CaseInsensitiv eDict)
304 auth = merge_setting(auth, self.auth)
305 proxies = merge_setting(proxies, self.proxies)
306 hooks = merge_setting(hooks, self.hooks)
307 stream = merge_setting(stream, self.stream)
308 verify = merge_setting(verify, self.verify)
309 cert = merge_setting(cert, self.cert)
310
311 # Create the Request.
312 req = Request()
313 req.method = method.upper()
314 req.url = url
315 req.headers = headers
316 req.files = files
317 req.data = data
318 req.params = params
319 req.auth = auth
320 req.cookies = cookies
321 req.hooks = hooks
322
323 # Prepare the Request.
324 prep = req.prepare()
325
326 # Send the request.
327 send_kwargs = {
328 'stream': stream,
329 'timeout': timeout,
330 'verify': verify,
331 'cert': cert,
332 'proxies': proxies,
333 'allow_redirects': allow_redirects,
334 }
335 resp = self.send(prep, **send_kwargs)
336
337 return resp
338
339 def get(self, url, **kwargs):
340 """Sends a GET request. Returns :class:`Response` object.
341
342 :param url: URL for the new :class:`Request` object.
343 :param \*\*kwargs: Optional arguments that ``request`` takes.
344 """
345
346 kwargs.setdefault('allow_redirects', True)
347 return self.request('GET', url, **kwargs)
348
349 def options(self, url, **kwargs):
350 """Sends a OPTIONS request. Returns :class:`Response` object.
351
352 :param url: URL for the new :class:`Request` object.
353 :param \*\*kwargs: Optional arguments that ``request`` takes.
354 """
355
356 kwargs.setdefault('allow_redirects', True)
357 return self.request('OPTIONS', url, **kwargs)
358
359 def head(self, url, **kwargs):
360 """Sends a HEAD request. Returns :class:`Response` object.
361
362 :param url: URL for the new :class:`Request` object.
363 :param \*\*kwargs: Optional arguments that ``request`` takes.
364 """
365
366 kwargs.setdefault('allow_redirects', False)
367 return self.request('HEAD', url, **kwargs)
368
369 def post(self, url, data=None, **kwargs):
370 """Sends a POST request. Returns :class:`Response` object.
371
372 :param url: URL for the new :class:`Request` object.
373 :param data: (optional) Dictionary, bytes, or file-like object to send i n the body of the :class:`Request`.
374 :param \*\*kwargs: Optional arguments that ``request`` takes.
375 """
376
377 return self.request('POST', url, data=data, **kwargs)
378
379 def put(self, url, data=None, **kwargs):
380 """Sends a PUT request. Returns :class:`Response` object.
381
382 :param url: URL for the new :class:`Request` object.
383 :param data: (optional) Dictionary, bytes, or file-like object to send i n the body of the :class:`Request`.
384 :param \*\*kwargs: Optional arguments that ``request`` takes.
385 """
386
387 return self.request('PUT', url, data=data, **kwargs)
388
389 def patch(self, url, data=None, **kwargs):
390 """Sends a PATCH request. Returns :class:`Response` object.
391
392 :param url: URL for the new :class:`Request` object.
393 :param data: (optional) Dictionary, bytes, or file-like object to send i n the body of the :class:`Request`.
394 :param \*\*kwargs: Optional arguments that ``request`` takes.
395 """
396
397 return self.request('PATCH', url, data=data, **kwargs)
398
399 def delete(self, url, **kwargs):
400 """Sends a DELETE request. Returns :class:`Response` object.
401
402 :param url: URL for the new :class:`Request` object.
403 :param \*\*kwargs: Optional arguments that ``request`` takes.
404 """
405
406 return self.request('DELETE', url, **kwargs)
407
408 def send(self, request, **kwargs):
409 """Send a given PreparedRequest."""
410 # Set defaults that the hooks can utilize to ensure they always have
411 # the correct parameters to reproduce the previous request.
412 kwargs.setdefault('stream', self.stream)
413 kwargs.setdefault('verify', self.verify)
414 kwargs.setdefault('cert', self.cert)
415 kwargs.setdefault('proxies', self.proxies)
416
417 # It's possible that users might accidentally send a Request object.
418 # Guard against that specific failure case.
419 if getattr(request, 'prepare', None):
420 raise ValueError('You can only send PreparedRequests.')
421
422 # Set up variables needed for resolve_redirects and dispatching of
423 # hooks
424 allow_redirects = kwargs.pop('allow_redirects', True)
425 stream = kwargs.get('stream')
426 timeout = kwargs.get('timeout')
427 verify = kwargs.get('verify')
428 cert = kwargs.get('cert')
429 proxies = kwargs.get('proxies')
430 hooks = request.hooks
431
432 # Get the appropriate adapter to use
433 adapter = self.get_adapter(url=request.url)
434
435 # Start time (approximately) of the request
436 start = datetime.utcnow()
437 # Send the request
438 r = adapter.send(request, **kwargs)
439 # Total elapsed time of the request (approximately)
440 r.elapsed = datetime.utcnow() - start
441
442 # Response manipulation hooks
443 r = dispatch_hook('response', hooks, r, **kwargs)
444
445 # Persist cookies
446 extract_cookies_to_jar(self.cookies, request, r.raw)
447
448 # Redirect resolving generator.
449 gen = self.resolve_redirects(r, request, stream=stream,
450 timeout=timeout, verify=verify, cert=cert,
451 proxies=proxies)
452
453 # Resolve redirects if allowed.
454 history = [resp for resp in gen] if allow_redirects else []
455
456 # Shuffle things around if there's history.
457 if history:
458 # Insert the first (original) request at the start
459 history.insert(0, r)
460 # Get the last request made
461 r = history.pop()
462 r.history = tuple(history)
463
464 return r
465
466 def get_adapter(self, url):
467 """Returns the appropriate connnection adapter for the given URL."""
468 for (prefix, adapter) in self.adapters.items():
469
470 if url.startswith(prefix):
471 return adapter
472
473 # Nothing matches :-/
474 raise InvalidSchema("No connection adapters were found for '%s'" % url)
475
476 def close(self):
477 """Closes all adapters and as such the session"""
478 for _, v in self.adapters.items():
479 v.close()
480
481 def mount(self, prefix, adapter):
482 """Registers a connection adapter to a prefix.
483
484 Adapters are sorted in descending order by key length."""
485 self.adapters[prefix] = adapter
486 keys_to_move = [k for k in self.adapters if len(k) < len(prefix)]
487 for key in keys_to_move:
488 self.adapters[key] = self.adapters.pop(key)
489
490 def __getstate__(self):
491 return dict((attr, getattr(self, attr, None)) for attr in self.__attrs__ )
492
493 def __setstate__(self, state):
494 for attr, value in state.items():
495 setattr(self, attr, value)
496
497
498 def session():
499 """Returns a :class:`Session` for context-management."""
500
501 return Session()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698