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 9ab093ae2a7..5ef10dd4993 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -243,6 +243,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 16defeb4801..44366eed92c 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -252,6 +252,8 @@ class ClientSession: "_max_line_size", "_max_field_size", "_resolve_charset", + "_default_proxy", + "_default_proxy_auth", ) def __init__( @@ -261,6 +263,8 @@ def __init__( connector: Optional[BaseConnector] = 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, @@ -361,6 +365,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: raise TypeError( "Inheritance class {} from ClientSession " @@ -480,6 +487,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 0a7093661ff..19a4c196c33 100644 --- a/docs/client_advanced.rst +++ b/docs/client_advanced.rst @@ -605,6 +605,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 30c24a3de39..f8efeac077d 100644 --- a/tests/test_client_session.py +++ b/tests/test_client_session.py @@ -710,6 +710,55 @@ async def test_proxy_str(session: ClientSession, params: _Params) -> None: ] +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: