Skip to content

Commit 88c64f5

Browse files
fix: Concatenate paths in _get_base_url_with_base_path rather than use urljoin (#2448)
1 parent 197a140 commit 88c64f5

6 files changed

Lines changed: 32 additions & 11 deletions

File tree

cognite/client/_api/files.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from io import BufferedReader
99
from pathlib import Path
1010
from typing import Any, BinaryIO, Literal, TextIO, cast, overload
11-
from urllib.parse import urljoin, urlparse
11+
from urllib.parse import urlparse
1212

1313
from cognite.client._api_client import APIClient
1414
from cognite.client._constants import _RUNNING_IN_BROWSER, DEFAULT_LIMIT_READ
@@ -28,7 +28,7 @@
2828
)
2929
from cognite.client.data_classes.data_modeling import NodeId
3030
from cognite.client.exceptions import CogniteAPIError, CogniteAuthorizationError, CogniteFileUploadError
31-
from cognite.client.utils._auxiliary import find_duplicates
31+
from cognite.client.utils._auxiliary import append_url_path, find_duplicates
3232
from cognite.client.utils._concurrency import execute_tasks
3333
from cognite.client.utils._identifier import Identifier, IdentifierSequence
3434
from cognite.client.utils._validation import process_asset_subtree_ids, process_data_set_ids
@@ -646,7 +646,7 @@ def _upload_bytes(self, content: bytes | TextIO | BinaryIO, returned_file_metada
646646
if urlparse(upload_url).netloc:
647647
full_upload_url = upload_url
648648
else:
649-
full_upload_url = urljoin(self._config.base_url, upload_url)
649+
full_upload_url = append_url_path(self._config.base_url, upload_url)
650650
file_metadata = FileMetadata._load(returned_file_metadata)
651651
upload_response = self._http_client_with_retry.request(
652652
"PUT",

cognite/client/_api/org_apis/principals.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
import warnings
44
from collections.abc import Sequence
55
from typing import overload
6-
from urllib.parse import urljoin
76

87
from cognite.client._constants import DEFAULT_LIMIT_READ
98
from cognite.client._org_client import OrgAPIClient
109
from cognite.client.data_classes.principals import Principal, PrincipalList
10+
from cognite.client.utils._auxiliary import append_url_path
1111
from cognite.client.utils._identifier import PrincipalIdentifierSequence
1212
from cognite.client.utils.useful_types import SequenceNotStr
1313

@@ -33,7 +33,7 @@ def me(self) -> Principal:
3333
if self._api_version:
3434
path = f"/api/{self._api_version}{path}"
3535

36-
full_url = urljoin(self._auth_url, path)
36+
full_url = append_url_path(self._auth_url, path)
3737
headers = self._configure_headers(
3838
"application/json",
3939
additional_headers=self._config.headers.copy(),

cognite/client/_api_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
cast,
2020
overload,
2121
)
22-
from urllib.parse import urljoin
2322

2423
import requests.utils
2524
from requests import Response
@@ -45,6 +44,7 @@
4544
from cognite.client.exceptions import CogniteAPIError, CogniteNotFoundError, CogniteProjectAccessError
4645
from cognite.client.utils import _json
4746
from cognite.client.utils._auxiliary import (
47+
append_url_path,
4848
get_current_sdk_version,
4949
get_user_agent,
5050
interpolate_and_url_encode,
@@ -301,7 +301,7 @@ def _get_base_url_with_base_path(self) -> str:
301301
base_path = ""
302302
if self._api_version:
303303
base_path = f"/api/{self._api_version}/projects/{self._config.project}"
304-
return urljoin(self._config.base_url, base_path)
304+
return append_url_path(self._config.base_url, base_path)
305305

306306
def _is_retryable(self, method: str, path: str) -> bool:
307307
valid_methods = ["GET", "POST", "PUT", "DELETE", "PATCH"]

cognite/client/_org_client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
from abc import ABC
44
from functools import cached_property
5-
from urllib.parse import urljoin
65

76
from cognite.client._api_client import APIClient
87
from cognite.client.exceptions import CogniteAPIError
8+
from cognite.client.utils._auxiliary import append_url_path
99

1010

1111
class OrgAPIClient(APIClient, ABC):
@@ -18,7 +18,7 @@ def _get_base_url_with_base_path(self) -> str:
1818
base_path = f"/api/{self._api_version}/orgs/{self._organization}"
1919
# The OrganizationAPi uses the auth_url as the base for these endpoints instead of the
2020
# base_url like the rest of the SDK.
21-
return urljoin(self._auth_url, base_path)
21+
return append_url_path(self._auth_url, base_path)
2222

2323
@cached_property
2424
def _organization(self) -> str:
@@ -28,7 +28,7 @@ def _organization(self) -> str:
2828
api_subversion=self._api_subversion,
2929
)
3030
# This is an internal endpoint, not part of the public API
31-
full_url = urljoin(self._config.base_url, f"/api/v1/projects/{self._config.project}")
31+
full_url = append_url_path(self._config.base_url, f"/api/v1/projects/{self._config.project}")
3232
response = self._http_client_with_retry.request(method="GET", url=full_url, headers=headers)
3333
if response.status_code != 200:
3434
raise CogniteAPIError(

cognite/client/utils/_auxiliary.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
TypeVar,
1515
overload,
1616
)
17-
from urllib.parse import quote
17+
from urllib.parse import quote, urlparse, urlunparse
1818

1919
from cognite.client.utils import _json
2020
from cognite.client.utils._importing import local_import
@@ -277,3 +277,9 @@ def flatten_dict(d: dict[str, Any], parent_keys: tuple[str, ...], sep: str = "."
277277
else:
278278
items.append((sep.join((*parent_keys, key)), value))
279279
return dict(items)
280+
281+
282+
def append_url_path(base_url: str, path: str) -> str:
283+
parsed = urlparse(base_url)
284+
new_path = f"{parsed.path.rstrip('/')}/{path.lstrip('/')}"
285+
return urlunparse(parsed._replace(path=new_path)).rstrip("/")

tests/tests_unit/test_api_client.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,21 @@ def test__do_request_raises_unmodified_exception(self, api_client_with_token):
199199
exc_msg = exc_info.value.args[0]
200200
assert "contain NaN(s) or +/- Inf!" not in exc_msg
201201

202+
def test_client_with_base_url_including_path_segments(self, cognite_client):
203+
client = APIClient(
204+
ClientConfig(
205+
client_name="any",
206+
project="test-project",
207+
base_url="https://bla.bla.com/extra",
208+
max_workers=1,
209+
headers={"x-cdp-app": "python-sdk-integration-tests"},
210+
credentials=Token(lambda: "abc"),
211+
),
212+
api_version="v1",
213+
cognite_client=cognite_client,
214+
)
215+
assert client._get_base_url_with_base_path() == "https://bla.bla.com/extra/api/v1/projects/test-project"
216+
202217

203218
class SomeUpdate(CogniteUpdate):
204219
@property

0 commit comments

Comments
 (0)