annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/site-packages/requests/sessions.py @ 68:5028fdace37b

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