From 366b0c3fcd1219e51ecfc5b0248ce0879d8e1ab8 Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Fri, 29 Apr 2022 14:35:12 +0300 Subject: [PATCH] `httpie cli check-updates` --- docs/README.md | 8 ++++ httpie/internal/update_warnings.py | 67 +++++++++++++++++++-------- httpie/manager/cli.py | 3 ++ httpie/manager/tasks/__init__.py | 2 + httpie/manager/tasks/check_updates.py | 10 ++++ tests/test_update_warnings.py | 25 +++++++++- 6 files changed, 95 insertions(+), 20 deletions(-) create mode 100644 httpie/manager/tasks/check_updates.py diff --git a/docs/README.md b/docs/README.md index 5fcc8105..92f50405 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2402,6 +2402,14 @@ This command is currently in beta. ### `httpie cli` +#### `httpie cli check-updates` + +You can check whether a new update is available for your system by running `httpie cli check-updates`: + +```bash-termible +$ httpie cli check-updates +```` + #### `httpie cli export-args` `httpie cli export-args` command can expose the parser specification of `http`/`https` commands diff --git a/httpie/internal/update_warnings.py b/httpie/internal/update_warnings.py index fc1e7682..31da8846 100644 --- a/httpie/internal/update_warnings.py +++ b/httpie/internal/update_warnings.py @@ -2,7 +2,7 @@ import json from contextlib import nullcontext, suppress from datetime import datetime, timedelta from pathlib import Path -from typing import Any, Callable +from typing import Any, Optional, Callable import requests @@ -23,6 +23,10 @@ A new HTTPie release ({last_released_version}) is available. To see how you can update, please visit https://httpie.io/docs/cli/{installation_method} """ +ALREADY_UP_TO_DATE_MESSAGE = """\ +You are already up-to-date. +""" + def _read_data_error_free(file: Path) -> Any: # If the file is broken / non-existent, ignore it. @@ -48,8 +52,11 @@ def _fetch_updates(env: Environment) -> str: json.dump(data, stream) -def fetch_updates(): - spawn_daemon('fetch_updates') +def fetch_updates(env: Environment, lazy: bool = True): + if lazy: + spawn_daemon('fetch_updates') + else: + _fetch_updates(env) def maybe_fetch_updates(env: Environment) -> None: @@ -65,7 +72,7 @@ def maybe_fetch_updates(env: Environment) -> None: if current_date < earliest_fetch_date: return None - fetch_updates() + fetch_updates(env) def _get_suppress_context(env: Environment) -> Any: @@ -97,13 +104,48 @@ def _update_checker( return wrapper +def _get_update_status(env: Environment) -> Optional[str]: + """If there is a new update available, return the warning text. + Otherwise just return None.""" + file = env.config.version_info_file + if not file.exists(): + return None + + with _get_suppress_context(env): + # If the user quickly spawns multiple httpie processes + # we don't want to end in a race. + with open_with_lockfile(file) as stream: + version_info = json.load(stream) + + available_channels = version_info['last_released_versions'] + if BUILD_CHANNEL not in available_channels: + return None + + current_version = httpie.__version__ + last_released_version = available_channels[BUILD_CHANNEL] + if not is_version_greater(last_released_version, current_version): + return None + + text = UPDATE_MESSAGE_FORMAT.format( + last_released_version=last_released_version, + installation_method=BUILD_CHANNEL, + ) + return text + + +def get_update_status(env: Environment) -> str: + return _get_update_status(env) or ALREADY_UP_TO_DATE_MESSAGE + + @_update_checker def check_updates(env: Environment) -> None: if env.config.get('disable_update_warnings'): return None file = env.config.version_info_file - if not file.exists(): + update_status = _get_update_status(env) + + if not update_status: return None # If the user quickly spawns multiple httpie processes @@ -111,10 +153,6 @@ def check_updates(env: Environment) -> None: with open_with_lockfile(file) as stream: version_info = json.load(stream) - available_channels = version_info['last_released_versions'] - if BUILD_CHANNEL not in available_channels: - return None - # We don't want to spam the user with too many warnings, # so we'll only warn every once a while (WARN_INTERNAL). current_date = datetime.now() @@ -126,16 +164,7 @@ def check_updates(env: Environment) -> None: if current_date < earliest_warn_date: return None - current_version = httpie.__version__ - last_released_version = available_channels[BUILD_CHANNEL] - if not is_version_greater(last_released_version, current_version): - return None - - text = UPDATE_MESSAGE_FORMAT.format( - last_released_version=last_released_version, - installation_method=BUILD_CHANNEL, - ) - env.log_error(text, level=Levels.WARNING) + env.log_error(update_status, level=Levels.WARNING) version_info['last_warned_date'] = current_date.isoformat() with open_with_lockfile(file, 'w') as stream: diff --git a/httpie/manager/cli.py b/httpie/manager/cli.py index c36ee813..f264c156 100644 --- a/httpie/manager/cli.py +++ b/httpie/manager/cli.py @@ -23,6 +23,9 @@ COMMANDS = { 'default': 'json' } ], + 'check-updates': [ + 'Check for updates' + ], 'sessions': { 'help': 'Manage HTTPie sessions', 'upgrade': [ diff --git a/httpie/manager/tasks/__init__.py b/httpie/manager/tasks/__init__.py index 9c591a24..b9b30fb3 100644 --- a/httpie/manager/tasks/__init__.py +++ b/httpie/manager/tasks/__init__.py @@ -1,9 +1,11 @@ from httpie.manager.tasks.sessions import cli_sessions from httpie.manager.tasks.export_args import cli_export_args from httpie.manager.tasks.plugins import cli_plugins +from httpie.manager.tasks.check_updates import cli_check_updates CLI_TASKS = { 'sessions': cli_sessions, 'export-args': cli_export_args, 'plugins': cli_plugins, + 'check-updates': cli_check_updates } diff --git a/httpie/manager/tasks/check_updates.py b/httpie/manager/tasks/check_updates.py new file mode 100644 index 00000000..07fd1240 --- /dev/null +++ b/httpie/manager/tasks/check_updates.py @@ -0,0 +1,10 @@ +import argparse +from httpie.context import Environment +from httpie.status import ExitStatus +from httpie.internal.update_warnings import fetch_updates, get_update_status + + +def cli_check_updates(env: Environment, args: argparse.Namespace) -> ExitStatus: + fetch_updates(env, lazy=False) + env.stdout.write(get_update_status(env)) + return ExitStatus.SUCCESS diff --git a/tests/test_update_warnings.py b/tests/test_update_warnings.py index 9b0400c8..b2c24c36 100644 --- a/tests/test_update_warnings.py +++ b/tests/test_update_warnings.py @@ -9,8 +9,9 @@ import pytest from httpie.internal.daemon_runner import STATUS_FILE from httpie.internal.daemons import spawn_daemon +from httpie.status import ExitStatus -from .utils import PersistentMockEnvironment, http +from .utils import PersistentMockEnvironment, http, httpie BUILD_CHANNEL = 'test' BUILD_CHANNEL_2 = 'test2' @@ -166,6 +167,28 @@ def test_check_updates_first_time_after_data_fetch_unknown_build_channel( assert not check_update_warnings(r.stderr) +def test_cli_check_updates( + static_fetch_data, higher_build_channel +): + r = httpie('cli', 'check-updates') + assert r.exit_status == ExitStatus.SUCCESS + assert check_update_warnings(r) + + +@pytest.mark.parametrize( + "build_channel", [ + pytest.lazy_fixture("lower_build_channel"), + pytest.lazy_fixture("unknown_build_channel") + ] +) +def test_cli_check_updates_not_shown( + static_fetch_data, build_channel +): + r = httpie('cli', 'check-updates') + assert r.exit_status == ExitStatus.SUCCESS + assert not check_update_warnings(r) + + @pytest.fixture def with_warnings(tmp_path): env = PersistentMockEnvironment()