From fd2cb579e93f553b6f73358ff5b0bb3aa7e685e0 Mon Sep 17 00:00:00 2001 From: ezilber-akamai Date: Fri, 11 Sep 2026 14:13:16 -0400 Subject: [PATCH] Fixed bug causing deeply nested object lists to be improperly displayed in --help output --- linodecli/baked/request.py | 10 +++- .../api_request_test_foobar_post.yaml | 13 +++++ tests/unit/test_operation.py | 49 +++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/linodecli/baked/request.py b/linodecli/baked/request.py index 52b5be73d..195a96435 100644 --- a/linodecli/baked/request.py +++ b/linodecli/baked/request.py @@ -182,12 +182,20 @@ def _parse_request_model( depth=depth, ) - # Handle arrays of objects that not marked as JSON + # Handle arrays of objects that not marked as JSON. + # NOTE: We only expand an array of objects into individual child + # arguments when it is not already nested under another list + # (i.e. parent is None). The CLI can only associate one level of + # nested list objects, so a list of objects nested within another + # list must be treated as JSON instead. + # Otherwise, we would generate child arguments that can never + # be used because they always conflict with their implicit JSON parent. elif ( v.type == "array" and v.items and v.items.type == "object" and v.extensions.get("linode-cli-format") != "json" + and parent is None ): # handle lists of objects as a special case, where each property # of the object in the list is its own argument diff --git a/tests/fixtures/api_request_test_foobar_post.yaml b/tests/fixtures/api_request_test_foobar_post.yaml index 0dbb6e65b..b67e9f83e 100644 --- a/tests/fixtures/api_request_test_foobar_post.yaml +++ b/tests/fixtures/api_request_test_foobar_post.yaml @@ -119,6 +119,19 @@ components: description: An arbitrary deeply nested array. items: type: string + field_object_array: + type: array + description: An arbitrary array of objects nested within a list. + items: + type: object + description: An arbitrary deeply nested object. + properties: + nested_object_string: + type: string + description: A string on a deeply nested list object. + nested_object_int: + type: number + description: An int on a deeply nested list object. field_string: type: string description: An arbitrary field. diff --git a/tests/unit/test_operation.py b/tests/unit/test_operation.py index 1e3118e2b..5ccf072a7 100644 --- a/tests/unit/test_operation.py +++ b/tests/unit/test_operation.py @@ -196,6 +196,7 @@ def test_parse_args_object_list(self, create_operation): "field_int": 123, "field_dict": {"nested_string": "test2", "nested_int": 789}, "field_array": ExplicitJsonValue(json_value=["foo", "bar"]), + "field_object_array": None, # We expect this to be filtered out later "nullable_string": None, # We expect this to be filtered out later }, {"field_int": 456, "field_dict": {"nested_string": "test3"}}, @@ -218,6 +219,54 @@ def test_parse_args_object_list_json(self, create_operation): assert result.object_list.json_value == expected + def test_nested_object_list_treated_as_json(self, create_operation): + """ + An array of objects nested within another array of objects (e.g. + --object_list.field_object_array) can only be specified as JSON. + It should not be expanded into unusable child arguments (e.g. + --object_list.field_object_array.nested_object_string). + """ + args_by_path = {arg.path: arg for arg in create_operation.args} + + nested = args_by_path.get("object_list.field_object_array") + assert nested is not None + assert nested.format == "json" + assert nested.datatype == "object" + assert nested.is_child + assert nested.parent == "object_list" + assert not nested.is_parent + + # No child arguments should have been generated for its properties. + assert "object_list.field_object_array.nested_object_string" not in ( + args_by_path + ) + assert "object_list.field_object_array.nested_object_int" not in ( + args_by_path + ) + + def test_parse_args_nested_object_list_json(self, create_operation): + """ + A nested array of objects should be accepted as a JSON string value + associated with each entry of its parent list. + """ + result = create_operation.parse_args( + [ + "--object_list.field_string", + "test1", + "--object_list.field_object_array", + json.dumps([{"nested_object_string": "foo"}]), + ] + ) + + assert result.object_list == [ + { + "field_string": "test1", + "field_object_array": ExplicitJsonValue( + json_value=[{"nested_object_string": "foo"}] + ), + }, + ] + def test_parse_args_conflicting_parent_child(self, create_operation): stderr_buf = io.StringIO()