Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/requests/sessions.py: 23%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2requests.sessions
3~~~~~~~~~~~~~~~~~
5This module provides a Session object to manage and persist settings across
6requests (cookies, auth, proxies).
7"""
9from __future__ import annotations
11import os
12import sys
13import time
14from collections import OrderedDict
15from collections.abc import Generator, Mapping, MutableMapping
16from datetime import timedelta
17from typing import TYPE_CHECKING, Any, cast
19from ._internal_utils import to_native_string
20from ._types import is_prepared as _is_prepared
21from .adapters import HTTPAdapter
22from .auth import _basic_auth_str # type: ignore[reportPrivateUsage]
23from .compat import cookielib, urljoin, urlparse
24from .cookies import (
25 RequestsCookieJar,
26 cookiejar_from_dict,
27 extract_cookies_to_jar,
28 merge_cookies,
29)
30from .exceptions import (
31 ChunkedEncodingError,
32 ContentDecodingError,
33 InvalidSchema,
34 TooManyRedirects,
35)
36from .hooks import default_hooks, dispatch_hook
38# formerly defined here, reexposed here for backward compatibility
39from .models import ( # noqa: F401
40 DEFAULT_REDIRECT_LIMIT,
41 REDIRECT_STATI, # type: ignore[reportUnusedImport]
42 PreparedRequest,
43 Request,
44 Response,
45)
46from .status_codes import codes
47from .structures import CaseInsensitiveDict
48from .utils import ( # noqa: F401
49 DEFAULT_PORTS,
50 default_headers,
51 get_auth_from_url,
52 get_environ_proxies,
53 get_netrc_auth,
54 requote_uri,
55 resolve_proxies,
56 rewind_body,
57 should_bypass_proxies, # type: ignore[reportUnusedImport] # re-export for external consumers
58 to_key_val_list,
59)
61if TYPE_CHECKING:
62 from http.cookiejar import CookieJar
64 from typing_extensions import Self, Unpack
66 from . import _types as _t
67 from .adapters import BaseAdapter
69# Preferred clock, based on which one is more accurate on a given system.
70if sys.platform == "win32":
71 preferred_clock = time.perf_counter
72else:
73 preferred_clock = time.time
76def merge_setting(
77 request_setting: Any, session_setting: Any, dict_class: type = OrderedDict
78) -> Any:
79 """Determines appropriate setting for a given request, taking into account
80 the explicit setting on that request, and the setting in the session. If a
81 setting is a dictionary, they will be merged together using `dict_class`
82 """
84 if session_setting is None:
85 return request_setting
87 if request_setting is None:
88 return session_setting
90 # Bypass if not a dictionary (e.g. verify)
91 if not (
92 isinstance(session_setting, Mapping) and isinstance(request_setting, Mapping)
93 ):
94 return request_setting
96 merged_setting = dict_class(to_key_val_list(session_setting)) # type: ignore[arg-type] # isinstance narrows Any to Mapping[Unknown]
97 merged_setting.update(to_key_val_list(request_setting)) # type: ignore[arg-type]
99 # Remove keys that are set to None. Extract keys first to avoid altering
100 # the dictionary during iteration.
101 none_keys = [k for (k, v) in merged_setting.items() if v is None]
102 for key in none_keys:
103 del merged_setting[key]
105 return merged_setting
108def merge_hooks(
109 request_hooks: _t.HooksType,
110 session_hooks: _t.HooksType,
111 dict_class: type = OrderedDict,
112) -> _t.HooksType:
113 """Properly merges both requests and session hooks.
115 This is necessary because when request_hooks == {'response': []}, the
116 merge breaks Session hooks entirely.
117 """
118 if session_hooks is None or session_hooks.get("response") == []:
119 return request_hooks
121 if request_hooks is None or request_hooks.get("response") == []:
122 return session_hooks
124 return merge_setting(request_hooks, session_hooks, dict_class)
127class SessionRedirectMixin:
128 max_redirects: int
129 trust_env: bool
130 cookies: RequestsCookieJar
132 def send(self, request: PreparedRequest, **kwargs: Any) -> Response: ...
134 def get_redirect_target(self, resp: Response) -> str | None:
135 """Receives a Response. Returns a redirect URI or ``None``"""
136 # Due to the nature of how requests processes redirects this method will
137 # be called at least once upon the original response and at least twice
138 # on each subsequent redirect response (if any).
139 # If a custom mixin is used to handle this logic, it may be advantageous
140 # to cache the redirect location onto the response object as a private
141 # attribute.
142 if resp.is_redirect:
143 location = resp.headers["location"]
144 # Currently the underlying http module on py3 decode headers
145 # in latin1, but empirical evidence suggests that latin1 is very
146 # rarely used with non-ASCII characters in HTTP headers.
147 # It is more likely to get UTF8 header rather than latin1.
148 # This causes incorrect handling of UTF8 encoded location headers.
149 # To solve this, we re-encode the location in latin1.
150 location = location.encode("latin1")
151 return to_native_string(location, "utf8")
152 return None
154 def should_strip_auth(self, old_url: str, new_url: str) -> bool:
155 """Decide whether Authorization header should be removed when redirecting"""
156 old_parsed = urlparse(old_url)
157 new_parsed = urlparse(new_url)
158 if old_parsed.hostname != new_parsed.hostname:
159 return True
160 # Special case: allow http -> https redirect when using the standard
161 # ports. This isn't specified by RFC 7235, but is kept to avoid
162 # breaking backwards compatibility with older versions of requests
163 # that allowed any redirects on the same host.
164 if (
165 old_parsed.scheme == "http"
166 and old_parsed.port in (80, None)
167 and new_parsed.scheme == "https"
168 and new_parsed.port in (443, None)
169 ):
170 return False
172 # Handle default port usage corresponding to scheme.
173 changed_port = old_parsed.port != new_parsed.port
174 changed_scheme = old_parsed.scheme != new_parsed.scheme
175 default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None)
176 if (
177 not changed_scheme
178 and old_parsed.port in default_port
179 and new_parsed.port in default_port
180 ):
181 return False
183 # Standard case: root URI must match
184 return changed_port or changed_scheme
186 def resolve_redirects(
187 self,
188 resp: Response,
189 req: PreparedRequest,
190 stream: bool = False,
191 timeout: _t.TimeoutType = None,
192 verify: _t.VerifyType = True,
193 cert: _t.CertType = None,
194 proxies: dict[str, str] | None = None,
195 yield_requests: bool = False,
196 **adapter_kwargs: Any,
197 ) -> Generator[Response, None, None]:
198 """Receives a Response. Returns a generator of Responses or Requests."""
200 hist: list[Response] = [] # keep track of history
202 url = self.get_redirect_target(resp)
203 previous_fragment = urlparse(req.url).fragment
204 while url:
205 prepared_request = req.copy()
207 # Update history and keep track of redirects.
208 resp.history = hist[:]
209 hist.append(resp)
211 try:
212 resp.content # Consume socket so it can be released
213 except (ChunkedEncodingError, ContentDecodingError, RuntimeError):
214 resp.raw.read(decode_content=False)
216 if len(resp.history) >= self.max_redirects:
217 raise TooManyRedirects(
218 f"Exceeded {self.max_redirects} redirects.", response=resp
219 )
221 # Release the connection back into the pool.
222 resp.close()
224 # Handle redirection without scheme (see: RFC 1808 Section 4)
225 if url.startswith("//"):
226 parsed_rurl = urlparse(resp.url)
227 url = ":".join([to_native_string(parsed_rurl.scheme), url])
229 # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2)
230 parsed = urlparse(url)
231 if parsed.fragment == "" and previous_fragment:
232 parsed = parsed._replace(fragment=previous_fragment)
233 elif parsed.fragment:
234 previous_fragment = parsed.fragment
235 url = parsed.geturl()
237 # Facilitate relative 'location' headers, as allowed by RFC 7231.
238 # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
239 # Compliant with RFC3986, we percent encode the url.
240 if not parsed.netloc:
241 url = urljoin(resp.url, requote_uri(url))
242 else:
243 url = requote_uri(url)
245 prepared_request.url = to_native_string(url)
247 self.rebuild_method(prepared_request, resp)
249 # https://github.com/psf/requests/issues/1084
250 if resp.status_code not in (
251 codes.temporary_redirect,
252 codes.permanent_redirect,
253 ):
254 # https://github.com/psf/requests/issues/3490
255 purged_headers = ("Content-Length", "Content-Type", "Transfer-Encoding")
256 for header in purged_headers:
257 prepared_request.headers.pop(header, None)
258 prepared_request.body = None
260 headers = prepared_request.headers
261 headers.pop("Cookie", None)
263 # Extract any cookies sent on the response to the cookiejar
264 # in the new request. Because we've mutated our copied prepared
265 # request, use the old one that we haven't yet touched.
266 cookie_jar = cast("CookieJar", prepared_request._cookies) # type: ignore[reportPrivateUsage]
267 extract_cookies_to_jar(cookie_jar, req, resp.raw)
268 merge_cookies(cookie_jar, self.cookies)
269 prepared_request.prepare_cookies(cookie_jar)
271 # Rebuild auth and proxy information.
272 proxies = self.rebuild_proxies(prepared_request, proxies)
273 self.rebuild_auth(prepared_request, resp)
275 # A failed tell() sets `_body_position` to `object()`. This non-None
276 # value ensures `rewindable` will be True, allowing us to raise an
277 # UnrewindableBodyError, instead of hanging the connection.
278 rewindable = prepared_request._body_position is not None and ( # type: ignore[reportPrivateUsage]
279 "Content-Length" in headers or "Transfer-Encoding" in headers
280 )
282 # Attempt to rewind consumed file-like object.
283 if rewindable:
284 rewind_body(prepared_request)
286 # Override the original request.
287 req = prepared_request
289 if yield_requests:
290 yield req # type: ignore[misc] # Internal use only, returns PreparedRequest
291 else:
292 resp = self.send(
293 req,
294 stream=stream,
295 timeout=timeout,
296 verify=verify,
297 cert=cert,
298 proxies=proxies,
299 allow_redirects=False,
300 **adapter_kwargs,
301 )
303 extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
305 # extract redirect url, if any, for the next loop
306 url = self.get_redirect_target(resp)
307 yield resp
309 def rebuild_auth(
310 self, prepared_request: PreparedRequest, response: Response
311 ) -> None:
312 """When being redirected we may want to strip authentication from the
313 request to avoid leaking credentials. This method intelligently removes
314 and reapplies authentication where possible to avoid credential loss.
315 """
316 original_request = response.request
317 assert _is_prepared(original_request)
318 assert _is_prepared(prepared_request)
320 headers = prepared_request.headers
321 original_url = original_request.url
322 url = prepared_request.url
324 if "Authorization" in headers and self.should_strip_auth(original_url, url):
325 # If we get redirected to a new host, we should strip out any
326 # authentication headers.
327 del headers["Authorization"]
329 # .netrc might have more auth for us on our new host.
330 new_auth = get_netrc_auth(url) if self.trust_env else None
331 if new_auth is not None:
332 prepared_request.prepare_auth(new_auth)
334 def rebuild_proxies(
335 self,
336 prepared_request: PreparedRequest,
337 proxies: dict[str, str] | None,
338 ) -> dict[str, str]:
339 """This method re-evaluates the proxy configuration by considering the
340 environment variables. If we are redirected to a URL covered by
341 NO_PROXY, we strip the proxy configuration. Otherwise, we set missing
342 proxy keys for this URL (in case they were stripped by a previous
343 redirect).
345 This method also replaces the Proxy-Authorization header where
346 necessary.
348 :rtype: dict
349 """
350 assert _is_prepared(prepared_request)
351 headers = prepared_request.headers
352 scheme = urlparse(prepared_request.url).scheme
353 new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env)
355 if "Proxy-Authorization" in headers:
356 del headers["Proxy-Authorization"]
358 try:
359 username, password = get_auth_from_url(new_proxies[scheme])
360 except KeyError:
361 username, password = None, None
363 # urllib3 handles proxy authorization for us in the standard adapter.
364 # Avoid appending this to TLS tunneled requests where it may be leaked.
365 if not scheme.startswith("https") and username and password:
366 headers["Proxy-Authorization"] = _basic_auth_str(username, password)
368 return new_proxies
370 def rebuild_method(
371 self, prepared_request: PreparedRequest, response: Response
372 ) -> None:
373 """When being redirected we may want to change the method of the request
374 based on certain specs or browser behavior.
375 """
376 method = prepared_request.method
378 # https://tools.ietf.org/html/rfc7231#section-6.4.4
379 if response.status_code == codes.see_other and method != "HEAD":
380 method = "GET"
382 # Do what the browsers do, despite standards...
383 # First, turn 302s into GETs.
384 if response.status_code == codes.found and method != "HEAD":
385 method = "GET"
387 # Second, if a POST is responded to with a 301, turn it into a GET.
388 # This bizarre behaviour is explained in Issue 1704.
389 if response.status_code == codes.moved and method == "POST":
390 method = "GET"
392 prepared_request.method = method
395class Session(SessionRedirectMixin):
396 """A Requests session.
398 Provides cookie persistence, connection-pooling, and configuration.
400 Basic Usage::
402 >>> import requests
403 >>> s = requests.Session()
404 >>> s.get('https://httpbin.org/get')
405 <Response [200]>
407 Or as a context manager::
409 >>> with requests.Session() as s:
410 ... s.get('https://httpbin.org/get')
411 <Response [200]>
412 """
414 headers: CaseInsensitiveDict[str]
415 auth: _t.AuthType
416 proxies: dict[str, str]
417 hooks: dict[str, list[_t.HookType]]
418 params: MutableMapping[str, Any]
419 stream: bool
420 verify: _t.VerifyType
421 cert: _t.CertType
422 max_redirects: int
423 trust_env: bool
424 cookies: RequestsCookieJar
425 adapters: MutableMapping[str, BaseAdapter]
427 __attrs__: list[str] = [
428 "headers",
429 "cookies",
430 "auth",
431 "proxies",
432 "hooks",
433 "params",
434 "verify",
435 "cert",
436 "adapters",
437 "stream",
438 "trust_env",
439 "max_redirects",
440 ]
442 def __init__(self) -> None:
443 #: A case-insensitive dictionary of headers to be sent on each
444 #: :class:`Request <Request>` sent from this
445 #: :class:`Session <Session>`.
446 self.headers = default_headers()
448 #: Default Authentication tuple or object to attach to
449 #: :class:`Request <Request>`.
450 self.auth = None
452 #: Dictionary mapping protocol or protocol and host to the URL of the proxy
453 #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to
454 #: be used on each :class:`Request <Request>`.
455 self.proxies = {}
457 #: Event-handling hooks.
458 self.hooks = default_hooks()
460 #: Dictionary of querystring data to attach to each
461 #: :class:`Request <Request>`. The dictionary values may be lists for
462 #: representing multivalued query parameters.
463 self.params = {}
465 #: Stream response content default.
466 self.stream = False
468 #: SSL Verification default.
469 #: Defaults to `True`, requiring requests to verify the TLS certificate at the
470 #: remote end.
471 #: If verify is set to `False`, requests will accept any TLS certificate
472 #: presented by the server, and will ignore hostname mismatches and/or
473 #: expired certificates, which will make your application vulnerable to
474 #: man-in-the-middle (MitM) attacks.
475 #: Only set this to `False` for testing.
476 #: If verify is set to a string, it must be the path to a CA bundle file
477 #: that will be used to verify the TLS certificate.
478 self.verify = True
480 #: SSL client certificate default, if String, path to ssl client
481 #: cert file (.pem). If Tuple, ('cert', 'key') pair.
482 self.cert = None
484 #: Maximum number of redirects allowed. If the request exceeds this
485 #: limit, a :class:`TooManyRedirects` exception is raised.
486 #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is
487 #: 30.
488 self.max_redirects = DEFAULT_REDIRECT_LIMIT
490 #: Trust environment settings for proxy configuration, default
491 #: authentication and similar.
492 self.trust_env = True
494 #: A CookieJar containing all currently outstanding cookies set on this
495 #: session. By default it is a
496 #: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but
497 #: may be any other ``cookielib.CookieJar`` compatible object.
498 self.cookies = cookiejar_from_dict({})
500 # Default connection adapters.
501 self.adapters = OrderedDict()
502 self.mount("https://", HTTPAdapter())
503 self.mount("http://", HTTPAdapter())
505 def __enter__(self) -> Self:
506 return self
508 def __exit__(self, *args: Any) -> None:
509 self.close()
511 def prepare_request(self, request: Request) -> PreparedRequest:
512 """Constructs a :class:`PreparedRequest <PreparedRequest>` for
513 transmission and returns it. The :class:`PreparedRequest` has settings
514 merged from the :class:`Request <Request>` instance and those of the
515 :class:`Session`.
517 :param request: :class:`Request` instance to prepare with this
518 session's settings.
519 :rtype: requests.PreparedRequest
520 """
521 url = cast("_t.UriType", request.url)
522 method = cast(str, request.method)
524 cookies = request.cookies or {}
526 # Bootstrap CookieJar.
527 if not isinstance(cookies, cookielib.CookieJar):
528 cookies = cookiejar_from_dict(cookies)
530 # Merge with session cookies
531 merged_cookies = merge_cookies(
532 merge_cookies(RequestsCookieJar(), self.cookies), cookies
533 )
535 # Set environment's basic authentication if not explicitly set.
536 auth = request.auth
537 if self.trust_env and not auth and not self.auth:
538 auth = get_netrc_auth(url)
540 p = PreparedRequest()
541 p.prepare(
542 method=method.upper(),
543 url=url,
544 files=request.files,
545 data=request.data,
546 json=request.json,
547 headers=merge_setting(
548 request.headers, self.headers, dict_class=CaseInsensitiveDict
549 ),
550 params=merge_setting(request.params, self.params),
551 auth=merge_setting(auth, self.auth),
552 cookies=merged_cookies,
553 hooks=merge_hooks(request.hooks, self.hooks),
554 )
555 return p
557 def request(
558 self,
559 method: str,
560 url: _t.UriType,
561 params: _t.ParamsType = None,
562 data: _t.DataType = None,
563 headers: _t.HeadersType = None,
564 cookies: RequestsCookieJar | CookieJar | dict[str, str] | None = None,
565 files: _t.FilesType = None,
566 auth: _t.AuthType = None,
567 timeout: _t.TimeoutType = None,
568 allow_redirects: bool = True,
569 proxies: dict[str, str] | None = None,
570 hooks: _t.HooksInputType | None = None,
571 stream: bool | None = None,
572 verify: _t.VerifyType | None = None,
573 cert: _t.CertType = None,
574 json: _t.JsonType = None,
575 ) -> Response:
576 """Constructs a :class:`Request <Request>`, prepares it and sends it.
577 Returns :class:`Response <Response>` object.
579 :param method: method for the new :class:`Request` object.
580 :param url: URL for the new :class:`Request` object.
581 :param params: (optional) Dictionary or bytes to be sent in the query
582 string for the :class:`Request`.
583 :param data: (optional) Dictionary, list of tuples, bytes, or file-like
584 object to send in the body of the :class:`Request`.
585 :param json: (optional) json to send in the body of the
586 :class:`Request`.
587 :param headers: (optional) Dictionary of HTTP Headers to send with the
588 :class:`Request`.
589 :param cookies: (optional) Dict or CookieJar object to send with the
590 :class:`Request`.
591 :param files: (optional) Dictionary of ``'filename': file-like-objects``
592 for multipart encoding upload.
593 :param auth: (optional) Auth tuple or callable to enable
594 Basic/Digest/Custom HTTP Auth.
595 :param timeout: (optional) How many seconds to wait for the server to send
596 data before giving up, as a float, or a :ref:`(connect timeout,
597 read timeout) <timeouts>` tuple.
598 :type timeout: float or tuple
599 :param allow_redirects: (optional) Set to True by default.
600 :type allow_redirects: bool
601 :param proxies: (optional) Dictionary mapping protocol or protocol and
602 hostname to the URL of the proxy.
603 :param hooks: (optional) Dictionary mapping hook name to one event or
604 list of events, event must be callable.
605 :param stream: (optional) whether to immediately download the response
606 content. Defaults to ``False``.
607 :param verify: (optional) Either a boolean, in which case it controls whether we verify
608 the server's TLS certificate, or a string, in which case it must be a path
609 to a CA bundle to use. Defaults to ``True``. When set to
610 ``False``, requests will accept any TLS certificate presented by
611 the server, and will ignore hostname mismatches and/or expired
612 certificates, which will make your application vulnerable to
613 man-in-the-middle (MitM) attacks. Setting verify to ``False``
614 may be useful during local development or testing.
615 :param cert: (optional) if String, path to ssl client cert file (.pem).
616 If Tuple, ('cert', 'key') pair.
617 :rtype: requests.Response
618 """
619 if isinstance(url, bytes):
620 url = url.decode("utf-8")
622 # Create the Request.
623 req = Request(
624 method=method.upper(),
625 url=url,
626 headers=headers,
627 files=files,
628 data=data or {},
629 json=json,
630 params=params or {},
631 auth=auth,
632 cookies=cookies,
633 hooks=hooks,
634 )
635 prep = self.prepare_request(req)
637 assert _is_prepared(prep)
639 proxies = proxies or {}
641 settings = self.merge_environment_settings(
642 prep.url, proxies, stream, verify, cert
643 )
645 # Send the request.
646 send_kwargs = {
647 "timeout": timeout,
648 "allow_redirects": allow_redirects,
649 }
650 send_kwargs.update(settings)
651 resp = self.send(prep, **send_kwargs)
653 return resp
655 def get(
656 self,
657 url: _t.UriType,
658 params: _t.ParamsType = None,
659 **kwargs: Unpack[_t.GetKwargs],
660 ) -> Response:
661 r"""Sends a GET request. Returns :class:`Response` object.
663 :param url: URL for the new :class:`Request` object.
664 :param params: (optional) Dictionary, list of tuples or bytes to send
665 in the query string for the :class:`Request`.
666 :param \*\*kwargs: Optional arguments that ``request`` takes.
667 :rtype: requests.Response
668 """
670 kwargs.setdefault("allow_redirects", True)
671 return self.request("GET", url, params=params, **kwargs)
673 def options(self, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
674 r"""Sends a OPTIONS request. Returns :class:`Response` object.
676 :param url: URL for the new :class:`Request` object.
677 :param \*\*kwargs: Optional arguments that ``request`` takes.
678 :rtype: requests.Response
679 """
681 kwargs.setdefault("allow_redirects", True)
682 return self.request("OPTIONS", url, **kwargs)
684 def head(self, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
685 r"""Sends a HEAD request. Returns :class:`Response` object.
687 :param url: URL for the new :class:`Request` object.
688 :param \*\*kwargs: Optional arguments that ``request`` takes.
689 :rtype: requests.Response
690 """
692 kwargs.setdefault("allow_redirects", False)
693 return self.request("HEAD", url, **kwargs)
695 def post(
696 self,
697 url: _t.UriType,
698 data: _t.DataType = None,
699 json: _t.JsonType = None,
700 **kwargs: Unpack[_t.PostKwargs],
701 ) -> Response:
702 r"""Sends a POST request. Returns :class:`Response` object.
704 :param url: URL for the new :class:`Request` object.
705 :param data: (optional) Dictionary, list of tuples, bytes, or file-like
706 object to send in the body of the :class:`Request`.
707 :param json: (optional) json to send in the body of the :class:`Request`.
708 :param \*\*kwargs: Optional arguments that ``request`` takes.
709 :rtype: requests.Response
710 """
712 return self.request("POST", url, data=data, json=json, **kwargs)
714 def put(
715 self, url: _t.UriType, data: _t.DataType = None, **kwargs: Unpack[_t.DataKwargs]
716 ) -> Response:
717 r"""Sends a PUT request. Returns :class:`Response` object.
719 :param url: URL for the new :class:`Request` object.
720 :param data: (optional) Dictionary, list of tuples, bytes, or file-like
721 object to send in the body of the :class:`Request`.
722 :param \*\*kwargs: Optional arguments that ``request`` takes.
723 :rtype: requests.Response
724 """
726 return self.request("PUT", url, data=data, **kwargs)
728 def patch(
729 self, url: _t.UriType, data: _t.DataType = None, **kwargs: Unpack[_t.DataKwargs]
730 ) -> Response:
731 r"""Sends a PATCH request. Returns :class:`Response` object.
733 :param url: URL for the new :class:`Request` object.
734 :param data: (optional) Dictionary, list of tuples, bytes, or file-like
735 object to send in the body of the :class:`Request`.
736 :param \*\*kwargs: Optional arguments that ``request`` takes.
737 :rtype: requests.Response
738 """
740 return self.request("PATCH", url, data=data, **kwargs)
742 def delete(self, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
743 r"""Sends a DELETE request. Returns :class:`Response` object.
745 :param url: URL for the new :class:`Request` object.
746 :param \*\*kwargs: Optional arguments that ``request`` takes.
747 :rtype: requests.Response
748 """
750 return self.request("DELETE", url, **kwargs)
752 def send(self, request: PreparedRequest, **kwargs: Any) -> Response:
753 """Send a given PreparedRequest.
755 :rtype: requests.Response
756 """
757 # Set defaults that the hooks can utilize to ensure they always have
758 # the correct parameters to reproduce the previous request.
759 kwargs.setdefault("stream", self.stream)
760 kwargs.setdefault("verify", self.verify)
761 kwargs.setdefault("cert", self.cert)
762 if "proxies" not in kwargs:
763 kwargs["proxies"] = resolve_proxies(request, self.proxies, self.trust_env)
765 # It's possible that users might accidentally send a Request object.
766 # Guard against that specific failure case.
767 if isinstance(request, Request):
768 raise ValueError("You can only send PreparedRequests.")
770 assert _is_prepared(request)
772 # Set up variables needed for resolve_redirects and dispatching of hooks
773 allow_redirects = kwargs.pop("allow_redirects", True)
774 stream = kwargs.get("stream")
775 hooks = request.hooks
777 # Get the appropriate adapter to use
778 adapter = self.get_adapter(url=request.url)
780 # Start time (approximately) of the request
781 start = preferred_clock()
783 # Send the request
784 r = adapter.send(request, **kwargs)
786 # Total elapsed time of the request (approximately)
787 elapsed = preferred_clock() - start
788 r.elapsed = timedelta(seconds=elapsed)
790 # Response manipulation hooks
791 r = dispatch_hook("response", hooks, r, **kwargs)
793 # Persist cookies
794 if r.history:
795 # If the hooks create history then we want those cookies too
796 for resp in r.history:
797 extract_cookies_to_jar(self.cookies, resp.request, resp.raw)
799 extract_cookies_to_jar(self.cookies, request, r.raw)
801 # Resolve redirects if allowed.
802 if allow_redirects:
803 # Redirect resolving generator.
804 gen = self.resolve_redirects(r, request, **kwargs)
805 history = [resp for resp in gen]
806 else:
807 history = []
809 # Shuffle things around if there's history.
810 if history:
811 # Insert the first (original) request at the start
812 history.insert(0, r)
813 # Get the last request made
814 r = history.pop()
815 r.history = history
817 # If redirects aren't being followed, store the response on the Request for Response.next().
818 if not allow_redirects:
819 try:
820 r._next = next( # type: ignore[assignment] # yield_requests=True returns PreparedRequest
821 self.resolve_redirects(r, request, yield_requests=True, **kwargs)
822 )
823 except StopIteration:
824 pass
826 if not stream:
827 r.content
829 return r
831 def merge_environment_settings(
832 self,
833 url: str,
834 proxies: dict[str, str] | None,
835 stream: bool | None,
836 verify: _t.VerifyType | None,
837 cert: _t.CertType,
838 ) -> dict[str, Any]:
839 """
840 Check the environment and merge it with some settings.
842 :rtype: dict
843 """
844 # Gather clues from the surrounding environment.
845 if self.trust_env:
846 # Set environment's proxies.
847 no_proxy = proxies.get("no_proxy") if proxies is not None else None
848 env_proxies = get_environ_proxies(url, no_proxy=no_proxy)
849 if proxies is not None:
850 for k, v in env_proxies.items():
851 proxies.setdefault(k, v)
853 # Look for requests environment configuration
854 # and be compatible with cURL.
855 if verify is True or verify is None:
856 verify = (
857 os.environ.get("REQUESTS_CA_BUNDLE")
858 or os.environ.get("CURL_CA_BUNDLE")
859 or verify
860 )
862 # Merge all the kwargs.
863 proxies = merge_setting(proxies, self.proxies)
864 stream = merge_setting(stream, self.stream)
865 verify = merge_setting(verify, self.verify)
866 cert = merge_setting(cert, self.cert)
868 return {"proxies": proxies, "stream": stream, "verify": verify, "cert": cert}
870 def get_adapter(self, url: str) -> BaseAdapter:
871 """
872 Returns the appropriate connection adapter for the given URL.
874 :rtype: requests.adapters.BaseAdapter
875 """
876 for prefix, adapter in self.adapters.items():
877 if url.lower().startswith(prefix.lower()):
878 return adapter
880 # Nothing matches :-/
881 raise InvalidSchema(f"No connection adapters were found for {url!r}")
883 def close(self) -> None:
884 """Closes all adapters and as such the session"""
885 for v in self.adapters.values():
886 v.close()
888 def mount(self, prefix: str, adapter: BaseAdapter) -> None:
889 """Registers a connection adapter to a prefix.
891 Adapters are sorted in descending order by prefix length.
892 """
893 self.adapters[prefix] = adapter
894 keys_to_move = [k for k in self.adapters if len(k) < len(prefix)]
896 for key in keys_to_move:
897 self.adapters[key] = self.adapters.pop(key)
899 def __getstate__(self) -> dict[str, Any]:
900 state = {attr: getattr(self, attr, None) for attr in self.__attrs__}
901 return state
903 def __setstate__(self, state: dict[str, Any]) -> None:
904 for attr, value in state.items():
905 setattr(self, attr, value)
908def session() -> Session:
909 """
910 Returns a :class:`Session` for context-management.
912 .. deprecated:: 1.0.0
914 This method has been deprecated since version 1.0.0 and is only kept for
915 backwards compatibility. New code should use :class:`~requests.sessions.Session`
916 to create a session. This may be removed at a future date.
918 :rtype: Session
919 """
920 return Session()