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