From e28758dc0d9580090c19dfd87b40f328bf2e377a Mon Sep 17 00:00:00 2001 From: BartoszCki Date: Thu, 13 Jun 2019 18:53:34 +0200 Subject: [PATCH 1/2] Add checking for new version --- gradient/__init__.py | 29 ++++++++++ gradient/cli/cli.py | 2 +- gradient/logger.py | 4 +- gradient/version_checker.py | 47 ++++++++++++++++ tests/functional/test_machines.py | 2 +- tests/unit/test_version_checker.py | 90 ++++++++++++++++++++++++++++++ 6 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 gradient/version_checker.py create mode 100644 tests/unit/test_version_checker.py diff --git a/gradient/__init__.py b/gradient/__init__.py index 73303b2b..12425c74 100644 --- a/gradient/__init__.py +++ b/gradient/__init__.py @@ -1,12 +1,41 @@ +import platform + from gradient_statsd import Client as StatsdClient from .config import config from .login import login, logout from .utils import print_json_pretty from .cli.cli import cli as _cli_entry_point +from .version_checker import look_for_new_version _ = StatsdClient # to keep import safe from "Optimize Imports", auto code cleanup, etc. +def _look_for_new_version_with_timeout(): + if not platform.system() == "Linux": + look_for_new_version() + return + + import signal + + class TimeoutError(Exception): + pass + + def handler(signum, frame): + raise TimeoutError + + signal.signal(signal.SIGALRM, handler) + signal.alarm(1) + + try: + look_for_new_version() + except TimeoutError: + pass + + signal.alarm(0) + + def main(): + _look_for_new_version_with_timeout() _cli_entry_point() + diff --git a/gradient/cli/cli.py b/gradient/cli/cli.py index 3c1367df..bfe73740 100644 --- a/gradient/cli/cli.py +++ b/gradient/cli/cli.py @@ -19,7 +19,7 @@ def cli(): @cli.command("version", help="Show the version and exit") -def version(): +def get_version(): command = login_commands.ShowVersionCommand() command.execute() diff --git a/gradient/logger.py b/gradient/logger.py index 01b3829f..f09ef56a 100644 --- a/gradient/logger.py +++ b/gradient/logger.py @@ -54,9 +54,9 @@ def log_error_response(data): error(str(message)) -def debug(messages): +def debug(message): if config.DEBUG: - log("DEBUG: {}".format(messages)) + log("DEBUG: {}".format(message)) def log_response(response, success_msg, error_msg): diff --git a/gradient/version_checker.py b/gradient/version_checker.py new file mode 100644 index 00000000..e485d13b --- /dev/null +++ b/gradient/version_checker.py @@ -0,0 +1,47 @@ +import six + +if six.PY2: + import xmlrpclib +else: + import xmlrpc.client as xmlrpclib + +from distutils.version import StrictVersion + +from gradient import logger +from gradient.version import version + + +class PackageNotFoundError(Exception): + pass + + +class VersionChecker(object): + def is_up_to_date(self, module_name, current_version): + version_in_repository = self.get_version_from_repository(module_name) + + up_to_date = StrictVersion(current_version) >= StrictVersion(version_in_repository) + return up_to_date, version_in_repository + + def get_version_from_repository(self, module_name, repository_url="http://pypi.python.org/pypi"): + pypi = xmlrpclib.ServerProxy(repository_url) + versions = pypi.package_releases(module_name) + if not versions: + raise PackageNotFoundError("Package {} not found".format(module_name)) + + return versions[0] + + +def look_for_new_version(): + vc = VersionChecker() + try: + g = vc.is_up_to_date("gradient", version) + up_to_date, version_from_repository = g + except Exception as e: + logger.debug(e) + return + + if not up_to_date: + msg = "Warning: this version of the Gradient CLI ({current_version}) is out of date. " \ + "Some functionality might not be supported until you upgrade. \n\n" \ + "Run `pip install -U gradient` to upgrade\n".format(current_version=version) + logger.warning(msg) diff --git a/tests/functional/test_machines.py b/tests/functional/test_machines.py index e86c60d7..ed766002 100644 --- a/tests/functional/test_machines.py +++ b/tests/functional/test_machines.py @@ -820,7 +820,7 @@ def test_should_send_valid_post_request_and_print_table_when_machines_list_was_u assert result.exit_code == 0 @mock.patch("gradient.client.requests.get") - def test_should_send_valid_post_request_when_machines_list_was_used_with_api_key_option(self, get_patched): + def test_should_send_valid_post_request_when_machines_show_was_used_with_api_key_option(self, get_patched): get_patched.return_value = MockResponse(json_data=self.EXPECTED_RESPONSE_JSON, status_code=200) cli_runner = CliRunner() diff --git a/tests/unit/test_version_checker.py b/tests/unit/test_version_checker.py new file mode 100644 index 00000000..56c9a52b --- /dev/null +++ b/tests/unit/test_version_checker.py @@ -0,0 +1,90 @@ +import mock +import pytest + +from gradient import version_checker + + +@mock.patch("gradient.version_checker.VersionChecker.is_up_to_date") +@mock.patch("gradient.version_checker.logger") +def test_should_check_for_new_gradient_version_and_print_proper_warning_when_current_version_is_old(logger_patched, + is_up_to_date_patched): + is_up_to_date_patched.return_value = (False, "1.2.3") + + version_checker.look_for_new_version() + + is_up_to_date_patched.assert_called_once() + logger_patched.warning.assert_called_once_with( + "Warning: this version of the Gradient CLI (0.2.0a0) is out of date. " + "Some functionality might not be supported until you upgrade. \n\n" + "Run `pip install -U gradient` to upgrade\n") + + +@mock.patch("gradient.version_checker.VersionChecker.is_up_to_date") +@mock.patch("gradient.version_checker.logger") +def test_should_check_for_new_gradient_version_and_not_print_anything_when_current_version_is_latest(logger_patched, + is_up_to_date_patched): + is_up_to_date_patched.return_value = (True, "1.2.3") + + version_checker.look_for_new_version() + + is_up_to_date_patched.assert_called_once() + logger_patched.warning.assert_not_called() + + +@mock.patch("gradient.version_checker.xmlrpclib.ServerProxy") +def test_should_return_package_version_when_get_version_was_run(sever_proxy_class_patched): + pypi_patched = mock.MagicMock() + pypi_patched.package_releases.return_value = ["1.2.3"] + sever_proxy_class_patched.return_value = pypi_patched + + vc = version_checker.VersionChecker() + version = vc.get_version_from_repository("some_module_name") + + sever_proxy_class_patched.assert_called_with("http://pypi.python.org/pypi") + assert version == "1.2.3" + + +@mock.patch("gradient.version_checker.xmlrpclib.ServerProxy") +def test_should_raise_proper_exception_when_get_version_was_run_and_package_was_not_found(sever_proxy_class_patched): + pypi_patched = mock.MagicMock() + pypi_patched.package_releases.return_value = [] + sever_proxy_class_patched.return_value = pypi_patched + + vc = version_checker.VersionChecker() + with pytest.raises(version_checker.PackageNotFoundError): + vc.get_version_from_repository("some_module_name") + + sever_proxy_class_patched.assert_called_with("http://pypi.python.org/pypi") + + +@mock.patch("gradient.version_checker.VersionChecker.get_version_from_repository") +def test_should_return_true_when_current_version_equals_latest_from_pypi(get_version_patched): + get_version_patched.return_value = "1.2.3" + + vc = version_checker.VersionChecker() + up_to_date, version_in_repository = vc.is_up_to_date("some_module_name", "1.2.3") + + assert up_to_date + assert version_in_repository == "1.2.3" + + +@mock.patch("gradient.version_checker.VersionChecker.get_version_from_repository") +def test_should_return_true_when_current_version_is_higher_than_latest_from_pypi(get_version_patched): + get_version_patched.return_value = "1.2.3" + + vc = version_checker.VersionChecker() + up_to_date, version_in_repository = vc.is_up_to_date("some_module_name", "1.2.4a0") + + assert up_to_date + assert version_in_repository == "1.2.3" + + +@mock.patch("gradient.version_checker.VersionChecker.get_version_from_repository") +def test_should_return_false_when_current_version_is_lower_than_latest_from_pypi(get_version_patched): + get_version_patched.return_value = "1.2.3" + + vc = version_checker.VersionChecker() + up_to_date, version_in_repository = vc.is_up_to_date("some_module_name", "1.2.1") + + assert not up_to_date + assert version_in_repository == "1.2.3" From de86befcad7245de41f5e65e4bf83b5a8a2dc4e5 Mon Sep 17 00:00:00 2001 From: BartoszCki Date: Mon, 17 Jun 2019 14:56:26 +0200 Subject: [PATCH 2/2] Fix autocomplete - broken by checking version --- gradient/__init__.py | 31 +----------- gradient/version_checker.py | 77 ++++++++++++++++++++++-------- tests/unit/test_version_checker.py | 25 ++++++---- 3 files changed, 75 insertions(+), 58 deletions(-) diff --git a/gradient/__init__.py b/gradient/__init__.py index 12425c74..690e4c35 100644 --- a/gradient/__init__.py +++ b/gradient/__init__.py @@ -1,41 +1,14 @@ -import platform - +from gradient import version_checker from gradient_statsd import Client as StatsdClient from .config import config from .login import login, logout from .utils import print_json_pretty from .cli.cli import cli as _cli_entry_point -from .version_checker import look_for_new_version _ = StatsdClient # to keep import safe from "Optimize Imports", auto code cleanup, etc. -def _look_for_new_version_with_timeout(): - if not platform.system() == "Linux": - look_for_new_version() - return - - import signal - - class TimeoutError(Exception): - pass - - def handler(signum, frame): - raise TimeoutError - - signal.signal(signal.SIGALRM, handler) - signal.alarm(1) - - try: - look_for_new_version() - except TimeoutError: - pass - - signal.alarm(0) - - def main(): - _look_for_new_version_with_timeout() + version_checker.GradientVersionChecker.look_for_new_version_with_timeout() _cli_entry_point() - diff --git a/gradient/version_checker.py b/gradient/version_checker.py index e485d13b..4a990bd7 100644 --- a/gradient/version_checker.py +++ b/gradient/version_checker.py @@ -1,15 +1,17 @@ +import platform +import sys +from distutils.version import StrictVersion + import six +from gradient import logger +from gradient.version import version + if six.PY2: import xmlrpclib else: import xmlrpc.client as xmlrpclib -from distutils.version import StrictVersion - -from gradient import logger -from gradient.version import version - class PackageNotFoundError(Exception): pass @@ -31,17 +33,54 @@ def get_version_from_repository(self, module_name, repository_url="http://pypi.p return versions[0] -def look_for_new_version(): - vc = VersionChecker() - try: - g = vc.is_up_to_date("gradient", version) - up_to_date, version_from_repository = g - except Exception as e: - logger.debug(e) - return - - if not up_to_date: - msg = "Warning: this version of the Gradient CLI ({current_version}) is out of date. " \ - "Some functionality might not be supported until you upgrade. \n\n" \ - "Run `pip install -U gradient` to upgrade\n".format(current_version=version) - logger.warning(msg) +class GradientVersionChecker(object): + @classmethod + def look_for_new_version_with_timeout(cls): + if not cls._should_check_version(): + return + + if not platform.system() == "Linux": + cls.look_for_new_version() + return + + import signal + + class TimeoutError(Exception): + pass + + def handler(signum, frame): + raise TimeoutError + + signal.signal(signal.SIGALRM, handler) + signal.alarm(1) + + try: + cls.look_for_new_version() + except TimeoutError: + pass + + signal.alarm(0) + + @staticmethod + def look_for_new_version(): + vc = VersionChecker() + try: + up_to_date, version_from_repository = vc.is_up_to_date("gradient", version) + except Exception as e: + logger.debug(e) + return + + if not up_to_date: + msg = "Warning: this version of the Gradient CLI ({current_version}) is out of date. " \ + "Some functionality might not be supported until you upgrade. \n\n" \ + "Run `pip install -U gradient` to upgrade\n".format(current_version=version) + logger.warning(msg) + + @staticmethod + def _should_check_version(): + if not hasattr(sys.stdin, "isatty"): + return False + if not sys.stdin.isatty() or not sys.stdout.isatty(): + return False + + return True diff --git a/tests/unit/test_version_checker.py b/tests/unit/test_version_checker.py index 56c9a52b..a15a8092 100644 --- a/tests/unit/test_version_checker.py +++ b/tests/unit/test_version_checker.py @@ -2,30 +2,35 @@ import pytest from gradient import version_checker +from gradient.version import version +@mock.patch("gradient.version_checker.GradientVersionChecker._should_check_version") @mock.patch("gradient.version_checker.VersionChecker.is_up_to_date") @mock.patch("gradient.version_checker.logger") -def test_should_check_for_new_gradient_version_and_print_proper_warning_when_current_version_is_old(logger_patched, - is_up_to_date_patched): +def test_should_check_for_new_gradient_version_and_print_proper_warning_when_current_version_is_old( + logger_patched, is_up_to_date_patched, should_check_patched): + should_check_patched.return_value = True is_up_to_date_patched.return_value = (False, "1.2.3") - version_checker.look_for_new_version() + version_checker.GradientVersionChecker.look_for_new_version() is_up_to_date_patched.assert_called_once() logger_patched.warning.assert_called_once_with( - "Warning: this version of the Gradient CLI (0.2.0a0) is out of date. " + "Warning: this version of the Gradient CLI ({}) is out of date. " "Some functionality might not be supported until you upgrade. \n\n" - "Run `pip install -U gradient` to upgrade\n") + "Run `pip install -U gradient` to upgrade\n".format(version)) +@mock.patch("gradient.version_checker.GradientVersionChecker._should_check_version") @mock.patch("gradient.version_checker.VersionChecker.is_up_to_date") @mock.patch("gradient.version_checker.logger") -def test_should_check_for_new_gradient_version_and_not_print_anything_when_current_version_is_latest(logger_patched, - is_up_to_date_patched): +def test_should_check_for_new_gradient_version_and_not_print_anything_when_current_version_is_latest( + logger_patched, is_up_to_date_patched, should_check_patched): + should_check_patched.return_value = True is_up_to_date_patched.return_value = (True, "1.2.3") - version_checker.look_for_new_version() + version_checker.GradientVersionChecker.look_for_new_version() is_up_to_date_patched.assert_called_once() logger_patched.warning.assert_not_called() @@ -38,10 +43,10 @@ def test_should_return_package_version_when_get_version_was_run(sever_proxy_clas sever_proxy_class_patched.return_value = pypi_patched vc = version_checker.VersionChecker() - version = vc.get_version_from_repository("some_module_name") + latest_version = vc.get_version_from_repository("some_module_name") sever_proxy_class_patched.assert_called_with("http://pypi.python.org/pypi") - assert version == "1.2.3" + assert latest_version == "1.2.3" @mock.patch("gradient.version_checker.xmlrpclib.ServerProxy")