Skip to content

Commit

Permalink
Allow URLs with paths that end with / as base_url in ClientSession (a…
Browse files Browse the repository at this point in the history
…io-libs#9530)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Sam Bull <[email protected]>
Co-authored-by: J. Nick Koston <[email protected]>
  • Loading branch information
4 people authored Oct 28, 2024
1 parent 06b2398 commit f3a3f60
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 7 deletions.
2 changes: 2 additions & 0 deletions CHANGES/9530.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Updated :py:class:`~aiohttp.ClientSession` to support paths in ``base_url`` parameter.
``base_url`` paths must end with a ``/`` -- by :user:`Cycloctane`.
8 changes: 4 additions & 4 deletions aiohttp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,9 @@ def __init__(
else:
self._base_url = URL(base_url)
self._base_url_origin = self._base_url.origin()
assert (
self._base_url_origin == self._base_url
), "Only absolute URLs without path part are supported"
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 '/'")

loop = asyncio.get_running_loop()

Expand Down Expand Up @@ -415,7 +415,7 @@ def _build_url(self, str_or_url: StrOrURL) -> URL:
if self._base_url is None:
return url
else:
assert not url.absolute and url.path.startswith("/")
assert not url.absolute
return self._base_url.join(url)

async def _request(
Expand Down
17 changes: 14 additions & 3 deletions docs/client_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,20 @@ The client session supports the context manager protocol for self closing.


:param base_url: Base part of the URL (optional)
If set, it allows to skip the base part (https://docs.aiohttp.org) in
request calls. It must not include a path (as in
https://docs.aiohttp.org/en/stable).
If set, allows to join a base part to relative URLs in request calls.
If the URL has a path it must have a trailing ``/`` (as in
https://docs.aiohttp.org/en/stable/).

Note that URL joining follows :rfc:`3986`. This means, in the most
common case the request URLs should have no leading slash, e.g.::

session = ClientSession(base_url="http://example.com/foo/")

await session.request("GET", "bar")
# request for http://example.com/foo/bar

await session.request("GET", "/bar")
# request for http://example.com/bar

.. versionadded:: 3.8

Expand Down
23 changes: 23 additions & 0 deletions tests/test_client_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,24 @@ async def test_requote_redirect_url_default_disable() -> None:
URL("http://example.com/test"),
id="base_url=URL('http://example.com') url='/test'",
),
pytest.param(
URL("http://example.com/test1/"),
"test2",
URL("http://example.com/test1/test2"),
id="base_url=URL('http://example.com/test1/') url='test2'",
),
pytest.param(
URL("http://example.com/test1/"),
"/test2",
URL("http://example.com/test2"),
id="base_url=URL('http://example.com/test1/') url='/test2'",
),
pytest.param(
URL("http://example.com/test1/"),
"test2?q=foo#bar",
URL("http://example.com/test1/test2?q=foo#bar"),
id="base_url=URL('http://example.com/test1/') url='test2?q=foo#bar'",
),
],
)
async def test_build_url_returns_expected_url(
Expand All @@ -1134,6 +1152,11 @@ async def test_build_url_returns_expected_url(
assert session._build_url(url) == expected_url


async def test_base_url_without_trailing_slash() -> None:
with pytest.raises(ValueError, match="base_url must have a trailing '/'"):
ClientSession(base_url="http://example.com/test")


async def test_instantiation_with_invalid_timeout_value(
loop: asyncio.AbstractEventLoop,
) -> None:
Expand Down

0 comments on commit f3a3f60

Please sign in to comment.