comparison venv/lib/python2.7/site-packages/requests/sessions.py @ 0:d67268158946 draft

planemo upload commit a3f181f5f126803c654b3a66dd4e83a48f7e203b
author bcclaywell
date Mon, 12 Oct 2015 17:43:33 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:d67268158946
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 .auth import _basic_auth_str
16 from .compat import cookielib, OrderedDict, urljoin, urlparse
17 from .cookies import (
18 cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies)
19 from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT
20 from .hooks import default_hooks, dispatch_hook
21 from .utils import to_key_val_list, default_headers, to_native_string
22 from .exceptions import (
23 TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError)
24 from .packages.urllib3._collections import RecentlyUsedContainer
25 from .structures import CaseInsensitiveDict
26
27 from .adapters import HTTPAdapter
28
29 from .utils import (
30 requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies,
31 get_auth_from_url
32 )
33
34 from .status_codes import codes
35
36 # formerly defined here, reexposed here for backward compatibility
37 from .models import REDIRECT_STATI
38
39 REDIRECT_CACHE_SIZE = 1000
40
41
42 def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
43 """
44 Determines appropriate setting for a given request, taking into account the
45 explicit setting on that request, and the setting in the session. If a
46 setting is a dictionary, they will be merged together using `dict_class`
47 """
48
49 if session_setting is None:
50 return request_setting
51
52 if request_setting is None:
53 return session_setting
54
55 # Bypass if not a dictionary (e.g. verify)
56 if not (
57 isinstance(session_setting, Mapping) and
58 isinstance(request_setting, Mapping)
59 ):
60 return request_setting
61
62 merged_setting = dict_class(to_key_val_list(session_setting))
63 merged_setting.update(to_key_val_list(request_setting))
64
65 # Remove keys that are set to None. Extract keys first to avoid altering
66 # the dictionary during iteration.
67 none_keys = [k for (k, v) in merged_setting.items() if v is None]
68 for key in none_keys:
69 del merged_setting[key]
70
71 return merged_setting
72
73
74 def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
75 """
76 Properly merges both requests and session hooks.
77
78 This is necessary because when request_hooks == {'response': []}, the
79 merge breaks Session hooks entirely.
80 """
81 if session_hooks is None or session_hooks.get('response') == []:
82 return request_hooks
83
84 if request_hooks is None or request_hooks.get('response') == []:
85 return session_hooks
86
87 return merge_setting(request_hooks, session_hooks, dict_class)
88
89
90 class SessionRedirectMixin(object):
91 def resolve_redirects(self, resp, req, stream=False, timeout=None,
92 verify=True, cert=None, proxies=None, **adapter_kwargs):
93 """Receives a Response. Returns a generator of Responses."""
94
95 i = 0
96 hist = [] # keep track of history
97
98 while resp.is_redirect:
99 prepared_request = req.copy()
100
101 if i > 0:
102 # Update history and keep track of redirects.
103 hist.append(resp)
104 new_hist = list(hist)
105 resp.history = new_hist
106
107 try:
108 resp.content # Consume socket so it can be released
109 except (ChunkedEncodingError, ContentDecodingError, RuntimeError):
110 resp.raw.read(decode_content=False)
111
112 if i >= self.max_redirects:
113 raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects)
114
115 # Release the connection back into the pool.
116 resp.close()
117
118 url = resp.headers['location']
119 method = req.method
120
121 # Handle redirection without scheme (see: RFC 1808 Section 4)
122 if url.startswith('//'):
123 parsed_rurl = urlparse(resp.url)
124 url = '%s:%s' % (parsed_rurl.scheme, url)
125
126 # The scheme should be lower case...
127 parsed = urlparse(url)
128 url = parsed.geturl()
129
130 # Facilitate relative 'location' headers, as allowed by RFC 7231.
131 # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
132 # Compliant with RFC3986, we percent encode the url.
133 if not parsed.netloc:
134 url = urljoin(resp.url, requote_uri(url))
135 else:
136 url = requote_uri(url)
137
138 prepared_request.url = to_native_string(url)
139 # Cache the url, unless it redirects to itself.
140 if resp.is_permanent_redirect and req.url != prepared_request.url:
141 self.redirect_cache[req.url] = prepared_request.url
142
143 # http://tools.ietf.org/html/rfc7231#section-6.4.4
144 if (resp.status_code == codes.see_other and
145 method != 'HEAD'):
146 method = 'GET'
147
148 # Do what the browsers do, despite standards...
149 # First, turn 302s into GETs.
150 if resp.status_code == codes.found and method != 'HEAD':
151 method = 'GET'
152
153 # Second, if a POST is responded to with a 301, turn it into a GET.
154 # This bizarre behaviour is explained in Issue 1704.
155 if resp.status_code == codes.moved and method == 'POST':
156 method = 'GET'
157
158 prepared_request.method = method
159
160 # https://github.com/kennethreitz/requests/issues/1084
161 if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect):
162 if 'Content-Length' in prepared_request.headers:
163 del prepared_request.headers['Content-Length']
164
165 prepared_request.body = None
166
167 headers = prepared_request.headers
168 try:
169 del headers['Cookie']
170 except KeyError:
171 pass
172
173 # Extract any cookies sent on the response to the cookiejar
174 # in the new request. Because we've mutated our copied prepared
175 # request, use the old one that we haven't yet touched.
176 extract_cookies_to_jar(prepared_request._cookies, req, resp.raw)
177 prepared_request._cookies.update(self.cookies)
178 prepared_request.prepare_cookies(prepared_request._cookies)
179
180 # Rebuild auth and proxy information.
181 proxies = self.rebuild_proxies(prepared_request, proxies)
182 self.rebuild_auth(prepared_request, resp)
183
184 # Override the original request.
185 req = prepared_request
186
187 resp = self.send(
188 req,
189 stream=stream,
190 timeout=timeout,
191 verify=verify,
192 cert=cert,
193 proxies=proxies,
194 allow_redirects=False,
195 **adapter_kwargs
196 )
197
198 extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
199
200 i += 1
201 yield resp
202
203 def rebuild_auth(self, prepared_request, response):
204 """
205 When being redirected we may want to strip authentication from the
206 request to avoid leaking credentials. This method intelligently removes
207 and reapplies authentication where possible to avoid credential loss.
208 """
209 headers = prepared_request.headers
210 url = prepared_request.url
211
212 if 'Authorization' in headers:
213 # If we get redirected to a new host, we should strip out any
214 # authentication headers.
215 original_parsed = urlparse(response.request.url)
216 redirect_parsed = urlparse(url)
217
218 if (original_parsed.hostname != redirect_parsed.hostname):
219 del headers['Authorization']
220
221 # .netrc might have more auth for us on our new host.
222 new_auth = get_netrc_auth(url) if self.trust_env else None
223 if new_auth is not None:
224 prepared_request.prepare_auth(new_auth)
225
226 return
227
228 def rebuild_proxies(self, prepared_request, proxies):
229 """
230 This method re-evaluates the proxy configuration by considering the
231 environment variables. If we are redirected to a URL covered by
232 NO_PROXY, we strip the proxy configuration. Otherwise, we set missing
233 proxy keys for this URL (in case they were stripped by a previous
234 redirect).
235
236 This method also replaces the Proxy-Authorization header where
237 necessary.
238 """
239 headers = prepared_request.headers
240 url = prepared_request.url
241 scheme = urlparse(url).scheme
242 new_proxies = proxies.copy() if proxies is not None else {}
243
244 if self.trust_env and not should_bypass_proxies(url):
245 environ_proxies = get_environ_proxies(url)
246
247 proxy = environ_proxies.get(scheme)
248
249 if proxy:
250 new_proxies.setdefault(scheme, environ_proxies[scheme])
251
252 if 'Proxy-Authorization' in headers:
253 del headers['Proxy-Authorization']
254
255 try:
256 username, password = get_auth_from_url(new_proxies[scheme])
257 except KeyError:
258 username, password = None, None
259
260 if username and password:
261 headers['Proxy-Authorization'] = _basic_auth_str(username, password)
262
263 return new_proxies
264
265
266 class Session(SessionRedirectMixin):
267 """A Requests session.
268
269 Provides cookie persistence, connection-pooling, and configuration.
270
271 Basic Usage::
272
273 >>> import requests
274 >>> s = requests.Session()
275 >>> s.get('http://httpbin.org/get')
276 200
277
278 Or as a context manager::
279
280 >>> with requests.Session() as s:
281 >>> s.get('http://httpbin.org/get')
282 200
283 """
284
285 __attrs__ = [
286 'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify',
287 'cert', 'prefetch', 'adapters', 'stream', 'trust_env',
288 'max_redirects',
289 ]
290
291 def __init__(self):
292
293 #: A case-insensitive dictionary of headers to be sent on each
294 #: :class:`Request <Request>` sent from this
295 #: :class:`Session <Session>`.
296 self.headers = default_headers()
297
298 #: Default Authentication tuple or object to attach to
299 #: :class:`Request <Request>`.
300 self.auth = None
301
302 #: Dictionary mapping protocol or protocol and host to the URL of the proxy
303 #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to
304 #: be used on each :class:`Request <Request>`.
305 self.proxies = {}
306
307 #: Event-handling hooks.
308 self.hooks = default_hooks()
309
310 #: Dictionary of querystring data to attach to each
311 #: :class:`Request <Request>`. The dictionary values may be lists for
312 #: representing multivalued query parameters.
313 self.params = {}
314
315 #: Stream response content default.
316 self.stream = False
317
318 #: SSL Verification default.
319 self.verify = True
320
321 #: SSL certificate default.
322 self.cert = None
323
324 #: Maximum number of redirects allowed. If the request exceeds this
325 #: limit, a :class:`TooManyRedirects` exception is raised.
326 self.max_redirects = DEFAULT_REDIRECT_LIMIT
327
328 #: Trust environement settings for proxy configuration, default
329 #: authentication and similar.
330 self.trust_env = True
331
332 #: A CookieJar containing all currently outstanding cookies set on this
333 #: session. By default it is a
334 #: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but
335 #: may be any other ``cookielib.CookieJar`` compatible object.
336 self.cookies = cookiejar_from_dict({})
337
338 # Default connection adapters.
339 self.adapters = OrderedDict()
340 self.mount('https://', HTTPAdapter())
341 self.mount('http://', HTTPAdapter())
342
343 # Only store 1000 redirects to prevent using infinite memory
344 self.redirect_cache = RecentlyUsedContainer(REDIRECT_CACHE_SIZE)
345
346 def __enter__(self):
347 return self
348
349 def __exit__(self, *args):
350 self.close()
351
352 def prepare_request(self, request):
353 """Constructs a :class:`PreparedRequest <PreparedRequest>` for
354 transmission and returns it. The :class:`PreparedRequest` has settings
355 merged from the :class:`Request <Request>` instance and those of the
356 :class:`Session`.
357
358 :param request: :class:`Request` instance to prepare with this
359 session's settings.
360 """
361 cookies = request.cookies or {}
362
363 # Bootstrap CookieJar.
364 if not isinstance(cookies, cookielib.CookieJar):
365 cookies = cookiejar_from_dict(cookies)
366
367 # Merge with session cookies
368 merged_cookies = merge_cookies(
369 merge_cookies(RequestsCookieJar(), self.cookies), cookies)
370
371
372 # Set environment's basic authentication if not explicitly set.
373 auth = request.auth
374 if self.trust_env and not auth and not self.auth:
375 auth = get_netrc_auth(request.url)
376
377 p = PreparedRequest()
378 p.prepare(
379 method=request.method.upper(),
380 url=request.url,
381 files=request.files,
382 data=request.data,
383 json=request.json,
384 headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict),
385 params=merge_setting(request.params, self.params),
386 auth=merge_setting(auth, self.auth),
387 cookies=merged_cookies,
388 hooks=merge_hooks(request.hooks, self.hooks),
389 )
390 return p
391
392 def request(self, method, url,
393 params=None,
394 data=None,
395 headers=None,
396 cookies=None,
397 files=None,
398 auth=None,
399 timeout=None,
400 allow_redirects=True,
401 proxies=None,
402 hooks=None,
403 stream=None,
404 verify=None,
405 cert=None,
406 json=None):
407 """Constructs a :class:`Request <Request>`, prepares it and sends it.
408 Returns :class:`Response <Response>` object.
409
410 :param method: method for the new :class:`Request` object.
411 :param url: URL for the new :class:`Request` object.
412 :param params: (optional) Dictionary or bytes to be sent in the query
413 string for the :class:`Request`.
414 :param data: (optional) Dictionary, bytes, or file-like object to send
415 in the body of the :class:`Request`.
416 :param json: (optional) json to send in the body of the
417 :class:`Request`.
418 :param headers: (optional) Dictionary of HTTP Headers to send with the
419 :class:`Request`.
420 :param cookies: (optional) Dict or CookieJar object to send with the
421 :class:`Request`.
422 :param files: (optional) Dictionary of ``'filename': file-like-objects``
423 for multipart encoding upload.
424 :param auth: (optional) Auth tuple or callable to enable
425 Basic/Digest/Custom HTTP Auth.
426 :param timeout: (optional) How long to wait for the server to send
427 data before giving up, as a float, or a :ref:`(connect timeout,
428 read timeout) <timeouts>` tuple.
429 :type timeout: float or tuple
430 :param allow_redirects: (optional) Set to True by default.
431 :type allow_redirects: bool
432 :param proxies: (optional) Dictionary mapping protocol or protocol and
433 hostname to the URL of the proxy.
434 :param stream: (optional) whether to immediately download the response
435 content. Defaults to ``False``.
436 :param verify: (optional) if ``True``, the SSL cert will be verified.
437 A CA_BUNDLE path can also be provided.
438 :param cert: (optional) if String, path to ssl client cert file (.pem).
439 If Tuple, ('cert', 'key') pair.
440 """
441
442 method = to_native_string(method)
443
444 # Create the Request.
445 req = Request(
446 method = method.upper(),
447 url = url,
448 headers = headers,
449 files = files,
450 data = data or {},
451 json = json,
452 params = params or {},
453 auth = auth,
454 cookies = cookies,
455 hooks = hooks,
456 )
457 prep = self.prepare_request(req)
458
459 proxies = proxies or {}
460
461 settings = self.merge_environment_settings(
462 prep.url, proxies, stream, verify, cert
463 )
464
465 # Send the request.
466 send_kwargs = {
467 'timeout': timeout,
468 'allow_redirects': allow_redirects,
469 }
470 send_kwargs.update(settings)
471 resp = self.send(prep, **send_kwargs)
472
473 return resp
474
475 def get(self, url, **kwargs):
476 """Sends a GET request. Returns :class:`Response` object.
477
478 :param url: URL for the new :class:`Request` object.
479 :param \*\*kwargs: Optional arguments that ``request`` takes.
480 """
481
482 kwargs.setdefault('allow_redirects', True)
483 return self.request('GET', url, **kwargs)
484
485 def options(self, url, **kwargs):
486 """Sends a OPTIONS request. Returns :class:`Response` object.
487
488 :param url: URL for the new :class:`Request` object.
489 :param \*\*kwargs: Optional arguments that ``request`` takes.
490 """
491
492 kwargs.setdefault('allow_redirects', True)
493 return self.request('OPTIONS', url, **kwargs)
494
495 def head(self, url, **kwargs):
496 """Sends a HEAD request. Returns :class:`Response` object.
497
498 :param url: URL for the new :class:`Request` object.
499 :param \*\*kwargs: Optional arguments that ``request`` takes.
500 """
501
502 kwargs.setdefault('allow_redirects', False)
503 return self.request('HEAD', url, **kwargs)
504
505 def post(self, url, data=None, json=None, **kwargs):
506 """Sends a POST request. Returns :class:`Response` object.
507
508 :param url: URL for the new :class:`Request` object.
509 :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
510 :param json: (optional) json to send in the body of the :class:`Request`.
511 :param \*\*kwargs: Optional arguments that ``request`` takes.
512 """
513
514 return self.request('POST', url, data=data, json=json, **kwargs)
515
516 def put(self, url, data=None, **kwargs):
517 """Sends a PUT request. Returns :class:`Response` object.
518
519 :param url: URL for the new :class:`Request` object.
520 :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
521 :param \*\*kwargs: Optional arguments that ``request`` takes.
522 """
523
524 return self.request('PUT', url, data=data, **kwargs)
525
526 def patch(self, url, data=None, **kwargs):
527 """Sends a PATCH request. Returns :class:`Response` object.
528
529 :param url: URL for the new :class:`Request` object.
530 :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
531 :param \*\*kwargs: Optional arguments that ``request`` takes.
532 """
533
534 return self.request('PATCH', url, data=data, **kwargs)
535
536 def delete(self, url, **kwargs):
537 """Sends a DELETE request. Returns :class:`Response` object.
538
539 :param url: URL for the new :class:`Request` object.
540 :param \*\*kwargs: Optional arguments that ``request`` takes.
541 """
542
543 return self.request('DELETE', url, **kwargs)
544
545 def send(self, request, **kwargs):
546 """Send a given PreparedRequest."""
547 # Set defaults that the hooks can utilize to ensure they always have
548 # the correct parameters to reproduce the previous request.
549 kwargs.setdefault('stream', self.stream)
550 kwargs.setdefault('verify', self.verify)
551 kwargs.setdefault('cert', self.cert)
552 kwargs.setdefault('proxies', self.proxies)
553
554 # It's possible that users might accidentally send a Request object.
555 # Guard against that specific failure case.
556 if not isinstance(request, PreparedRequest):
557 raise ValueError('You can only send PreparedRequests.')
558
559 checked_urls = set()
560 while request.url in self.redirect_cache:
561 checked_urls.add(request.url)
562 new_url = self.redirect_cache.get(request.url)
563 if new_url in checked_urls:
564 break
565 request.url = new_url
566
567 # Set up variables needed for resolve_redirects and dispatching of hooks
568 allow_redirects = kwargs.pop('allow_redirects', True)
569 stream = kwargs.get('stream')
570 hooks = request.hooks
571
572 # Get the appropriate adapter to use
573 adapter = self.get_adapter(url=request.url)
574
575 # Start time (approximately) of the request
576 start = datetime.utcnow()
577
578 # Send the request
579 r = adapter.send(request, **kwargs)
580
581 # Total elapsed time of the request (approximately)
582 r.elapsed = datetime.utcnow() - start
583
584 # Response manipulation hooks
585 r = dispatch_hook('response', hooks, r, **kwargs)
586
587 # Persist cookies
588 if r.history:
589
590 # If the hooks create history then we want those cookies too
591 for resp in r.history:
592 extract_cookies_to_jar(self.cookies, resp.request, resp.raw)
593
594 extract_cookies_to_jar(self.cookies, request, r.raw)
595
596 # Redirect resolving generator.
597 gen = self.resolve_redirects(r, request, **kwargs)
598
599 # Resolve redirects if allowed.
600 history = [resp for resp in gen] if allow_redirects else []
601
602 # Shuffle things around if there's history.
603 if history:
604 # Insert the first (original) request at the start
605 history.insert(0, r)
606 # Get the last request made
607 r = history.pop()
608 r.history = history
609
610 if not stream:
611 r.content
612
613 return r
614
615 def merge_environment_settings(self, url, proxies, stream, verify, cert):
616 """Check the environment and merge it with some settings."""
617 # Gather clues from the surrounding environment.
618 if self.trust_env:
619 # Set environment's proxies.
620 env_proxies = get_environ_proxies(url) or {}
621 for (k, v) in env_proxies.items():
622 proxies.setdefault(k, v)
623
624 # Look for requests environment configuration and be compatible
625 # with cURL.
626 if verify is True or verify is None:
627 verify = (os.environ.get('REQUESTS_CA_BUNDLE') or
628 os.environ.get('CURL_CA_BUNDLE'))
629
630 # Merge all the kwargs.
631 proxies = merge_setting(proxies, self.proxies)
632 stream = merge_setting(stream, self.stream)
633 verify = merge_setting(verify, self.verify)
634 cert = merge_setting(cert, self.cert)
635
636 return {'verify': verify, 'proxies': proxies, 'stream': stream,
637 'cert': cert}
638
639 def get_adapter(self, url):
640 """Returns the appropriate connnection adapter for the given URL."""
641 for (prefix, adapter) in self.adapters.items():
642
643 if url.lower().startswith(prefix):
644 return adapter
645
646 # Nothing matches :-/
647 raise InvalidSchema("No connection adapters were found for '%s'" % url)
648
649 def close(self):
650 """Closes all adapters and as such the session"""
651 for v in self.adapters.values():
652 v.close()
653
654 def mount(self, prefix, adapter):
655 """Registers a connection adapter to a prefix.
656
657 Adapters are sorted in descending order by key length."""
658
659 self.adapters[prefix] = adapter
660 keys_to_move = [k for k in self.adapters if len(k) < len(prefix)]
661
662 for key in keys_to_move:
663 self.adapters[key] = self.adapters.pop(key)
664
665 def __getstate__(self):
666 state = dict((attr, getattr(self, attr, None)) for attr in self.__attrs__)
667 state['redirect_cache'] = dict(self.redirect_cache)
668 return state
669
670 def __setstate__(self, state):
671 redirect_cache = state.pop('redirect_cache', {})
672 for attr, value in state.items():
673 setattr(self, attr, value)
674
675 self.redirect_cache = RecentlyUsedContainer(REDIRECT_CACHE_SIZE)
676 for redirect, to in redirect_cache.items():
677 self.redirect_cache[redirect] = to
678
679
680 def session():
681 """Returns a :class:`Session` for context-management."""
682
683 return Session()