From 8da6441c87532d20d3c8dad538e46dc99d073927 Mon Sep 17 00:00:00 2001 From: Dmitrii Agibov Date: Tue, 13 Dec 2022 09:39:21 +0000 Subject: MLIA-460 Revisit backend management - Provide command for backend installation in case if backend is not available - Fix issue with connection timeout during downloading - Show installation tools output only in verbose mode Change-Id: Ic0e495ba19879cc2cda4fd0bce20b57ba896cfeb --- src/mlia/backend/errors.py | 12 ++++++++++ src/mlia/backend/tosa_checker/compat.py | 6 ++--- src/mlia/cli/main.py | 9 ++++++++ src/mlia/utils/download.py | 3 ++- src/mlia/utils/py_manager.py | 41 ++++++++++++++++++++++++--------- tests/test_backend_tosa_compat.py | 5 +++- tests/test_cli_main.py | 29 +++++++++++++++++++++++ tests/test_utils_py_manager.py | 30 +++++++++++++++--------- 8 files changed, 108 insertions(+), 27 deletions(-) create mode 100644 src/mlia/backend/errors.py diff --git a/src/mlia/backend/errors.py b/src/mlia/backend/errors.py new file mode 100644 index 0000000..bd5da95 --- /dev/null +++ b/src/mlia/backend/errors.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: Copyright 2022, Arm Limited and/or its affiliates. +# SPDX-License-Identifier: Apache-2.0 +"""Backend errors.""" + + +class BackendUnavailableError(Exception): + """Backend unavailable error.""" + + def __init__(self, msg: str, backend: str) -> None: + """Init error.""" + super().__init__(msg) + self.backend = backend diff --git a/src/mlia/backend/tosa_checker/compat.py b/src/mlia/backend/tosa_checker/compat.py index e1bcb24..bd21774 100644 --- a/src/mlia/backend/tosa_checker/compat.py +++ b/src/mlia/backend/tosa_checker/compat.py @@ -8,6 +8,7 @@ from typing import Any from typing import cast from typing import Protocol +from mlia.backend.errors import BackendUnavailableError from mlia.core.typing import PathOrFileLike @@ -45,9 +46,8 @@ def get_tosa_compatibility_info( checker = get_tosa_checker(tflite_model_path) if checker is None: - raise Exception( - "TOSA checker is not available. " - "Please make sure that 'tosa-checker' backend is installed." + raise BackendUnavailableError( + "Backend tosa-checker is not available", "tosa-checker" ) ops = [ diff --git a/src/mlia/cli/main.py b/src/mlia/cli/main.py index 33a44e1..98fdb63 100644 --- a/src/mlia/cli/main.py +++ b/src/mlia/cli/main.py @@ -11,6 +11,7 @@ from inspect import signature from pathlib import Path from mlia import __version__ +from mlia.backend.errors import BackendUnavailableError from mlia.cli.commands import all_tests from mlia.cli.commands import backend_install from mlia.cli.commands import backend_list @@ -225,6 +226,14 @@ def run_command(args: argparse.Namespace) -> int: logger.error("Internal error: %s", err) except ConfigurationError as err: logger.error(err) + except BackendUnavailableError as err: + logger.error("Error: Backend %s is not available.", err.backend) + # apart from tosa-checker all other backends are currently optional + if err.backend in ("tosa-checker",): + logger.error( + 'Please use next command to install it: mlia-backend install "%s"', + err.backend, + ) except Exception as err: # pylint: disable=broad-except logger.error( "\nExecution finished with error: %s", diff --git a/src/mlia/utils/download.py b/src/mlia/utils/download.py index c8d0b69..e00be28 100644 --- a/src/mlia/utils/download.py +++ b/src/mlia/utils/download.py @@ -46,9 +46,10 @@ def download( show_progress: bool = False, label: str | None = None, chunk_size: int = 8192, + timeout: int = 30, ) -> None: """Download the file.""" - with requests.get(url, stream=True, timeout=10.0) as resp: + with requests.get(url, stream=True, timeout=timeout) as resp: resp.raise_for_status() content_chunks = resp.iter_content(chunk_size=chunk_size) diff --git a/src/mlia/utils/py_manager.py b/src/mlia/utils/py_manager.py index 5f98fcc..d7821d3 100644 --- a/src/mlia/utils/py_manager.py +++ b/src/mlia/utils/py_manager.py @@ -3,10 +3,16 @@ """Util functions for managing python packages.""" from __future__ import annotations +import logging +import subprocess # nosec import sys from importlib.metadata import distribution from importlib.metadata import PackageNotFoundError -from subprocess import check_call # nosec + +from mlia.core.errors import InternalError + + +logger = logging.getLogger(__name__) class PyPackageManager: @@ -45,16 +51,29 @@ class PyPackageManager: """Execute pip command.""" assert sys.executable, "Unable to launch pip command" - check_call( - [ - sys.executable, - "-m", - "pip", - "--disable-pip-version-check", - subcommand, - *params, - ] - ) + try: + output = subprocess.check_output( # nosec + [ + sys.executable, + "-m", + "pip", + "--disable-pip-version-check", + subcommand, + *params, + ], + stderr=subprocess.STDOUT, + text=True, + ) + returncode = 0 + except subprocess.CalledProcessError as err: + output = err.output + returncode = err.returncode + + for line in output.splitlines(): + logger.debug(line.rstrip()) + + if returncode != 0: + raise InternalError("Unable to install python package") def get_package_manager() -> PyPackageManager: diff --git a/tests/test_backend_tosa_compat.py b/tests/test_backend_tosa_compat.py index 4c4dc5a..52aaa44 100644 --- a/tests/test_backend_tosa_compat.py +++ b/tests/test_backend_tosa_compat.py @@ -10,6 +10,7 @@ from unittest.mock import MagicMock import pytest +from mlia.backend.errors import BackendUnavailableError from mlia.backend.tosa_checker.compat import get_tosa_compatibility_info from mlia.backend.tosa_checker.compat import Operator from mlia.backend.tosa_checker.compat import TOSACompatibilityInfo @@ -31,7 +32,9 @@ def test_compatibility_check_should_fail_if_checker_not_available( """Test that compatibility check should fail if TOSA checker is not available.""" replace_get_tosa_checker_with_mock(monkeypatch, None) - with pytest.raises(Exception, match="TOSA checker is not available"): + with pytest.raises( + BackendUnavailableError, match="Backend tosa-checker is not available" + ): get_tosa_compatibility_info(test_tflite_model) diff --git a/tests/test_cli_main.py b/tests/test_cli_main.py index 5f6beb6..925f1e4 100644 --- a/tests/test_cli_main.py +++ b/tests/test_cli_main.py @@ -15,10 +15,12 @@ from unittest.mock import MagicMock import pytest import mlia +from mlia.backend.errors import BackendUnavailableError from mlia.cli.main import backend_main from mlia.cli.main import CommandInfo from mlia.cli.main import main from mlia.core.context import ExecutionContext +from mlia.core.errors import InternalError from tests.utils.logging import clear_loggers @@ -358,6 +360,33 @@ def test_commands_execution_backend_main( MagicMock(side_effect=KeyboardInterrupt()), ["Execution has been interrupted"], ], + [ + False, + MagicMock( + side_effect=BackendUnavailableError( + "Backend sample is not available", "sample" + ) + ), + ["Error: Backend sample is not available."], + ], + [ + False, + MagicMock( + side_effect=BackendUnavailableError( + "Backend tosa-checker is not available", "tosa-checker" + ) + ), + [ + "Error: Backend tosa-checker is not available.", + "Please use next command to install it: " + 'mlia-backend install "tosa-checker"', + ], + ], + [ + False, + MagicMock(side_effect=InternalError("Unknown error")), + ["Internal error: Unknown error"], + ], ], ) def test_verbose_output( diff --git a/tests/test_utils_py_manager.py b/tests/test_utils_py_manager.py index e41680d..c9047f1 100644 --- a/tests/test_utils_py_manager.py +++ b/tests/test_utils_py_manager.py @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: Copyright 2022, Arm Limited and/or its affiliates. # SPDX-License-Identifier: Apache-2.0 """Tests for python package manager.""" +import subprocess # nosec import sys from unittest.mock import MagicMock @@ -16,13 +17,16 @@ def test_get_package_manager() -> None: assert isinstance(manager, PyPackageManager) -@pytest.fixture(name="mock_check_call") -def mock_check_call_fixture(monkeypatch: pytest.MonkeyPatch) -> MagicMock: +@pytest.fixture(name="mock_check_output") +def mock_check_output_fixture(monkeypatch: pytest.MonkeyPatch) -> MagicMock: """Mock check_call function.""" - mock_check_call = MagicMock() - monkeypatch.setattr("mlia.utils.py_manager.check_call", mock_check_call) + mock_check_output = MagicMock() - return mock_check_call + monkeypatch.setattr( + "mlia.utils.py_manager.subprocess.check_output", mock_check_output + ) + + return mock_check_output def test_py_package_manager_metadata() -> None: @@ -32,14 +36,14 @@ def test_py_package_manager_metadata() -> None: assert manager.packages_installed(["pytest", "mlia"]) -def test_py_package_manager_install(mock_check_call: MagicMock) -> None: +def test_py_package_manager_install(mock_check_output: MagicMock) -> None: """Test package installation.""" manager = PyPackageManager() with pytest.raises(ValueError, match="No package names provided"): manager.install([]) manager.install(["mlia", "pytest"]) - mock_check_call.assert_called_once_with( + mock_check_output.assert_called_once_with( [ sys.executable, "-m", @@ -48,18 +52,20 @@ def test_py_package_manager_install(mock_check_call: MagicMock) -> None: "install", "mlia", "pytest", - ] + ], + stderr=subprocess.STDOUT, + text=True, ) -def test_py_package_manager_uninstall(mock_check_call: MagicMock) -> None: +def test_py_package_manager_uninstall(mock_check_output: MagicMock) -> None: """Test package removal.""" manager = PyPackageManager() with pytest.raises(ValueError, match="No package names provided"): manager.uninstall([]) manager.uninstall(["mlia", "pytest"]) - mock_check_call.assert_called_once_with( + mock_check_output.assert_called_once_with( [ sys.executable, "-m", @@ -69,5 +75,7 @@ def test_py_package_manager_uninstall(mock_check_call: MagicMock) -> None: "--yes", "mlia", "pytest", - ] + ], + stderr=subprocess.STDOUT, + text=True, ) -- cgit v1.2.1