codekingpro/portable-devtools
114k
1"""2requests.sessions3~~~~~~~~~~~~~~~~~4 5This module provides a Session object to manage and persist settings across6requests (cookies, auth, proxies).7"""8import os9import sys10import time11from collections import OrderedDict12from datetime import timedelta13 14from ._internal_utils import to_native_string15from .adapters import HTTPAdapter16from .auth import _basic_auth_str17from .compat import Mapping, cookielib, urljoin, urlparse18from .cookies import (19 RequestsCookieJar,20 cookiejar_from_dict,21 extract_cookies_to_jar,22 merge_cookies,23)24from .exceptions import (25 ChunkedEncodingError,26 ContentDecodingError,27 InvalidSchema,28 TooManyRedirects,29)30from .hooks import default_hooks, dispatch_hook31 32# formerly defined here, reexposed here for backward compatibility33from .models import ( # noqa: F40134 DEFAULT_REDIRECT_LIMIT,35 REDIRECT_STATI,36 PreparedRequest,37 Request,38)39from .status_codes import codes40from .structures import CaseInsensitiveDict41from .utils import ( # noqa: F40142 DEFAULT_PORTS,43 default_headers,44 get_auth_from_url,45 get_environ_proxies,46 get_netrc_auth,47 requote_uri,48 resolve_proxies,49 rewind_body,50 should_bypass_proxies,51 to_key_val_list,52)53 54# Preferred clock, based on which one is more accurate on a given system.55if sys.platform == "win32":56 preferred_clock = time.perf_counter57else:58 preferred_clock = time.time59 60 61def merge_setting(request_setting, session_setting, dict_class=OrderedDict):62 """Determines appropriate setting for a given request, taking into account63 the explicit setting on that request, and the setting in the session. If a64 setting is a dictionary, they will be merged together using `dict_class`65 """66 67 if session_setting is None:68 return request_setting69 70 if request_setting is None:71 return session_setting72 73 # Bypass if not a dictionary (e.g. verify)74 if not (75 isinstance(session_setting, Mapping) and isinstance(request_setting, Mapping)76 ):77 return request_setting78 79 merged_setting = dict_class(to_key_val_list(session_setting))80 merged_setting.update(to_key_val_list(request_setting))81 82 # Remove keys that are set to None. Extract keys first to avoid altering83 # the dictionary during iteration.84 none_keys = [k for (k, v) in merged_setting.items() if v is None]85 for key in none_keys:86 del merged_setting[key]87 88 return merged_setting89 90 91def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):92 """Properly merges both requests and session hooks.93 94 This is necessary because when request_hooks == {'response': []}, the95 merge breaks Session hooks entirely.96 """97 if session_hooks is None or session_hooks.get("response") == []:98 return request_hooks99 100 if request_hooks is None or request_hooks.get("response") == []:101 return session_hooks102 103 return merge_setting(request_hooks, session_hooks, dict_class)104 105 106class SessionRedirectMixin:107 def get_redirect_target(self, resp):108 """Receives a Response. Returns a redirect URI or ``None``"""109 # Due to the nature of how requests processes redirects this method will110 # be called at least once upon the original response and at least twice111 # on each subsequent redirect response (if any).112 # If a custom mixin is used to handle this logic, it may be advantageous113 # to cache the redirect location onto the response object as a private114 # attribute.115 if resp.is_redirect:116 location = resp.headers["location"]117 # Currently the underlying http module on py3 decode headers118 # in latin1, but empirical evidence suggests that latin1 is very119 # rarely used with non-ASCII characters in HTTP headers.120 # It is more likely to get UTF8 header rather than latin1.121 # This causes incorrect handling of UTF8 encoded location headers.122 # To solve this, we re-encode the location in latin1.123 location = location.encode("latin1")124 return to_native_string(location, "utf8")125 return None126 127 def should_strip_auth(self, old_url, new_url):128 """Decide whether Authorization header should be removed when redirecting"""129 old_parsed = urlparse(old_url)130 new_parsed = urlparse(new_url)131 if old_parsed.hostname != new_parsed.hostname:132 return True133 # Special case: allow http -> https redirect when using the standard134 # ports. This isn't specified by RFC 7235, but is kept to avoid135 # breaking backwards compatibility with older versions of requests136 # that allowed any redirects on the same host.137 if (138 old_parsed.scheme == "http"139 and old_parsed.port in (80, None)140 and new_parsed.scheme == "https"141 and new_parsed.port in (443, None)142 ):143 return False144 145 # Handle default port usage corresponding to scheme.146 changed_port = old_parsed.port != new_parsed.port147 changed_scheme = old_parsed.scheme != new_parsed.scheme148 default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None)149 if (150 not changed_scheme151 and old_parsed.port in default_port152 and new_parsed.port in default_port153 ):154 return False155 156 # Standard case: root URI must match157 return changed_port or changed_scheme158 159 def resolve_redirects(160 self,161 resp,162 req,163 stream=False,164 timeout=None,165 verify=True,166 cert=None,167 proxies=None,168 yield_requests=False,169 **adapter_kwargs,170 ):171 """Receives a Response. Returns a generator of Responses or Requests."""172 173 hist = [] # keep track of history174 175 url = self.get_redirect_target(resp)176 previous_fragment = urlparse(req.url).fragment177 while url:178 prepared_request = req.copy()179 180 # Update history and keep track of redirects.181 # resp.history must ignore the original request in this loop182 hist.append(resp)183 resp.history = hist[1:]184 185 try:186 resp.content # Consume socket so it can be released187 except (ChunkedEncodingError, ContentDecodingError, RuntimeError):188 resp.raw.read(decode_content=False)189 190 if len(resp.history) >= self.max_redirects:191 raise TooManyRedirects(192 f"Exceeded {self.max_redirects} redirects.", response=resp193 )194 195 # Release the connection back into the pool.196 resp.close()197 198 # Handle redirection without scheme (see: RFC 1808 Section 4)199 if url.startswith("//"):200 parsed_rurl = urlparse(resp.url)201 url = ":".join([to_native_string(parsed_rurl.scheme), url])202 203 # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2)204 parsed = urlparse(url)205 if parsed.fragment == "" and previous_fragment:206 parsed = parsed._replace(fragment=previous_fragment)207 elif parsed.fragment:208 previous_fragment = parsed.fragment209 url = parsed.geturl()210 211 # Facilitate relative 'location' headers, as allowed by RFC 7231.212 # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')213 # Compliant with RFC3986, we percent encode the url.214 if not parsed.netloc:215 url = urljoin(resp.url, requote_uri(url))216 else:217 url = requote_uri(url)218 219 prepared_request.url = to_native_string(url)220 221 self.rebuild_method(prepared_request, resp)222 223 # https://github.com/psf/requests/issues/1084224 if resp.status_code not in (225 codes.temporary_redirect,226 codes.permanent_redirect,227 ):228 # https://github.com/psf/requests/issues/3490229 purged_headers = ("Content-Length", "Content-Type", "Transfer-Encoding")230 for header in purged_headers:231 prepared_request.headers.pop(header, None)232 prepared_request.body = None233 234 headers = prepared_request.headers235 headers.pop("Cookie", None)236 237 # Extract any cookies sent on the response to the cookiejar238 # in the new request. Because we've mutated our copied prepared239 # request, use the old one that we haven't yet touched.240 extract_cookies_to_jar(prepared_request._cookies, req, resp.raw)241 merge_cookies(prepared_request._cookies, self.cookies)242 prepared_request.prepare_cookies(prepared_request._cookies)243 244 # Rebuild auth and proxy information.245 proxies = self.rebuild_proxies(prepared_request, proxies)246 self.rebuild_auth(prepared_request, resp)247 248 # A failed tell() sets `_body_position` to `object()`. This non-None249 # value ensures `rewindable` will be True, allowing us to raise an250 # UnrewindableBodyError, instead of hanging the connection.251 rewindable = prepared_request._body_position is not None and (252 "Content-Length" in headers or "Transfer-Encoding" in headers253 )254 255 # Attempt to rewind consumed file-like object.256 if rewindable:257 rewind_body(prepared_request)258 259 # Override the original request.260 req = prepared_request261 262 if yield_requests:263 yield req264 else:265 266 resp = self.send(267 req,268 stream=stream,269 timeout=timeout,270 verify=verify,271 cert=cert,272 proxies=proxies,273 allow_redirects=False,274 **adapter_kwargs,275 )276 277 extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)278 279 # extract redirect url, if any, for the next loop280 url = self.get_redirect_target(resp)281 yield resp282 283 def rebuild_auth(self, prepared_request, response):284 """When being redirected we may want to strip authentication from the285 request to avoid leaking credentials. This method intelligently removes286 and reapplies authentication where possible to avoid credential loss.287 """288 headers = prepared_request.headers289 url = prepared_request.url290 291 if "Authorization" in headers and self.should_strip_auth(292 response.request.url, url293 ):294 # If we get redirected to a new host, we should strip out any295 # authentication headers.296 del headers["Authorization"]297 298 # .netrc might have more auth for us on our new host.299 new_auth = get_netrc_auth(url) if self.trust_env else None300 if new_auth is not None:301 prepared_request.prepare_auth(new_auth)302 303 def rebuild_proxies(self, prepared_request, proxies):304 """This method re-evaluates the proxy configuration by considering the305 environment variables. If we are redirected to a URL covered by306 NO_PROXY, we strip the proxy configuration. Otherwise, we set missing307 proxy keys for this URL (in case they were stripped by a previous308 redirect).309 310 This method also replaces the Proxy-Authorization header where311 necessary.312 313 :rtype: dict314 """315 headers = prepared_request.headers316 scheme = urlparse(prepared_request.url).scheme317 new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env)318 319 if "Proxy-Authorization" in headers:320 del headers["Proxy-Authorization"]321 322 try:323 username, password = get_auth_from_url(new_proxies[scheme])324 except KeyError:325 username, password = None, None326 327 # urllib3 handles proxy authorization for us in the standard adapter.328 # Avoid appending this to TLS tunneled requests where it may be leaked.329 if not scheme.startswith('https') and username and password:330 headers["Proxy-Authorization"] = _basic_auth_str(username, password)331 332 return new_proxies333 334 def rebuild_method(self, prepared_request, response):335 """When being redirected we may want to change the method of the request336 based on certain specs or browser behavior.337 """338 method = prepared_request.method339 340 # https://tools.ietf.org/html/rfc7231#section-6.4.4341 if response.status_code == codes.see_other and method != "HEAD":342 method = "GET"343 344 # Do what the browsers do, despite standards...345 # First, turn 302s into GETs.346 if response.status_code == codes.found and method != "HEAD":347 method = "GET"348 349 # Second, if a POST is responded to with a 301, turn it into a GET.350 # This bizarre behaviour is explained in Issue 1704.351 if response.status_code == codes.moved and method == "POST":352 method = "GET"353 354 prepared_request.method = method355 356 357class Session(SessionRedirectMixin):358 """A Requests session.359 360 Provides cookie persistence, connection-pooling, and configuration.361 362 Basic Usage::363 364 >>> import requests365 >>> s = requests.Session()366 >>> s.get('https://httpbin.org/get')367 <Response [200]>368 369 Or as a context manager::370 371 >>> with requests.Session() as s:372 ... s.get('https://httpbin.org/get')373 <Response [200]>374 """375 376 __attrs__ = [377 "headers",378 "cookies",379 "auth",380 "proxies",381 "hooks",382 "params",383 "verify",384 "cert",385 "adapters",386 "stream",387 "trust_env",388 "max_redirects",389 ]390 391 def __init__(self):392 393 #: A case-insensitive dictionary of headers to be sent on each394 #: :class:`Request <Request>` sent from this395 #: :class:`Session <Session>`.396 self.headers = default_headers()397 398 #: Default Authentication tuple or object to attach to399 #: :class:`Request <Request>`.400 self.auth = None401 402 #: Dictionary mapping protocol or protocol and host to the URL of the proxy403 #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to404 #: be used on each :class:`Request <Request>`.405 self.proxies = {}406 407 #: Event-handling hooks.408 self.hooks = default_hooks()409 410 #: Dictionary of querystring data to attach to each411 #: :class:`Request <Request>`. The dictionary values may be lists for412 #: representing multivalued query parameters.413 self.params = {}414 415 #: Stream response content default.416 self.stream = False417 418 #: SSL Verification default.419 #: Defaults to `True`, requiring requests to verify the TLS certificate at the420 #: remote end.421 #: If verify is set to `False`, requests will accept any TLS certificate422 #: presented by the server, and will ignore hostname mismatches and/or423 #: expired certificates, which will make your application vulnerable to424 #: man-in-the-middle (MitM) attacks.425 #: Only set this to `False` for testing.426 self.verify = True427 428 #: SSL client certificate default, if String, path to ssl client429 #: cert file (.pem). If Tuple, ('cert', 'key') pair.430 self.cert = None431 432 #: Maximum number of redirects allowed. If the request exceeds this433 #: limit, a :class:`TooManyRedirects` exception is raised.434 #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is435 #: 30.436 self.max_redirects = DEFAULT_REDIRECT_LIMIT437 438 #: Trust environment settings for proxy configuration, default439 #: authentication and similar.440 self.trust_env = True441 442 #: A CookieJar containing all currently outstanding cookies set on this443 #: session. By default it is a444 #: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but445 #: may be any other ``cookielib.CookieJar`` compatible object.446 self.cookies = cookiejar_from_dict({})447 448 # Default connection adapters.449 self.adapters = OrderedDict()450 self.mount("https://", HTTPAdapter())451 self.mount("http://", HTTPAdapter())452 453 def __enter__(self):454 return self455 456 def __exit__(self, *args):457 self.close()458 459 def prepare_request(self, request):460 """Constructs a :class:`PreparedRequest <PreparedRequest>` for461 transmission and returns it. The :class:`PreparedRequest` has settings462 merged from the :class:`Request <Request>` instance and those of the463 :class:`Session`.464 465 :param request: :class:`Request` instance to prepare with this466 session's settings.467 :rtype: requests.PreparedRequest468 """469 cookies = request.cookies or {}470 471 # Bootstrap CookieJar.472 if not isinstance(cookies, cookielib.CookieJar):473 cookies = cookiejar_from_dict(cookies)474 475 # Merge with session cookies476 merged_cookies = merge_cookies(477 merge_cookies(RequestsCookieJar(), self.cookies), cookies478 )479 480 # Set environment's basic authentication if not explicitly set.481 auth = request.auth482 if self.trust_env and not auth and not self.auth:483 auth = get_netrc_auth(request.url)484 485 p = PreparedRequest()486 p.prepare(487 method=request.method.upper(),488 url=request.url,489 files=request.files,490 data=request.data,491 json=request.json,492 headers=merge_setting(493 request.headers, self.headers, dict_class=CaseInsensitiveDict494 ),495 params=merge_setting(request.params, self.params),496 auth=merge_setting(auth, self.auth),497 cookies=merged_cookies,498 hooks=merge_hooks(request.hooks, self.hooks),499 )500 return p501 502 def request(503 self,504 method,505 url,506 params=None,507 data=None,508 headers=None,509 cookies=None,510 files=None,511 auth=None,512 timeout=None,513 allow_redirects=True,514 proxies=None,515 hooks=None,516 stream=None,517 verify=None,518 cert=None,519 json=None,520 ):521 """Constructs a :class:`Request <Request>`, prepares it and sends it.522 Returns :class:`Response <Response>` object.523 524 :param method: method for the new :class:`Request` object.525 :param url: URL for the new :class:`Request` object.526 :param params: (optional) Dictionary or bytes to be sent in the query527 string for the :class:`Request`.528 :param data: (optional) Dictionary, list of tuples, bytes, or file-like529 object to send in the body of the :class:`Request`.530 :param json: (optional) json to send in the body of the531 :class:`Request`.532 :param headers: (optional) Dictionary of HTTP Headers to send with the533 :class:`Request`.534 :param cookies: (optional) Dict or CookieJar object to send with the535 :class:`Request`.536 :param files: (optional) Dictionary of ``'filename': file-like-objects``537 for multipart encoding upload.538 :param auth: (optional) Auth tuple or callable to enable539 Basic/Digest/Custom HTTP Auth.540 :param timeout: (optional) How long to wait for the server to send541 data before giving up, as a float, or a :ref:`(connect timeout,542 read timeout) <timeouts>` tuple.543 :type timeout: float or tuple544 :param allow_redirects: (optional) Set to True by default.545 :type allow_redirects: bool546 :param proxies: (optional) Dictionary mapping protocol or protocol and547 hostname to the URL of the proxy.548 :param stream: (optional) whether to immediately download the response549 content. Defaults to ``False``.550 :param verify: (optional) Either a boolean, in which case it controls whether we verify551 the server's TLS certificate, or a string, in which case it must be a path552 to a CA bundle to use. Defaults to ``True``. When set to553 ``False``, requests will accept any TLS certificate presented by554 the server, and will ignore hostname mismatches and/or expired555 certificates, which will make your application vulnerable to556 man-in-the-middle (MitM) attacks. Setting verify to ``False``557 may be useful during local development or testing.558 :param cert: (optional) if String, path to ssl client cert file (.pem).559 If Tuple, ('cert', 'key') pair.560 :rtype: requests.Response561 """562 # Create the Request.563 req = Request(564 method=method.upper(),565 url=url,566 headers=headers,567 files=files,568 data=data or {},569 json=json,570 params=params or {},571 auth=auth,572 cookies=cookies,573 hooks=hooks,574 )575 prep = self.prepare_request(req)576 577 proxies = proxies or {}578 579 settings = self.merge_environment_settings(580 prep.url, proxies, stream, verify, cert581 )582 583 # Send the request.584 send_kwargs = {585 "timeout": timeout,586 "allow_redirects": allow_redirects,587 }588 send_kwargs.update(settings)589 resp = self.send(prep, **send_kwargs)590 591 return resp592 593 def get(self, url, **kwargs):594 r"""Sends a GET request. Returns :class:`Response` object.595 596 :param url: URL for the new :class:`Request` object.597 :param \*\*kwargs: Optional arguments that ``request`` takes.598 :rtype: requests.Response599 """600 601 kwargs.setdefault("allow_redirects", True)602 return self.request("GET", url, **kwargs)603 604 def options(self, url, **kwargs):605 r"""Sends a OPTIONS request. Returns :class:`Response` object.606 607 :param url: URL for the new :class:`Request` object.608 :param \*\*kwargs: Optional arguments that ``request`` takes.609 :rtype: requests.Response610 """611 612 kwargs.setdefault("allow_redirects", True)613 return self.request("OPTIONS", url, **kwargs)614 615 def head(self, url, **kwargs):616 r"""Sends a HEAD request. Returns :class:`Response` object.617 618 :param url: URL for the new :class:`Request` object.619 :param \*\*kwargs: Optional arguments that ``request`` takes.620 :rtype: requests.Response621 """622 623 kwargs.setdefault("allow_redirects", False)624 return self.request("HEAD", url, **kwargs)625 626 def post(self, url, data=None, json=None, **kwargs):627 r"""Sends a POST request. Returns :class:`Response` object.628 629 :param url: URL for the new :class:`Request` object.630 :param data: (optional) Dictionary, list of tuples, bytes, or file-like631 object to send in the body of the :class:`Request`.632 :param json: (optional) json to send in the body of the :class:`Request`.633 :param \*\*kwargs: Optional arguments that ``request`` takes.634 :rtype: requests.Response635 """636 637 return self.request("POST", url, data=data, json=json, **kwargs)638 639 def put(self, url, data=None, **kwargs):640 r"""Sends a PUT request. Returns :class:`Response` object.641 642 :param url: URL for the new :class:`Request` object.643 :param data: (optional) Dictionary, list of tuples, bytes, or file-like644 object to send in the body of the :class:`Request`.645 :param \*\*kwargs: Optional arguments that ``request`` takes.646 :rtype: requests.Response647 """648 649 return self.request("PUT", url, data=data, **kwargs)650 651 def patch(self, url, data=None, **kwargs):652 r"""Sends a PATCH request. Returns :class:`Response` object.653 654 :param url: URL for the new :class:`Request` object.655 :param data: (optional) Dictionary, list of tuples, bytes, or file-like656 object to send in the body of the :class:`Request`.657 :param \*\*kwargs: Optional arguments that ``request`` takes.658 :rtype: requests.Response659 """660 661 return self.request("PATCH", url, data=data, **kwargs)662 663 def delete(self, url, **kwargs):664 r"""Sends a DELETE request. Returns :class:`Response` object.665 666 :param url: URL for the new :class:`Request` object.667 :param \*\*kwargs: Optional arguments that ``request`` takes.668 :rtype: requests.Response669 """670 671 return self.request("DELETE", url, **kwargs)672 673 def send(self, request, **kwargs):674 """Send a given PreparedRequest.675 676 :rtype: requests.Response677 """678 # Set defaults that the hooks can utilize to ensure they always have679 # the correct parameters to reproduce the previous request.680 kwargs.setdefault("stream", self.stream)681 kwargs.setdefault("verify", self.verify)682 kwargs.setdefault("cert", self.cert)683 if "proxies" not in kwargs:684 kwargs["proxies"] = resolve_proxies(request, self.proxies, self.trust_env)685 686 # It's possible that users might accidentally send a Request object.687 # Guard against that specific failure case.688 if isinstance(request, Request):689 raise ValueError("You can only send PreparedRequests.")690 691 # Set up variables needed for resolve_redirects and dispatching of hooks692 allow_redirects = kwargs.pop("allow_redirects", True)693 stream = kwargs.get("stream")694 hooks = request.hooks695 696 # Get the appropriate adapter to use697 adapter = self.get_adapter(url=request.url)698 699 # Start time (approximately) of the request700 start = preferred_clock()701 702 # Send the request703 r = adapter.send(request, **kwargs)704 705 # Total elapsed time of the request (approximately)706 elapsed = preferred_clock() - start707 r.elapsed = timedelta(seconds=elapsed)708 709 # Response manipulation hooks710 r = dispatch_hook("response", hooks, r, **kwargs)711 712 # Persist cookies713 if r.history:714 715 # If the hooks create history then we want those cookies too716 for resp in r.history:717 extract_cookies_to_jar(self.cookies, resp.request, resp.raw)718 719 extract_cookies_to_jar(self.cookies, request, r.raw)720 721 # Resolve redirects if allowed.722 if allow_redirects:723 # Redirect resolving generator.724 gen = self.resolve_redirects(r, request, **kwargs)725 history = [resp for resp in gen]726 else:727 history = []728 729 # Shuffle things around if there's history.730 if history:731 # Insert the first (original) request at the start732 history.insert(0, r)733 # Get the last request made734 r = history.pop()735 r.history = history736 737 # If redirects aren't being followed, store the response on the Request for Response.next().738 if not allow_redirects:739 try:740 r._next = next(741 self.resolve_redirects(r, request, yield_requests=True, **kwargs)742 )743 except StopIteration:744 pass745 746 if not stream:747 r.content748 749 return r750 751 def merge_environment_settings(self, url, proxies, stream, verify, cert):752 """753 Check the environment and merge it with some settings.754 755 :rtype: dict756 """757 # Gather clues from the surrounding environment.758 if self.trust_env:759 # Set environment's proxies.760 no_proxy = proxies.get("no_proxy") if proxies is not None else None761 env_proxies = get_environ_proxies(url, no_proxy=no_proxy)762 for (k, v) in env_proxies.items():763 proxies.setdefault(k, v)764 765 # Look for requests environment configuration766 # and be compatible with cURL.767 if verify is True or verify is None:768 verify = (769 os.environ.get("REQUESTS_CA_BUNDLE")770 or os.environ.get("CURL_CA_BUNDLE")771 or verify772 )773 774 # Merge all the kwargs.775 proxies = merge_setting(proxies, self.proxies)776 stream = merge_setting(stream, self.stream)777 verify = merge_setting(verify, self.verify)778 cert = merge_setting(cert, self.cert)779 780 return {"proxies": proxies, "stream": stream, "verify": verify, "cert": cert}781 782 def get_adapter(self, url):783 """784 Returns the appropriate connection adapter for the given URL.785 786 :rtype: requests.adapters.BaseAdapter787 """788 for (prefix, adapter) in self.adapters.items():789 790 if url.lower().startswith(prefix.lower()):791 return adapter792 793 # Nothing matches :-/794 raise InvalidSchema(f"No connection adapters were found for {url!r}")795 796 def close(self):797 """Closes all adapters and as such the session"""798 for v in self.adapters.values():799 v.close()800 801 def mount(self, prefix, adapter):802 """Registers a connection adapter to a prefix.803 804 Adapters are sorted in descending order by prefix length.805 """806 self.adapters[prefix] = adapter807 keys_to_move = [k for k in self.adapters if len(k) < len(prefix)]808 809 for key in keys_to_move:810 self.adapters[key] = self.adapters.pop(key)811 812 def __getstate__(self):813 state = {attr: getattr(self, attr, None) for attr in self.__attrs__}814 return state815 816 def __setstate__(self, state):817 for attr, value in state.items():818 setattr(self, attr, value)819 820 821def session():822 """823 Returns a :class:`Session` for context-management.824 825 .. deprecated:: 1.0.0826 827 This method has been deprecated since version 1.0.0 and is only kept for828 backwards compatibility. New code should use :class:`~requests.sessions.Session`829 to create a session. This may be removed at a future date.830 831 :rtype: Session832 """833 return Session()834 