From 64c8696c7419c953c77512429c37639bd74d8c74 Mon Sep 17 00:00:00 2001 From: Duygu Hasan <87015481+duyguHsnHsn@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:59:55 +0300 Subject: [PATCH] vdk-control-cli: pass the printer class in JobDeploy creation and add additional memory printer (#2477) What:Introduced an enhanced feature, the MemoryPrinter, it conserves text for future use, instead of instant printing. Along with this, adapted the JobDeploy class to receive a printer object instead of an OutputFormat enum. Why: The inclusion of MemoryPrinter caters to use cases where an output needs to be stored for later representation, such as in a Jupyter UI. This modification in the JobDeploy class enables the printer object's use beyond JobDeploy: the printer can be initialised first, utilised within JobDeploy, and then accessed again to retrieve the stored output. This stops the need for accessing private fields within JobDeploy and promotes better coding practices. Signed-off-by: Duygu Hasan [hduygu@vmware.com](mailto:hduygu@vmware.com) --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../control/command_groups/job/deploy_cli.py | 3 +- .../command_groups/job/deploy_cli_impl.py | 20 +++--- .../internal/control/utils/output_printer.py | 34 +++++++++- .../control/utils/test_output_printer.py | 63 ++++++++++++++++--- 4 files changed, 100 insertions(+), 20 deletions(-) diff --git a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/deploy_cli.py b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/deploy_cli.py index 11db40e654..1fd06309ed 100644 --- a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/deploy_cli.py +++ b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/deploy_cli.py @@ -9,6 +9,7 @@ from vdk.internal.control.command_groups.job.deploy_cli_impl import JobDeploy from vdk.internal.control.configuration.defaults_config import load_default_team_name from vdk.internal.control.utils import cli_utils +from vdk.internal.control.utils import output_printer from vdk.internal.control.utils.cli_utils import get_or_prompt @@ -169,7 +170,7 @@ def deploy( rest_api_url: str, output: str, ): - cmd = JobDeploy(rest_api_url, output) + cmd = JobDeploy(rest_api_url, output_printer.create_printer(output)) if operation == DeployOperation.UPDATE.value or enabled is not None: name = get_or_prompt("Job Name", name) team = get_or_prompt("Job Team", team) diff --git a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/deploy_cli_impl.py b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/deploy_cli_impl.py index 02887739f7..721c822b88 100644 --- a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/deploy_cli_impl.py +++ b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/job/deploy_cli_impl.py @@ -19,9 +19,10 @@ from vdk.internal.control.job.job_config import JobConfig from vdk.internal.control.rest_lib.factory import ApiClientFactory from vdk.internal.control.rest_lib.rest_client_errors import ApiClientErrorDecorator -from vdk.internal.control.utils import output_printer from vdk.internal.control.utils.cli_utils import get_or_prompt -from vdk.internal.control.utils.output_printer import OutputFormat +from vdk.internal.control.utils.output_printer import Printer +from vdk.internal.control.utils.output_printer import PrinterJson +from vdk.internal.control.utils.output_printer import PrinterText log = logging.getLogger(__name__) @@ -30,7 +31,7 @@ class JobDeploy: ZIP_ARCHIVE_TYPE = "zip" ARCHIVE_SUFFIX = "-archive" - def __init__(self, rest_api_url: str, output_format: str): + def __init__(self, rest_api_url: str, printer: Printer): self.deploy_api = ApiClientFactory(rest_api_url).get_deploy_api() self.jobs_api = ApiClientFactory(rest_api_url).get_jobs_api() self.job_sources_api = ApiClientFactory(rest_api_url).get_jobs_sources_api() @@ -38,8 +39,7 @@ def __init__(self, rest_api_url: str, output_format: str): # Ultimately this will be user facing parameter (possibly fetched from config.ini) self.__deployment_id = "production" self.__job_archive = JobArchive() - self.__output_format = output_format - self.__printer = output_printer.create_printer(self.__output_format) + self.__printer = printer @staticmethod def __detect_keytab_files_in_job_directory(job_path: str) -> None: @@ -193,7 +193,7 @@ def __update_deployment(self, name: str, team: str, deployment: DataJobDeploymen self.deploy_api.deployment_update( team_name=team, job_name=name, data_job_deployment=deployment ) - if self.__output_format == OutputFormat.TEXT.value: + if isinstance(self.__printer, PrinterText): log.info( f"Request to deploy Data Job {name} using version {deployment.job_version} finished successfully.\n" f"It would take a few minutes for the Data Job to be deployed in the server.\n" @@ -239,7 +239,7 @@ def show(self, name: str, team: str) -> None: ), deployments, ) - if self.__output_format == OutputFormat.TEXT.value: + if isinstance(self.__printer, PrinterText): click.echo( "You can compare the version seen here to the one seen when " "deploying to verify your deployment was successful." @@ -283,7 +283,7 @@ def create( "Team Name", team or job_config.get_team() or load_default_team_name() ) - if self.__output_format == OutputFormat.TEXT.value: + if isinstance(self.__printer, PrinterText): log.info( f"Deploy Data Job with name {name} from directory {job_path} ... \n" ) @@ -294,10 +294,10 @@ def create( try: job_archive_binary = self.__archive_binary(archive_path) - if self.__output_format == OutputFormat.TEXT.value: + if isinstance(self.__printer, PrinterText): log.info("Uploading the data job might take some time ...") with click_spinner.spinner( - disable=(self.__output_format == OutputFormat.JSON.value) + disable=(isinstance(self.__printer, PrinterJson)) ): data_job_version = self.job_sources_api.sources_upload( team_name=team, diff --git a/projects/vdk-control-cli/src/vdk/internal/control/utils/output_printer.py b/projects/vdk-control-cli/src/vdk/internal/control/utils/output_printer.py index 9959910af7..96fd15d612 100644 --- a/projects/vdk-control-cli/src/vdk/internal/control/utils/output_printer.py +++ b/projects/vdk-control-cli/src/vdk/internal/control/utils/output_printer.py @@ -1,6 +1,7 @@ # Copyright 2023-2023 VMware, Inc. # SPDX-License-Identifier: Apache-2.0 import abc +import io import json from enum import Enum from enum import unique @@ -60,7 +61,7 @@ def decorator(cls): @printer("text") -class _PrinterText(Printer): +class PrinterText(Printer): def print_table(self, table: Optional[List[Dict[str, Any]]]) -> None: if table and len(table) > 0: click.echo(tabulate(table, headers="keys", tablefmt="fancy_grid")) @@ -93,7 +94,7 @@ def json_serial(obj): @printer("json") -class _PrinterJson(Printer): +class PrinterJson(Printer): def print_table(self, data: List[Dict[str, Any]]) -> None: if data: click.echo(json_format(data)) @@ -107,6 +108,35 @@ def print_dict(self, data: Dict[str, Any]) -> None: click.echo("{}") +class InMemoryTextPrinter(Printer): + def __init__(self): + self.__output_buffer = io.StringIO() + + def print_table(self, table: Optional[List[Dict[str, Any]]]) -> None: + if table and len(table) > 0: + print( + tabulate(table, headers="keys", tablefmt="fancy_grid"), + file=self.__output_buffer, + ) + else: + print("No Data.", file=self.__output_buffer) + + def print_dict(self, data: Optional[Dict[str, Any]]) -> None: + if data: + print( + tabulate( + [[k, v] for k, v in data.items()], + headers=("key", "value"), + ), + file=self.__output_buffer, + ) + else: + print("No Data.", file=self.__output_buffer) + + def get_memory(self): + return self.__output_buffer.getvalue() + + def create_printer(output_format: str) -> Printer: """ Creates a printer instance for the given output format. diff --git a/projects/vdk-control-cli/tests/vdk/internal/control/utils/test_output_printer.py b/projects/vdk-control-cli/tests/vdk/internal/control/utils/test_output_printer.py index 96a198299f..8a1a865f12 100644 --- a/projects/vdk-control-cli/tests/vdk/internal/control/utils/test_output_printer.py +++ b/projects/vdk-control-cli/tests/vdk/internal/control/utils/test_output_printer.py @@ -1,5 +1,6 @@ # Copyright 2023-2023 VMware, Inc. # SPDX-License-Identifier: Apache-2.0 +import unittest from typing import Any from typing import Dict from typing import List @@ -7,16 +8,17 @@ import pytest from vdk.internal.control.utils import output_printer -from vdk.internal.control.utils.output_printer import _PrinterJson -from vdk.internal.control.utils.output_printer import _PrinterText from vdk.internal.control.utils.output_printer import create_printer +from vdk.internal.control.utils.output_printer import InMemoryTextPrinter from vdk.internal.control.utils.output_printer import Printer +from vdk.internal.control.utils.output_printer import PrinterJson +from vdk.internal.control.utils.output_printer import PrinterText class TestPrinterText: def test_print_dict(self): with patch("click.echo") as mock_echo: - printer = _PrinterText() + printer = PrinterText() data = {"key": "value"} printer.print_dict(data) @@ -26,7 +28,7 @@ def test_print_dict(self): def test_print_table_with_data(self): with patch("click.echo") as mock_echo: - printer = _PrinterText() + printer = PrinterText() data = [{"key1": "value1", "key2": 2}, {"key1": "value3", "key2": 4}] @@ -45,7 +47,7 @@ def test_print_table_with_data(self): def test_print_table_with_no_data(self): with patch("click.echo") as mock_echo: - printer = _PrinterText() + printer = PrinterText() data = [] printer.print_table(data) @@ -57,7 +59,7 @@ def test_print_table_with_no_data(self): class TestPrinterJson: def test_print_dict(self): with patch("click.echo") as mock_echo: - printer = _PrinterJson() + printer = PrinterJson() data = {"key": "value"} @@ -68,7 +70,7 @@ def test_print_dict(self): def test_print_table(self): with patch("click.echo") as mock_echo: - printer = _PrinterJson() + printer = PrinterJson() data = [ {"key1": "value1", "key2": "value2"}, {"key1": "value3", "key2": "value4"}, @@ -79,6 +81,53 @@ def test_print_table(self): mock_echo.assert_called_once_with(expected_output) +class TestMemoryPrinter(unittest.TestCase): + def setUp(self): + self.printer = InMemoryTextPrinter() + + def test_print_dict(self): + data = {"key": "value"} + + self.printer.print_dict(data) + + output = self.printer.get_memory().strip() + + self.assertIn("key", output) + self.assertIn("value", output) + + def test_print_table(self): + data = [ + {"key1": "value1", "key2": "value2"}, + {"key1": "value3", "key2": "value4"}, + ] + self.printer.print_table(data) + + output = self.printer.get_memory().strip() + + self.assertIn("key1", output) + self.assertIn("key2", output) + self.assertIn("value1", output) + self.assertIn("value2", output) + self.assertIn("value3", output) + self.assertIn("value4", output) + + def test_print_dict_no_data(self): + self.printer.print_dict(None) + + expected_output = "No Data." + actual_output = self.printer.get_memory().strip() + + self.assertEqual(actual_output, expected_output) + + def test_print_table_no_data(self): + self.printer.print_table(None) + + expected_output = "No Data." + actual_output = self.printer.get_memory().strip() + + self.assertEqual(actual_output, expected_output) + + class TestCreatePrinter: def test_create_printer_with_registered_format(self): class MockPrinter(Printer):