From 849d316b2be4c34b431d8c729fbcbaa0a25f3ddf Mon Sep 17 00:00:00 2001 From: Ricardo Branco Date: Mon, 2 Sep 2024 23:41:51 +0200 Subject: [PATCH] Drop -o option --- README.md | 4 +-- cloudview/cloudview.py | 46 +++++++++------------------ cloudview/output.py | 67 --------------------------------------- tests/test_output.py | 71 ------------------------------------------ 4 files changed, 16 insertions(+), 172 deletions(-) delete mode 100644 cloudview/output.py delete mode 100644 tests/test_output.py diff --git a/README.md b/README.md index 6ecb812..34f767d 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Docker image available at `ghcr.io/ricardobranco777/cloudview:latest` ## Usage ``` -usage: cloudview.py [-h] [-c CONFIG] [-f FORMAT] [-l {none,debug,info,warning,error,critical}] [-o {text,json}] [-P {ec2,gce,azure_arm,openstack}] [-r] +usage: cloudview.py [-h] [-c CONFIG] [-f FORMAT] [-l {none,debug,info,warning,error,critical}] [-P {ec2,gce,azure_arm,openstack}] [-r] [-s {name,state,time}] [-S {error,migrating,normal,paused,pending,rebooting,reconfiguring,running,starting,stopped,stopping,suspended,terminated,unknown,updating}] [-t TIME_FORMAT] [-v] [--version] @@ -25,8 +25,6 @@ options: output fields (default: provider,name,size,state,time,location) -l {none,debug,info,warning,error,critical}, --log {none,debug,info,warning,error,critical} logging level (default: error) - -o {text,json}, --output {text,json} - output type (default: text) -P {ec2,gce,azure_arm,openstack}, --providers {ec2,gce,azure_arm,openstack} list only specified providers (default: None) -r, --reverse reverse sort (default: False) diff --git a/cloudview/cloudview.py b/cloudview/cloudview.py index 08d8c0c..8a05c1b 100644 --- a/cloudview/cloudview.py +++ b/cloudview/cloudview.py @@ -18,8 +18,7 @@ from .azure import Azure from .gce import GCE from .openstack import Openstack -from .instance import CSP, STATES -from .output import Output +from .instance import CSP, Instance, STATES from .utils import dateit, read_file from . import __version__ @@ -69,9 +68,9 @@ def get_clients( return clients -def print_instances(client: CSP) -> None: +def get_instances(client: CSP) -> list[Instance]: """ - Print instances + Get instances """ instances = [ instance @@ -82,23 +81,7 @@ def print_instances(client: CSP) -> None: instances.sort( key=itemgetter(args.sort, "name"), reverse=args.reverse # type:ignore ) - for instance in instances: - instance.provider = "/".join([instance.provider, instance.cloud]) - assert not isinstance(instance.time, str) - instance.time = dateit(instance.time, args.time) - Output().info(instance) - - -def print_info() -> None: - """ - Print information about instances - """ - clients = get_clients(config_file=args.config) - Output().header() - if len(clients) > 0: - with ThreadPoolExecutor(max_workers=len(clients)) as executor: - executor.map(print_instances, clients) - Output().footer() + return instances def parse_args() -> argparse.Namespace: @@ -124,13 +107,6 @@ def parse_args() -> argparse.Namespace: choices=["none", "debug", "info", "warning", "error", "critical"], help="logging level", ) - argparser.add_argument( - "-o", - "--output", - default="text", - choices=["text", "json"], - help="output type", - ) argparser.add_argument( "-P", "--providers", @@ -191,12 +167,20 @@ def main() -> None: "location": "<15", } keys = {key: keys.get(key, "") for key in args.fields.split(",")} - if args.verbose: keys |= {"id": ""} + output_format = " ".join(f"{{{key}:{align}}}" for key, align in keys.items()) + print(output_format.format_map({key: key.upper() for key in keys})) - Output(type=args.output.lower(), keys=keys) - print_info() + clients = get_clients(config_file=args.config) + if len(clients) > 0: + with ThreadPoolExecutor(max_workers=len(clients)) as executor: + for instances in executor.map(get_instances, clients): + for instance in instances: + instance.provider = f"{instance.provider}/{instance.cloud}" + assert not isinstance(instance.time, str) + instance.time = dateit(instance.time, args.time) + print(output_format.format_map(instance.__dict__)) if __name__ == "__main__": diff --git a/cloudview/output.py b/cloudview/output.py deleted file mode 100644 index ee09da0..0000000 --- a/cloudview/output.py +++ /dev/null @@ -1,67 +0,0 @@ -""" -Handle tabular output in these formats: text, json -""" - -import json - -from cloudview.singleton import Singleton - - -# pylint: disable=redefined-builtin -class Output(metaclass=Singleton): - """ - Helper class to handle tabular output in text, json - """ - - def __init__( - self, - type: str | None = None, - keys: dict[str, str] | list[str] | None = None, - **kwargs, - ) -> None: - """ - type must be either text, json - fmt is the format string used for text - keys are the items in the dictionary - """ - if type not in ("text", "json"): - raise ValueError(f"Invalid type: {type}") - self._type = type - if isinstance(keys, (list, tuple)): - self._keys = dict.fromkeys(keys, "") - else: - self._keys = keys or {} - if self._type == "text": - self._output_format = " ".join( - f"{{{key}:{align}}}" for key, align in self._keys.items() - ) - self._kwargs = kwargs - self._items: list[dict] = [] - - def header(self) -> None: - """ - Print the header for output - """ - if self._type == "text": - print( - self._output_format.format_map({key: key.upper() for key in self._keys}) - ) - - def info(self, item) -> None: - """ - Dump item information - """ - if self._type == "text": - if isinstance(item, dict): - print(self._output_format.format_map(item)) - else: - print(self._output_format.format_map(item.__dict__)) - elif self._type == "json": - self._items.append(item if isinstance(item, dict) else item.__dict__) - - def footer(self) -> None: - """ - Print the footer for output - """ - if self._type == "json": - print(json.dumps(self._items, indent=2, default=str)) diff --git a/tests/test_output.py b/tests/test_output.py deleted file mode 100644 index 98653ba..0000000 --- a/tests/test_output.py +++ /dev/null @@ -1,71 +0,0 @@ -# pylint: disable=missing-module-docstring,missing-function-docstring,missing-class-docstring,redefined-outer-name,protected-access - -import json - -import pytest - -from cloudview.instance import Instance -from cloudview.output import Output - - -@pytest.fixture -def text_output(): - return Output(type="text", keys=["name", "age"]) - - -@pytest.fixture -def json_output(): - return Output(type="json") - - -# Use the "monkeypatch" fixture to reset the singleton instance before each test -@pytest.fixture(autouse=True) -def reset_singleton(monkeypatch): - monkeypatch.setattr(Output, "_singleton_instance", None) - - -def test_invalid_output_type(): - with pytest.raises(ValueError, match="Invalid type: invalid_type"): - Output(type="invalid_type") - - -def test_text_output_header(text_output, capsys): - expected_header = "NAME AGE\n" - text_output.header() - captured = capsys.readouterr() - assert captured.out == expected_header - - -def test_text_output_info(text_output, capsys): - expected_info = "John 30\n" - text_output.info({"name": "John", "age": 30}) - captured = capsys.readouterr() - assert captured.out == expected_info - - -def test_json_output_dict(json_output, capsys): - json_output.header() - info = {"name": "John", "age": 30} - json_output.info(info) - json_output.footer() - captured = capsys.readouterr() - assert [info] == json.loads(captured.out) - - -def test_json_output_obj(json_output, capsys): - json_output.header() - info = Instance( - name="instance-1", - provider="P", - cloud="C", - id="id", - size="s", - time="T", - state="S", - location="L", - extra={}, - ) - json_output.info(info) - json_output.footer() - captured = capsys.readouterr() - assert [info.__dict__] == json.loads(captured.out)