codekingpro/portable-devtools
114k
1"""2requests.adapters3~~~~~~~~~~~~~~~~~4 5This module contains the transport adapters that Requests uses to define6and maintain connections.7"""8 9from __future__ import annotations10 11import os.path12import socket # noqa: F401 # type: ignore[reportUnusedImport]13import typing14import warnings15from typing import Any16 17from urllib3.exceptions import (18 ClosedPoolError,19 ConnectTimeoutError,20 LocationValueError,21 MaxRetryError,22 NewConnectionError,23 ProtocolError,24 ReadTimeoutError,25 ResponseError,26)27from urllib3.exceptions import HTTPError as _HTTPError28from urllib3.exceptions import InvalidHeader as _InvalidHeader29from urllib3.exceptions import ProxyError as _ProxyError30from urllib3.exceptions import SSLError as _SSLError31from urllib3.poolmanager import PoolManager, proxy_from_url32from urllib3.util import Timeout as TimeoutSauce33from urllib3.util import parse_url34from urllib3.util.retry import Retry35 36from .auth import _basic_auth_str # type: ignore[reportPrivateUsage]37from .compat import basestring, urlparse38from .cookies import extract_cookies_to_jar39from .exceptions import (40 ConnectionError,41 ConnectTimeout,42 InvalidHeader,43 InvalidProxyURL,44 InvalidSchema,45 InvalidURL,46 ProxyError,47 ReadTimeout,48 RetryError,49 SSLError,50)51from .models import Response52from .structures import CaseInsensitiveDict53from .utils import (54 DEFAULT_CA_BUNDLE_PATH,55 get_auth_from_url,56 get_encoding_from_headers,57 prepend_scheme_if_needed,58 select_proxy,59 urldefragauth,60)61 62try:63 from urllib3.contrib.socks import SOCKSProxyManager # type: ignore[assignment]64except ImportError:65 66 def SOCKSProxyManager(*args: Any, **kwargs: Any) -> None:67 raise InvalidSchema("Missing dependencies for SOCKS support.")68 69 70if typing.TYPE_CHECKING:71 from urllib3.connectionpool import HTTPConnectionPool72 from urllib3.poolmanager import PoolManager as _PoolManager73 74 from . import _types as _t75 from .models import PreparedRequest76 77from ._types import is_prepared as _is_prepared78 79DEFAULT_POOLBLOCK = False80DEFAULT_POOLSIZE = 1081DEFAULT_RETRIES = 082DEFAULT_POOL_TIMEOUT = None83 84 85def _urllib3_request_context(86 request: PreparedRequest,87 verify: bool | str | None,88 client_cert: tuple[str, str] | str | None,89 poolmanager: PoolManager,90) -> tuple[dict[str, Any], dict[str, Any]]:91 host_params: dict[str, Any] = {}92 pool_kwargs: dict[str, Any] = {}93 parsed_request_url = urlparse(request.url)94 scheme = parsed_request_url.scheme.lower()95 port = parsed_request_url.port96 97 cert_reqs = "CERT_REQUIRED"98 if verify is False:99 cert_reqs = "CERT_NONE"100 elif isinstance(verify, str):101 if not os.path.isdir(verify):102 pool_kwargs["ca_certs"] = verify103 else:104 pool_kwargs["ca_cert_dir"] = verify105 pool_kwargs["cert_reqs"] = cert_reqs106 if client_cert is not None:107 if isinstance(client_cert, tuple) and len(client_cert) == 2:108 pool_kwargs["cert_file"] = client_cert[0]109 pool_kwargs["key_file"] = client_cert[1]110 else:111 # According to our docs, we allow users to specify just the client112 # cert path113 pool_kwargs["cert_file"] = client_cert114 host_params = {115 "scheme": scheme,116 "host": parsed_request_url.hostname,117 "port": port,118 }119 return host_params, pool_kwargs120 121 122class BaseAdapter:123 """The Base Transport Adapter"""124 125 def __init__(self) -> None:126 super().__init__()127 128 def send(129 self,130 request: PreparedRequest,131 stream: bool = False,132 timeout: _t.TimeoutType = None,133 verify: _t.VerifyType = True,134 cert: _t.CertType = None,135 proxies: dict[str, str] | None = None,136 ) -> Response:137 """Sends PreparedRequest object. Returns Response object.138 139 :param request: The :class:`PreparedRequest <PreparedRequest>` being sent.140 :param stream: (optional) Whether to stream the request content.141 :param timeout: (optional) How long to wait for the server to send142 data before giving up, as a float, or a :ref:`(connect timeout,143 read timeout) <timeouts>` tuple.144 :type timeout: float or tuple145 :param verify: (optional) Either a boolean, in which case it controls whether we verify146 the server's TLS certificate, or a string, in which case it must be a path147 to a CA bundle to use148 :param cert: (optional) Any user-provided SSL certificate to be trusted.149 :param proxies: (optional) The proxies dictionary to apply to the request.150 """151 raise NotImplementedError152 153 def close(self) -> None:154 """Cleans up adapter specific items."""155 raise NotImplementedError156 157 158class HTTPAdapter(BaseAdapter):159 """The built-in HTTP Adapter for urllib3.160 161 Provides a general-case interface for Requests sessions to contact HTTP and162 HTTPS urls by implementing the Transport Adapter interface. This class will163 usually be created by the :class:`Session <Session>` class under the164 covers.165 166 :param pool_connections: The number of urllib3 connection pools to cache.167 :param pool_maxsize: The maximum number of connections to save in the pool.168 :param max_retries: The maximum number of retries each connection169 should attempt. Note, this applies only to failed DNS lookups, socket170 connections and connection timeouts, never to requests where data has171 made it to the server. By default, Requests does not retry failed172 connections. If you need granular control over the conditions under173 which we retry a request, import urllib3's ``Retry`` class and pass174 that instead.175 :param pool_block: Whether the connection pool should block for connections.176 177 Usage::178 179 >>> import requests180 >>> s = requests.Session()181 >>> a = requests.adapters.HTTPAdapter(max_retries=3)182 >>> s.mount('http://', a)183 """184 185 __attrs__: list[str] = [186 "max_retries",187 "config",188 "_pool_connections",189 "_pool_maxsize",190 "_pool_block",191 ]192 193 max_retries: Retry194 config: dict[str, Any]195 proxy_manager: dict[str, Any]196 _pool_connections: int197 _pool_maxsize: int198 _pool_block: bool199 poolmanager: _PoolManager200 201 def __init__(202 self,203 pool_connections: int = DEFAULT_POOLSIZE,204 pool_maxsize: int = DEFAULT_POOLSIZE,205 max_retries: int | Retry = DEFAULT_RETRIES,206 pool_block: bool = DEFAULT_POOLBLOCK,207 ) -> None:208 if max_retries == DEFAULT_RETRIES:209 self.max_retries = Retry(0, read=False)210 else:211 self.max_retries = Retry.from_int(max_retries)212 self.config = {}213 self.proxy_manager = {}214 215 super().__init__()216 217 self._pool_connections = pool_connections218 self._pool_maxsize = pool_maxsize219 self._pool_block = pool_block220 221 self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block)222 223 def __getstate__(self) -> dict[str, Any]:224 return {attr: getattr(self, attr, None) for attr in self.__attrs__}225 226 def __setstate__(self, state: dict[str, Any]) -> None:227 # Can't handle by adding 'proxy_manager' to self.__attrs__ because228 # self.poolmanager uses a lambda function, which isn't pickleable.229 self.proxy_manager = {}230 self.config = {}231 232 for attr, value in state.items():233 setattr(self, attr, value)234 235 self.init_poolmanager(236 self._pool_connections, self._pool_maxsize, block=self._pool_block237 )238 239 def init_poolmanager(240 self,241 connections: int,242 maxsize: int,243 block: bool = DEFAULT_POOLBLOCK,244 **pool_kwargs: Any,245 ) -> None:246 """Initializes a urllib3 PoolManager.247 248 This method should not be called from user code, and is only249 exposed for use when subclassing the250 :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.251 252 :param connections: The number of urllib3 connection pools to cache.253 :param maxsize: The maximum number of connections to save in the pool.254 :param block: Block when no free connections are available.255 :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager.256 """257 # save these values for pickling258 self._pool_connections = connections259 self._pool_maxsize = maxsize260 self._pool_block = block261 262 self.poolmanager = PoolManager(263 num_pools=connections,264 maxsize=maxsize,265 block=block,266 **pool_kwargs,267 )268 269 def proxy_manager_for(self, proxy: str, **proxy_kwargs: Any) -> Any:270 """Return urllib3 ProxyManager for the given proxy.271 272 This method should not be called from user code, and is only273 exposed for use when subclassing the274 :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.275 276 :param proxy: The proxy to return a urllib3 ProxyManager for.277 :param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager.278 :returns: ProxyManager279 :rtype: urllib3.ProxyManager280 """281 if proxy in self.proxy_manager:282 manager = self.proxy_manager[proxy]283 elif proxy.lower().startswith("socks"):284 username, password = get_auth_from_url(proxy)285 manager = self.proxy_manager[proxy] = SOCKSProxyManager(286 proxy,287 username=username,288 password=password,289 num_pools=self._pool_connections,290 maxsize=self._pool_maxsize,291 block=self._pool_block,292 **proxy_kwargs,293 )294 else:295 proxy_headers = self.proxy_headers(proxy)296 manager = self.proxy_manager[proxy] = proxy_from_url(297 proxy,298 proxy_headers=proxy_headers,299 num_pools=self._pool_connections,300 maxsize=self._pool_maxsize,301 block=self._pool_block,302 **proxy_kwargs,303 )304 305 return manager306 307 def cert_verify(308 self, conn: Any, url: str, verify: _t.VerifyType, cert: _t.CertType309 ) -> None:310 """Verify a SSL certificate. This method should not be called from user311 code, and is only exposed for use when subclassing the312 :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.313 314 :param conn: The urllib3 connection object associated with the cert.315 :param url: The requested URL.316 :param verify: Either a boolean, in which case it controls whether we verify317 the server's TLS certificate, or a string, in which case it must be a path318 to a CA bundle to use319 :param cert: The SSL certificate to verify.320 """321 if url.lower().startswith("https") and verify:322 cert_loc = None323 324 # Allow self-specified cert location.325 if verify is not True:326 cert_loc = verify327 328 if not cert_loc:329 cert_loc = DEFAULT_CA_BUNDLE_PATH330 331 if not cert_loc or not os.path.exists(cert_loc):332 raise OSError(333 f"Could not find a suitable TLS CA certificate bundle, "334 f"invalid path: {cert_loc}"335 )336 337 conn.cert_reqs = "CERT_REQUIRED"338 339 if not os.path.isdir(cert_loc):340 conn.ca_certs = cert_loc341 else:342 conn.ca_cert_dir = cert_loc343 else:344 conn.cert_reqs = "CERT_NONE"345 conn.ca_certs = None346 conn.ca_cert_dir = None347 348 if cert:349 if not isinstance(cert, basestring):350 conn.cert_file = cert[0]351 conn.key_file = cert[1]352 else:353 conn.cert_file = cert354 conn.key_file = None355 if conn.cert_file and not os.path.exists(conn.cert_file):356 raise OSError(357 f"Could not find the TLS certificate file, "358 f"invalid path: {conn.cert_file}"359 )360 if conn.key_file and not os.path.exists(conn.key_file):361 raise OSError(362 f"Could not find the TLS key file, invalid path: {conn.key_file}"363 )364 365 def build_response(self, req: PreparedRequest, resp: Any) -> Response:366 """Builds a :class:`Response <requests.Response>` object from a urllib3367 response. This should not be called from user code, and is only exposed368 for use when subclassing the369 :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`370 371 :param req: The :class:`PreparedRequest <PreparedRequest>` used to generate the response.372 :param resp: The urllib3 response object.373 :rtype: requests.Response374 """375 assert _is_prepared(req)376 response = Response()377 378 # Fallback to None if there's no status_code, for whatever reason.379 response.status_code = getattr(resp, "status", None) # type: ignore[assignment]380 381 # Make headers case-insensitive.382 response.headers = CaseInsensitiveDict(getattr(resp, "headers", {}))383 384 # Set encoding.385 response.encoding = get_encoding_from_headers(response.headers)386 response.raw = resp387 response.reason = response.raw.reason388 389 if isinstance(req.url, bytes):390 response.url = req.url.decode("utf-8")391 else:392 response.url = req.url393 394 # Add new cookies from the server.395 extract_cookies_to_jar(response.cookies, req, resp)396 397 # Give the Response some context.398 response.request = req399 response.connection = self400 401 return response402 403 def build_connection_pool_key_attributes(404 self, request: PreparedRequest, verify: _t.VerifyType, cert: _t.CertType = None405 ) -> tuple[dict[str, Any], dict[str, Any]]:406 """Build the PoolKey attributes used by urllib3 to return a connection.407 408 This looks at the PreparedRequest, the user-specified verify value,409 and the value of the cert parameter to determine what PoolKey values410 to use to select a connection from a given urllib3 Connection Pool.411 412 The SSL related pool key arguments are not consistently set. As of413 this writing, use the following to determine what keys may be in that414 dictionary:415 416 * If ``verify`` is ``True``, ``"ssl_context"`` will be set and will be the417 default Requests SSL Context418 * If ``verify`` is ``False``, ``"ssl_context"`` will not be set but419 ``"cert_reqs"`` will be set420 * If ``verify`` is a string, (i.e., it is a user-specified trust bundle)421 ``"ca_certs"`` will be set if the string is not a directory recognized422 by :py:func:`os.path.isdir`, otherwise ``"ca_cert_dir"`` will be423 set.424 * If ``"cert"`` is specified, ``"cert_file"`` will always be set. If425 ``"cert"`` is a tuple with a second item, ``"key_file"`` will also426 be present427 428 To override these settings, one may subclass this class, call this429 method and use the above logic to change parameters as desired. For430 example, if one wishes to use a custom :py:class:`ssl.SSLContext` one431 must both set ``"ssl_context"`` and based on what else they require,432 alter the other keys to ensure the desired behaviour.433 434 :param request:435 The PreparedRequest being sent over the connection.436 :type request:437 :class:`~requests.models.PreparedRequest`438 :param verify:439 Either a boolean, in which case it controls whether440 we verify the server's TLS certificate, or a string, in which case it441 must be a path to a CA bundle to use.442 :param cert:443 (optional) Any user-provided SSL certificate for client444 authentication (a.k.a., mTLS). This may be a string (i.e., just445 the path to a file which holds both certificate and key) or a446 tuple of length 2 with the certificate file path and key file447 path.448 :returns:449 A tuple of two dictionaries. The first is the "host parameters"450 portion of the Pool Key including scheme, hostname, and port. The451 second is a dictionary of SSLContext related parameters.452 """453 return _urllib3_request_context(request, verify, cert, self.poolmanager)454 455 def get_connection_with_tls_context(456 self,457 request: PreparedRequest,458 verify: _t.VerifyType,459 proxies: dict[str, str] | None = None,460 cert: _t.CertType = None,461 ) -> HTTPConnectionPool:462 """Returns a urllib3 connection for the given request and TLS settings.463 This should not be called from user code, and is only exposed for use464 when subclassing the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.465 466 :param request:467 The :class:`PreparedRequest <PreparedRequest>` object to be sent468 over the connection.469 :param verify:470 Either a boolean, in which case it controls whether we verify the471 server's TLS certificate, or a string, in which case it must be a472 path to a CA bundle to use.473 :param proxies:474 (optional) The proxies dictionary to apply to the request.475 :param cert:476 (optional) Any user-provided SSL certificate to be used for client477 authentication (a.k.a., mTLS).478 :rtype:479 urllib3.HTTPConnectionPool480 """481 assert _is_prepared(request)482 483 proxy = select_proxy(request.url, proxies)484 try:485 host_params, pool_kwargs = self.build_connection_pool_key_attributes(486 request,487 verify,488 cert,489 )490 except ValueError as e:491 raise InvalidURL(e, request=request)492 if proxy:493 proxy = prepend_scheme_if_needed(proxy, "http")494 proxy_url = parse_url(proxy)495 if not proxy_url.host:496 raise InvalidProxyURL(497 "Please check proxy URL. It is malformed "498 "and could be missing the host."499 )500 proxy_manager = self.proxy_manager_for(proxy)501 conn = proxy_manager.connection_from_host(502 **host_params, pool_kwargs=pool_kwargs503 )504 else:505 # Only scheme should be lower case506 conn = self.poolmanager.connection_from_host(507 **host_params, pool_kwargs=pool_kwargs508 )509 510 return conn511 512 def get_connection(513 self, url: str, proxies: dict[str, str] | None = None514 ) -> HTTPConnectionPool:515 """DEPRECATED: Users should move to `get_connection_with_tls_context`516 for all subclasses of HTTPAdapter using Requests>=2.32.2.517 518 Returns a urllib3 connection for the given URL. This should not be519 called from user code, and is only exposed for use when subclassing the520 :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.521 522 :param url: The URL to connect to.523 :param proxies: (optional) A Requests-style dictionary of proxies used on this request.524 :rtype: urllib3.HTTPConnectionPool525 """526 warnings.warn(527 (528 "`get_connection` has been deprecated in favor of "529 "`get_connection_with_tls_context`. Custom HTTPAdapter subclasses "530 "will need to migrate for Requests>=2.32.2. Please see "531 "https://github.com/psf/requests/pull/6710 for more details."532 ),533 DeprecationWarning,534 )535 proxy = select_proxy(url, proxies)536 537 if proxy:538 proxy = prepend_scheme_if_needed(proxy, "http")539 proxy_url = parse_url(proxy)540 if not proxy_url.host:541 raise InvalidProxyURL(542 "Please check proxy URL. It is malformed "543 "and could be missing the host."544 )545 proxy_manager = self.proxy_manager_for(proxy)546 conn = proxy_manager.connection_from_url(url)547 else:548 # Only scheme should be lower case549 parsed = urlparse(url)550 url = parsed.geturl()551 conn = self.poolmanager.connection_from_url(url)552 553 return conn554 555 def close(self) -> None:556 """Disposes of any internal state.557 558 Currently, this closes the PoolManager and any active ProxyManager,559 which closes any pooled connections.560 """561 self.poolmanager.clear()562 for proxy in self.proxy_manager.values():563 proxy.clear()564 565 def request_url(566 self, request: PreparedRequest, proxies: dict[str, str] | None567 ) -> str:568 """Obtain the url to use when making the final request.569 570 If the message is being sent through a HTTP proxy, the full URL has to571 be used. Otherwise, we should only use the path portion of the URL.572 573 This should not be called from user code, and is only exposed for use574 when subclassing the575 :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.576 577 :param request: The :class:`PreparedRequest <PreparedRequest>` being sent.578 :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs.579 :rtype: str580 """581 assert _is_prepared(request)582 583 proxy = select_proxy(request.url, proxies)584 scheme = urlparse(request.url).scheme585 586 is_proxied_http_request = proxy and scheme != "https"587 using_socks_proxy = False588 if proxy:589 proxy_scheme = urlparse(proxy).scheme.lower()590 using_socks_proxy = proxy_scheme.startswith("socks")591 592 url = request.path_url593 594 if is_proxied_http_request and not using_socks_proxy:595 url = urldefragauth(request.url)596 597 return url598 599 def add_headers(self, request: PreparedRequest, **kwargs: Any) -> None:600 """Add any headers needed by the connection. As of v2.0 this does601 nothing by default, but is left for overriding by users that subclass602 the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.603 604 This should not be called from user code, and is only exposed for use605 when subclassing the606 :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.607 608 :param request: The :class:`PreparedRequest <PreparedRequest>` to add headers to.609 :param kwargs: The keyword arguments from the call to send().610 """611 pass612 613 def proxy_headers(self, proxy: str) -> dict[str, str]:614 """Returns a dictionary of the headers to add to any request sent615 through a proxy. This works with urllib3 magic to ensure that they are616 correctly sent to the proxy, rather than in a tunnelled request if617 CONNECT is being used.618 619 This should not be called from user code, and is only exposed for use620 when subclassing the621 :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.622 623 :param proxy: The url of the proxy being used for this request.624 :rtype: dict625 """626 headers: dict[str, str] = {}627 username, password = get_auth_from_url(proxy)628 629 if username:630 headers["Proxy-Authorization"] = _basic_auth_str(username, password)631 632 return headers633 634 def send(635 self,636 request: PreparedRequest,637 stream: bool = False,638 timeout: _t.TimeoutType = None,639 verify: _t.VerifyType = True,640 cert: _t.CertType = None,641 proxies: dict[str, str] | None = None,642 ) -> Response:643 """Sends PreparedRequest object. Returns Response object.644 645 :param request: The :class:`PreparedRequest <PreparedRequest>` being sent.646 :param stream: (optional) Whether to stream the request content.647 :param timeout: (optional) How long to wait for the server to send648 data before giving up, as a float, or a :ref:`(connect timeout,649 read timeout) <timeouts>` tuple.650 :type timeout: float or tuple or urllib3 Timeout object651 :param verify: (optional) Either a boolean, in which case it controls whether652 we verify the server's TLS certificate, or a string, in which case it653 must be a path to a CA bundle to use654 :param cert: (optional) Any user-provided SSL certificate to be trusted.655 :param proxies: (optional) The proxies dictionary to apply to the request.656 :rtype: requests.Response657 """658 659 assert _is_prepared(request)660 661 try:662 conn = self.get_connection_with_tls_context(663 request, verify, proxies=proxies, cert=cert664 )665 except LocationValueError as e:666 raise InvalidURL(e, request=request)667 668 self.cert_verify(conn, request.url, verify, cert)669 url = self.request_url(request, proxies)670 self.add_headers(671 request,672 stream=stream,673 timeout=timeout,674 verify=verify,675 cert=cert,676 proxies=proxies,677 )678 679 chunked = not (request.body is None or "Content-Length" in request.headers)680 681 if isinstance(timeout, tuple):682 try:683 connect, read = timeout684 resolved_timeout = TimeoutSauce(connect=connect, read=read)685 except ValueError:686 raise ValueError(687 f"Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, "688 f"or a single float to set both timeouts to the same value."689 )690 elif isinstance(timeout, TimeoutSauce):691 resolved_timeout = timeout692 else:693 resolved_timeout = TimeoutSauce(connect=timeout, read=timeout)694 695 try:696 resp = conn.urlopen(697 method=request.method,698 url=url,699 body=request.body, # type: ignore[arg-type] # urllib3 stubs don't accept Iterable[bytes | str]700 headers=request.headers, # type: ignore[arg-type] # urllib3#3072701 redirect=False,702 assert_same_host=False,703 preload_content=False,704 decode_content=False,705 retries=self.max_retries,706 timeout=resolved_timeout,707 chunked=chunked,708 )709 710 except (ProtocolError, OSError) as err:711 raise ConnectionError(err, request=request)712 713 except MaxRetryError as e:714 if isinstance(e.reason, ConnectTimeoutError):715 # TODO: Remove this in 3.0.0: see #2811716 if not isinstance(e.reason, NewConnectionError):717 raise ConnectTimeout(e, request=request)718 719 if isinstance(e.reason, ResponseError):720 raise RetryError(e, request=request)721 722 if isinstance(e.reason, _ProxyError):723 raise ProxyError(e, request=request)724 725 if isinstance(e.reason, _SSLError):726 # This branch is for urllib3 v1.22 and later.727 raise SSLError(e, request=request)728 729 raise ConnectionError(e, request=request)730 731 except ClosedPoolError as e:732 raise ConnectionError(e, request=request)733 734 except _ProxyError as e:735 raise ProxyError(e)736 737 except (_SSLError, _HTTPError) as e:738 if isinstance(e, _SSLError):739 # This branch is for urllib3 versions earlier than v1.22740 raise SSLError(e, request=request)741 elif isinstance(e, ReadTimeoutError):742 raise ReadTimeout(e, request=request)743 elif isinstance(e, _InvalidHeader):744 raise InvalidHeader(e, request=request)745 else:746 raise747 748 return self.build_response(request, resp)749 