diff --git a/cli/openbb_cli/codegen/fetcher_gen.py b/cli/openbb_cli/codegen/fetcher_gen.py index 133df592d7e..c1b81cde63b 100644 --- a/cli/openbb_cli/codegen/fetcher_gen.py +++ b/cli/openbb_cli/codegen/fetcher_gen.py @@ -303,9 +303,11 @@ def _envelope_inner_schema( array_keys = [ k for k, v in props.items() if isinstance(v, dict) and v.get("type") == "array" ] + # Only resolve as an envelope if there is exactly one list of all dicts, + # or if the list is the only element present. if len(array_keys) == 1: items = props[array_keys[0]].get("items") - if isinstance(items, dict): + if isinstance(items, dict) and not _is_scalar_schema(items): return cast(dict[str, Any], items) return None diff --git a/cli/openbb_cli/codegen/post_gen.py b/cli/openbb_cli/codegen/post_gen.py index fb4a1b8e6ea..2b867571747 100644 --- a/cli/openbb_cli/codegen/post_gen.py +++ b/cli/openbb_cli/codegen/post_gen.py @@ -261,9 +261,11 @@ def _envelope_inner_schema( array_keys = [ k for k, v in props.items() if isinstance(v, dict) and v.get("type") == "array" ] + # Only resolve as an envelope if there is exactly one list of all dicts, + # or if the list is the only element present. if len(array_keys) == 1: items = props[array_keys[0]].get("items") - if isinstance(items, dict): + if isinstance(items, dict) and not _is_scalar_schema(items): return cast(dict[str, Any], items) return None diff --git a/cli/openbb_cli/dispatchers/_unpack.py b/cli/openbb_cli/dispatchers/_unpack.py index c7750615072..400bf6e6b2b 100644 --- a/cli/openbb_cli/dispatchers/_unpack.py +++ b/cli/openbb_cli/dispatchers/_unpack.py @@ -35,11 +35,13 @@ def unpack_response( if len(list_keys) == 1: key = list_keys[0] items = payload[key] - rows = [r for r in items if isinstance(r, dict)] - if not rows and items: - rows = [{"value": r} for r in items] metadata = {k: v for k, v in payload.items() if k != key} - return rows, metadata + # Only return this as an envelope if all items are dicts or the list is + # the only thing in the payload. + if all(isinstance(v, dict) for v in items): + return items, metadata + if len(payload) == 1: + return [{"value": r} for r in items], metadata if len(payload) == 1: only_value = next(iter(payload.values())) if isinstance(only_value, (list, dict)): diff --git a/cli/tests/test_codegen_post_gen.py b/cli/tests/test_codegen_post_gen.py index bf5b06f66a3..86662ed7cb7 100644 --- a/cli/tests/test_codegen_post_gen.py +++ b/cli/tests/test_codegen_post_gen.py @@ -294,13 +294,14 @@ def test_data_schema_descends_into_single_array_among_scalar_siblings(): }, } out = pg._data_schema({"response_schema": response}) - # Descended through ``items`` (single-property), then through ``tags`` - # (single array among scalar siblings), then wrapped the scalar item - # schema as a ``{value: str}`` row. + # Descended through ``items`` (single-property), then kept the + # inner object whole, as done in ``unpack_response``. assert out == { - "type": "object", - "properties": {"value": {"type": "string"}}, - "required": ["value"], + "properties": { + "id": {"type": "string"}, + "name": {"type": "string"}, + "tags": {"type": "array", "items": {"type": "string"}}, + } } diff --git a/cli/tests/test_dispatchers_unpack.py b/cli/tests/test_dispatchers_unpack.py index f7723a13b5e..f708bafc3a7 100644 --- a/cli/tests/test_dispatchers_unpack.py +++ b/cli/tests/test_dispatchers_unpack.py @@ -86,6 +86,13 @@ def test_unpack_response_does_not_descend_into_scalar_single_key_value(): assert metadata == {} +def test_unpack_response_keeps_record_with_scalar_list_field_whole(): + """A record whose single list holds scalars is a row, not an envelope.""" + rows, metadata = unpack_response({"id": "x", "tags": ["a", "b"]}) + assert rows == [{"id": "x", "tags": ["a", "b"]}] + assert metadata == {} + + def test_unpack_response_returns_multi_property_dict_as_single_row(): rows, metadata = unpack_response({"a": 1, "b": 2}) assert rows == [{"a": 1, "b": 2}]