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