jpayne@68: """ jpayne@68: requests.sessions jpayne@68: ~~~~~~~~~~~~~~~~~ jpayne@68: jpayne@68: This module provides a Session object to manage and persist settings across jpayne@68: requests (cookies, auth, proxies). jpayne@68: """ jpayne@68: import os jpayne@68: import sys jpayne@68: import time jpayne@68: from collections import OrderedDict jpayne@68: from datetime import timedelta jpayne@68: jpayne@68: from ._internal_utils import to_native_string jpayne@68: from .adapters import HTTPAdapter jpayne@68: from .auth import _basic_auth_str jpayne@68: from .compat import Mapping, cookielib, urljoin, urlparse jpayne@68: from .cookies import ( jpayne@68: RequestsCookieJar, jpayne@68: cookiejar_from_dict, jpayne@68: extract_cookies_to_jar, jpayne@68: merge_cookies, jpayne@68: ) jpayne@68: from .exceptions import ( jpayne@68: ChunkedEncodingError, jpayne@68: ContentDecodingError, jpayne@68: InvalidSchema, jpayne@68: TooManyRedirects, jpayne@68: ) jpayne@68: from .hooks import default_hooks, dispatch_hook jpayne@68: jpayne@68: # formerly defined here, reexposed here for backward compatibility jpayne@68: from .models import ( # noqa: F401 jpayne@68: DEFAULT_REDIRECT_LIMIT, jpayne@68: REDIRECT_STATI, jpayne@68: PreparedRequest, jpayne@68: Request, jpayne@68: ) jpayne@68: from .status_codes import codes jpayne@68: from .structures import CaseInsensitiveDict jpayne@68: from .utils import ( # noqa: F401 jpayne@68: DEFAULT_PORTS, jpayne@68: default_headers, jpayne@68: get_auth_from_url, jpayne@68: get_environ_proxies, jpayne@68: get_netrc_auth, jpayne@68: requote_uri, jpayne@68: resolve_proxies, jpayne@68: rewind_body, jpayne@68: should_bypass_proxies, jpayne@68: to_key_val_list, jpayne@68: ) jpayne@68: jpayne@68: # Preferred clock, based on which one is more accurate on a given system. jpayne@68: if sys.platform == "win32": jpayne@68: preferred_clock = time.perf_counter jpayne@68: else: jpayne@68: preferred_clock = time.time jpayne@68: jpayne@68: jpayne@68: def merge_setting(request_setting, session_setting, dict_class=OrderedDict): jpayne@68: """Determines appropriate setting for a given request, taking into account jpayne@68: the explicit setting on that request, and the setting in the session. If a jpayne@68: setting is a dictionary, they will be merged together using `dict_class` jpayne@68: """ jpayne@68: jpayne@68: if session_setting is None: jpayne@68: return request_setting jpayne@68: jpayne@68: if request_setting is None: jpayne@68: return session_setting jpayne@68: jpayne@68: # Bypass if not a dictionary (e.g. verify) jpayne@68: if not ( jpayne@68: isinstance(session_setting, Mapping) and isinstance(request_setting, Mapping) jpayne@68: ): jpayne@68: return request_setting jpayne@68: jpayne@68: merged_setting = dict_class(to_key_val_list(session_setting)) jpayne@68: merged_setting.update(to_key_val_list(request_setting)) jpayne@68: jpayne@68: # Remove keys that are set to None. Extract keys first to avoid altering jpayne@68: # the dictionary during iteration. jpayne@68: none_keys = [k for (k, v) in merged_setting.items() if v is None] jpayne@68: for key in none_keys: jpayne@68: del merged_setting[key] jpayne@68: jpayne@68: return merged_setting jpayne@68: jpayne@68: jpayne@68: def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict): jpayne@68: """Properly merges both requests and session hooks. jpayne@68: jpayne@68: This is necessary because when request_hooks == {'response': []}, the jpayne@68: merge breaks Session hooks entirely. jpayne@68: """ jpayne@68: if session_hooks is None or session_hooks.get("response") == []: jpayne@68: return request_hooks jpayne@68: jpayne@68: if request_hooks is None or request_hooks.get("response") == []: jpayne@68: return session_hooks jpayne@68: jpayne@68: return merge_setting(request_hooks, session_hooks, dict_class) jpayne@68: jpayne@68: jpayne@68: class SessionRedirectMixin: jpayne@68: def get_redirect_target(self, resp): jpayne@68: """Receives a Response. Returns a redirect URI or ``None``""" jpayne@68: # Due to the nature of how requests processes redirects this method will jpayne@68: # be called at least once upon the original response and at least twice jpayne@68: # on each subsequent redirect response (if any). jpayne@68: # If a custom mixin is used to handle this logic, it may be advantageous jpayne@68: # to cache the redirect location onto the response object as a private jpayne@68: # attribute. jpayne@68: if resp.is_redirect: jpayne@68: location = resp.headers["location"] jpayne@68: # Currently the underlying http module on py3 decode headers jpayne@68: # in latin1, but empirical evidence suggests that latin1 is very jpayne@68: # rarely used with non-ASCII characters in HTTP headers. jpayne@68: # It is more likely to get UTF8 header rather than latin1. jpayne@68: # This causes incorrect handling of UTF8 encoded location headers. jpayne@68: # To solve this, we re-encode the location in latin1. jpayne@68: location = location.encode("latin1") jpayne@68: return to_native_string(location, "utf8") jpayne@68: return None jpayne@68: jpayne@68: def should_strip_auth(self, old_url, new_url): jpayne@68: """Decide whether Authorization header should be removed when redirecting""" jpayne@68: old_parsed = urlparse(old_url) jpayne@68: new_parsed = urlparse(new_url) jpayne@68: if old_parsed.hostname != new_parsed.hostname: jpayne@68: return True jpayne@68: # Special case: allow http -> https redirect when using the standard jpayne@68: # ports. This isn't specified by RFC 7235, but is kept to avoid jpayne@68: # breaking backwards compatibility with older versions of requests jpayne@68: # that allowed any redirects on the same host. jpayne@68: if ( jpayne@68: old_parsed.scheme == "http" jpayne@68: and old_parsed.port in (80, None) jpayne@68: and new_parsed.scheme == "https" jpayne@68: and new_parsed.port in (443, None) jpayne@68: ): jpayne@68: return False jpayne@68: jpayne@68: # Handle default port usage corresponding to scheme. jpayne@68: changed_port = old_parsed.port != new_parsed.port jpayne@68: changed_scheme = old_parsed.scheme != new_parsed.scheme jpayne@68: default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None) jpayne@68: if ( jpayne@68: not changed_scheme jpayne@68: and old_parsed.port in default_port jpayne@68: and new_parsed.port in default_port jpayne@68: ): jpayne@68: return False jpayne@68: jpayne@68: # Standard case: root URI must match jpayne@68: return changed_port or changed_scheme jpayne@68: jpayne@68: def resolve_redirects( jpayne@68: self, jpayne@68: resp, jpayne@68: req, jpayne@68: stream=False, jpayne@68: timeout=None, jpayne@68: verify=True, jpayne@68: cert=None, jpayne@68: proxies=None, jpayne@68: yield_requests=False, jpayne@68: **adapter_kwargs, jpayne@68: ): jpayne@68: """Receives a Response. Returns a generator of Responses or Requests.""" jpayne@68: jpayne@68: hist = [] # keep track of history jpayne@68: jpayne@68: url = self.get_redirect_target(resp) jpayne@68: previous_fragment = urlparse(req.url).fragment jpayne@68: while url: jpayne@68: prepared_request = req.copy() jpayne@68: jpayne@68: # Update history and keep track of redirects. jpayne@68: # resp.history must ignore the original request in this loop jpayne@68: hist.append(resp) jpayne@68: resp.history = hist[1:] jpayne@68: jpayne@68: try: jpayne@68: resp.content # Consume socket so it can be released jpayne@68: except (ChunkedEncodingError, ContentDecodingError, RuntimeError): jpayne@68: resp.raw.read(decode_content=False) jpayne@68: jpayne@68: if len(resp.history) >= self.max_redirects: jpayne@68: raise TooManyRedirects( jpayne@68: f"Exceeded {self.max_redirects} redirects.", response=resp jpayne@68: ) jpayne@68: jpayne@68: # Release the connection back into the pool. jpayne@68: resp.close() jpayne@68: jpayne@68: # Handle redirection without scheme (see: RFC 1808 Section 4) jpayne@68: if url.startswith("//"): jpayne@68: parsed_rurl = urlparse(resp.url) jpayne@68: url = ":".join([to_native_string(parsed_rurl.scheme), url]) jpayne@68: jpayne@68: # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2) jpayne@68: parsed = urlparse(url) jpayne@68: if parsed.fragment == "" and previous_fragment: jpayne@68: parsed = parsed._replace(fragment=previous_fragment) jpayne@68: elif parsed.fragment: jpayne@68: previous_fragment = parsed.fragment jpayne@68: url = parsed.geturl() jpayne@68: jpayne@68: # Facilitate relative 'location' headers, as allowed by RFC 7231. jpayne@68: # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') jpayne@68: # Compliant with RFC3986, we percent encode the url. jpayne@68: if not parsed.netloc: jpayne@68: url = urljoin(resp.url, requote_uri(url)) jpayne@68: else: jpayne@68: url = requote_uri(url) jpayne@68: jpayne@68: prepared_request.url = to_native_string(url) jpayne@68: jpayne@68: self.rebuild_method(prepared_request, resp) jpayne@68: jpayne@68: # https://github.com/psf/requests/issues/1084 jpayne@68: if resp.status_code not in ( jpayne@68: codes.temporary_redirect, jpayne@68: codes.permanent_redirect, jpayne@68: ): jpayne@68: # https://github.com/psf/requests/issues/3490 jpayne@68: purged_headers = ("Content-Length", "Content-Type", "Transfer-Encoding") jpayne@68: for header in purged_headers: jpayne@68: prepared_request.headers.pop(header, None) jpayne@68: prepared_request.body = None jpayne@68: jpayne@68: headers = prepared_request.headers jpayne@68: headers.pop("Cookie", None) jpayne@68: jpayne@68: # Extract any cookies sent on the response to the cookiejar jpayne@68: # in the new request. Because we've mutated our copied prepared jpayne@68: # request, use the old one that we haven't yet touched. jpayne@68: extract_cookies_to_jar(prepared_request._cookies, req, resp.raw) jpayne@68: merge_cookies(prepared_request._cookies, self.cookies) jpayne@68: prepared_request.prepare_cookies(prepared_request._cookies) jpayne@68: jpayne@68: # Rebuild auth and proxy information. jpayne@68: proxies = self.rebuild_proxies(prepared_request, proxies) jpayne@68: self.rebuild_auth(prepared_request, resp) jpayne@68: jpayne@68: # A failed tell() sets `_body_position` to `object()`. This non-None jpayne@68: # value ensures `rewindable` will be True, allowing us to raise an jpayne@68: # UnrewindableBodyError, instead of hanging the connection. jpayne@68: rewindable = prepared_request._body_position is not None and ( jpayne@68: "Content-Length" in headers or "Transfer-Encoding" in headers jpayne@68: ) jpayne@68: jpayne@68: # Attempt to rewind consumed file-like object. jpayne@68: if rewindable: jpayne@68: rewind_body(prepared_request) jpayne@68: jpayne@68: # Override the original request. jpayne@68: req = prepared_request jpayne@68: jpayne@68: if yield_requests: jpayne@68: yield req jpayne@68: else: jpayne@68: resp = self.send( jpayne@68: req, jpayne@68: stream=stream, jpayne@68: timeout=timeout, jpayne@68: verify=verify, jpayne@68: cert=cert, jpayne@68: proxies=proxies, jpayne@68: allow_redirects=False, jpayne@68: **adapter_kwargs, jpayne@68: ) jpayne@68: jpayne@68: extract_cookies_to_jar(self.cookies, prepared_request, resp.raw) jpayne@68: jpayne@68: # extract redirect url, if any, for the next loop jpayne@68: url = self.get_redirect_target(resp) jpayne@68: yield resp jpayne@68: jpayne@68: def rebuild_auth(self, prepared_request, response): jpayne@68: """When being redirected we may want to strip authentication from the jpayne@68: request to avoid leaking credentials. This method intelligently removes jpayne@68: and reapplies authentication where possible to avoid credential loss. jpayne@68: """ jpayne@68: headers = prepared_request.headers jpayne@68: url = prepared_request.url jpayne@68: jpayne@68: if "Authorization" in headers and self.should_strip_auth( jpayne@68: response.request.url, url jpayne@68: ): jpayne@68: # If we get redirected to a new host, we should strip out any jpayne@68: # authentication headers. jpayne@68: del headers["Authorization"] jpayne@68: jpayne@68: # .netrc might have more auth for us on our new host. jpayne@68: new_auth = get_netrc_auth(url) if self.trust_env else None jpayne@68: if new_auth is not None: jpayne@68: prepared_request.prepare_auth(new_auth) jpayne@68: jpayne@68: def rebuild_proxies(self, prepared_request, proxies): jpayne@68: """This method re-evaluates the proxy configuration by considering the jpayne@68: environment variables. If we are redirected to a URL covered by jpayne@68: NO_PROXY, we strip the proxy configuration. Otherwise, we set missing jpayne@68: proxy keys for this URL (in case they were stripped by a previous jpayne@68: redirect). jpayne@68: jpayne@68: This method also replaces the Proxy-Authorization header where jpayne@68: necessary. jpayne@68: jpayne@68: :rtype: dict jpayne@68: """ jpayne@68: headers = prepared_request.headers jpayne@68: scheme = urlparse(prepared_request.url).scheme jpayne@68: new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env) jpayne@68: jpayne@68: if "Proxy-Authorization" in headers: jpayne@68: del headers["Proxy-Authorization"] jpayne@68: jpayne@68: try: jpayne@68: username, password = get_auth_from_url(new_proxies[scheme]) jpayne@68: except KeyError: jpayne@68: username, password = None, None jpayne@68: jpayne@68: # urllib3 handles proxy authorization for us in the standard adapter. jpayne@68: # Avoid appending this to TLS tunneled requests where it may be leaked. jpayne@68: if not scheme.startswith("https") and username and password: jpayne@68: headers["Proxy-Authorization"] = _basic_auth_str(username, password) jpayne@68: jpayne@68: return new_proxies jpayne@68: jpayne@68: def rebuild_method(self, prepared_request, response): jpayne@68: """When being redirected we may want to change the method of the request jpayne@68: based on certain specs or browser behavior. jpayne@68: """ jpayne@68: method = prepared_request.method jpayne@68: jpayne@68: # https://tools.ietf.org/html/rfc7231#section-6.4.4 jpayne@68: if response.status_code == codes.see_other and method != "HEAD": jpayne@68: method = "GET" jpayne@68: jpayne@68: # Do what the browsers do, despite standards... jpayne@68: # First, turn 302s into GETs. jpayne@68: if response.status_code == codes.found and method != "HEAD": jpayne@68: method = "GET" jpayne@68: jpayne@68: # Second, if a POST is responded to with a 301, turn it into a GET. jpayne@68: # This bizarre behaviour is explained in Issue 1704. jpayne@68: if response.status_code == codes.moved and method == "POST": jpayne@68: method = "GET" jpayne@68: jpayne@68: prepared_request.method = method jpayne@68: jpayne@68: jpayne@68: class Session(SessionRedirectMixin): jpayne@68: """A Requests session. jpayne@68: jpayne@68: Provides cookie persistence, connection-pooling, and configuration. jpayne@68: jpayne@68: Basic Usage:: jpayne@68: jpayne@68: >>> import requests jpayne@68: >>> s = requests.Session() jpayne@68: >>> s.get('https://httpbin.org/get') jpayne@68: jpayne@68: jpayne@68: Or as a context manager:: jpayne@68: jpayne@68: >>> with requests.Session() as s: jpayne@68: ... s.get('https://httpbin.org/get') jpayne@68: jpayne@68: """ jpayne@68: jpayne@68: __attrs__ = [ jpayne@68: "headers", jpayne@68: "cookies", jpayne@68: "auth", jpayne@68: "proxies", jpayne@68: "hooks", jpayne@68: "params", jpayne@68: "verify", jpayne@68: "cert", jpayne@68: "adapters", jpayne@68: "stream", jpayne@68: "trust_env", jpayne@68: "max_redirects", jpayne@68: ] jpayne@68: jpayne@68: def __init__(self): jpayne@68: #: A case-insensitive dictionary of headers to be sent on each jpayne@68: #: :class:`Request ` sent from this jpayne@68: #: :class:`Session `. jpayne@68: self.headers = default_headers() jpayne@68: jpayne@68: #: Default Authentication tuple or object to attach to jpayne@68: #: :class:`Request `. jpayne@68: self.auth = None jpayne@68: jpayne@68: #: Dictionary mapping protocol or protocol and host to the URL of the proxy jpayne@68: #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to jpayne@68: #: be used on each :class:`Request `. jpayne@68: self.proxies = {} jpayne@68: jpayne@68: #: Event-handling hooks. jpayne@68: self.hooks = default_hooks() jpayne@68: jpayne@68: #: Dictionary of querystring data to attach to each jpayne@68: #: :class:`Request `. The dictionary values may be lists for jpayne@68: #: representing multivalued query parameters. jpayne@68: self.params = {} jpayne@68: jpayne@68: #: Stream response content default. jpayne@68: self.stream = False jpayne@68: jpayne@68: #: SSL Verification default. jpayne@68: #: Defaults to `True`, requiring requests to verify the TLS certificate at the jpayne@68: #: remote end. jpayne@68: #: If verify is set to `False`, requests will accept any TLS certificate jpayne@68: #: presented by the server, and will ignore hostname mismatches and/or jpayne@68: #: expired certificates, which will make your application vulnerable to jpayne@68: #: man-in-the-middle (MitM) attacks. jpayne@68: #: Only set this to `False` for testing. jpayne@68: self.verify = True jpayne@68: jpayne@68: #: SSL client certificate default, if String, path to ssl client jpayne@68: #: cert file (.pem). If Tuple, ('cert', 'key') pair. jpayne@68: self.cert = None jpayne@68: jpayne@68: #: Maximum number of redirects allowed. If the request exceeds this jpayne@68: #: limit, a :class:`TooManyRedirects` exception is raised. jpayne@68: #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is jpayne@68: #: 30. jpayne@68: self.max_redirects = DEFAULT_REDIRECT_LIMIT jpayne@68: jpayne@68: #: Trust environment settings for proxy configuration, default jpayne@68: #: authentication and similar. jpayne@68: self.trust_env = True jpayne@68: jpayne@68: #: A CookieJar containing all currently outstanding cookies set on this jpayne@68: #: session. By default it is a jpayne@68: #: :class:`RequestsCookieJar `, but jpayne@68: #: may be any other ``cookielib.CookieJar`` compatible object. jpayne@68: self.cookies = cookiejar_from_dict({}) jpayne@68: jpayne@68: # Default connection adapters. jpayne@68: self.adapters = OrderedDict() jpayne@68: self.mount("https://", HTTPAdapter()) jpayne@68: self.mount("http://", HTTPAdapter()) jpayne@68: jpayne@68: def __enter__(self): jpayne@68: return self jpayne@68: jpayne@68: def __exit__(self, *args): jpayne@68: self.close() jpayne@68: jpayne@68: def prepare_request(self, request): jpayne@68: """Constructs a :class:`PreparedRequest ` for jpayne@68: transmission and returns it. The :class:`PreparedRequest` has settings jpayne@68: merged from the :class:`Request ` instance and those of the jpayne@68: :class:`Session`. jpayne@68: jpayne@68: :param request: :class:`Request` instance to prepare with this jpayne@68: session's settings. jpayne@68: :rtype: requests.PreparedRequest jpayne@68: """ jpayne@68: cookies = request.cookies or {} jpayne@68: jpayne@68: # Bootstrap CookieJar. jpayne@68: if not isinstance(cookies, cookielib.CookieJar): jpayne@68: cookies = cookiejar_from_dict(cookies) jpayne@68: jpayne@68: # Merge with session cookies jpayne@68: merged_cookies = merge_cookies( jpayne@68: merge_cookies(RequestsCookieJar(), self.cookies), cookies jpayne@68: ) jpayne@68: jpayne@68: # Set environment's basic authentication if not explicitly set. jpayne@68: auth = request.auth jpayne@68: if self.trust_env and not auth and not self.auth: jpayne@68: auth = get_netrc_auth(request.url) jpayne@68: jpayne@68: p = PreparedRequest() jpayne@68: p.prepare( jpayne@68: method=request.method.upper(), jpayne@68: url=request.url, jpayne@68: files=request.files, jpayne@68: data=request.data, jpayne@68: json=request.json, jpayne@68: headers=merge_setting( jpayne@68: request.headers, self.headers, dict_class=CaseInsensitiveDict jpayne@68: ), jpayne@68: params=merge_setting(request.params, self.params), jpayne@68: auth=merge_setting(auth, self.auth), jpayne@68: cookies=merged_cookies, jpayne@68: hooks=merge_hooks(request.hooks, self.hooks), jpayne@68: ) jpayne@68: return p jpayne@68: jpayne@68: def request( jpayne@68: self, jpayne@68: method, jpayne@68: url, jpayne@68: params=None, jpayne@68: data=None, jpayne@68: headers=None, jpayne@68: cookies=None, jpayne@68: files=None, jpayne@68: auth=None, jpayne@68: timeout=None, jpayne@68: allow_redirects=True, jpayne@68: proxies=None, jpayne@68: hooks=None, jpayne@68: stream=None, jpayne@68: verify=None, jpayne@68: cert=None, jpayne@68: json=None, jpayne@68: ): jpayne@68: """Constructs a :class:`Request `, prepares it and sends it. jpayne@68: Returns :class:`Response ` object. jpayne@68: jpayne@68: :param method: method for the new :class:`Request` object. jpayne@68: :param url: URL for the new :class:`Request` object. jpayne@68: :param params: (optional) Dictionary or bytes to be sent in the query jpayne@68: string for the :class:`Request`. jpayne@68: :param data: (optional) Dictionary, list of tuples, bytes, or file-like jpayne@68: object to send in the body of the :class:`Request`. jpayne@68: :param json: (optional) json to send in the body of the jpayne@68: :class:`Request`. jpayne@68: :param headers: (optional) Dictionary of HTTP Headers to send with the jpayne@68: :class:`Request`. jpayne@68: :param cookies: (optional) Dict or CookieJar object to send with the jpayne@68: :class:`Request`. jpayne@68: :param files: (optional) Dictionary of ``'filename': file-like-objects`` jpayne@68: for multipart encoding upload. jpayne@68: :param auth: (optional) Auth tuple or callable to enable jpayne@68: Basic/Digest/Custom HTTP Auth. jpayne@68: :param timeout: (optional) How long to wait for the server to send jpayne@68: data before giving up, as a float, or a :ref:`(connect timeout, jpayne@68: read timeout) ` tuple. jpayne@68: :type timeout: float or tuple jpayne@68: :param allow_redirects: (optional) Set to True by default. jpayne@68: :type allow_redirects: bool jpayne@68: :param proxies: (optional) Dictionary mapping protocol or protocol and jpayne@68: hostname to the URL of the proxy. jpayne@68: :param hooks: (optional) Dictionary mapping hook name to one event or jpayne@68: list of events, event must be callable. jpayne@68: :param stream: (optional) whether to immediately download the response jpayne@68: content. Defaults to ``False``. jpayne@68: :param verify: (optional) Either a boolean, in which case it controls whether we verify jpayne@68: the server's TLS certificate, or a string, in which case it must be a path jpayne@68: to a CA bundle to use. Defaults to ``True``. When set to jpayne@68: ``False``, requests will accept any TLS certificate presented by jpayne@68: the server, and will ignore hostname mismatches and/or expired jpayne@68: certificates, which will make your application vulnerable to jpayne@68: man-in-the-middle (MitM) attacks. Setting verify to ``False`` jpayne@68: may be useful during local development or testing. jpayne@68: :param cert: (optional) if String, path to ssl client cert file (.pem). jpayne@68: If Tuple, ('cert', 'key') pair. jpayne@68: :rtype: requests.Response jpayne@68: """ jpayne@68: # Create the Request. jpayne@68: req = Request( jpayne@68: method=method.upper(), jpayne@68: url=url, jpayne@68: headers=headers, jpayne@68: files=files, jpayne@68: data=data or {}, jpayne@68: json=json, jpayne@68: params=params or {}, jpayne@68: auth=auth, jpayne@68: cookies=cookies, jpayne@68: hooks=hooks, jpayne@68: ) jpayne@68: prep = self.prepare_request(req) jpayne@68: jpayne@68: proxies = proxies or {} jpayne@68: jpayne@68: settings = self.merge_environment_settings( jpayne@68: prep.url, proxies, stream, verify, cert jpayne@68: ) jpayne@68: jpayne@68: # Send the request. jpayne@68: send_kwargs = { jpayne@68: "timeout": timeout, jpayne@68: "allow_redirects": allow_redirects, jpayne@68: } jpayne@68: send_kwargs.update(settings) jpayne@68: resp = self.send(prep, **send_kwargs) jpayne@68: jpayne@68: return resp jpayne@68: jpayne@68: def get(self, url, **kwargs): jpayne@68: r"""Sends a GET request. Returns :class:`Response` object. jpayne@68: jpayne@68: :param url: URL for the new :class:`Request` object. jpayne@68: :param \*\*kwargs: Optional arguments that ``request`` takes. jpayne@68: :rtype: requests.Response jpayne@68: """ jpayne@68: jpayne@68: kwargs.setdefault("allow_redirects", True) jpayne@68: return self.request("GET", url, **kwargs) jpayne@68: jpayne@68: def options(self, url, **kwargs): jpayne@68: r"""Sends a OPTIONS request. Returns :class:`Response` object. jpayne@68: jpayne@68: :param url: URL for the new :class:`Request` object. jpayne@68: :param \*\*kwargs: Optional arguments that ``request`` takes. jpayne@68: :rtype: requests.Response jpayne@68: """ jpayne@68: jpayne@68: kwargs.setdefault("allow_redirects", True) jpayne@68: return self.request("OPTIONS", url, **kwargs) jpayne@68: jpayne@68: def head(self, url, **kwargs): jpayne@68: r"""Sends a HEAD request. Returns :class:`Response` object. jpayne@68: jpayne@68: :param url: URL for the new :class:`Request` object. jpayne@68: :param \*\*kwargs: Optional arguments that ``request`` takes. jpayne@68: :rtype: requests.Response jpayne@68: """ jpayne@68: jpayne@68: kwargs.setdefault("allow_redirects", False) jpayne@68: return self.request("HEAD", url, **kwargs) jpayne@68: jpayne@68: def post(self, url, data=None, json=None, **kwargs): jpayne@68: r"""Sends a POST request. Returns :class:`Response` object. jpayne@68: jpayne@68: :param url: URL for the new :class:`Request` object. jpayne@68: :param data: (optional) Dictionary, list of tuples, bytes, or file-like jpayne@68: object to send in the body of the :class:`Request`. jpayne@68: :param json: (optional) json to send in the body of the :class:`Request`. jpayne@68: :param \*\*kwargs: Optional arguments that ``request`` takes. jpayne@68: :rtype: requests.Response jpayne@68: """ jpayne@68: jpayne@68: return self.request("POST", url, data=data, json=json, **kwargs) jpayne@68: jpayne@68: def put(self, url, data=None, **kwargs): jpayne@68: r"""Sends a PUT request. Returns :class:`Response` object. jpayne@68: jpayne@68: :param url: URL for the new :class:`Request` object. jpayne@68: :param data: (optional) Dictionary, list of tuples, bytes, or file-like jpayne@68: object to send in the body of the :class:`Request`. jpayne@68: :param \*\*kwargs: Optional arguments that ``request`` takes. jpayne@68: :rtype: requests.Response jpayne@68: """ jpayne@68: jpayne@68: return self.request("PUT", url, data=data, **kwargs) jpayne@68: jpayne@68: def patch(self, url, data=None, **kwargs): jpayne@68: r"""Sends a PATCH request. Returns :class:`Response` object. jpayne@68: jpayne@68: :param url: URL for the new :class:`Request` object. jpayne@68: :param data: (optional) Dictionary, list of tuples, bytes, or file-like jpayne@68: object to send in the body of the :class:`Request`. jpayne@68: :param \*\*kwargs: Optional arguments that ``request`` takes. jpayne@68: :rtype: requests.Response jpayne@68: """ jpayne@68: jpayne@68: return self.request("PATCH", url, data=data, **kwargs) jpayne@68: jpayne@68: def delete(self, url, **kwargs): jpayne@68: r"""Sends a DELETE request. Returns :class:`Response` object. jpayne@68: jpayne@68: :param url: URL for the new :class:`Request` object. jpayne@68: :param \*\*kwargs: Optional arguments that ``request`` takes. jpayne@68: :rtype: requests.Response jpayne@68: """ jpayne@68: jpayne@68: return self.request("DELETE", url, **kwargs) jpayne@68: jpayne@68: def send(self, request, **kwargs): jpayne@68: """Send a given PreparedRequest. jpayne@68: jpayne@68: :rtype: requests.Response jpayne@68: """ jpayne@68: # Set defaults that the hooks can utilize to ensure they always have jpayne@68: # the correct parameters to reproduce the previous request. jpayne@68: kwargs.setdefault("stream", self.stream) jpayne@68: kwargs.setdefault("verify", self.verify) jpayne@68: kwargs.setdefault("cert", self.cert) jpayne@68: if "proxies" not in kwargs: jpayne@68: kwargs["proxies"] = resolve_proxies(request, self.proxies, self.trust_env) jpayne@68: jpayne@68: # It's possible that users might accidentally send a Request object. jpayne@68: # Guard against that specific failure case. jpayne@68: if isinstance(request, Request): jpayne@68: raise ValueError("You can only send PreparedRequests.") jpayne@68: jpayne@68: # Set up variables needed for resolve_redirects and dispatching of hooks jpayne@68: allow_redirects = kwargs.pop("allow_redirects", True) jpayne@68: stream = kwargs.get("stream") jpayne@68: hooks = request.hooks jpayne@68: jpayne@68: # Get the appropriate adapter to use jpayne@68: adapter = self.get_adapter(url=request.url) jpayne@68: jpayne@68: # Start time (approximately) of the request jpayne@68: start = preferred_clock() jpayne@68: jpayne@68: # Send the request jpayne@68: r = adapter.send(request, **kwargs) jpayne@68: jpayne@68: # Total elapsed time of the request (approximately) jpayne@68: elapsed = preferred_clock() - start jpayne@68: r.elapsed = timedelta(seconds=elapsed) jpayne@68: jpayne@68: # Response manipulation hooks jpayne@68: r = dispatch_hook("response", hooks, r, **kwargs) jpayne@68: jpayne@68: # Persist cookies jpayne@68: if r.history: jpayne@68: # If the hooks create history then we want those cookies too jpayne@68: for resp in r.history: jpayne@68: extract_cookies_to_jar(self.cookies, resp.request, resp.raw) jpayne@68: jpayne@68: extract_cookies_to_jar(self.cookies, request, r.raw) jpayne@68: jpayne@68: # Resolve redirects if allowed. jpayne@68: if allow_redirects: jpayne@68: # Redirect resolving generator. jpayne@68: gen = self.resolve_redirects(r, request, **kwargs) jpayne@68: history = [resp for resp in gen] jpayne@68: else: jpayne@68: history = [] jpayne@68: jpayne@68: # Shuffle things around if there's history. jpayne@68: if history: jpayne@68: # Insert the first (original) request at the start jpayne@68: history.insert(0, r) jpayne@68: # Get the last request made jpayne@68: r = history.pop() jpayne@68: r.history = history jpayne@68: jpayne@68: # If redirects aren't being followed, store the response on the Request for Response.next(). jpayne@68: if not allow_redirects: jpayne@68: try: jpayne@68: r._next = next( jpayne@68: self.resolve_redirects(r, request, yield_requests=True, **kwargs) jpayne@68: ) jpayne@68: except StopIteration: jpayne@68: pass jpayne@68: jpayne@68: if not stream: jpayne@68: r.content jpayne@68: jpayne@68: return r jpayne@68: jpayne@68: def merge_environment_settings(self, url, proxies, stream, verify, cert): jpayne@68: """ jpayne@68: Check the environment and merge it with some settings. jpayne@68: jpayne@68: :rtype: dict jpayne@68: """ jpayne@68: # Gather clues from the surrounding environment. jpayne@68: if self.trust_env: jpayne@68: # Set environment's proxies. jpayne@68: no_proxy = proxies.get("no_proxy") if proxies is not None else None jpayne@68: env_proxies = get_environ_proxies(url, no_proxy=no_proxy) jpayne@68: for k, v in env_proxies.items(): jpayne@68: proxies.setdefault(k, v) jpayne@68: jpayne@68: # Look for requests environment configuration jpayne@68: # and be compatible with cURL. jpayne@68: if verify is True or verify is None: jpayne@68: verify = ( jpayne@68: os.environ.get("REQUESTS_CA_BUNDLE") jpayne@68: or os.environ.get("CURL_CA_BUNDLE") jpayne@68: or verify jpayne@68: ) jpayne@68: jpayne@68: # Merge all the kwargs. jpayne@68: proxies = merge_setting(proxies, self.proxies) jpayne@68: stream = merge_setting(stream, self.stream) jpayne@68: verify = merge_setting(verify, self.verify) jpayne@68: cert = merge_setting(cert, self.cert) jpayne@68: jpayne@68: return {"proxies": proxies, "stream": stream, "verify": verify, "cert": cert} jpayne@68: jpayne@68: def get_adapter(self, url): jpayne@68: """ jpayne@68: Returns the appropriate connection adapter for the given URL. jpayne@68: jpayne@68: :rtype: requests.adapters.BaseAdapter jpayne@68: """ jpayne@68: for prefix, adapter in self.adapters.items(): jpayne@68: if url.lower().startswith(prefix.lower()): jpayne@68: return adapter jpayne@68: jpayne@68: # Nothing matches :-/ jpayne@68: raise InvalidSchema(f"No connection adapters were found for {url!r}") jpayne@68: jpayne@68: def close(self): jpayne@68: """Closes all adapters and as such the session""" jpayne@68: for v in self.adapters.values(): jpayne@68: v.close() jpayne@68: jpayne@68: def mount(self, prefix, adapter): jpayne@68: """Registers a connection adapter to a prefix. jpayne@68: jpayne@68: Adapters are sorted in descending order by prefix length. jpayne@68: """ jpayne@68: self.adapters[prefix] = adapter jpayne@68: keys_to_move = [k for k in self.adapters if len(k) < len(prefix)] jpayne@68: jpayne@68: for key in keys_to_move: jpayne@68: self.adapters[key] = self.adapters.pop(key) jpayne@68: jpayne@68: def __getstate__(self): jpayne@68: state = {attr: getattr(self, attr, None) for attr in self.__attrs__} jpayne@68: return state jpayne@68: jpayne@68: def __setstate__(self, state): jpayne@68: for attr, value in state.items(): jpayne@68: setattr(self, attr, value) jpayne@68: jpayne@68: jpayne@68: def session(): jpayne@68: """ jpayne@68: Returns a :class:`Session` for context-management. jpayne@68: jpayne@68: .. deprecated:: 1.0.0 jpayne@68: jpayne@68: This method has been deprecated since version 1.0.0 and is only kept for jpayne@68: backwards compatibility. New code should use :class:`~requests.sessions.Session` jpayne@68: to create a session. This may be removed at a future date. jpayne@68: jpayne@68: :rtype: Session jpayne@68: """ jpayne@68: return Session()