Skip to content

Commit

Permalink
Add a base_url option to ClientSession (#6129)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrew Svetlov <andrew.svetlov@gmail.com>
Co-authored-by: Sviatoslav Sydorenko <wk.cvs.github@sydorenko.org.ua>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
4 people committed Oct 31, 2021
1 parent 46336a9 commit 527b1b9
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGES/6013.feature
@@ -0,0 +1 @@
Added ``base_url`` parameter to the initializer of :class:`~aiohttp.ClientSession`.
20 changes: 19 additions & 1 deletion aiohttp/client.py
Expand Up @@ -169,6 +169,7 @@ class ClientSession:
"""First-class interface for making HTTP requests."""

__slots__ = (
"_base_url",
"_source_traceback",
"_connector",
"_loop",
Expand All @@ -193,6 +194,7 @@ class ClientSession:

def __init__(
self,
base_url: Optional[StrOrURL] = None,
*,
connector: Optional[BaseConnector] = None,
cookies: Optional[LooseCookies] = None,
Expand All @@ -216,6 +218,14 @@ def __init__(
trace_configs: Optional[List[TraceConfig]] = None,
read_bufsize: int = 2 ** 16,
) -> None:
if base_url is None or isinstance(base_url, URL):
self._base_url: Optional[URL] = base_url
else:
self._base_url = URL(base_url)
assert (
self._base_url.origin() == self._base_url
), "Only absolute URLs without path part are supported"

loop = asyncio.get_running_loop()

if connector is None:
Expand Down Expand Up @@ -304,6 +314,14 @@ def request(
"""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.is_absolute() and url.path.startswith("/")
return self._base_url.join(url)

async def _request(
self,
method: str,
Expand Down Expand Up @@ -363,7 +381,7 @@ async def _request(
proxy_headers = self._prepare_headers(proxy_headers)

try:
url = URL(str_or_url)
url = self._build_url(str_or_url)
except ValueError as e:
raise InvalidURL(str_or_url) from e

Expand Down
12 changes: 12 additions & 0 deletions docs/client_quickstart.rst
Expand Up @@ -55,6 +55,18 @@ Other HTTP methods are available as well::
session.options('http://httpbin.org/get')
session.patch('http://httpbin.org/patch', data=b'data')

To make several requests to the same site more simple, the parameter ``base_url``
of :class:`ClientSession` constructor can be used. For example to request different
endpoints of ``http://httpbin.org`` can be used the following code::

async with aiohttp.ClientSession('http://httpbin.org') as session:
async with session.get('/get'):
pass
async with session.post('/post', data=b'data'):
pass
async with session.put('/put', data=b'data'):
pass

.. note::

Don't create a session per request. Most likely you need a session
Expand Down
8 changes: 7 additions & 1 deletion docs/client_reference.rst
Expand Up @@ -38,7 +38,8 @@ Usage example::

The client session supports the context manager protocol for self closing.

.. class:: ClientSession(*, connector=None, loop=None, cookies=None, \
.. class:: ClientSession(base_url=None, *, \
connector=None, cookies=None, \
headers=None, skip_auto_headers=None, \
auth=None, json_serialize=json.dumps, \
version=aiohttp.HttpVersion11, \
Expand All @@ -56,6 +57,11 @@ The client session supports the context manager protocol for self closing.
The class for creating client sessions and making requests.


:param base_url: Base part of the URL (optional)
If set it allows to skip the base part in request calls.

.. versionadded:: 3.8

:param aiohttp.BaseConnector connector: BaseConnector
sub-class instance to support connection pooling.

Expand Down
36 changes: 36 additions & 0 deletions tests/test_client_session.py
Expand Up @@ -816,3 +816,39 @@ async def test_requote_redirect_url_default_disable() -> None:
session = ClientSession(requote_redirect_url=False)
assert not session.requote_redirect_url
await session.close()


@pytest.mark.parametrize(
("base_url", "url", "expected_url"),
[
pytest.param(
None,
"http://example.com/test",
URL("http://example.com/test"),
id="base_url=None url='http://example.com/test'",
),
pytest.param(
None,
URL("http://example.com/test"),
URL("http://example.com/test"),
id="base_url=None url=URL('http://example.com/test')",
),
pytest.param(
"http://example.com",
"/test",
URL("http://example.com/test"),
id="base_url='http://example.com' url='/test'",
),
pytest.param(
URL("http://example.com"),
"/test",
URL("http://example.com/test"),
id="base_url=URL('http://example.com') url='/test'",
),
],
)
async def test_build_url_returns_expected_url(
create_session, base_url, url, expected_url
) -> None:
session = await create_session(base_url)
assert session._build_url(url) == expected_url

0 comments on commit 527b1b9

Please sign in to comment.