From a4f954eb706c9256de1b6ed22f55221c87e840d5 Mon Sep 17 00:00:00 2001 From: Pallab Pain Date: Sat, 22 Apr 2023 13:26:29 +0530 Subject: [PATCH] feat(parameter): adds spinner and constants This commit updates the parameter commands with the new spinner and replaces literals with constants wherever applicable. --- riocli/parameter/__init__.py | 17 ++++------ riocli/parameter/apply.py | 54 ++++++++++++++++++++++--------- riocli/parameter/delete.py | 43 +++++++++++++++++-------- riocli/parameter/diff.py | 39 +++++++++++++++++------ riocli/parameter/download.py | 50 ++++++++++++++++++++--------- riocli/parameter/list.py | 13 ++++++-- riocli/parameter/upload.py | 62 +++++++++++++++++++++++++----------- riocli/parameter/utils.py | 28 +++++++++++----- 8 files changed, 213 insertions(+), 93 deletions(-) diff --git a/riocli/parameter/__init__.py b/riocli/parameter/__init__.py index ed99fbec..b6f3f780 100644 --- a/riocli/parameter/__init__.py +++ b/riocli/parameter/__init__.py @@ -15,6 +15,7 @@ import click from click_help_colors import HelpColorsGroup +from riocli.constants import Colors from riocli.parameter.apply import apply_configurations from riocli.parameter.delete import delete_configurations from riocli.parameter.diff import diff_configurations @@ -23,26 +24,22 @@ from riocli.parameter.upload import upload_configurations -# from riocli.parameter.diff import diff_configurations -# from riocli.parameter.download import download_configurations - - @click.group( invoke_without_command=False, cls=HelpColorsGroup, - help_headers_color='yellow', - help_options_color='green', + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, ) def parameter() -> None: """ - Define groups of executables to deploy together + Manage configuration parameters for your devices and deployments """ pass -parameter.add_command(upload_configurations) -parameter.add_command(download_configurations) parameter.add_command(diff_configurations) parameter.add_command(apply_configurations) -parameter.add_command(list_configuration_trees) parameter.add_command(delete_configurations) +parameter.add_command(upload_configurations) +parameter.add_command(download_configurations) +parameter.add_command(list_configuration_trees) diff --git a/riocli/parameter/apply.py b/riocli/parameter/apply.py index 22c368c3..0db0facb 100644 --- a/riocli/parameter/apply.py +++ b/riocli/parameter/apply.py @@ -1,4 +1,4 @@ -# Copyright 2021 Rapyuta Robotics +# Copyright 2023 Rapyuta Robotics # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,22 +14,35 @@ import typing import click +from click_help_colors import HelpColorsCommand +from yaspin import kbi_safe_yaspin from riocli.config import new_client +from riocli.constants import Colors, Symbols from riocli.utils import tabulate_data, print_separator -@click.command('apply') +@click.command( + 'apply', + cls=HelpColorsCommand, + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, +) @click.option('--devices', type=click.STRING, multiple=True, default=(), help='Device names to apply configurations') @click.option('--tree-names', type=click.STRING, multiple=True, default=None, help='Tree names to apply') @click.option('--retry-limit', type=click.INT, default=0, help='Retry limit') -@click.option('-f', '--force', '--silent', 'silent', is_flag=True, type=click.BOOL, default=False, +@click.option('-f', '--force', '--silent', 'silent', is_flag=True, + type=click.BOOL, default=False, help="Skip confirmation") -def apply_configurations(devices: typing.List, tree_names: str = None, retry_limit: int = 0, - silent: bool = False) -> None: +def apply_configurations( + devices: typing.List, + tree_names: str = None, + retry_limit: int = 0, + silent: bool = False, +) -> None: """ Apply a set of configurations to a list of devices """ @@ -40,27 +53,38 @@ def apply_configurations(devices: typing.List, tree_names: str = None, retry_lim device_map = {d.name: d for d in online_devices} if devices: - device_ids = {device_map[d].uuid: d for d in devices if d in device_map} + device_ids = {device_map[d].uuid: d for d in devices if + d in device_map} else: device_ids = {v.uuid: k for k, v in device_map.items()} if not device_ids: - click.secho("invalid devices or no device is currently online", fg='red') + click.secho( + "{} Invalid devices or no device is currently online".format( + Symbols.ERROR), + fg=Colors.RED) raise SystemExit(1) - click.secho('Online Devices: {}'.format(','.join(device_ids.values())), fg='green') + click.secho('Online Devices: {}'.format(','.join(device_ids.values())), + fg=Colors.GREEN) - printable_tree_names = ','.join(tree_names) if tree_names != "" else "*all*" - click.secho('Config Trees: {}'.format(printable_tree_names), fg='green') + printable_tree_names = ','.join( + tree_names) if tree_names != "" else "*all*" + + click.secho('Config Trees: {}'.format(printable_tree_names), + fg=Colors.GREEN) if not silent: click.confirm( "Do you want to apply the configurations?", default=True, abort=True) - response = client.apply_parameters(list(device_ids.keys()), - list(tree_names), - retry_limit) + with kbi_safe_yaspin(text='Applying parameters...') as spinner: + response = client.apply_parameters( + list(device_ids.keys()), + list(tree_names), + retry_limit + ) print_separator() @@ -72,6 +96,6 @@ def apply_configurations(devices: typing.List, tree_names: str = None, retry_lim tabulate_data(result, headers=["Device", "Success"]) except IOError as e: - click.secho(str(e.__traceback__), fg='red') - click.secho(str(e), fg='red') + click.secho(str(e.__traceback__), fg=Colors.RED) + click.secho(str(e), fg=Colors.RED) raise SystemExit(1) from e diff --git a/riocli/parameter/delete.py b/riocli/parameter/delete.py index eb5bf60f..0c1d84e2 100644 --- a/riocli/parameter/delete.py +++ b/riocli/parameter/delete.py @@ -1,4 +1,4 @@ -# Copyright 2021 Rapyuta Robotics +# Copyright 2023 Rapyuta Robotics # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,30 +13,47 @@ # limitations under the License. import click +from click_help_colors import HelpColorsCommand from rapyuta_io.utils.rest_client import HttpMethod +from yaspin import kbi_safe_yaspin +from riocli.constants import Colors, Symbols from riocli.parameter.utils import _api_call -@click.command('delete') -@click.option('-f', '--force', '--silent', 'silent', is_flag=True, default=False, +@click.command( + 'delete', + cls=HelpColorsCommand, + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, +) +@click.option('-f', '--force', '--silent', 'silent', is_flag=True, + default=False, help="Skip confirmation") @click.argument('tree', type=click.STRING) -def delete_configurations(tree: str, silent: bool = False) -> None: +def delete_configurations( + tree: str, + silent: bool = False +) -> None: """ - Deletes the Configuration Parameter Tree. + Deletes the configuration parameter tree. """ click.secho('Configuration Parameter {} will be deleted'.format(tree)) if not silent: click.confirm('Do you want to proceed?', default=True, abort=True) - try: - data = _api_call(HttpMethod.DELETE, name=tree) - if data.get('data') != 'ok': - raise Exception('Something went wrong!') + with kbi_safe_yaspin(text='Deleting...', timer=True) as spinner: + try: + data = _api_call(HttpMethod.DELETE, name=tree) + if data.get('data') != 'ok': + raise Exception('Failed to delete configuration') - except IOError as e: - click.secho(str(e.__traceback__), fg='red') - click.secho(str(e), fg='red') - raise SystemExit(1) + spinner.text = click.style( + 'Configuration deleted successfully.', + fg=Colors.GREEN) + spinner.green.ok(Symbols.SUCCESS) + except IOError as e: + spinner.text = click.style(e, fg=Colors.RED) + spinner.red.fail(Symbols.ERROR) + raise SystemExit(1) diff --git a/riocli/parameter/diff.py b/riocli/parameter/diff.py index 52ca1449..f2265caf 100644 --- a/riocli/parameter/diff.py +++ b/riocli/parameter/diff.py @@ -24,32 +24,46 @@ from typing import Tuple import click +from click_help_colors import HelpColorsCommand from rapyuta_io.utils.error import APIError, InternalServerError +from yaspin import kbi_safe_yaspin from riocli.config import new_client +from riocli.constants import Colors from riocli.parameter.utils import filter_trees -@click.command('diff') +@click.command( + 'diff', + cls=HelpColorsCommand, + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, +) @click.option('--tree-names', type=click.STRING, multiple=True, default=None, help='Tree names to fetch') @click.argument('path', type=click.Path(exists=True), required=False) def diff_configurations(path: str, tree_names: Tuple = None) -> None: """ - Diff between the Local and Cloud Configuration Trees. + Diff between the local and cloud configuration trees. """ trees = filter_trees(path, tree_names) try: client = new_client() with TemporaryDirectory(prefix='riocli-') as tmp_path: - client.download_configurations(tmp_path, tree_names=list(tree_names)) + with kbi_safe_yaspin( + text="Fetching configurations from the cloud..."): + client.download_configurations( + tmp_path, + tree_names=list(tree_names) + ) for tree in trees: - left_tree, right_tree = os.path.join(tmp_path, tree), os.path.join(path, tree) + left_tree = os.path.join(tmp_path, tree) + right_tree = os.path.join(path, tree) diff_tree(left_tree, right_tree) except (APIError, InternalServerError) as e: - click.secho(str(e), fg='red') + click.secho(str(e), fg=Colors.RED) raise SystemExit(1) @@ -57,15 +71,18 @@ def diff_tree(left: str, right: str) -> None: comp = dircmp(left, right) for f in comp.diff_files: - remote_file, local_file = os.path.join(comp.left, f), os.path.join(comp.right, f) + remote_file, local_file = os.path.join(comp.left, f), os.path.join( + comp.right, f) diff_file(remote_file, local_file) for f in comp.right_only: - remote_file, local_file = os.path.join(comp.left, f), os.path.join(comp.right, f) + remote_file, local_file = os.path.join(comp.left, f), os.path.join( + comp.right, f) changed_file(remote_file, local_file, right_only=True) for f in comp.left_only: - remote_file, local_file = os.path.join(comp.left, f), os.path.join(comp.right, f) + remote_file, local_file = os.path.join(comp.left, f), os.path.join( + comp.right, f) changed_file(remote_file, local_file, left_only=True) @@ -80,13 +97,15 @@ def diff_file(left: str, right: str): changed_file(left, right, binary=True) return - diff = unified_diff(left_lines, right_lines, fromfile=left, tofile=right, lineterm='') + diff = unified_diff(left_lines, right_lines, fromfile=left, tofile=right, + lineterm='') for line in diff: click.secho(line) -def changed_file(left: str, right: str, left_only: bool = False, right_only: bool = False, binary: bool = False): +def changed_file(left: str, right: str, left_only: bool = False, + right_only: bool = False, binary: bool = False): click.secho('--- {}'.format(left)) click.secho('+++ {}'.format(right)) diff --git a/riocli/parameter/download.py b/riocli/parameter/download.py index cf514bb2..cc653c29 100644 --- a/riocli/parameter/download.py +++ b/riocli/parameter/download.py @@ -12,14 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. import typing +from os.path import abspath from tempfile import mkdtemp -from xmlrpc.client import Boolean import click -from click_spinner import spinner +from click_help_colors import HelpColorsCommand from riocli.config import new_client -from riocli.parameter.utils import display_trees +from riocli.constants import Symbols, Colors +from riocli.utils.spinner import with_spinner # ----------------------------------------------------------------------------- @@ -30,29 +31,48 @@ # ----------------------------------------------------------------------------- -@click.command('download') -@click.option('--tree-names', type=click.STRING, multiple=True, default=None, help='Tree names to fetch') -@click.option('--overwrite', '--delete-existing', 'delete_existing', is_flag=True, +@click.command( + 'download', + cls=HelpColorsCommand, + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, +) +@click.option('--tree-names', type=click.STRING, multiple=True, default=None, + help='Tree names to fetch') +@click.option('--overwrite', '--delete-existing', 'delete_existing', + is_flag=True, help='Overwrite existing parameter tree') @click.argument('path', type=click.Path(exists=True), required=False) -def download_configurations(path: str, tree_names: typing.Tuple[str] = None, delete_existing: Boolean = False) -> None: +@with_spinner(text='Download configurations...', timer=True) +def download_configurations( + path: str, + tree_names: typing.Tuple[str] = None, + delete_existing: bool = False, + spinner=None +) -> None: """ - Download the Configuration Parameter trees. + Download configuration parameter trees from rapyuta.io """ if path is None: - # Not using the Context Manager because we need to persist the Temporary directory. + # Not using the Context Manager because + # we need to persist the temporary directory. path = mkdtemp() - click.secho('Downloading at {}'.format(path)) + spinner.write('Downloading at {}'.format(abspath(path))) try: client = new_client() - with spinner(): - client.download_configurations(path, tree_names=list(tree_names), - delete_existing_trees=delete_existing) + client.download_configurations( + path, + tree_names=list(tree_names), + delete_existing_trees=delete_existing + ) - click.secho('✅ Configuration Parameters downloaded successfully', fg='green') + spinner.text = click.style("Configurations downloaded successfully.", + fg=Colors.GREEN) + spinner.green.ok(Symbols.SUCCESS) except Exception as e: - click.secho(e, fg='red') + spinner.text = click.style(e, fg=Colors.RED) + spinner.red.fail(Symbols.ERROR) raise SystemExit(1) diff --git a/riocli/parameter/list.py b/riocli/parameter/list.py index 5be5ebee..2995cdf5 100644 --- a/riocli/parameter/list.py +++ b/riocli/parameter/list.py @@ -12,13 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. import click +from click_help_colors import HelpColorsCommand from rapyuta_io.utils.rest_client import HttpMethod +from riocli.constants import Colors from riocli.parameter.utils import _api_call from riocli.utils import tabulate_data -@click.command('list') +@click.command( + 'list', + cls=HelpColorsCommand, + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, +) def list_configuration_trees() -> None: """ List the Configuration Parameter Trees. @@ -26,12 +33,12 @@ def list_configuration_trees() -> None: try: data = _api_call(HttpMethod.GET) if 'data' not in data: - raise Exception('Something went wrong!') + raise Exception('Failed to list configurations') trees = [[tree] for tree in data['data']] tabulate_data(trees, headers=['Tree Name']) except Exception as e: - click.secho(str(e), fg='red') + click.secho(str(e), fg=Colors.RED) raise SystemExit(1) diff --git a/riocli/parameter/upload.py b/riocli/parameter/upload.py index d8f6fcaf..1cd59157 100644 --- a/riocli/parameter/upload.py +++ b/riocli/parameter/upload.py @@ -15,39 +15,63 @@ import typing import click -from click_spinner import spinner +from click_help_colors import HelpColorsCommand +from yaspin import kbi_safe_yaspin from riocli.config import new_client +from riocli.constants import Colors, Symbols from riocli.parameter.utils import filter_trees, display_trees -@click.command('upload') -@click.option('--tree-names', type=click.STRING, multiple=True, default=[], help='Directory names to upload') -@click.option('--recreate', '--delete-existing', 'delete_existing', is_flag=True, +@click.command( + 'upload', + cls=HelpColorsCommand, + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, +) +@click.option('--tree-names', type=click.STRING, multiple=True, default=[], + help='Directory names to upload') +@click.option('--recreate', '--delete-existing', 'delete_existing', + is_flag=True, help='Overwrite existing parameter tree') -@click.option('-f', '--force', '--silent', 'silent', is_flag=True, default=False, help="Skip confirmation") +@click.option('-f', '--force', '--silent', 'silent', is_flag=True, + default=False, help="Skip confirmation") @click.argument('path', type=click.Path(exists=True)) -def upload_configurations(path: str, tree_names: typing.Tuple[str] = None, delete_existing: bool = False, - silent: bool = False) -> None: +def upload_configurations( + path: str, + tree_names: typing.Tuple[str] = None, + delete_existing: bool = False, + silent: bool = False +) -> None: """ - Upload a set of Configuration Parameter directory trees. + Upload a directories as configuration parameters. """ trees = filter_trees(path, tree_names) - click.secho('Following Trees will be uploaded') - click.secho('') + click.secho('Following configuration trees will be uploaded') + click.secho() display_trees(path, trees) if not silent: click.confirm('Do you want to proceed?', default=True, abort=True) - try: - client = new_client() - with spinner(): - client.upload_configurations(rootdir=path, tree_names=trees, delete_existing_trees=delete_existing, - as_folder=True) + client = new_client() - click.secho('✅ Configuration parameters uploaded successfully', fg='green') - except Exception as e: - click.secho(str(e), fg='red') - raise SystemExit(1) + with kbi_safe_yaspin(text="Uploading configurations...", + timer=True) as spinner: + try: + client.upload_configurations( + rootdir=path, + tree_names=trees, + delete_existing_trees=delete_existing, + as_folder=True + ) + + spinner.text = click.style( + 'Configuration parameters uploaded successfully', + fg=Colors.GREEN) + spinner.green.ok(Symbols.SUCCESS) + except Exception as e: + spinner.text = click.style(e, fg=Colors.RED) + spinner.red.fail(Symbols.ERROR) + raise SystemExit(1) diff --git a/riocli/parameter/utils.py b/riocli/parameter/utils.py index 9de43fca..ed527c92 100644 --- a/riocli/parameter/utils.py +++ b/riocli/parameter/utils.py @@ -22,9 +22,13 @@ from rapyuta_io.utils import RestClient from riocli.config import Configuration +from riocli.constants import Colors -def filter_trees(root_dir: str, tree_names: typing.Tuple[str]) -> typing.List[str]: +def filter_trees( + root_dir: str, + tree_names: typing.Tuple[str] +) -> typing.List[str]: trees = [] for each in os.listdir(root_dir): full_path = os.path.join(root_dir, each) @@ -43,15 +47,19 @@ def filter_trees(root_dir: str, tree_names: typing.Tuple[str]) -> typing.List[st return trees -def display_trees(root_dir: str, trees: typing.List[str] = []) -> None: +def display_trees(root_dir: str, trees: typing.List[str]) -> None: + trees = trees or [] for each in trees: tree_out = display_tree(os.path.join(root_dir, each), string_rep=True) - click.secho(tree_out, fg='yellow') + click.secho(tree_out, fg=Colors.YELLOW) -def _api_call(method: str, name: typing.Union[str, None] = None, - payload: typing.Union[typing.Dict, None] = None, load_response: bool = True, - ) -> typing.Any: +def _api_call( + method: str, + name: typing.Union[str, None] = None, + payload: typing.Union[typing.Dict, None] = None, + load_response: bool = True, +) -> typing.Any: config = Configuration() catalog_host = config.data.get( 'core_api_host', 'https://gaapiserver.apps.rapyuta.io') @@ -59,7 +67,8 @@ def _api_call(method: str, name: typing.Union[str, None] = None, if name: url = '{}/{}'.format(url, name) headers = config.get_auth_header() - response = RestClient(url).method(method).headers( headers).execute(payload=payload) + response = RestClient(url).method(method).headers(headers).execute( + payload=payload) data = None err_msg = 'error in the api call' if load_response: @@ -77,5 +86,8 @@ def phase3(self) -> None: # shallow=False enables the behaviour of matching the File content. The # original dircmp Class only compares os.Stat between the files, and # gives no way to modify the behaviour. - f_comp = filecmp.cmpfiles(self.left, self.right, self.common_files, shallow=False) + f_comp = filecmp.cmpfiles(self.left, + self.right, + self.common_files, + shallow=False) self.same_files, self.diff_files, self.funny_files = f_comp