Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions packages/abstractions/kiota_abstractions/multipart_body.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
raise ValueError("Part name cannot be null")
if not content_type:
raise ValueError("Content type cannot be null")
if not part_value:
if part_value is None:
raise ValueError("Part value cannot be null")
value: tuple[str, Any, Optional[str]] = (content_type, part_value, filename)
self.parts[self._normalize_part_name(part_name)] = value
Expand Down Expand Up @@ -92,7 +92,7 @@
"""
raise NotImplementedError()

def serialize(self, writer: SerializationWriter) -> None:

Check failure on line 95 in packages/abstractions/kiota_abstractions/multipart_body.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 16 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=microsoft_kiota-abstractions-python&issues=AaGYPPCW7JDpWu2q_jsu&open=AaGYPPCW7JDpWu2q_jsu&pullRequest=841
"""Writes the objects properties to the current writer.

Args:
Expand All @@ -100,10 +100,14 @@
"""
if not writer:
raise ValueError("Serialization writer cannot be null")
if not self.request_adapter or not self.request_adapter.get_serialization_writer_factory():
raise ValueError("Request adapter or serialization writer factory cannot be null")
if not self.parts:
raise ValueError("No parts to serialize")
if any(isinstance(part[1], Parsable) for part in self.parts.values()):
if (

Check warning on line 106 in packages/abstractions/kiota_abstractions/multipart_body.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Merge this if statement with the enclosing one.

See more on https://sonarcloud.io/project/issues?id=microsoft_kiota-abstractions-python&issues=AaGYPPCV7JDpWu2q_jst&open=AaGYPPCV7JDpWu2q_jst&pullRequest=841
not self.request_adapter
or not self.request_adapter.get_serialization_writer_factory()
):
raise ValueError("Request adapter or serialization writer factory cannot be null")

first = True
for part_name, part_value in self.parts.items():
Expand All @@ -115,7 +119,7 @@
writer.write_str_value("", f"--{self.boundary}")
writer.write_str_value("Content-Type", f"{part_value[0]}")
writer.write_str_value(
"Content-Disposition", self._get_comtent_disposition(part_name, part_value)
"Content-Disposition", self._get_content_disposition(part_name, part_value)
)
self._add_new_line(writer)

Expand All @@ -139,13 +143,15 @@
def _add_new_line(self, writer: SerializationWriter) -> None:
writer.write_str_value("", "")

def _get_comtent_disposition(
def _get_content_disposition(
self, part_name: str, part_value: tuple[str, Any, Optional[str]]
) -> str:
if len(part_value) >= 3 and part_value[2] is not None:
return f'form-data; name="{part_name}"; filename="{part_value[2]}"'
return f'form-data; name="{part_name}"'

_get_comtent_disposition = _get_content_disposition

def _write_parsable(
self, writer: SerializationWriter, part_value: tuple[str, Any, Optional[str]]
) -> None:
Expand Down
59 changes: 57 additions & 2 deletions packages/abstractions/tests/test_multipart_body.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import io
from unittest.mock import Mock

import pytest

from kiota_abstractions.multipart_body import MultipartBody
from kiota_abstractions.serialization import SerializationWriter

from .conftest import MockEntity


def test_defensive():
"""Tests initialization of MultipartBody objects
Expand Down Expand Up @@ -32,7 +37,8 @@ def test_defensive():
with pytest.raises(ValueError) as excinfo:
multipart.serialize(None)
assert "Serialization writer cannot be null" in str(excinfo.value)



def test_add_or_replace_part():
"""Tests adding or replacing a part in the multipart body
"""
Expand All @@ -42,6 +48,7 @@ def test_add_or_replace_part():
multipart.add_or_replace_part("text", "text/plain", "Hello, World! 2")
assert multipart.get_part_value("text") == "Hello, World! 2"


def test_remove_part():
"""Tests removing a part from the multipart body
"""
Expand All @@ -50,4 +57,52 @@ def test_remove_part():
assert multipart.get_part_value("text") == "Hello, World!"
assert multipart.remove_part("text")
assert not multipart.get_part_value("text")
assert not multipart.remove_part("text")
assert not multipart.remove_part("text")


def test_add_or_replace_part_empty_values():
"""Tests adding empty byte and string parts to the multipart body."""
multipart = MultipartBody()
multipart.add_or_replace_part("empty_bytes", "application/octet-stream", b"")
assert multipart.get_part_value("empty_bytes") == b""

multipart.add_or_replace_part("empty_str", "text/plain", "")
assert multipart.get_part_value("empty_str") == ""


def test_content_disposition():
multipart = MultipartBody()
disp_with_file = multipart._get_content_disposition(
"doc", ("application/pdf", b"data", "doc.pdf")
)
assert disp_with_file == 'form-data; name="doc"; filename="doc.pdf"'

disp_without_file = multipart._get_content_disposition("text", ("text/plain", "hello", None))
assert disp_without_file == 'form-data; name="text"'

# Verify backward-compatible alias
assert multipart._get_comtent_disposition(
"doc", ("application/pdf", b"data", "doc.pdf")
) == disp_with_file


def test_serialize_without_request_adapter():
multipart = MultipartBody()
multipart.add_or_replace_part("text", "text/plain", "Hello")
multipart.add_or_replace_part("binary", "application/octet-stream", b"123")
multipart.add_or_replace_part("stream", "application/octet-stream", io.BytesIO(b"456"))

mock_writer = Mock(spec=SerializationWriter)
multipart.serialize(mock_writer)
assert mock_writer.write_str_value.called
assert mock_writer.write_bytes_value.called


def test_serialize_parsable_requires_request_adapter():
multipart = MultipartBody()
multipart.add_or_replace_part("entity", "application/json", MockEntity())

mock_writer = Mock(spec=SerializationWriter)
with pytest.raises(ValueError) as excinfo:
multipart.serialize(mock_writer)
assert "Request adapter or serialization writer factory cannot be null" in str(excinfo.value)
Loading