12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550 |
- """HTTP Client for asyncio."""
- import asyncio
- import base64
- import hashlib
- import json
- import os
- import sys
- import traceback
- import warnings
- from contextlib import suppress
- from types import TracebackType
- from typing import (
- TYPE_CHECKING,
- Any,
- Awaitable,
- Callable,
- Coroutine,
- Final,
- FrozenSet,
- Generator,
- Generic,
- Iterable,
- List,
- Mapping,
- Optional,
- Set,
- Tuple,
- Type,
- TypedDict,
- TypeVar,
- Union,
- )
- import attr
- from multidict import CIMultiDict, MultiDict, MultiDictProxy, istr
- from yarl import URL
- from . import hdrs, http, payload
- from ._websocket.reader import WebSocketDataQueue
- from .abc import AbstractCookieJar
- from .client_exceptions import (
- ClientConnectionError,
- ClientConnectionResetError,
- ClientConnectorCertificateError,
- ClientConnectorDNSError,
- ClientConnectorError,
- ClientConnectorSSLError,
- ClientError,
- ClientHttpProxyError,
- ClientOSError,
- ClientPayloadError,
- ClientProxyConnectionError,
- ClientResponseError,
- ClientSSLError,
- ConnectionTimeoutError,
- ContentTypeError,
- InvalidURL,
- InvalidUrlClientError,
- InvalidUrlRedirectClientError,
- NonHttpUrlClientError,
- NonHttpUrlRedirectClientError,
- RedirectClientError,
- ServerConnectionError,
- ServerDisconnectedError,
- ServerFingerprintMismatch,
- ServerTimeoutError,
- SocketTimeoutError,
- TooManyRedirects,
- WSMessageTypeError,
- WSServerHandshakeError,
- )
- from .client_reqrep import (
- ClientRequest as ClientRequest,
- ClientResponse as ClientResponse,
- Fingerprint as Fingerprint,
- RequestInfo as RequestInfo,
- _merge_ssl_params,
- )
- from .client_ws import (
- DEFAULT_WS_CLIENT_TIMEOUT,
- ClientWebSocketResponse as ClientWebSocketResponse,
- ClientWSTimeout as ClientWSTimeout,
- )
- from .connector import (
- HTTP_AND_EMPTY_SCHEMA_SET,
- BaseConnector as BaseConnector,
- NamedPipeConnector as NamedPipeConnector,
- TCPConnector as TCPConnector,
- UnixConnector as UnixConnector,
- )
- from .cookiejar import CookieJar
- from .helpers import (
- _SENTINEL,
- DEBUG,
- EMPTY_BODY_METHODS,
- BasicAuth,
- TimeoutHandle,
- get_env_proxy_for_url,
- sentinel,
- strip_auth_from_url,
- )
- from .http import WS_KEY, HttpVersion, WebSocketReader, WebSocketWriter
- from .http_websocket import WSHandshakeError, ws_ext_gen, ws_ext_parse
- from .tracing import Trace, TraceConfig
- from .typedefs import JSONEncoder, LooseCookies, LooseHeaders, Query, StrOrURL
- __all__ = (
- # client_exceptions
- "ClientConnectionError",
- "ClientConnectionResetError",
- "ClientConnectorCertificateError",
- "ClientConnectorDNSError",
- "ClientConnectorError",
- "ClientConnectorSSLError",
- "ClientError",
- "ClientHttpProxyError",
- "ClientOSError",
- "ClientPayloadError",
- "ClientProxyConnectionError",
- "ClientResponseError",
- "ClientSSLError",
- "ConnectionTimeoutError",
- "ContentTypeError",
- "InvalidURL",
- "InvalidUrlClientError",
- "RedirectClientError",
- "NonHttpUrlClientError",
- "InvalidUrlRedirectClientError",
- "NonHttpUrlRedirectClientError",
- "ServerConnectionError",
- "ServerDisconnectedError",
- "ServerFingerprintMismatch",
- "ServerTimeoutError",
- "SocketTimeoutError",
- "TooManyRedirects",
- "WSServerHandshakeError",
- # client_reqrep
- "ClientRequest",
- "ClientResponse",
- "Fingerprint",
- "RequestInfo",
- # connector
- "BaseConnector",
- "TCPConnector",
- "UnixConnector",
- "NamedPipeConnector",
- # client_ws
- "ClientWebSocketResponse",
- # client
- "ClientSession",
- "ClientTimeout",
- "ClientWSTimeout",
- "request",
- "WSMessageTypeError",
- )
- if TYPE_CHECKING:
- from ssl import SSLContext
- else:
- SSLContext = None
- if sys.version_info >= (3, 11) and TYPE_CHECKING:
- from typing import Unpack
- class _RequestOptions(TypedDict, total=False):
- params: Query
- data: Any
- json: Any
- cookies: Union[LooseCookies, None]
- headers: Union[LooseHeaders, None]
- skip_auto_headers: Union[Iterable[str], None]
- auth: Union[BasicAuth, None]
- allow_redirects: bool
- max_redirects: int
- compress: Union[str, bool, None]
- chunked: Union[bool, None]
- expect100: bool
- raise_for_status: Union[None, bool, Callable[[ClientResponse], Awaitable[None]]]
- read_until_eof: bool
- proxy: Union[StrOrURL, None]
- proxy_auth: Union[BasicAuth, None]
- timeout: "Union[ClientTimeout, _SENTINEL, None]"
- ssl: Union[SSLContext, bool, Fingerprint]
- server_hostname: Union[str, None]
- proxy_headers: Union[LooseHeaders, None]
- trace_request_ctx: Union[Mapping[str, Any], None]
- read_bufsize: Union[int, None]
- auto_decompress: Union[bool, None]
- max_line_size: Union[int, None]
- max_field_size: Union[int, None]
- @attr.s(auto_attribs=True, frozen=True, slots=True)
- class ClientTimeout:
- total: Optional[float] = None
- connect: Optional[float] = None
- sock_read: Optional[float] = None
- sock_connect: Optional[float] = None
- ceil_threshold: float = 5
- # pool_queue_timeout: Optional[float] = None
- # dns_resolution_timeout: Optional[float] = None
- # socket_connect_timeout: Optional[float] = None
- # connection_acquiring_timeout: Optional[float] = None
- # new_connection_timeout: Optional[float] = None
- # http_header_timeout: Optional[float] = None
- # response_body_timeout: Optional[float] = None
- # to create a timeout specific for a single request, either
- # - create a completely new one to overwrite the default
- # - or use http://www.attrs.org/en/stable/api.html#attr.evolve
- # to overwrite the defaults
- # 5 Minute default read timeout
- DEFAULT_TIMEOUT: Final[ClientTimeout] = ClientTimeout(total=5 * 60, sock_connect=30)
- # https://www.rfc-editor.org/rfc/rfc9110#section-9.2.2
- IDEMPOTENT_METHODS = frozenset({"GET", "HEAD", "OPTIONS", "TRACE", "PUT", "DELETE"})
- _RetType = TypeVar("_RetType", ClientResponse, ClientWebSocketResponse)
- _CharsetResolver = Callable[[ClientResponse, bytes], str]
- class ClientSession:
- """First-class interface for making HTTP requests."""
- ATTRS = frozenset(
- [
- "_base_url",
- "_base_url_origin",
- "_source_traceback",
- "_connector",
- "_loop",
- "_cookie_jar",
- "_connector_owner",
- "_default_auth",
- "_version",
- "_json_serialize",
- "_requote_redirect_url",
- "_timeout",
- "_raise_for_status",
- "_auto_decompress",
- "_trust_env",
- "_default_headers",
- "_skip_auto_headers",
- "_request_class",
- "_response_class",
- "_ws_response_class",
- "_trace_configs",
- "_read_bufsize",
- "_max_line_size",
- "_max_field_size",
- "_resolve_charset",
- "_default_proxy",
- "_default_proxy_auth",
- "_retry_connection",
- "requote_redirect_url",
- ]
- )
- _source_traceback: Optional[traceback.StackSummary] = None
- _connector: Optional[BaseConnector] = None
- def __init__(
- self,
- base_url: Optional[StrOrURL] = None,
- *,
- connector: Optional[BaseConnector] = None,
- loop: Optional[asyncio.AbstractEventLoop] = None,
- cookies: Optional[LooseCookies] = None,
- headers: Optional[LooseHeaders] = None,
- proxy: Optional[StrOrURL] = None,
- proxy_auth: Optional[BasicAuth] = None,
- skip_auto_headers: Optional[Iterable[str]] = None,
- auth: Optional[BasicAuth] = None,
- json_serialize: JSONEncoder = json.dumps,
- request_class: Type[ClientRequest] = ClientRequest,
- response_class: Type[ClientResponse] = ClientResponse,
- ws_response_class: Type[ClientWebSocketResponse] = ClientWebSocketResponse,
- version: HttpVersion = http.HttpVersion11,
- cookie_jar: Optional[AbstractCookieJar] = None,
- connector_owner: bool = True,
- raise_for_status: Union[
- bool, Callable[[ClientResponse], Awaitable[None]]
- ] = False,
- read_timeout: Union[float, _SENTINEL] = sentinel,
- conn_timeout: Optional[float] = None,
- timeout: Union[object, ClientTimeout] = sentinel,
- auto_decompress: bool = True,
- trust_env: bool = False,
- requote_redirect_url: bool = True,
- trace_configs: Optional[List[TraceConfig]] = None,
- read_bufsize: int = 2**16,
- max_line_size: int = 8190,
- max_field_size: int = 8190,
- fallback_charset_resolver: _CharsetResolver = lambda r, b: "utf-8",
- ) -> None:
- # We initialise _connector to None immediately, as it's referenced in __del__()
- # and could cause issues if an exception occurs during initialisation.
- self._connector: Optional[BaseConnector] = None
- if loop is None:
- if connector is not None:
- loop = connector._loop
- loop = loop or asyncio.get_running_loop()
- if base_url is None or isinstance(base_url, URL):
- self._base_url: Optional[URL] = base_url
- self._base_url_origin = None if base_url is None else base_url.origin()
- else:
- self._base_url = URL(base_url)
- self._base_url_origin = self._base_url.origin()
- assert self._base_url.absolute, "Only absolute URLs are supported"
- if self._base_url is not None and not self._base_url.path.endswith("/"):
- raise ValueError("base_url must have a trailing '/'")
- if timeout is sentinel or timeout is None:
- self._timeout = DEFAULT_TIMEOUT
- if read_timeout is not sentinel:
- warnings.warn(
- "read_timeout is deprecated, use timeout argument instead",
- DeprecationWarning,
- stacklevel=2,
- )
- self._timeout = attr.evolve(self._timeout, total=read_timeout)
- if conn_timeout is not None:
- self._timeout = attr.evolve(self._timeout, connect=conn_timeout)
- warnings.warn(
- "conn_timeout is deprecated, use timeout argument instead",
- DeprecationWarning,
- stacklevel=2,
- )
- else:
- if not isinstance(timeout, ClientTimeout):
- raise ValueError(
- f"timeout parameter cannot be of {type(timeout)} type, "
- "please use 'timeout=ClientTimeout(...)'",
- )
- self._timeout = timeout
- if read_timeout is not sentinel:
- raise ValueError(
- "read_timeout and timeout parameters "
- "conflict, please setup "
- "timeout.read"
- )
- if conn_timeout is not None:
- raise ValueError(
- "conn_timeout and timeout parameters "
- "conflict, please setup "
- "timeout.connect"
- )
- if connector is None:
- connector = TCPConnector(loop=loop)
- if connector._loop is not loop:
- raise RuntimeError("Session and connector has to use same event loop")
- self._loop = loop
- if loop.get_debug():
- self._source_traceback = traceback.extract_stack(sys._getframe(1))
- if cookie_jar is None:
- cookie_jar = CookieJar(loop=loop)
- self._cookie_jar = cookie_jar
- if cookies:
- self._cookie_jar.update_cookies(cookies)
- self._connector = connector
- self._connector_owner = connector_owner
- self._default_auth = auth
- self._version = version
- self._json_serialize = json_serialize
- self._raise_for_status = raise_for_status
- self._auto_decompress = auto_decompress
- self._trust_env = trust_env
- self._requote_redirect_url = requote_redirect_url
- self._read_bufsize = read_bufsize
- self._max_line_size = max_line_size
- self._max_field_size = max_field_size
- # Convert to list of tuples
- if headers:
- real_headers: CIMultiDict[str] = CIMultiDict(headers)
- else:
- real_headers = CIMultiDict()
- self._default_headers: CIMultiDict[str] = real_headers
- if skip_auto_headers is not None:
- self._skip_auto_headers = frozenset(istr(i) for i in skip_auto_headers)
- else:
- self._skip_auto_headers = frozenset()
- self._request_class = request_class
- self._response_class = response_class
- self._ws_response_class = ws_response_class
- self._trace_configs = trace_configs or []
- for trace_config in self._trace_configs:
- trace_config.freeze()
- self._resolve_charset = fallback_charset_resolver
- self._default_proxy = proxy
- self._default_proxy_auth = proxy_auth
- self._retry_connection: bool = True
- def __init_subclass__(cls: Type["ClientSession"]) -> None:
- warnings.warn(
- "Inheritance class {} from ClientSession "
- "is discouraged".format(cls.__name__),
- DeprecationWarning,
- stacklevel=2,
- )
- if DEBUG:
- def __setattr__(self, name: str, val: Any) -> None:
- if name not in self.ATTRS:
- warnings.warn(
- "Setting custom ClientSession.{} attribute "
- "is discouraged".format(name),
- DeprecationWarning,
- stacklevel=2,
- )
- super().__setattr__(name, val)
- def __del__(self, _warnings: Any = warnings) -> None:
- if not self.closed:
- kwargs = {"source": self}
- _warnings.warn(
- f"Unclosed client session {self!r}", ResourceWarning, **kwargs
- )
- context = {"client_session": self, "message": "Unclosed client session"}
- if self._source_traceback is not None:
- context["source_traceback"] = self._source_traceback
- self._loop.call_exception_handler(context)
- if sys.version_info >= (3, 11) and TYPE_CHECKING:
- def request(
- self,
- method: str,
- url: StrOrURL,
- **kwargs: Unpack[_RequestOptions],
- ) -> "_RequestContextManager": ...
- else:
- def request(
- self, method: str, url: StrOrURL, **kwargs: Any
- ) -> "_RequestContextManager":
- """Perform HTTP request."""
- return _RequestContextManager(self._request(method, url, **kwargs))
- def _build_url(self, str_or_url: StrOrURL) -> URL:
- url = URL(str_or_url)
- if self._base_url is None:
- return url
- else:
- assert not url.absolute
- return self._base_url.join(url)
- async def _request(
- self,
- method: str,
- str_or_url: StrOrURL,
- *,
- params: Query = None,
- data: Any = None,
- json: Any = None,
- cookies: Optional[LooseCookies] = None,
- headers: Optional[LooseHeaders] = None,
- skip_auto_headers: Optional[Iterable[str]] = None,
- auth: Optional[BasicAuth] = None,
- allow_redirects: bool = True,
- max_redirects: int = 10,
- compress: Union[str, bool, None] = None,
- chunked: Optional[bool] = None,
- expect100: bool = False,
- raise_for_status: Union[
- None, bool, Callable[[ClientResponse], Awaitable[None]]
- ] = None,
- read_until_eof: bool = True,
- proxy: Optional[StrOrURL] = None,
- proxy_auth: Optional[BasicAuth] = None,
- timeout: Union[ClientTimeout, _SENTINEL] = sentinel,
- verify_ssl: Optional[bool] = None,
- fingerprint: Optional[bytes] = None,
- ssl_context: Optional[SSLContext] = None,
- ssl: Union[SSLContext, bool, Fingerprint] = True,
- server_hostname: Optional[str] = None,
- proxy_headers: Optional[LooseHeaders] = None,
- trace_request_ctx: Optional[Mapping[str, Any]] = None,
- read_bufsize: Optional[int] = None,
- auto_decompress: Optional[bool] = None,
- max_line_size: Optional[int] = None,
- max_field_size: Optional[int] = None,
- ) -> ClientResponse:
- # NOTE: timeout clamps existing connect and read timeouts. We cannot
- # set the default to None because we need to detect if the user wants
- # to use the existing timeouts by setting timeout to None.
- if self.closed:
- raise RuntimeError("Session is closed")
- ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint)
- if data is not None and json is not None:
- raise ValueError(
- "data and json parameters can not be used at the same time"
- )
- elif json is not None:
- data = payload.JsonPayload(json, dumps=self._json_serialize)
- if not isinstance(chunked, bool) and chunked is not None:
- warnings.warn("Chunk size is deprecated #1615", DeprecationWarning)
- redirects = 0
- history: List[ClientResponse] = []
- version = self._version
- params = params or {}
- # Merge with default headers and transform to CIMultiDict
- headers = self._prepare_headers(headers)
- try:
- url = self._build_url(str_or_url)
- except ValueError as e:
- raise InvalidUrlClientError(str_or_url) from e
- assert self._connector is not None
- if url.scheme not in self._connector.allowed_protocol_schema_set:
- raise NonHttpUrlClientError(url)
- skip_headers: Optional[Iterable[istr]]
- if skip_auto_headers is not None:
- skip_headers = {
- istr(i) for i in skip_auto_headers
- } | self._skip_auto_headers
- elif self._skip_auto_headers:
- skip_headers = self._skip_auto_headers
- else:
- skip_headers = None
- if proxy is None:
- proxy = self._default_proxy
- if proxy_auth is None:
- proxy_auth = self._default_proxy_auth
- if proxy is None:
- proxy_headers = None
- else:
- proxy_headers = self._prepare_headers(proxy_headers)
- try:
- proxy = URL(proxy)
- except ValueError as e:
- raise InvalidURL(proxy) from e
- if timeout is sentinel:
- real_timeout: ClientTimeout = self._timeout
- else:
- if not isinstance(timeout, ClientTimeout):
- real_timeout = ClientTimeout(total=timeout)
- else:
- real_timeout = timeout
- # timeout is cumulative for all request operations
- # (request, redirects, responses, data consuming)
- tm = TimeoutHandle(
- self._loop, real_timeout.total, ceil_threshold=real_timeout.ceil_threshold
- )
- handle = tm.start()
- if read_bufsize is None:
- read_bufsize = self._read_bufsize
- if auto_decompress is None:
- auto_decompress = self._auto_decompress
- if max_line_size is None:
- max_line_size = self._max_line_size
- if max_field_size is None:
- max_field_size = self._max_field_size
- traces = [
- Trace(
- self,
- trace_config,
- trace_config.trace_config_ctx(trace_request_ctx=trace_request_ctx),
- )
- for trace_config in self._trace_configs
- ]
- for trace in traces:
- await trace.send_request_start(method, url.update_query(params), headers)
- timer = tm.timer()
- try:
- with timer:
- # https://www.rfc-editor.org/rfc/rfc9112.html#name-retrying-requests
- retry_persistent_connection = (
- self._retry_connection and method in IDEMPOTENT_METHODS
- )
- while True:
- url, auth_from_url = strip_auth_from_url(url)
- if not url.raw_host:
- # NOTE: Bail early, otherwise, causes `InvalidURL` through
- # NOTE: `self._request_class()` below.
- err_exc_cls = (
- InvalidUrlRedirectClientError
- if redirects
- else InvalidUrlClientError
- )
- raise err_exc_cls(url)
- # If `auth` was passed for an already authenticated URL,
- # disallow only if this is the initial URL; this is to avoid issues
- # with sketchy redirects that are not the caller's responsibility
- if not history and (auth and auth_from_url):
- raise ValueError(
- "Cannot combine AUTH argument with "
- "credentials encoded in URL"
- )
- # Override the auth with the one from the URL only if we
- # have no auth, or if we got an auth from a redirect URL
- if auth is None or (history and auth_from_url is not None):
- auth = auth_from_url
- if (
- auth is None
- and self._default_auth
- and (
- not self._base_url or self._base_url_origin == url.origin()
- )
- ):
- auth = self._default_auth
- # It would be confusing if we support explicit
- # Authorization header with auth argument
- if (
- headers is not None
- and auth is not None
- and hdrs.AUTHORIZATION in headers
- ):
- raise ValueError(
- "Cannot combine AUTHORIZATION header "
- "with AUTH argument or credentials "
- "encoded in URL"
- )
- all_cookies = self._cookie_jar.filter_cookies(url)
- if cookies is not None:
- tmp_cookie_jar = CookieJar(
- quote_cookie=self._cookie_jar.quote_cookie
- )
- tmp_cookie_jar.update_cookies(cookies)
- req_cookies = tmp_cookie_jar.filter_cookies(url)
- if req_cookies:
- all_cookies.load(req_cookies)
- if proxy is not None:
- proxy = URL(proxy)
- elif self._trust_env:
- with suppress(LookupError):
- proxy, proxy_auth = get_env_proxy_for_url(url)
- req = self._request_class(
- method,
- url,
- params=params,
- headers=headers,
- skip_auto_headers=skip_headers,
- data=data,
- cookies=all_cookies,
- auth=auth,
- version=version,
- compress=compress,
- chunked=chunked,
- expect100=expect100,
- loop=self._loop,
- response_class=self._response_class,
- proxy=proxy,
- proxy_auth=proxy_auth,
- timer=timer,
- session=self,
- ssl=ssl if ssl is not None else True,
- server_hostname=server_hostname,
- proxy_headers=proxy_headers,
- traces=traces,
- trust_env=self.trust_env,
- )
- # connection timeout
- try:
- conn = await self._connector.connect(
- req, traces=traces, timeout=real_timeout
- )
- except asyncio.TimeoutError as exc:
- raise ConnectionTimeoutError(
- f"Connection timeout to host {url}"
- ) from exc
- assert conn.transport is not None
- assert conn.protocol is not None
- conn.protocol.set_response_params(
- timer=timer,
- skip_payload=method in EMPTY_BODY_METHODS,
- read_until_eof=read_until_eof,
- auto_decompress=auto_decompress,
- read_timeout=real_timeout.sock_read,
- read_bufsize=read_bufsize,
- timeout_ceil_threshold=self._connector._timeout_ceil_threshold,
- max_line_size=max_line_size,
- max_field_size=max_field_size,
- )
- try:
- try:
- resp = await req.send(conn)
- try:
- await resp.start(conn)
- except BaseException:
- resp.close()
- raise
- except BaseException:
- conn.close()
- raise
- except (ClientOSError, ServerDisconnectedError):
- if retry_persistent_connection:
- retry_persistent_connection = False
- continue
- raise
- except ClientError:
- raise
- except OSError as exc:
- if exc.errno is None and isinstance(exc, asyncio.TimeoutError):
- raise
- raise ClientOSError(*exc.args) from exc
- if cookies := resp._cookies:
- self._cookie_jar.update_cookies(cookies, resp.url)
- # redirects
- if resp.status in (301, 302, 303, 307, 308) and allow_redirects:
- for trace in traces:
- await trace.send_request_redirect(
- method, url.update_query(params), headers, resp
- )
- redirects += 1
- history.append(resp)
- if max_redirects and redirects >= max_redirects:
- resp.close()
- raise TooManyRedirects(
- history[0].request_info, tuple(history)
- )
- # For 301 and 302, mimic IE, now changed in RFC
- # https://github.com/kennethreitz/requests/pull/269
- if (resp.status == 303 and resp.method != hdrs.METH_HEAD) or (
- resp.status in (301, 302) and resp.method == hdrs.METH_POST
- ):
- method = hdrs.METH_GET
- data = None
- if headers.get(hdrs.CONTENT_LENGTH):
- headers.pop(hdrs.CONTENT_LENGTH)
- r_url = resp.headers.get(hdrs.LOCATION) or resp.headers.get(
- hdrs.URI
- )
- if r_url is None:
- # see github.com/aio-libs/aiohttp/issues/2022
- break
- else:
- # reading from correct redirection
- # response is forbidden
- resp.release()
- try:
- parsed_redirect_url = URL(
- r_url, encoded=not self._requote_redirect_url
- )
- except ValueError as e:
- raise InvalidUrlRedirectClientError(
- r_url,
- "Server attempted redirecting to a location that does not look like a URL",
- ) from e
- scheme = parsed_redirect_url.scheme
- if scheme not in HTTP_AND_EMPTY_SCHEMA_SET:
- resp.close()
- raise NonHttpUrlRedirectClientError(r_url)
- elif not scheme:
- parsed_redirect_url = url.join(parsed_redirect_url)
- try:
- redirect_origin = parsed_redirect_url.origin()
- except ValueError as origin_val_err:
- raise InvalidUrlRedirectClientError(
- parsed_redirect_url,
- "Invalid redirect URL origin",
- ) from origin_val_err
- if url.origin() != redirect_origin:
- auth = None
- headers.pop(hdrs.AUTHORIZATION, None)
- url = parsed_redirect_url
- params = {}
- resp.release()
- continue
- break
- # check response status
- if raise_for_status is None:
- raise_for_status = self._raise_for_status
- if raise_for_status is None:
- pass
- elif callable(raise_for_status):
- await raise_for_status(resp)
- elif raise_for_status:
- resp.raise_for_status()
- # register connection
- if handle is not None:
- if resp.connection is not None:
- resp.connection.add_callback(handle.cancel)
- else:
- handle.cancel()
- resp._history = tuple(history)
- for trace in traces:
- await trace.send_request_end(
- method, url.update_query(params), headers, resp
- )
- return resp
- except BaseException as e:
- # cleanup timer
- tm.close()
- if handle:
- handle.cancel()
- handle = None
- for trace in traces:
- await trace.send_request_exception(
- method, url.update_query(params), headers, e
- )
- raise
- def ws_connect(
- self,
- url: StrOrURL,
- *,
- method: str = hdrs.METH_GET,
- protocols: Iterable[str] = (),
- timeout: Union[ClientWSTimeout, _SENTINEL] = sentinel,
- receive_timeout: Optional[float] = None,
- autoclose: bool = True,
- autoping: bool = True,
- heartbeat: Optional[float] = None,
- auth: Optional[BasicAuth] = None,
- origin: Optional[str] = None,
- params: Query = None,
- headers: Optional[LooseHeaders] = None,
- proxy: Optional[StrOrURL] = None,
- proxy_auth: Optional[BasicAuth] = None,
- ssl: Union[SSLContext, bool, Fingerprint] = True,
- verify_ssl: Optional[bool] = None,
- fingerprint: Optional[bytes] = None,
- ssl_context: Optional[SSLContext] = None,
- server_hostname: Optional[str] = None,
- proxy_headers: Optional[LooseHeaders] = None,
- compress: int = 0,
- max_msg_size: int = 4 * 1024 * 1024,
- ) -> "_WSRequestContextManager":
- """Initiate websocket connection."""
- return _WSRequestContextManager(
- self._ws_connect(
- url,
- method=method,
- protocols=protocols,
- timeout=timeout,
- receive_timeout=receive_timeout,
- autoclose=autoclose,
- autoping=autoping,
- heartbeat=heartbeat,
- auth=auth,
- origin=origin,
- params=params,
- headers=headers,
- proxy=proxy,
- proxy_auth=proxy_auth,
- ssl=ssl,
- verify_ssl=verify_ssl,
- fingerprint=fingerprint,
- ssl_context=ssl_context,
- server_hostname=server_hostname,
- proxy_headers=proxy_headers,
- compress=compress,
- max_msg_size=max_msg_size,
- )
- )
- async def _ws_connect(
- self,
- url: StrOrURL,
- *,
- method: str = hdrs.METH_GET,
- protocols: Iterable[str] = (),
- timeout: Union[ClientWSTimeout, _SENTINEL] = sentinel,
- receive_timeout: Optional[float] = None,
- autoclose: bool = True,
- autoping: bool = True,
- heartbeat: Optional[float] = None,
- auth: Optional[BasicAuth] = None,
- origin: Optional[str] = None,
- params: Query = None,
- headers: Optional[LooseHeaders] = None,
- proxy: Optional[StrOrURL] = None,
- proxy_auth: Optional[BasicAuth] = None,
- ssl: Union[SSLContext, bool, Fingerprint] = True,
- verify_ssl: Optional[bool] = None,
- fingerprint: Optional[bytes] = None,
- ssl_context: Optional[SSLContext] = None,
- server_hostname: Optional[str] = None,
- proxy_headers: Optional[LooseHeaders] = None,
- compress: int = 0,
- max_msg_size: int = 4 * 1024 * 1024,
- ) -> ClientWebSocketResponse:
- if timeout is not sentinel:
- if isinstance(timeout, ClientWSTimeout):
- ws_timeout = timeout
- else:
- warnings.warn(
- "parameter 'timeout' of type 'float' "
- "is deprecated, please use "
- "'timeout=ClientWSTimeout(ws_close=...)'",
- DeprecationWarning,
- stacklevel=2,
- )
- ws_timeout = ClientWSTimeout(ws_close=timeout)
- else:
- ws_timeout = DEFAULT_WS_CLIENT_TIMEOUT
- if receive_timeout is not None:
- warnings.warn(
- "float parameter 'receive_timeout' "
- "is deprecated, please use parameter "
- "'timeout=ClientWSTimeout(ws_receive=...)'",
- DeprecationWarning,
- stacklevel=2,
- )
- ws_timeout = attr.evolve(ws_timeout, ws_receive=receive_timeout)
- if headers is None:
- real_headers: CIMultiDict[str] = CIMultiDict()
- else:
- real_headers = CIMultiDict(headers)
- default_headers = {
- hdrs.UPGRADE: "websocket",
- hdrs.CONNECTION: "Upgrade",
- hdrs.SEC_WEBSOCKET_VERSION: "13",
- }
- for key, value in default_headers.items():
- real_headers.setdefault(key, value)
- sec_key = base64.b64encode(os.urandom(16))
- real_headers[hdrs.SEC_WEBSOCKET_KEY] = sec_key.decode()
- if protocols:
- real_headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = ",".join(protocols)
- if origin is not None:
- real_headers[hdrs.ORIGIN] = origin
- if compress:
- extstr = ws_ext_gen(compress=compress)
- real_headers[hdrs.SEC_WEBSOCKET_EXTENSIONS] = extstr
- # For the sake of backward compatibility, if user passes in None, convert it to True
- if ssl is None:
- warnings.warn(
- "ssl=None is deprecated, please use ssl=True",
- DeprecationWarning,
- stacklevel=2,
- )
- ssl = True
- ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint)
- # send request
- resp = await self.request(
- method,
- url,
- params=params,
- headers=real_headers,
- read_until_eof=False,
- auth=auth,
- proxy=proxy,
- proxy_auth=proxy_auth,
- ssl=ssl,
- server_hostname=server_hostname,
- proxy_headers=proxy_headers,
- )
- try:
- # check handshake
- if resp.status != 101:
- raise WSServerHandshakeError(
- resp.request_info,
- resp.history,
- message="Invalid response status",
- status=resp.status,
- headers=resp.headers,
- )
- if resp.headers.get(hdrs.UPGRADE, "").lower() != "websocket":
- raise WSServerHandshakeError(
- resp.request_info,
- resp.history,
- message="Invalid upgrade header",
- status=resp.status,
- headers=resp.headers,
- )
- if resp.headers.get(hdrs.CONNECTION, "").lower() != "upgrade":
- raise WSServerHandshakeError(
- resp.request_info,
- resp.history,
- message="Invalid connection header",
- status=resp.status,
- headers=resp.headers,
- )
- # key calculation
- r_key = resp.headers.get(hdrs.SEC_WEBSOCKET_ACCEPT, "")
- match = base64.b64encode(hashlib.sha1(sec_key + WS_KEY).digest()).decode()
- if r_key != match:
- raise WSServerHandshakeError(
- resp.request_info,
- resp.history,
- message="Invalid challenge response",
- status=resp.status,
- headers=resp.headers,
- )
- # websocket protocol
- protocol = None
- if protocols and hdrs.SEC_WEBSOCKET_PROTOCOL in resp.headers:
- resp_protocols = [
- proto.strip()
- for proto in resp.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",")
- ]
- for proto in resp_protocols:
- if proto in protocols:
- protocol = proto
- break
- # websocket compress
- notakeover = False
- if compress:
- compress_hdrs = resp.headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS)
- if compress_hdrs:
- try:
- compress, notakeover = ws_ext_parse(compress_hdrs)
- except WSHandshakeError as exc:
- raise WSServerHandshakeError(
- resp.request_info,
- resp.history,
- message=exc.args[0],
- status=resp.status,
- headers=resp.headers,
- ) from exc
- else:
- compress = 0
- notakeover = False
- conn = resp.connection
- assert conn is not None
- conn_proto = conn.protocol
- assert conn_proto is not None
- # For WS connection the read_timeout must be either receive_timeout or greater
- # None == no timeout, i.e. infinite timeout, so None is the max timeout possible
- if ws_timeout.ws_receive is None:
- # Reset regardless
- conn_proto.read_timeout = None
- elif conn_proto.read_timeout is not None:
- conn_proto.read_timeout = max(
- ws_timeout.ws_receive, conn_proto.read_timeout
- )
- transport = conn.transport
- assert transport is not None
- reader = WebSocketDataQueue(conn_proto, 2**16, loop=self._loop)
- conn_proto.set_parser(WebSocketReader(reader, max_msg_size), reader)
- writer = WebSocketWriter(
- conn_proto,
- transport,
- use_mask=True,
- compress=compress,
- notakeover=notakeover,
- )
- except BaseException:
- resp.close()
- raise
- else:
- return self._ws_response_class(
- reader,
- writer,
- protocol,
- resp,
- ws_timeout,
- autoclose,
- autoping,
- self._loop,
- heartbeat=heartbeat,
- compress=compress,
- client_notakeover=notakeover,
- )
- def _prepare_headers(self, headers: Optional[LooseHeaders]) -> "CIMultiDict[str]":
- """Add default headers and transform it to CIMultiDict"""
- # Convert headers to MultiDict
- result = CIMultiDict(self._default_headers)
- if headers:
- if not isinstance(headers, (MultiDictProxy, MultiDict)):
- headers = CIMultiDict(headers)
- added_names: Set[str] = set()
- for key, value in headers.items():
- if key in added_names:
- result.add(key, value)
- else:
- result[key] = value
- added_names.add(key)
- return result
- if sys.version_info >= (3, 11) and TYPE_CHECKING:
- def get(
- self,
- url: StrOrURL,
- **kwargs: Unpack[_RequestOptions],
- ) -> "_RequestContextManager": ...
- def options(
- self,
- url: StrOrURL,
- **kwargs: Unpack[_RequestOptions],
- ) -> "_RequestContextManager": ...
- def head(
- self,
- url: StrOrURL,
- **kwargs: Unpack[_RequestOptions],
- ) -> "_RequestContextManager": ...
- def post(
- self,
- url: StrOrURL,
- **kwargs: Unpack[_RequestOptions],
- ) -> "_RequestContextManager": ...
- def put(
- self,
- url: StrOrURL,
- **kwargs: Unpack[_RequestOptions],
- ) -> "_RequestContextManager": ...
- def patch(
- self,
- url: StrOrURL,
- **kwargs: Unpack[_RequestOptions],
- ) -> "_RequestContextManager": ...
- def delete(
- self,
- url: StrOrURL,
- **kwargs: Unpack[_RequestOptions],
- ) -> "_RequestContextManager": ...
- else:
- def get(
- self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any
- ) -> "_RequestContextManager":
- """Perform HTTP GET request."""
- return _RequestContextManager(
- self._request(
- hdrs.METH_GET, url, allow_redirects=allow_redirects, **kwargs
- )
- )
- def options(
- self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any
- ) -> "_RequestContextManager":
- """Perform HTTP OPTIONS request."""
- return _RequestContextManager(
- self._request(
- hdrs.METH_OPTIONS, url, allow_redirects=allow_redirects, **kwargs
- )
- )
- def head(
- self, url: StrOrURL, *, allow_redirects: bool = False, **kwargs: Any
- ) -> "_RequestContextManager":
- """Perform HTTP HEAD request."""
- return _RequestContextManager(
- self._request(
- hdrs.METH_HEAD, url, allow_redirects=allow_redirects, **kwargs
- )
- )
- def post(
- self, url: StrOrURL, *, data: Any = None, **kwargs: Any
- ) -> "_RequestContextManager":
- """Perform HTTP POST request."""
- return _RequestContextManager(
- self._request(hdrs.METH_POST, url, data=data, **kwargs)
- )
- def put(
- self, url: StrOrURL, *, data: Any = None, **kwargs: Any
- ) -> "_RequestContextManager":
- """Perform HTTP PUT request."""
- return _RequestContextManager(
- self._request(hdrs.METH_PUT, url, data=data, **kwargs)
- )
- def patch(
- self, url: StrOrURL, *, data: Any = None, **kwargs: Any
- ) -> "_RequestContextManager":
- """Perform HTTP PATCH request."""
- return _RequestContextManager(
- self._request(hdrs.METH_PATCH, url, data=data, **kwargs)
- )
- def delete(self, url: StrOrURL, **kwargs: Any) -> "_RequestContextManager":
- """Perform HTTP DELETE request."""
- return _RequestContextManager(
- self._request(hdrs.METH_DELETE, url, **kwargs)
- )
- async def close(self) -> None:
- """Close underlying connector.
- Release all acquired resources.
- """
- if not self.closed:
- if self._connector is not None and self._connector_owner:
- await self._connector.close()
- self._connector = None
- @property
- def closed(self) -> bool:
- """Is client session closed.
- A readonly property.
- """
- return self._connector is None or self._connector.closed
- @property
- def connector(self) -> Optional[BaseConnector]:
- """Connector instance used for the session."""
- return self._connector
- @property
- def cookie_jar(self) -> AbstractCookieJar:
- """The session cookies."""
- return self._cookie_jar
- @property
- def version(self) -> Tuple[int, int]:
- """The session HTTP protocol version."""
- return self._version
- @property
- def requote_redirect_url(self) -> bool:
- """Do URL requoting on redirection handling."""
- return self._requote_redirect_url
- @requote_redirect_url.setter
- def requote_redirect_url(self, val: bool) -> None:
- """Do URL requoting on redirection handling."""
- warnings.warn(
- "session.requote_redirect_url modification is deprecated #2778",
- DeprecationWarning,
- stacklevel=2,
- )
- self._requote_redirect_url = val
- @property
- def loop(self) -> asyncio.AbstractEventLoop:
- """Session's loop."""
- warnings.warn(
- "client.loop property is deprecated", DeprecationWarning, stacklevel=2
- )
- return self._loop
- @property
- def timeout(self) -> ClientTimeout:
- """Timeout for the session."""
- return self._timeout
- @property
- def headers(self) -> "CIMultiDict[str]":
- """The default headers of the client session."""
- return self._default_headers
- @property
- def skip_auto_headers(self) -> FrozenSet[istr]:
- """Headers for which autogeneration should be skipped"""
- return self._skip_auto_headers
- @property
- def auth(self) -> Optional[BasicAuth]:
- """An object that represents HTTP Basic Authorization"""
- return self._default_auth
- @property
- def json_serialize(self) -> JSONEncoder:
- """Json serializer callable"""
- return self._json_serialize
- @property
- def connector_owner(self) -> bool:
- """Should connector be closed on session closing"""
- return self._connector_owner
- @property
- def raise_for_status(
- self,
- ) -> Union[bool, Callable[[ClientResponse], Awaitable[None]]]:
- """Should `ClientResponse.raise_for_status()` be called for each response."""
- return self._raise_for_status
- @property
- def auto_decompress(self) -> bool:
- """Should the body response be automatically decompressed."""
- return self._auto_decompress
- @property
- def trust_env(self) -> bool:
- """
- Should proxies information from environment or netrc be trusted.
- Information is from HTTP_PROXY / HTTPS_PROXY environment variables
- or ~/.netrc file if present.
- """
- return self._trust_env
- @property
- def trace_configs(self) -> List[TraceConfig]:
- """A list of TraceConfig instances used for client tracing"""
- return self._trace_configs
- def detach(self) -> None:
- """Detach connector from session without closing the former.
- Session is switched to closed state anyway.
- """
- self._connector = None
- def __enter__(self) -> None:
- raise TypeError("Use async with instead")
- def __exit__(
- self,
- exc_type: Optional[Type[BaseException]],
- exc_val: Optional[BaseException],
- exc_tb: Optional[TracebackType],
- ) -> None:
- # __exit__ should exist in pair with __enter__ but never executed
- pass # pragma: no cover
- async def __aenter__(self) -> "ClientSession":
- return self
- async def __aexit__(
- self,
- exc_type: Optional[Type[BaseException]],
- exc_val: Optional[BaseException],
- exc_tb: Optional[TracebackType],
- ) -> None:
- await self.close()
- class _BaseRequestContextManager(Coroutine[Any, Any, _RetType], Generic[_RetType]):
- __slots__ = ("_coro", "_resp")
- def __init__(self, coro: Coroutine["asyncio.Future[Any]", None, _RetType]) -> None:
- self._coro: Coroutine["asyncio.Future[Any]", None, _RetType] = coro
- def send(self, arg: None) -> "asyncio.Future[Any]":
- return self._coro.send(arg)
- def throw(self, *args: Any, **kwargs: Any) -> "asyncio.Future[Any]":
- return self._coro.throw(*args, **kwargs)
- def close(self) -> None:
- return self._coro.close()
- def __await__(self) -> Generator[Any, None, _RetType]:
- ret = self._coro.__await__()
- return ret
- def __iter__(self) -> Generator[Any, None, _RetType]:
- return self.__await__()
- async def __aenter__(self) -> _RetType:
- self._resp: _RetType = await self._coro
- return await self._resp.__aenter__()
- async def __aexit__(
- self,
- exc_type: Optional[Type[BaseException]],
- exc: Optional[BaseException],
- tb: Optional[TracebackType],
- ) -> None:
- await self._resp.__aexit__(exc_type, exc, tb)
- _RequestContextManager = _BaseRequestContextManager[ClientResponse]
- _WSRequestContextManager = _BaseRequestContextManager[ClientWebSocketResponse]
- class _SessionRequestContextManager:
- __slots__ = ("_coro", "_resp", "_session")
- def __init__(
- self,
- coro: Coroutine["asyncio.Future[Any]", None, ClientResponse],
- session: ClientSession,
- ) -> None:
- self._coro = coro
- self._resp: Optional[ClientResponse] = None
- self._session = session
- async def __aenter__(self) -> ClientResponse:
- try:
- self._resp = await self._coro
- except BaseException:
- await self._session.close()
- raise
- else:
- return self._resp
- async def __aexit__(
- self,
- exc_type: Optional[Type[BaseException]],
- exc: Optional[BaseException],
- tb: Optional[TracebackType],
- ) -> None:
- assert self._resp is not None
- self._resp.close()
- await self._session.close()
- if sys.version_info >= (3, 11) and TYPE_CHECKING:
- def request(
- method: str,
- url: StrOrURL,
- *,
- version: HttpVersion = http.HttpVersion11,
- connector: Optional[BaseConnector] = None,
- loop: Optional[asyncio.AbstractEventLoop] = None,
- **kwargs: Unpack[_RequestOptions],
- ) -> _SessionRequestContextManager: ...
- else:
- def request(
- method: str,
- url: StrOrURL,
- *,
- version: HttpVersion = http.HttpVersion11,
- connector: Optional[BaseConnector] = None,
- loop: Optional[asyncio.AbstractEventLoop] = None,
- **kwargs: Any,
- ) -> _SessionRequestContextManager:
- """Constructs and sends a request.
- Returns response object.
- method - HTTP method
- url - request url
- params - (optional) Dictionary or bytes to be sent in the query
- string of the new request
- data - (optional) Dictionary, bytes, or file-like object to
- send in the body of the request
- json - (optional) Any json compatible python object
- headers - (optional) Dictionary of HTTP Headers to send with
- the request
- cookies - (optional) Dict object to send with the request
- auth - (optional) BasicAuth named tuple represent HTTP Basic Auth
- auth - aiohttp.helpers.BasicAuth
- allow_redirects - (optional) If set to False, do not follow
- redirects
- version - Request HTTP version.
- compress - Set to True if request has to be compressed
- with deflate encoding.
- chunked - Set to chunk size for chunked transfer encoding.
- expect100 - Expect 100-continue response from server.
- connector - BaseConnector sub-class instance to support
- connection pooling.
- read_until_eof - Read response until eof if response
- does not have Content-Length header.
- loop - Optional event loop.
- timeout - Optional ClientTimeout settings structure, 5min
- total timeout by default.
- Usage::
- >>> import aiohttp
- >>> async with aiohttp.request('GET', 'http://python.org/') as resp:
- ... print(resp)
- ... data = await resp.read()
- <ClientResponse(https://www.python.org/) [200 OK]>
- """
- connector_owner = False
- if connector is None:
- connector_owner = True
- connector = TCPConnector(loop=loop, force_close=True)
- session = ClientSession(
- loop=loop,
- cookies=kwargs.pop("cookies", None),
- version=version,
- timeout=kwargs.pop("timeout", sentinel),
- connector=connector,
- connector_owner=connector_owner,
- )
- return _SessionRequestContextManager(
- session._request(method, url, **kwargs),
- session,
- )
|