cookies: CookieTypes = None,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
max_redirects: int = DEFAULT_MAX_REDIRECTS,
- base_url: URLTypes = None,
+ base_url: URLTypes = "",
trust_env: bool = True,
):
- if base_url is None:
- self.base_url = URL("")
- else:
- self.base_url = URL(base_url)
+ self._base_url = self._enforce_trailing_slash(URL(base_url))
self.auth = auth
self._params = QueryParams(params)
def trust_env(self) -> bool:
return self._trust_env
+ def _enforce_trailing_slash(self, url: URL) -> URL:
+ if url.path.endswith("/"):
+ return url
+ return url.copy_with(path=url.path + "/")
+
def _get_proxy_map(
self, proxies: typing.Optional[ProxiesTypes], allow_env_proxies: bool,
) -> typing.Dict[str, typing.Optional[Proxy]]:
proxy = Proxy(url=proxies) if isinstance(proxies, (str, URL)) else proxies
return {"all": proxy}
+ @property
+ def base_url(self) -> URL:
+ """
+ Base URL to use when sending requests with relative URLs.
+ """
+ return self._base_url
+
+ @base_url.setter
+ def base_url(self, url: URLTypes) -> None:
+ self._base_url = self._enforce_trailing_slash(URL(url))
+
@property
def headers(self) -> Headers:
"""
Merge a URL argument together with any 'base_url' on the client,
to create the URL used for the outgoing request.
"""
- return self.base_url.join(url)
+ merge_url = URL(url)
+ if merge_url.is_relative_url:
+ # We always ensure the base_url paths include the trailing '/',
+ # and always strip any leading '/' from the merge URL.
+ merge_url = merge_url.copy_with(path=merge_url.path.lstrip("/"))
+ return self.base_url.join(merge_url)
+ return merge_url
def _merge_cookies(
self, cookies: CookieTypes = None
limits: Limits = DEFAULT_LIMITS,
pool_limits: Limits = None,
max_redirects: int = DEFAULT_MAX_REDIRECTS,
- base_url: URLTypes = None,
+ base_url: URLTypes = "",
transport: httpcore.SyncHTTPTransport = None,
app: typing.Callable = None,
trust_env: bool = True,
limits: Limits = DEFAULT_LIMITS,
pool_limits: Limits = None,
max_redirects: int = DEFAULT_MAX_REDIRECTS,
- base_url: URLTypes = None,
+ base_url: URLTypes = "",
transport: httpcore.AsyncHTTPTransport = None,
app: typing.Callable = None,
trust_env: bool = True,
assert response.url == base_url
-def test_merge_url():
+def test_merge_absolute_url():
client = httpx.Client(base_url="https://www.example.com/")
- request = client.build_request("GET", "http://www.example.com")
- assert request.url.scheme == "http"
+ request = client.build_request("GET", "http://www.example.com/")
+ assert request.url == httpx.URL("http://www.example.com/")
assert not request.url.is_ssl
+def test_merge_relative_url():
+ client = httpx.Client(base_url="https://www.example.com/")
+ request = client.build_request("GET", "/testing/123")
+ assert request.url == httpx.URL("https://www.example.com/testing/123")
+
+
+def test_merge_relative_url_with_path():
+ client = httpx.Client(base_url="https://www.example.com/some/path")
+ request = client.build_request("GET", "/testing/123")
+ assert request.url == httpx.URL("https://www.example.com/some/path/testing/123")
+
+
+def test_merge_relative_url_with_dotted_path():
+ client = httpx.Client(base_url="https://www.example.com/some/path")
+ request = client.build_request("GET", "../testing/123")
+ assert request.url == httpx.URL("https://www.example.com/some/testing/123")
+
+
def test_pool_limits_deprecated():
limits = httpx.Limits()
-from httpx import AsyncClient, Cookies, Headers
+from httpx import URL, AsyncClient, Cookies, Headers
+
+
+def test_client_base_url():
+ client = AsyncClient()
+ client.base_url = "https://www.example.org/" # type: ignore
+ assert isinstance(client.base_url, URL)
+ assert client.base_url == URL("https://www.example.org/")
+
+
+def test_client_base_url_without_trailing_slash():
+ client = AsyncClient()
+ client.base_url = "https://www.example.org/path" # type: ignore
+ assert isinstance(client.base_url, URL)
+ assert client.base_url == URL("https://www.example.org/path/")
+
+
+def test_client_base_url_with_trailing_slash():
+ client = AsyncClient()
+ client.base_url = "https://www.example.org/path/" # type: ignore
+ assert isinstance(client.base_url, URL)
+ assert client.base_url == URL("https://www.example.org/path/")
def test_client_headers():