diff --git a/linodecli/__init__.py b/linodecli/__init__.py index 116d8b5e..235ecc28 100755 --- a/linodecli/__init__.py +++ b/linodecli/__init__.py @@ -32,7 +32,7 @@ print_help_plugins, ) from .helpers import handle_url_overrides -from .output import OutputMode +from .output.output_handler import OutputMode from .version import __version__ VERSION = __version__ @@ -64,54 +64,19 @@ def main(): # pylint: disable=too-many-branches,too-many-statements ) parsed, args = register_args(parser).parse_known_args() - # output/formatting settings - if parsed.text: - cli.output_handler.mode = OutputMode.delimited - elif parsed.json: - cli.output_handler.mode = OutputMode.json - cli.output_handler.columns = "*" - elif parsed.markdown: - cli.output_handler.mode = OutputMode.markdown - elif parsed.ascii_table: - cli.output_handler.mode = OutputMode.ascii_table - - if parsed.delimiter: - cli.output_handler.delimiter = parsed.delimiter - if parsed.pretty: - cli.output_handler.mode = OutputMode.json - cli.output_handler.pretty_json = True - cli.output_handler.columns = "*" - if parsed.no_headers: - cli.output_handler.headers = False + cli.output_handler.configure(parsed, cli.suppress_warnings) + if parsed.all_rows: cli.pagination = False - elif parsed.format: - cli.output_handler.columns = parsed.format cli.defaults = not parsed.no_defaults cli.retry_count = 0 cli.no_retry = parsed.no_retry cli.suppress_warnings = parsed.suppress_warnings - - if parsed.all_columns or parsed.all: - if parsed.all and not cli.suppress_warnings: - print( - "WARNING: '--all' is a deprecated flag, " - "and will be removed in a future version. " - "Please consider use '--all-columns' instead." - ) - cli.output_handler.columns = "*" - cli.page = parsed.page cli.page_size = parsed.page_size cli.debug_request = parsed.debug - cli.output_handler.suppress_warnings = parsed.suppress_warnings - cli.output_handler.disable_truncation = parsed.no_truncation - cli.output_handler.column_width = parsed.column_width - cli.output_handler.single_table = parsed.single_table - cli.output_handler.tables = parsed.table - if parsed.as_user and not skip_config: cli.config.set_user(parsed.as_user) diff --git a/linodecli/arg_helpers.py b/linodecli/arg_helpers.py index 8cd242ad..dc1c2499 100644 --- a/linodecli/arg_helpers.py +++ b/linodecli/arg_helpers.py @@ -12,10 +12,11 @@ from linodecli import plugins from linodecli.helpers import ( - pagination_args_shared, register_args_shared, register_debug_arg, + register_pagination_args_shared, ) +from linodecli.output.helpers import register_output_args_shared def register_args(parser): @@ -41,98 +42,20 @@ def register_args(parser): action="store_true", help="Display information about a command, action, or the CLI overall.", ) - parser.add_argument( - "--text", - action="store_true", - help="Display text output with a delimiter (defaults to tabs).", - ) - parser.add_argument( - "--delimiter", - metavar="DELIMITER", - type=str, - help="The delimiter when displaying raw output.", - ) - parser.add_argument( - "--json", action="store_true", help="Display output as JSON." - ) - parser.add_argument( - "--markdown", - action="store_true", - help="Display output in Markdown format.", - ) - parser.add_argument( - "--ascii-table", - action="store_true", - help="Display output in an ASCII table.", - ) - parser.add_argument( - "--pretty", - action="store_true", - help="If set, pretty-print JSON output.", - ) - parser.add_argument( - "--no-headers", - action="store_true", - help="If set, does not display headers in output.", - ) - parser.add_argument( - "--all", - action="store_true", - help=( - "Deprecated flag. An alias of '--all-columns', " - "scheduled to be removed in a future version." - ), - ) - parser.add_argument( - "--all-columns", - action="store_true", - help=( - "If set, displays all possible columns instead of " - "the default columns. This may not work well on some terminals." - ), - ) - parser.add_argument( - "--format", - metavar="FORMAT", - type=str, - help="The columns to display in output. Provide a comma-" - "separated list of column names.", - ) + parser.add_argument( "--no-defaults", action="store_true", help="Suppress default values for arguments. Default values " "are configured on initial setup or with linode-cli configure", ) - parser.add_argument( - "--no-truncation", - action="store_true", - default=False, - help="Prevent the truncation of long values in command outputs.", - ) + parser.add_argument( "--no-retry", action="store_true", help="Skip retrying on common errors like timeouts.", ) - parser.add_argument( - "--single-table", - action="store_true", - help="Disable printing multiple tables for complex API responses.", - ) - parser.add_argument( - "--table", - type=str, - action="append", - help="The specific table(s) to print in output of a command.", - ) - parser.add_argument( - "--column-width", - type=int, - default=None, - help="Sets the maximum width of each column in outputted tables. " - "By default, columns are dynamically sized to fit the terminal.", - ) + parser.add_argument( "--version", "-v", @@ -140,7 +63,8 @@ def register_args(parser): help="Prints version information and exits.", ) - pagination_args_shared(parser) + register_output_args_shared(parser) + register_pagination_args_shared(parser) register_args_shared(parser) register_debug_arg(parser) diff --git a/linodecli/baked/operation.py b/linodecli/baked/operation.py index de2aad2c..3bd1c6fb 100644 --- a/linodecli/baked/operation.py +++ b/linodecli/baked/operation.py @@ -18,7 +18,7 @@ from linodecli.baked.request import OpenAPIFilteringRequest, OpenAPIRequest from linodecli.baked.response import OpenAPIResponse -from linodecli.output import OutputHandler +from linodecli.output.output_handler import OutputHandler from linodecli.overrides import OUTPUT_OVERRIDES diff --git a/linodecli/cli.py b/linodecli/cli.py index 09a69e2d..d9c2651e 100644 --- a/linodecli/cli.py +++ b/linodecli/cli.py @@ -9,10 +9,10 @@ from openapi3 import OpenAPI -from .api_request import do_request, get_all_pages -from .baked import OpenAPIOperation -from .configuration import CLIConfig -from .output import OutputHandler, OutputMode +from linodecli.api_request import do_request, get_all_pages +from linodecli.baked import OpenAPIOperation +from linodecli.configuration import CLIConfig +from linodecli.output.output_handler import OutputHandler, OutputMode METHODS = ("get", "post", "put", "delete") diff --git a/linodecli/helpers.py b/linodecli/helpers.py index 08f8023e..eb6dba6b 100644 --- a/linodecli/helpers.py +++ b/linodecli/helpers.py @@ -39,7 +39,7 @@ def handle_url_overrides( ).geturl() -def pagination_args_shared(parser: ArgumentParser): +def register_pagination_args_shared(parser: ArgumentParser): """ Add pagination related arguments to the given ArgumentParser that may be shared across the CLI and plugins. diff --git a/linodecli/output/__init__.py b/linodecli/output/__init__.py new file mode 100644 index 00000000..d24b8b27 --- /dev/null +++ b/linodecli/output/__init__.py @@ -0,0 +1,6 @@ +""" +Output formatting module for CLI and plugins. +""" + +from .helpers import get_output_handler, register_output_args_shared +from .output_handler import OutputHandler diff --git a/linodecli/output/helpers.py b/linodecli/output/helpers.py new file mode 100644 index 00000000..a207faf0 --- /dev/null +++ b/linodecli/output/helpers.py @@ -0,0 +1,103 @@ +""" +Helpers for CLI output arguments and OutputHandler. +""" + +from argparse import ArgumentParser, Namespace + +from linodecli.output.output_handler import OutputHandler + + +def register_output_args_shared(parser: ArgumentParser): + """ + Add output formatting related arguments to the ArgumentParser. + """ + parser.add_argument( + "--text", + action="store_true", + help="Display text output with a delimiter (defaults to tabs).", + ) + parser.add_argument( + "--delimiter", + metavar="DELIMITER", + type=str, + help="The delimiter when displaying raw output.", + ) + parser.add_argument( + "--json", action="store_true", help="Display output as JSON." + ) + parser.add_argument( + "--markdown", + action="store_true", + help="Display output in Markdown format.", + ) + + parser.add_argument( + "--ascii-table", + action="store_true", + help="Display output in an ASCII table.", + ) + parser.add_argument( + "--pretty", + action="store_true", + help="If set, pretty-print JSON output.", + ) + parser.add_argument( + "--no-headers", + action="store_true", + help="If set, does not display headers in output.", + ) + parser.add_argument( + "--all", + action="store_true", + help=( + "Deprecated flag. An alias of '--all-columns', " + "scheduled to be removed in a future version." + ), + ) + parser.add_argument( + "--all-columns", + action="store_true", + help=( + "If set, displays all possible columns instead of " + "the default columns. This may not work well on some terminals." + ), + ) + parser.add_argument( + "--format", + metavar="FORMAT", + type=str, + help="The columns to display in output. Provide a comma-" + "separated list of column names.", + ) + parser.add_argument( + "--no-truncation", + action="store_true", + default=False, + help="Prevent the truncation of long values in command outputs.", + ) + parser.add_argument( + "--single-table", + action="store_true", + help="Disable printing multiple tables for complex API responses.", + ) + parser.add_argument( + "--table", + type=str, + action="append", + help="The specific table(s) to print in output of a command.", + ) + parser.add_argument( + "--column-width", + type=int, + default=None, + help="Sets the maximum width of each column in outputted tables. " + "By default, columns are dynamically sized to fit the terminal.", + ) + + +def get_output_handler(parsed: Namespace, suppress_warnings: bool = False): + """ + Create a new OutputHandler and configure it with the parsed arguments. + """ + output_handler = OutputHandler() + output_handler.configure(parsed, suppress_warnings) diff --git a/linodecli/output.py b/linodecli/output/output_handler.py similarity index 88% rename from linodecli/output.py rename to linodecli/output/output_handler.py index adbdc806..47639ca7 100644 --- a/linodecli/output.py +++ b/linodecli/output/output_handler.py @@ -4,7 +4,8 @@ import copy import json -from enum import Enum +from argparse import Namespace +from enum import Enum, auto from sys import stdout from typing import IO, Any, Dict, List, Optional, Union, cast @@ -21,11 +22,11 @@ class OutputMode(Enum): Enum for output modes """ - table = 1 - delimited = 2 - json = 3 - markdown = 4 - ascii_table = 5 + table = auto() + delimited = auto() + json = auto() + markdown = auto() + ascii_table = auto() class OutputHandler: # pylint: disable=too-few-public-methods,too-many-instance-attributes @@ -404,3 +405,47 @@ def _build_output_content( content.append([value_transform(attr, model) for attr in columns]) return content + + def configure( + self, + parsed: Namespace, + suppress_warnings: bool = False, + ): + """ + Configure the given OutputHandler with the parsed arguments. + """ + if parsed.text: + self.mode = OutputMode.delimited + elif parsed.json: + self.mode = OutputMode.json + self.columns = "*" + elif parsed.markdown: + self.mode = OutputMode.markdown + elif parsed.ascii_table: + self.mode = OutputMode.ascii_table + + if parsed.delimiter: + self.delimiter = parsed.delimiter + if parsed.pretty: + self.mode = OutputMode.json + self.pretty_json = True + self.columns = "*" + if parsed.no_headers: + self.headers = False + + self.suppress_warnings = parsed.suppress_warnings + self.disable_truncation = parsed.no_truncation + self.column_width = parsed.column_width + self.single_table = parsed.single_table + self.tables = parsed.table + + if parsed.all_columns or parsed.all: + if parsed.all and not suppress_warnings: + print( + "WARNING: '--all' is a deprecated flag, " + "and will be removed in a future version. " + "Please consider use '--all-columns' instead." + ) + self.columns = "*" + elif parsed.format: + self.columns = parsed.format diff --git a/linodecli/overrides.py b/linodecli/overrides.py index 51c36631..290df65f 100644 --- a/linodecli/overrides.py +++ b/linodecli/overrides.py @@ -12,7 +12,7 @@ from rich.console import Console from rich.table import Table -from linodecli.output import OutputMode +from linodecli.output.output_handler import OutputMode OUTPUT_OVERRIDES = {} diff --git a/linodecli/plugins/obj/list.py b/linodecli/plugins/obj/list.py index 19fadbb7..220bd4f2 100644 --- a/linodecli/plugins/obj/list.py +++ b/linodecli/plugins/obj/list.py @@ -7,7 +7,7 @@ from rich import print as rprint -from linodecli.helpers import pagination_args_shared +from linodecli.helpers import register_pagination_args_shared from linodecli.plugins import inherit_plugin_args from linodecli.plugins.obj.config import PLUGIN_BASE from linodecli.plugins.obj.helpers import ( @@ -31,7 +31,7 @@ def list_objects_or_buckets( Lists buckets or objects """ parser = inherit_plugin_args(ArgumentParser(PLUGIN_BASE + " ls")) - pagination_args_shared(parser) + register_pagination_args_shared(parser) parser.add_argument( "bucket", @@ -130,7 +130,7 @@ def list_all_objects( """ # this is for printing help when --help is in the args parser = inherit_plugin_args(ArgumentParser(PLUGIN_BASE + " la")) - pagination_args_shared(parser) + register_pagination_args_shared(parser) parsed = parser.parse_args(args) diff --git a/tests/integration/cli/test_host_overrides.py b/tests/integration/cli/test_host_overrides.py index d9b2faa1..e9ae6690 100644 --- a/tests/integration/cli/test_host_overrides.py +++ b/tests/integration/cli/test_host_overrides.py @@ -11,7 +11,10 @@ def test_cli_command_fails_to_access_invalid_host(monkeypatch: MonkeyPatch): process = exec_failing_test_command(["linode-cli", "linodes", "ls"]) output = process.stderr.decode() - assert "Max retries exceeded with url: //wrongapi.linode.com" in output + expected_output = ["Max retries exceeded with url:", "wrongapi.linode.com"] + + for eo in expected_output: + assert eo in output def test_cli_uses_v4beta_when_override_is_set(monkeypatch: MonkeyPatch): diff --git a/tests/integration/linodes/test_backups.py b/tests/integration/linodes/test_backups.py index c0fdbf60..2fd93ca6 100755 --- a/tests/integration/linodes/test_backups.py +++ b/tests/integration/linodes/test_backups.py @@ -44,6 +44,25 @@ def create_linode_backup_disabled_setup(): delete_target_id("linodes", linode_id) +def check_account_settings(): + result = exec_test_command( + [ + "linode-cli", + "account", + "settings", + "--text", + "--format", + "managed", + "--no-headers", + ] + ).stdout.decode() + + return result + + +@pytest.mark.skipif( + check_account_settings(), reason="Account is managed, skipping the test.." +) def test_create_linode_with_backup_disabled( create_linode_backup_disabled_setup, ): diff --git a/tests/unit/test_helpers.py b/tests/unit/test_helpers.py index 433001ea..91b9808c 100644 --- a/tests/unit/test_helpers.py +++ b/tests/unit/test_helpers.py @@ -1,6 +1,9 @@ from argparse import ArgumentParser -from linodecli.helpers import pagination_args_shared, register_args_shared +from linodecli.helpers import ( + register_args_shared, + register_pagination_args_shared, +) class TestHelpers: @@ -10,7 +13,7 @@ class TestHelpers: def test_pagination_args_shared(self): parser = ArgumentParser() - pagination_args_shared(parser) + register_pagination_args_shared(parser) args = parser.parse_args( ["--page", "2", "--page-size", "50", "--all-rows"]