Skip to content

Commit 6904cd7

Browse files
authored
fix(files): raise CogniteFileUploadError instead of leaking CogniteHTTPStatusError on blob PUT failures (#2646)
1 parent 630a343 commit 6904cd7

3 files changed

Lines changed: 81 additions & 23 deletions

File tree

cognite/client/_api/files.py

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@
3636
TimestampRange,
3737
)
3838
from cognite.client.data_classes.data_modeling import NodeId
39-
from cognite.client.exceptions import CogniteAPIError, CogniteAuthorizationError, CogniteFileUploadError
39+
from cognite.client.exceptions import (
40+
CogniteAPIError,
41+
CogniteAuthorizationError,
42+
CogniteFileUploadError,
43+
CogniteHTTPStatusError,
44+
)
4045
from cognite.client.utils._auxiliary import append_url_path, find_duplicates, unpack_items
4146
from cognite.client.utils._concurrency import AsyncSDKTask, execute_async_tasks
4247
from cognite.client.utils._identifier import Identifier, IdentifierSequence
@@ -783,16 +788,17 @@ async def _upload_bytes(
783788
if file_size is not None:
784789
headers["Content-Length"] = str(file_size)
785790

786-
upload_response = await self._request(
787-
"PUT",
788-
full_url=full_upload_url,
789-
content=file_content,
790-
headers=headers,
791-
timeout=self._config.file_transfer_timeout,
792-
semaphore=self._get_semaphore("upload"),
793-
)
794-
if not upload_response.is_success:
795-
raise CogniteFileUploadError(message=upload_response.text, code=upload_response.status_code)
791+
try:
792+
await self._request(
793+
"PUT",
794+
full_url=full_upload_url,
795+
content=file_content,
796+
headers=headers,
797+
timeout=self._config.file_transfer_timeout,
798+
semaphore=self._get_semaphore("upload"),
799+
)
800+
except CogniteHTTPStatusError as err:
801+
raise CogniteFileUploadError(message=err.response.text, code=err.status_code) from None
796802
return file_metadata
797803

798804
async def upload_bytes(
@@ -1074,16 +1080,17 @@ async def _upload_multipart_part(
10741080
if file_size is not None:
10751081
headers["Content-Length"] = str(file_size)
10761082

1077-
upload_response = await self._request(
1078-
"PUT",
1079-
full_url=upload_url,
1080-
content=file_content,
1081-
headers=headers,
1082-
timeout=self._config.file_transfer_timeout,
1083-
semaphore=self._get_semaphore("upload"),
1084-
)
1085-
if not upload_response.is_success:
1086-
raise CogniteFileUploadError(message=upload_response.text, code=upload_response.status_code)
1083+
try:
1084+
await self._request(
1085+
"PUT",
1086+
full_url=upload_url,
1087+
content=file_content,
1088+
headers=headers,
1089+
timeout=self._config.file_transfer_timeout,
1090+
semaphore=self._get_semaphore("upload"),
1091+
)
1092+
except CogniteHTTPStatusError as err:
1093+
raise CogniteFileUploadError(message=err.response.text, code=err.status_code) from None
10871094

10881095
async def _complete_multipart_upload(self, session: FileMultipartUploadSession) -> None:
10891096
"""Complete a multipart upload. Once this returns the file can be downloaded.

cognite/client/_sync_api/files.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""
22
===============================================================================
3-
d8fa16955b8c30a8af32a19dcb33c4a8
3+
44f1fb8ed3bbf859ea01358b27381995
44
This file is auto-generated from the Async API modules, - do not edit manually!
55
===============================================================================
66
"""

tests/tests_unit/test_api/test_files.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
FileMetadataWrite,
3030
)
3131
from cognite.client.data_classes.labels import Label, LabelFilter
32-
from cognite.client.exceptions import CogniteAPIError, CogniteAuthorizationError
32+
from cognite.client.exceptions import CogniteAPIError, CogniteAuthorizationError, CogniteFileUploadError
3333
from tests.tests_unit.conftest import DefaultResourceGenerator
3434
from tests.utils import get_or_raise, get_url, jsgz_load
3535

@@ -836,6 +836,57 @@ def test_upload_path_does_not_exist(self, cognite_client: CogniteClient) -> None
836836
with pytest.raises(FileNotFoundError):
837837
cognite_client.files.upload(path=Path("/no/such/path"))
838838

839+
def test_upload_bytes_400_raises_file_upload_error(
840+
self,
841+
cognite_client: CogniteClient,
842+
example_file: dict[str, Any],
843+
async_client: AsyncCogniteClient,
844+
httpx_mock: HTTPXMock,
845+
) -> None:
846+
"""Bug in 8.0.0 to 8.6.0: a 400 from the blob storage PUT raised CogniteHTTPStatusError instead of CogniteFileUploadError."""
847+
httpx_mock.add_response(
848+
method="POST",
849+
url=get_url(async_client.files) + "/files?overwrite=false",
850+
status_code=200,
851+
json=example_file,
852+
)
853+
httpx_mock.add_response(method="PUT", url="https://upload.here", status_code=400, text="Bad Request")
854+
855+
with pytest.raises(CogniteFileUploadError) as exc_info:
856+
cognite_client.files.upload_bytes(content=b"content", name="bla")
857+
858+
assert exc_info.value.code == 400
859+
860+
def test_upload_content_part_400_raises_file_upload_error(
861+
self,
862+
cognite_client: CogniteClient,
863+
example_file: dict[str, Any],
864+
async_client: AsyncCogniteClient,
865+
httpx_mock: HTTPXMock,
866+
tmp_path: Path,
867+
) -> None:
868+
"""Bug in 8.0.0 to 8.6.0: a 400 from the blob storage PUT during multipart upload raised CogniteHTTPStatusError
869+
instead of CogniteFileUploadError.
870+
"""
871+
multipart_response = {
872+
"items": [{**example_file, "uploadUrls": ["https://upload.here/part0"], "uploadId": "test-id"}]
873+
}
874+
httpx_mock.add_response(
875+
method="POST",
876+
url=re.compile(re.escape(get_url(async_client.files) + "/files/multiuploadlink") + r"\?.*"),
877+
status_code=200,
878+
json=multipart_response,
879+
)
880+
httpx_mock.add_response(method="PUT", url="https://upload.here/part0", status_code=400, text="Bad Request")
881+
882+
test_file = tmp_path / "test.bin"
883+
test_file.write_bytes(b"x")
884+
885+
with pytest.raises(CogniteFileUploadError) as exc_info:
886+
cognite_client.files.upload_content(path=test_file, instance_id=NodeId("test-space", "test-0001"))
887+
888+
assert exc_info.value.code == 400
889+
839890
@pytest.mark.parametrize(
840891
"file_size, expected_parts",
841892
[

0 commit comments

Comments
 (0)