Skip to content

Commit 53b0751

Browse files
proposal: Add support for printing additional tables in command outputs (#511)
1 parent 9246840 commit 53b0751

File tree

12 files changed

+563
-92
lines changed

12 files changed

+563
-92
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ clean:
5151
.PHONY: testunit
5252
testunit: export LINODE_CLI_TEST_MODE = 1
5353
testunit:
54-
pytest tests/unit
54+
pytest -v tests/unit
5555

5656
.PHONY: testint
5757
testint:

README.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,5 +509,9 @@ added to Linode's OpenAPI spec:
509509
| | | paired with ``x-linode-cli-nested-list``, allowing a schema to describe the flattened |
510510
| | | object instead of the original object. |
511511
+-----------------------------+-------------+-------------------------------------------------------------------------------------------+
512+
|x-linode-cli-subtables | content-type| Indicates that certain response attributes should be printed in a separate "sub"-table. |
513+
| | | This allows certain endpoints with nested structures in the response to be displayed |
514+
| | | correctly. |
515+
+-----------------------------+-------------+-------------------------------------------------------------------------------------------+
512516

513517
.. _Specification Extensions: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.1.md#specificationExtensions

linodecli/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ def main(): # pylint: disable=too-many-branches,too-many-statements
103103
cli.output_handler.suppress_warnings = parsed.suppress_warnings
104104
cli.output_handler.disable_truncation = parsed.no_truncation
105105
cli.output_handler.column_width = parsed.column_width
106+
cli.output_handler.single_table = parsed.single_table
107+
cli.output_handler.tables = parsed.table
106108

107109
if parsed.as_user and not skip_config:
108110
cli.config.set_user(parsed.as_user)

linodecli/api_request.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,11 +360,10 @@ def _handle_error(ctx, response):
360360
for error in resp_json["errors"]
361361
]
362362
ctx.output_handler.print(
363-
None,
364363
data,
364+
["field", "reason"],
365365
title="errors",
366366
to=sys.stderr,
367-
columns=["field", "reason"],
368367
)
369368
sys.exit(1)
370369

linodecli/arg_helpers.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,17 @@ def register_args(parser):
116116
action="store_true",
117117
help="Skip retrying on common errors like timeouts.",
118118
)
119+
parser.add_argument(
120+
"--single-table",
121+
action="store_true",
122+
help="Disable printing multiple tables for complex API responses.",
123+
)
124+
parser.add_argument(
125+
"--table",
126+
type=str,
127+
action="append",
128+
help="The specific table(s) to print in output of a command.",
129+
)
119130
parser.add_argument(
120131
"--column-width",
121132
type=int,

linodecli/baked/operation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ def process_response_json(
319319
return
320320

321321
json = self.response_model.fix_json(json)
322-
handler.print(self.response_model, json)
322+
handler.print_response(self.response_model, json)
323323

324324
def _add_args_filter(self, parser):
325325
# build args for filtering

linodecli/baked/response.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class OpenAPIResponseAttr:
2727
from it.
2828
"""
2929

30-
def __init__(self, name, schema, prefix=None):
30+
def __init__(self, name, schema, prefix=None, nested_list_depth=0):
3131
"""
3232
:param name: The key that held this schema in the properties list, representing
3333
its name in a response.
@@ -37,13 +37,19 @@ def __init__(self, name, schema, prefix=None):
3737
:param prefix: The json path style prefix (dot notation) to this schema
3838
in the response object
3939
:type prefix: str
40+
:param nested_list_depth: The number of nested lists this attribute is nested in.
41+
:type: nested_list_depth: int
4042
"""
4143
#: The name of this attribute, which is the full json path to it within the schema
4244
self.name = name if prefix is None else prefix + "." + name
4345

4446
#: If this attribute is filterable in GET requests
4547
self.filterable = schema.extensions.get("linode-filterable")
4648

49+
#: The depth of this nested attribute in lists. This is necessary to prevent displaying
50+
#: list nested items in normal tables.
51+
self.nested_list_depth = nested_list_depth
52+
4753
#: The description of this argument, for help display. Only used for filterable attributes.
4854
self.description = (
4955
schema.description.split(".")[0] if schema.description else ""
@@ -90,8 +96,14 @@ def _get_value(self, model):
9096
"""
9197
value = model
9298
for part in self.name.split("."):
93-
if value is None or value == {}:
99+
if (
100+
value is None
101+
or value == {}
102+
or isinstance(value, list)
103+
or part not in value
104+
):
94105
return None
106+
95107
value = value[part]
96108
return value
97109

@@ -133,7 +145,7 @@ def get_string(self, model):
133145
return value
134146

135147

136-
def _parse_response_model(schema, prefix=None):
148+
def _parse_response_model(schema, prefix=None, nested_list_depth=0):
137149
"""
138150
Recursively parses all properties of this schema to create a flattened set of
139151
OpenAPIResponseAttr objects that allow the CLI to display this response in a
@@ -142,18 +154,31 @@ def _parse_response_model(schema, prefix=None):
142154
become a new OpenAPIResponseAttr instance, and this process is
143155
recursive to include the properties of properties and so on.
144156
:type schema: openapi3.Schema
157+
:param nested_list_depth: The number of nested lists this attribute is nested in.
158+
:type: nested_list_depth: int
145159
:returns: The list of parsed OpenAPIResponseAttr objects representing this schema
146160
:rtype: List[OpenAPIResponseAttr]
147161
"""
148162
attrs = []
149163

150164
if schema.properties is not None:
151165
for k, v in schema.properties.items():
166+
pref = prefix + "." + k if prefix else k
167+
152168
if v.type == "object":
153-
pref = prefix + "." + k if prefix else k
154169
attrs += _parse_response_model(v, prefix=pref)
170+
elif v.type == "array" and v.items.type == "object":
171+
attrs += _parse_response_model(
172+
v.items,
173+
prefix=pref,
174+
nested_list_depth=nested_list_depth + 1,
175+
)
155176
else:
156-
attrs.append(OpenAPIResponseAttr(k, v, prefix=prefix))
177+
attrs.append(
178+
OpenAPIResponseAttr(
179+
k, v, prefix=prefix, nested_list_depth=nested_list_depth
180+
)
181+
)
157182

158183
return attrs
159184

@@ -189,6 +214,7 @@ def __init__(self, response):
189214
self.attrs = _parse_response_model(response.schema)
190215
self.rows = response.extensions.get("linode-cli-rows")
191216
self.nested_list = response.extensions.get("linode-cli-nested-list")
217+
self.subtables = response.extensions.get("linode-cli-subtables")
192218

193219
def fix_json(self, json):
194220
"""

linodecli/cli.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -118,17 +118,6 @@ def handle_command(self, command, action, args):
118118
Given a command, action, and remaining kwargs, finds and executes the
119119
action
120120
"""
121-
if (command, action) in [
122-
("linodes", "ips-list"),
123-
("firewalls", "rules-list"),
124-
] and "--json" not in args:
125-
print(
126-
"This output contains a nested structure that may not properly "
127-
+ "be displayed by linode-cli.",
128-
"A fix is currently on the roadmap but has not yet been implemented.",
129-
"Please use --json for endpoints like this in the meantime.",
130-
file=sys.stderr,
131-
)
132121

133122
try:
134123
operation = self.find_operation(command, action)

0 commit comments

Comments
 (0)