diff --git a/src/openai/_qs.py b/src/openai/_qs.py index de8c99bc63..ace90c5c54 100644 --- a/src/openai/_qs.py +++ b/src/openai/_qs.py @@ -76,9 +76,12 @@ def _stringify_item( items: list[tuple[str, str]] = [] nested_format = opts.nested_format for subkey, subvalue in value.items(): + if nested_format not in get_args(NestedFormat): + raise NotImplementedError( + f"Unknown nested_format value: {nested_format}, choose from {', '.join(get_args(NestedFormat))}" + ) items.extend( self._stringify_item( - # TODO: error if unknown format f"{key}.{subkey}" if nested_format == "dots" else f"{key}[{subkey}]", subvalue, opts, diff --git a/src/openai/_utils/_utils.py b/src/openai/_utils/_utils.py index 90494748cc..44d3287f46 100644 --- a/src/openai/_utils/_utils.py +++ b/src/openai/_utils/_utils.py @@ -276,8 +276,8 @@ def wrapper(*args: object, **kwargs: object) -> object: else: assert len(variants) > 0 - # TODO: this error message is not deterministic - missing = list(set(variants[0]) - given_params) + # preserve declaration order for deterministic error messages + missing = [param for param in variants[0] if param not in given_params] if len(missing) > 1: msg = f"Missing required arguments: {human_join([quote(arg) for arg in missing])}" else: diff --git a/tests/test_qs.py b/tests/test_qs.py index 697b8a95ec..bd54761dde 100644 --- a/tests/test_qs.py +++ b/tests/test_qs.py @@ -73,6 +73,29 @@ def test_array_brackets(method: str) -> None: assert unquote(serialise({"a": {"b": [True, False, None, True]}})) == "a[b][]=true&a[b][]=false&a[b][]=true" +@pytest.mark.parametrize("method", ["class", "function"]) +def test_array_indices(method: str) -> None: + if method == "class": + serialise = Querystring(array_format="indices").stringify + else: + serialise = partial(stringify, array_format="indices") + + assert unquote(serialise({"in": ["foo", "bar"]})) == "in[0]=foo&in[1]=bar" + assert unquote(serialise({"a": {"b": [True, False]}})) == "a[b][0]=true&a[b][1]=false" + assert ( + unquote(serialise({"a": {"b": [True, False, None, True]}})) + == "a[b][0]=true&a[b][1]=false&a[b][3]=true" + ) + + def test_unknown_array_format() -> None: - with pytest.raises(NotImplementedError, match="Unknown array_format value: foo, choose from comma, repeat"): + with pytest.raises( + NotImplementedError, + match="Unknown array_format value: foo, choose from comma, repeat, indices, brackets", + ): stringify({"a": ["foo", "bar"]}, array_format=cast(Any, "foo")) + + +def test_unknown_nested_format() -> None: + with pytest.raises(NotImplementedError, match="Unknown nested_format value: foo, choose from dots, brackets"): + stringify({"a": {"b": "c"}}, nested_format=cast(Any, "foo")) diff --git a/tests/test_required_args.py b/tests/test_required_args.py index 5d1a5224ff..fec4598367 100644 --- a/tests/test_required_args.py +++ b/tests/test_required_args.py @@ -47,18 +47,16 @@ def foo(a: str = "", *, b: str = "", c: str = "") -> str | None: assert foo(a="a", b="b", c="c") == "a b c" - error_message = r"Missing required arguments.*" - - with pytest.raises(TypeError, match=error_message): + with pytest.raises(TypeError, match=r"Missing required arguments: 'a', 'b' or 'c'"): foo() - with pytest.raises(TypeError, match=error_message): + with pytest.raises(TypeError, match=r"Missing required arguments: 'b' or 'c'"): foo(a="a") - with pytest.raises(TypeError, match=error_message): + with pytest.raises(TypeError, match=r"Missing required arguments: 'a' or 'c'"): foo(b="b") - with pytest.raises(TypeError, match=error_message): + with pytest.raises(TypeError, match=r"Missing required arguments: 'a' or 'b'"): foo(c="c") with pytest.raises(TypeError, match=r"Missing required argument: 'a'"): @@ -78,7 +76,6 @@ def foo(*, a: str | None = None, b: str | None = None) -> str | None: assert foo(a=None) is None assert foo(b=None) is None - # TODO: this error message could probably be improved with pytest.raises( TypeError, match=r"Missing required arguments; Expected either \('a'\) or \('b'\) arguments to be given",