|
24 | 24 |
|
25 | 25 | from __future__ import annotations |
26 | 26 |
|
27 | | -try: |
28 | | - from urllib.parse import urljoin |
29 | | -except ImportError: # pragma: no cover |
30 | | - from urlparse import urljoin # pyright: ignore[reportMissingImports] |
31 | | - |
32 | 27 | from typing import Any |
33 | 28 |
|
34 | 29 | import httpx |
@@ -167,6 +162,13 @@ def base_url(self) -> str | None: |
167 | 162 | def base_url(self, value: str | None) -> None: |
168 | 163 | self._base_url = value |
169 | 164 |
|
| 165 | + def _build_url(self, path: str) -> str: |
| 166 | + """Join the base_url and path, and handle trailing slashes.""" |
| 167 | + if not self.base_url or path.startswith(("http://", "https://")): |
| 168 | + return path |
| 169 | + |
| 170 | + return f"{self.base_url.rstrip('/')}/{path.lstrip('/')}" |
| 171 | + |
170 | 172 | @property |
171 | 173 | def timeout(self) -> int | None: |
172 | 174 | """ |
@@ -334,7 +336,7 @@ def raw_get(self, path: str, **kwargs: Any) -> Response: # noqa: ANN401 |
334 | 336 | raise AttributeError(msg) |
335 | 337 | try: |
336 | 338 | return self._s.get( |
337 | | - urljoin(self.base_url, path), |
| 339 | + self._build_url(path), |
338 | 340 | params=kwargs, |
339 | 341 | headers=self.headers, |
340 | 342 | timeout=self.timeout, |
@@ -364,7 +366,7 @@ def raw_post(self, path: str, data: dict | str | MultipartEncoder, **kwargs: Any |
364 | 366 | raise AttributeError(msg) |
365 | 367 | try: |
366 | 368 | return self._s.post( |
367 | | - urljoin(self.base_url, path), |
| 369 | + self._build_url(path), |
368 | 370 | params=kwargs, |
369 | 371 | data=data, |
370 | 372 | headers=self.headers, |
@@ -396,7 +398,7 @@ def raw_put(self, path: str, data: dict | str | MultipartEncoder, **kwargs: Any) |
396 | 398 |
|
397 | 399 | try: |
398 | 400 | return self._s.put( |
399 | | - urljoin(self.base_url, path), |
| 401 | + self._build_url(path), |
400 | 402 | params=kwargs, |
401 | 403 | data=data, |
402 | 404 | headers=self.headers, |
@@ -428,7 +430,7 @@ def raw_delete(self, path: str, data: dict | None = None, **kwargs: Any) -> Resp |
428 | 430 |
|
429 | 431 | try: |
430 | 432 | return self._s.delete( |
431 | | - urljoin(self.base_url, path), |
| 433 | + self._build_url(path), |
432 | 434 | params=kwargs, |
433 | 435 | data=data or {}, |
434 | 436 | headers=self.headers, |
@@ -458,7 +460,7 @@ async def a_raw_get(self, path: str, **kwargs: Any) -> AsyncResponse: # noqa: A |
458 | 460 |
|
459 | 461 | try: |
460 | 462 | return await self.async_s.get( |
461 | | - urljoin(self.base_url, path), |
| 463 | + self._build_url(path), |
462 | 464 | params=self._filter_query_params(kwargs), |
463 | 465 | headers=self.headers, |
464 | 466 | timeout=self.timeout, |
@@ -493,7 +495,7 @@ async def a_raw_post( |
493 | 495 | try: |
494 | 496 | return await self.async_s.request( |
495 | 497 | method="POST", |
496 | | - url=urljoin(self.base_url, path), |
| 498 | + url=self._build_url(path), |
497 | 499 | params=self._filter_query_params(kwargs), |
498 | 500 | **self._prepare_httpx_request_content(data), |
499 | 501 | headers=self.headers, |
@@ -528,7 +530,7 @@ async def a_raw_put( |
528 | 530 |
|
529 | 531 | try: |
530 | 532 | return await self.async_s.put( |
531 | | - urljoin(self.base_url, path), |
| 533 | + self._build_url(path), |
532 | 534 | params=self._filter_query_params(kwargs), |
533 | 535 | **self._prepare_httpx_request_content(data), |
534 | 536 | headers=self.headers, |
@@ -564,7 +566,7 @@ async def a_raw_delete( |
564 | 566 | try: |
565 | 567 | return await self.async_s.request( |
566 | 568 | method="DELETE", |
567 | | - url=urljoin(self.base_url, path), |
| 569 | + url=self._build_url(path), |
568 | 570 | **self._prepare_httpx_request_content(data or {}), |
569 | 571 | params=self._filter_query_params(kwargs), |
570 | 572 | headers=self.headers, |
|
0 commit comments