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
2 changes: 1 addition & 1 deletion packages/abstractions/kiota_abstractions/date_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from sys import version_info as sys_version_info
import re
from datetime import datetime, time, timedelta
from sys import version_info as sys_version_info

_ISO8601_DURATION_PATTERN = re.compile(
"^P" # Duration P indicator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,14 @@ def _get_sanitized_value(self, value: Any) -> Any:
sanitized_value = temp_date_with_tz_info.isoformat("T")
elif any([isinstance(value, UUID), isinstance(value, date), isinstance(value, time)]):
sanitized_value = str(value)
elif isinstance(value, dict):
# Map-style query parameter: drop None entries per RFC 6570 §2.3 "undefined"
# semantics, then normalise remaining values. StdUriTemplate raises ValueError
# when it encounters None inside a map, so we strip them here.
sanitized_value = {
str(k): self._get_sanitized_value(v)
for k, v in value.items() if k is not None and v is not None
}
Comment on lines +312 to +319
return sanitized_value

def _decode_uri_string(self, uri: Optional[str]) -> str:
Expand Down
95 changes: 93 additions & 2 deletions packages/abstractions/tests/test_request_information.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,97 @@ def test_sets_time_only_values_in_path_parameters():
request_info = RequestInformation(Method.GET, "https://example.com/daysFrom/{startDate}")
request_info.path_parameters["startDate"] = datetime(year=2020, month=8, day=1, hour=0, minute=20, second=0, microsecond=0).time()
assert request_info.url == "https://example.com/daysFrom/00%3A20%3A00"




def test_expands_map_query_parameter_as_individual_key_value_pairs():
"""Tests that a dict query parameter expands as individual key=value pairs via RFC 6570."""
request_info = RequestInformation(Method.GET, "http://localhost/articles{?query*}")
request_info.query_parameters["query"] = {
"filter": "equals(published,true)",
"sort": "-createdAt",
}
url = request_info.url
assert "?" in url
assert "filter=equals%28published%2Ctrue%29" in url
assert "sort=-createdAt" in url


def test_map_query_parameter_none_values_are_omitted():
"""Tests that None values within a map query parameter are silently dropped."""
request_info = RequestInformation(Method.GET, "http://localhost/articles{?query*}")
request_info.query_parameters["query"] = {
"include": "author",
"exclude": None,
}
url = request_info.url
assert "include=author" in url
assert "exclude" not in url


def test_map_query_parameter_empty_string_value_is_included():
"""Tests that an empty-string value in a map query parameter is preserved."""
request_info = RequestInformation(Method.GET, "http://localhost/articles{?query*}")
request_info.query_parameters["query"] = {"search": ""}
url = request_info.url
assert "search=" in url


def test_empty_map_query_parameter_produces_no_query_string():
"""Tests that an empty dict query parameter produces no query string."""
request_info = RequestInformation(Method.GET, "http://localhost/articles{?query*}")
request_info.query_parameters["query"] = {}
url = request_info.url
assert "?" not in url


def test_all_none_map_query_parameter_produces_no_query_string():
"""Tests that a dict with all None values produces no query string."""
request_info = RequestInformation(Method.GET, "http://localhost/articles{?query*}")
request_info.query_parameters["query"] = {"a": None, "b": None}
url = request_info.url
assert "?" not in url


def test_mix_of_map_and_scalar_query_parameters():
"""Tests that map and scalar query parameters can coexist in the same URL."""
request_info = RequestInformation(Method.GET, "http://localhost/articles{?query*,top}")
request_info.query_parameters["query"] = {"filter": "active"}
request_info.query_parameters["top"] = 5
url = request_info.url
assert "filter=active" in url
assert "top=5" in url


def test_map_query_parameter_via_set_query_string_parameters_from_raw_object():
"""Tests that a dict field on a dataclass query params object is handled correctly."""
from dataclasses import dataclass
from typing import Optional

@dataclass
class MapQueryParams:
query: Optional[dict] = None

request_info = RequestInformation(Method.GET, "http://localhost/articles{?query*}")
params = MapQueryParams(query={"filter": "equals(published,true)", "sort": "-createdAt"})
request_info.set_query_string_parameters_from_raw_object(params)
url = request_info.url
assert "filter=equals%28published%2Ctrue%29" in url
assert "sort=-createdAt" in url


def test_map_query_parameter_with_none_key_or_varied_types():
"""Tests that None keys are ignored and varied value types are sanitized."""
request_info = RequestInformation(Method.GET, "http://localhost/articles{?query*}")
request_info.query_parameters["query"] = {
"top": 10,
"active": True,
"enumVal": TestEnum.VALUE1,
"nullVal": None,
None: "ignoredNullKey",
}
url = request_info.url
assert "top=10" in url
assert "active=true" in url
assert "enumVal=value1" in url
assert "nullVal" not in url
assert "ignoredNullKey" not in url
Loading