Package: httpie Followup-For: Bug #1041792 Control: tags -1 patch quick patch attached
>From 3ce3cacb8d3f7beff8d684a088ac4ecc016cc9c1 Mon Sep 17 00:00:00 2001 From: Your Name <y...@example.com> Date: Wed, 2 Aug 2023 11:23:38 +0000 Subject: [PATCH] disable updates
--- httpie/core.py | 2 - httpie/internal/daemon_runner.py | 2 - httpie/internal/update_warnings.py | 171 ------------------- httpie/manager/tasks/__init__.py | 2 - httpie/manager/tasks/check_updates.py | 10 -- tests/test_update_warnings.py | 237 -------------------------- 6 files changed, 424 deletions(-) delete mode 100644 httpie/internal/update_warnings.py delete mode 100644 httpie/manager/tasks/check_updates.py delete mode 100644 tests/test_update_warnings.py diff --git a/httpie/core.py b/httpie/core.py index d0c26dc..0755e6d 100644 --- a/httpie/core.py +++ b/httpie/core.py @@ -24,7 +24,6 @@ from .output.writer import write_message, write_stream, write_raw_data, MESSAGE_ from .plugins.registry import plugin_manager from .status import ExitStatus, http_status_to_exit_status from .utils import unwrap_context -from .internal.update_warnings import check_updates from .internal.daemon_runner import is_daemon_mode, run_daemon_task @@ -95,7 +94,6 @@ def raw_main( raise exit_status = ExitStatus.ERROR else: - check_updates(env) try: exit_status = main_program( args=parsed_args, diff --git a/httpie/internal/daemon_runner.py b/httpie/internal/daemon_runner.py index ec20b50..b2d3fb1 100644 --- a/httpie/internal/daemon_runner.py +++ b/httpie/internal/daemon_runner.py @@ -3,7 +3,6 @@ from contextlib import redirect_stderr, redirect_stdout from typing import List from httpie.context import Environment -from httpie.internal.update_warnings import _fetch_updates, _get_suppress_context from httpie.status import ExitStatus STATUS_FILE = '.httpie-test-daemon-status' @@ -23,7 +22,6 @@ def _check_status(env): DAEMONIZED_TASKS = { 'check_status': _check_status, - 'fetch_updates': _fetch_updates, } diff --git a/httpie/internal/update_warnings.py b/httpie/internal/update_warnings.py deleted file mode 100644 index a4b80d4..0000000 --- a/httpie/internal/update_warnings.py +++ /dev/null @@ -1,171 +0,0 @@ -import json -from contextlib import nullcontext, suppress -from datetime import datetime, timedelta -from pathlib import Path -from typing import Any, Optional, Callable - -import requests - -import httpie -from httpie.context import Environment, LogLevel -from httpie.internal.__build_channel__ import BUILD_CHANNEL -from httpie.internal.daemons import spawn_daemon -from httpie.utils import is_version_greater, open_with_lockfile - -# Automatically updated package version index. -PACKAGE_INDEX_LINK = 'https://packages.httpie.io/latest.json' - -FETCH_INTERVAL = timedelta(weeks=2) -WARN_INTERVAL = timedelta(weeks=1) - -UPDATE_MESSAGE_FORMAT = """\ -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. - try: - with open(file) as stream: - return json.load(stream) - except (ValueError, OSError): - return {} - - -def _fetch_updates(env: Environment) -> str: - file = env.config.version_info_file - data = _read_data_error_free(file) - - response = requests.get(PACKAGE_INDEX_LINK, verify=False) - response.raise_for_status() - - data.setdefault('last_warned_date', None) - data['last_fetched_date'] = datetime.now().isoformat() - data['last_released_versions'] = response.json() - - with open_with_lockfile(file, 'w') as stream: - json.dump(data, stream) - - -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: - if env.config.get('disable_update_warnings'): - return None - - data = _read_data_error_free(env.config.version_info_file) - - if data: - current_date = datetime.now() - last_fetched_date = datetime.fromisoformat(data['last_fetched_date']) - earliest_fetch_date = last_fetched_date + FETCH_INTERVAL - if current_date < earliest_fetch_date: - return None - - fetch_updates(env) - - -def _get_suppress_context(env: Environment) -> Any: - """Return a context manager that suppress - all possible errors. - - Note: if you have set the developer_mode=True in - your config, then it will show all errors for easier - debugging.""" - if env.config.developer_mode: - return nullcontext() - else: - return suppress(BaseException) - - -def _update_checker( - func: Callable[[Environment], None] -) -> Callable[[Environment], None]: - """Control the execution of the update checker (suppress errors, trigger - auto updates etc.)""" - - def wrapper(env: Environment) -> None: - with _get_suppress_context(env): - func(env) - - with _get_suppress_context(env): - maybe_fetch_updates(env) - - 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 - update_status = _get_update_status(env) - - if not update_status: - return None - - # 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) - - # 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() - last_warned_date = version_info['last_warned_date'] - if last_warned_date is not None: - earliest_warn_date = ( - datetime.fromisoformat(last_warned_date) + WARN_INTERVAL - ) - if current_date < earliest_warn_date: - return None - - env.log_error(update_status, level=LogLevel.INFO) - version_info['last_warned_date'] = current_date.isoformat() - - with open_with_lockfile(file, 'w') as stream: - json.dump(version_info, stream) diff --git a/httpie/manager/tasks/__init__.py b/httpie/manager/tasks/__init__.py index b9b30fb..9c591a2 100644 --- a/httpie/manager/tasks/__init__.py +++ b/httpie/manager/tasks/__init__.py @@ -1,11 +1,9 @@ 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 deleted file mode 100644 index 07fd124..0000000 --- a/httpie/manager/tasks/check_updates.py +++ /dev/null @@ -1,10 +0,0 @@ -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 deleted file mode 100644 index b2c24c3..0000000 --- a/tests/test_update_warnings.py +++ /dev/null @@ -1,237 +0,0 @@ -import json -import tempfile -import time -from contextlib import suppress -from datetime import datetime -from pathlib import Path - -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, httpie - -BUILD_CHANNEL = 'test' -BUILD_CHANNEL_2 = 'test2' -UNKNOWN_BUILD_CHANNEL = 'test3' - -HIGHEST_VERSION = '999.999.999' -LOWEST_VERSION = '1.1.1' - -FIXED_DATE = datetime(1970, 1, 1).isoformat() - -MAX_ATTEMPT = 40 -MAX_TIMEOUT = 2.0 - - -def check_update_warnings(text): - return 'A new HTTPie release' in text - - -@pytest.mark.requires_external_processes -def test_daemon_runner(): - # We have a pseudo daemon task called 'check_status' - # which creates a temp file called STATUS_FILE under - # user's temp directory. This test simply ensures that - # we create a daemon that successfully performs the - # external task. - - status_file = Path(tempfile.gettempdir()) / STATUS_FILE - with suppress(FileNotFoundError): - status_file.unlink() - - spawn_daemon('check_status') - - for attempt in range(MAX_ATTEMPT): - time.sleep(MAX_TIMEOUT / MAX_ATTEMPT) - if status_file.exists(): - break - else: - pytest.fail( - 'Maximum number of attempts failed for daemon status check.' - ) - - assert status_file.exists() - - -def test_fetch(static_fetch_data, without_warnings): - http('fetch_updates', '--daemon', env=without_warnings) - - with open(without_warnings.config.version_info_file) as stream: - version_data = json.load(stream) - - assert version_data['last_warned_date'] is None - assert version_data['last_fetched_date'] is not None - assert ( - version_data['last_released_versions'][BUILD_CHANNEL] - == HIGHEST_VERSION - ) - assert ( - version_data['last_released_versions'][BUILD_CHANNEL_2] - == LOWEST_VERSION - ) - - -def test_fetch_dont_override_existing_layout( - static_fetch_data, without_warnings -): - with open(without_warnings.config.version_info_file, 'w') as stream: - existing_layout = { - 'last_warned_date': FIXED_DATE, - 'last_fetched_date': FIXED_DATE, - 'last_released_versions': {BUILD_CHANNEL: LOWEST_VERSION}, - } - json.dump(existing_layout, stream) - - http('fetch_updates', '--daemon', env=without_warnings) - - with open(without_warnings.config.version_info_file) as stream: - version_data = json.load(stream) - - # The "last updated at" field should not be modified, but the - # rest need to be updated. - assert version_data['last_warned_date'] == FIXED_DATE - assert version_data['last_fetched_date'] != FIXED_DATE - assert ( - version_data['last_released_versions'][BUILD_CHANNEL] - == HIGHEST_VERSION - ) - - -def test_fetch_broken_json(static_fetch_data, without_warnings): - with open(without_warnings.config.version_info_file, 'w') as stream: - stream.write('$$broken$$') - - http('fetch_updates', '--daemon', env=without_warnings) - - with open(without_warnings.config.version_info_file) as stream: - version_data = json.load(stream) - - assert ( - version_data['last_released_versions'][BUILD_CHANNEL] - == HIGHEST_VERSION - ) - - -def test_check_updates_disable_warnings( - without_warnings, httpbin, fetch_update_mock -): - r = http(httpbin + '/get', env=without_warnings) - assert not fetch_update_mock.called - assert not check_update_warnings(r.stderr) - - -def test_check_updates_first_invocation( - with_warnings, httpbin, fetch_update_mock -): - r = http(httpbin + '/get', env=with_warnings) - assert fetch_update_mock.called - assert not check_update_warnings(r.stderr) - - -@pytest.mark.parametrize( - 'should_issue_warning, build_channel', - [ - (False, pytest.lazy_fixture('lower_build_channel')), - (True, pytest.lazy_fixture('higher_build_channel')), - ], -) -def test_check_updates_first_time_after_data_fetch( - with_warnings, - httpbin, - fetch_update_mock, - static_fetch_data, - should_issue_warning, - build_channel, -): - http('fetch_updates', '--daemon', env=with_warnings) - r = http(httpbin + '/get', env=with_warnings) - - assert not fetch_update_mock.called - assert (not should_issue_warning) or check_update_warnings(r.stderr) - - -def test_check_updates_first_time_after_data_fetch_unknown_build_channel( - with_warnings, - httpbin, - fetch_update_mock, - static_fetch_data, - unknown_build_channel, -): - http('fetch_updates', '--daemon', env=with_warnings) - r = http(httpbin + '/get', env=with_warnings) - - assert not fetch_update_mock.called - 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() - env.config['version_info_file'] = tmp_path / 'version.json' - env.config['disable_update_warnings'] = False - return env - - -@pytest.fixture -def without_warnings(tmp_path): - env = PersistentMockEnvironment() - env.config['version_info_file'] = tmp_path / 'version.json' - env.config['disable_update_warnings'] = True - return env - - -@pytest.fixture -def fetch_update_mock(mocker): - mock_fetch = mocker.patch('httpie.internal.update_warnings.fetch_updates') - return mock_fetch - - -@pytest.fixture -def static_fetch_data(mocker): - mock_get = mocker.patch('requests.get') - mock_get.return_value.status_code = 200 - mock_get.return_value.json.return_value = { - BUILD_CHANNEL: HIGHEST_VERSION, - BUILD_CHANNEL_2: LOWEST_VERSION, - } - return mock_get - - -@pytest.fixture -def unknown_build_channel(mocker): - mocker.patch('httpie.internal.update_warnings.BUILD_CHANNEL', UNKNOWN_BUILD_CHANNEL) - - -@pytest.fixture -def higher_build_channel(mocker): - mocker.patch('httpie.internal.update_warnings.BUILD_CHANNEL', BUILD_CHANNEL) - - -@pytest.fixture -def lower_build_channel(mocker): - mocker.patch('httpie.internal.update_warnings.BUILD_CHANNEL', BUILD_CHANNEL_2) -- 2.39.2