diff --git a/src/openai/_qs.py b/src/openai/_qs.py index 4127c19c62..8dc8b6797e 100644 --- a/src/openai/_qs.py +++ b/src/openai/_qs.py @@ -85,10 +85,13 @@ def _stringify_item( if isinstance(value, (list, tuple)): array_format = opts.array_format if array_format == "comma": + values = [item for item in value if item is not None] + if not values: + return [] return [ ( key, - ",".join(self._primitive_value_to_str(item) for item in value if item is not None), + ",".join(self._primitive_value_to_str(item) for item in values), ), ] elif array_format == "repeat": @@ -112,10 +115,9 @@ def _stringify_item( f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}" ) - serialised = self._primitive_value_to_str(value) - if not serialised: + if value is None: return [] - return [(key, serialised)] + return [(key, self._primitive_value_to_str(value))] def _primitive_value_to_str(self, value: PrimitiveData) -> str: # copied from httpx diff --git a/tests/test_qs.py b/tests/test_qs.py index 697b8a95ec..722c98c898 100644 --- a/tests/test_qs.py +++ b/tests/test_qs.py @@ -5,6 +5,7 @@ import pytest from openai._qs import Querystring, stringify +from openai._types import ArrayFormat def test_empty() -> None: @@ -52,6 +53,17 @@ def test_array_comma(method: str) -> None: assert unquote(serialise({"in": ["foo", "bar"]})) == "in=foo,bar" assert unquote(serialise({"a": {"b": [True, False]}})) == "a[b]=true,false" assert unquote(serialise({"a": {"b": [True, False, None, True]}})) == "a[b]=true,false,true" + assert unquote(serialise({"filter": []})) == "" + assert unquote(serialise({"filter": [None]})) == "" + assert unquote(serialise({"filter": [""]})) == "filter=" + assert unquote(serialise({"filter": [None, "active"]})) == "filter=active" + + +@pytest.mark.parametrize("array_format", ["comma", "repeat", "indices", "brackets"]) +def test_empty_array_omits_parameter(array_format: ArrayFormat) -> None: + # Non-empty output remains format-specific; only the empty outcome is shared. + assert stringify({"filter": []}, array_format=array_format) == "" + assert stringify({"filter": [None]}, array_format=array_format) == "" def test_array_repeat() -> None: diff --git a/tests/test_qs_empty_values.py b/tests/test_qs_empty_values.py new file mode 100644 index 0000000000..658b74cc7c --- /dev/null +++ b/tests/test_qs_empty_values.py @@ -0,0 +1,19 @@ +from urllib.parse import unquote + +from openai._qs import stringify + + +def test_empty_string_scalar_is_preserved() -> None: + assert stringify({"filter": ""}) == "filter=" + + +def test_none_scalar_is_still_omitted() -> None: + assert stringify({"filter": None}) == "" + + +def test_nested_empty_string_is_preserved() -> None: + assert unquote(stringify({"filter": {"name": ""}})) == "filter[name]=" + + +def test_empty_string_in_repeat_array_is_preserved() -> None: + assert stringify({"filter": ["", "active"]}) == "filter=&filter=active"