From 8c3ad3b8292d7df9963a1932348103e5a3a786da Mon Sep 17 00:00:00 2001 From: meshya Date: Fri, 27 Sep 2024 20:29:26 +0330 Subject: [PATCH 1/5] add proxy and proxy_auth varriables to ClientSession.__init__ (#9207) (cherry picked from commit 970c5d9d84afc8fb762ff04940b5e25449a1f6ae) --- CHANGES/9207.feature.rst | 1 + CONTRIBUTORS.txt | 1 + aiohttp/client.py | 68 ++++++++++++++++++++---------------- docs/client_advanced.rst | 7 ++++ tests/test_client_session.py | 55 +++++++++++++++++++++++++++-- 5 files changed, 99 insertions(+), 33 deletions(-) create mode 100644 CHANGES/9207.feature.rst diff --git a/CHANGES/9207.feature.rst b/CHANGES/9207.feature.rst new file mode 100644 index 00000000000..d9ac55c8520 --- /dev/null +++ b/CHANGES/9207.feature.rst @@ -0,0 +1 @@ +Added ``proxy`` and ``proxy_auth`` parameters to ``ClientSession`` -- by :user:`meshya`. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 92e1666fbc6..30d6a10ab82 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -236,6 +236,7 @@ Matthieu Hauglustaine Matthieu Rigal Matvey Tingaev Meet Mangukiya +Meshya Michael Ihnatenko Michał Górny Mikhail Burshteyn diff --git a/aiohttp/client.py b/aiohttp/client.py index c893b06bb11..f2acd5c398c 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -225,39 +225,35 @@ class ClientTimeout: class ClientSession: """First-class interface for making HTTP requests.""" - ATTRS = frozenset( - [ - "_base_url", - "_source_traceback", - "_connector", - "requote_redirect_url", - "_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", - ] + __slots__ = ( + "_base_url", + "_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", ) - _source_traceback: Optional[traceback.StackSummary] = None - _connector: Optional[BaseConnector] = None - def __init__( self, base_url: Optional[StrOrURL] = None, @@ -266,6 +262,8 @@ def __init__( 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, @@ -396,6 +394,9 @@ def __init__( self._resolve_charset = fallback_charset_resolver + self._default_proxy = proxy + self._default_proxy_auth = proxy_auth + def __init_subclass__(cls: Type["ClientSession"]) -> None: warnings.warn( "Inheritance class {} from ClientSession " @@ -531,6 +532,11 @@ async def _request( for i in skip_auto_headers: skip_headers.add(istr(i)) + if proxy is None: + proxy = self._default_proxy + if proxy_auth is None: + proxy_auth = self._default_proxy_auth + if proxy is not None: try: proxy = URL(proxy) diff --git a/docs/client_advanced.rst b/docs/client_advanced.rst index 26594a21b1c..1e338851f60 100644 --- a/docs/client_advanced.rst +++ b/docs/client_advanced.rst @@ -567,6 +567,13 @@ Authentication credentials can be passed in proxy URL:: session.get("http://python.org", proxy="http://user:pass@some.proxy.com") +And you may set default proxy:: + + proxy_auth = aiohttp.BasicAuth('user', 'pass') + async with aiohttp.ClientSession(proxy="http://proxy.com", proxy_auth=proxy_auth) as session: + async with session.get("http://python.org") as resp: + print(resp.status) + Contrary to the ``requests`` library, it won't read environment variables by default. But you can do so by passing ``trust_env=True`` into :class:`aiohttp.ClientSession` diff --git a/tests/test_client_session.py b/tests/test_client_session.py index 86f3a1b6c6e..aa5bd39aa08 100644 --- a/tests/test_client_session.py +++ b/tests/test_client_session.py @@ -659,8 +659,59 @@ def test_proxy_str(session, params) -> None: ] -async def test_request_tracing(loop, aiohttp_client) -> None: - async def handler(request): +async def test_default_proxy(loop: asyncio.AbstractEventLoop) -> None: + proxy_url = URL("http://proxy.example.com") + proxy_auth = mock.Mock() + proxy_url2 = URL("http://proxy.example2.com") + proxy_auth2 = mock.Mock() + + class OnCall(Exception): + pass + + request_class_mock = mock.Mock(side_effect=OnCall()) + session = ClientSession( + proxy=proxy_url, proxy_auth=proxy_auth, request_class=request_class_mock + ) + + assert session._default_proxy == proxy_url, "`ClientSession._default_proxy` not set" + assert ( + session._default_proxy_auth == proxy_auth + ), "`ClientSession._default_proxy_auth` not set" + + with pytest.raises(OnCall): + await session.get( + "http://example.com", + ) + + assert request_class_mock.called, "request class not called" + assert ( + request_class_mock.call_args[1].get("proxy") == proxy_url + ), "`ClientSession._request` uses default proxy not one used in ClientSession.get" + assert ( + request_class_mock.call_args[1].get("proxy_auth") == proxy_auth + ), "`ClientSession._request` uses default proxy_auth not one used in ClientSession.get" + + request_class_mock.reset_mock() + with pytest.raises(OnCall): + await session.get( + "http://example.com", proxy=proxy_url2, proxy_auth=proxy_auth2 + ) + + assert request_class_mock.called, "request class not called" + assert ( + request_class_mock.call_args[1].get("proxy") == proxy_url2 + ), "`ClientSession._request` uses default proxy not one used in ClientSession.get" + assert ( + request_class_mock.call_args[1].get("proxy_auth") == proxy_auth2 + ), "`ClientSession._request` uses default proxy_auth not one used in ClientSession.get" + + await session.close() + + +async def test_request_tracing( + loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient +) -> None: + async def handler(request: web.Request) -> web.Response: return web.json_response({"ok": True}) app = web.Application() From 53c178673af6c215b10bf49529260cb63b26954d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 1 Oct 2024 14:13:24 -0500 Subject: [PATCH 2/5] Fix merge --- aiohttp/client.py | 60 ++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/aiohttp/client.py b/aiohttp/client.py index f2acd5c398c..d31b728a10c 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -225,35 +225,41 @@ class ClientTimeout: class ClientSession: """First-class interface for making HTTP requests.""" - __slots__ = ( - "_base_url", - "_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", + ATTRS = frozenset( + [ + "_base_url", + "_source_traceback", + "_connector", + "requote_redirect_url", + "_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", + ] ) + _source_traceback: Optional[traceback.StackSummary] = None + _connector: Optional[BaseConnector] = None + def __init__( self, base_url: Optional[StrOrURL] = None, From fb7fb5733c4a1e81501c4475f7dfc90f5b9cd3ae Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 1 Oct 2024 14:15:08 -0500 Subject: [PATCH 3/5] Update aiohttp/client.py --- aiohttp/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiohttp/client.py b/aiohttp/client.py index a04a458e1cc..a5c57bb25ac 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -542,7 +542,7 @@ async def _request( if proxy_auth is None: proxy_auth = self._default_proxy_auth - if proxy is not None: + if proxy is None: proxy_headers = None else: proxy_headers = self._prepare_headers(proxy_headers) From b53525438b2269aeade792e457381f7515e6e2c1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 1 Oct 2024 14:16:26 -0500 Subject: [PATCH 4/5] Update tests/test_client_session.py --- tests/test_client_session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_client_session.py b/tests/test_client_session.py index aa5bd39aa08..029bdb89155 100644 --- a/tests/test_client_session.py +++ b/tests/test_client_session.py @@ -709,7 +709,7 @@ class OnCall(Exception): async def test_request_tracing( - loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient + loop: asyncio.AbstractEventLoop, aiohttp_client ) -> None: async def handler(request: web.Request) -> web.Response: return web.json_response({"ok": True}) From f2894440cb564f96fd0c2e84b35f1389ed48cf2a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 19:16:57 +0000 Subject: [PATCH 5/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_client_session.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_client_session.py b/tests/test_client_session.py index 029bdb89155..89a06466767 100644 --- a/tests/test_client_session.py +++ b/tests/test_client_session.py @@ -708,9 +708,7 @@ class OnCall(Exception): await session.close() -async def test_request_tracing( - loop: asyncio.AbstractEventLoop, aiohttp_client -) -> None: +async def test_request_tracing(loop: asyncio.AbstractEventLoop, aiohttp_client) -> None: async def handler(request: web.Request) -> web.Response: return web.json_response({"ok": True})