Skip to content

Commit f028a92

Browse files
new: Add support for output overrides and add domains zone-file override (#449)
## 📝 Description This change introduces a new output override system that allows us to modularly add custom output logic for specific generated commands. This is useful for cleaning up outputs for endpoints with responses that may be too complicated to account for in the spec parser.
1 parent 2b477df commit f028a92

File tree

3 files changed

+106
-0
lines changed

3 files changed

+106
-0
lines changed

linodecli/operation.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
from linodecli.helpers import handle_url_overrides
1313

14+
from .overrides import OUTPUT_OVERRIDES
15+
1416

1517
def parse_boolean(value):
1618
"""
@@ -346,6 +348,12 @@ def process_response_json(
346348
if self.response_model is None:
347349
return
348350

351+
override = OUTPUT_OVERRIDES.get(
352+
(self.command, self.action, handler.mode)
353+
)
354+
if override is not None and not override(self, handler, json):
355+
return
356+
349357
json = self.response_model.fix_json(json)
350358

351359
handler.print(self.response_model, json)

linodecli/overrides.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""
2+
Contains wrappers for overriding certain pieces of command-handling logic.
3+
This allows us to easily alter per-command outputs, etc. without making
4+
large changes to the OpenAPI spec.
5+
"""
6+
7+
from linodecli.output import OutputMode
8+
9+
OUTPUT_OVERRIDES = {}
10+
11+
12+
def output_override(command: str, action: str, output_mode: OutputMode):
13+
"""
14+
A decorator function for adding a new output override handler.
15+
16+
Output override functions should have the following signature::
17+
18+
@output_override("command", "action", OutputMode.{output_mode})
19+
def my_override(operation, output_handler, json_data) -> bool:
20+
...
21+
22+
If the returned bool is False, the original output functionality will be skipped.
23+
Otherwise, the original output functionality will continue as normal.
24+
"""
25+
26+
def inner(func):
27+
OUTPUT_OVERRIDES[(command, action, output_mode)] = func
28+
29+
return inner
30+
31+
32+
@output_override("domains", "zone-file", OutputMode.delimited)
33+
def handle_domains_zone_file(operation, output_handler, json_data) -> bool:
34+
# pylint: disable=unused-argument
35+
"""
36+
Fix for output of 'linode-cli domains zone-file --text {id}'.
37+
"""
38+
print("\n".join(json_data["zone_file"]))
39+
return False

tests/unit/test_overrides.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import contextlib
2+
import io
3+
from unittest.mock import patch
4+
5+
from linodecli import ModelAttr, OutputMode, ResponseModel
6+
from linodecli.overrides import OUTPUT_OVERRIDES
7+
8+
9+
class TestOverrides:
10+
"""
11+
Unit tests for linodecli.overrides
12+
"""
13+
14+
def test_domains_zone_file(self, mock_cli, list_operation):
15+
response_json = {"zone_file": ["line 1", "line 2"]}
16+
override_signature = ("domains", "zone-file", OutputMode.delimited)
17+
18+
list_operation.response_model = ResponseModel(
19+
[ModelAttr("zone_file", False, False, "array", item_type="string")]
20+
)
21+
list_operation.command = "domains"
22+
list_operation.action = "zone-file"
23+
mock_cli.output_handler.mode = OutputMode.delimited
24+
25+
stdout_buf = io.StringIO()
26+
27+
with contextlib.redirect_stdout(stdout_buf):
28+
list_operation.process_response_json(
29+
response_json, mock_cli.output_handler
30+
)
31+
32+
assert stdout_buf.getvalue() == "line 1\nline 2\n"
33+
34+
# Validate that the override will continue execution if it returns true
35+
def patch_func(*a):
36+
OUTPUT_OVERRIDES[override_signature](*a)
37+
return True
38+
39+
with patch(
40+
"linodecli.operation.OUTPUT_OVERRIDES",
41+
{override_signature: patch_func},
42+
), patch.object(mock_cli.output_handler, "print") as p:
43+
list_operation.process_response_json(
44+
response_json, mock_cli.output_handler
45+
)
46+
assert p.called
47+
48+
# Change the action to bypass the override
49+
stdout_buf = io.StringIO()
50+
51+
list_operation.action = "zone-notfile"
52+
mock_cli.output_handler.mode = OutputMode.delimited
53+
54+
with contextlib.redirect_stdout(stdout_buf):
55+
list_operation.process_response_json(
56+
response_json, mock_cli.output_handler
57+
)
58+
59+
assert stdout_buf.getvalue() != "line 1\nline 2\n"

0 commit comments

Comments
 (0)