|
| 1 | +# Copyright (c) Microsoft Corporation. |
| 2 | +# Licensed under the MIT License. |
| 3 | + |
| 4 | +""" |
| 5 | +Version update checking for Fabric CLI. |
| 6 | +
|
| 7 | +This module checks PyPI for newer versions of ms-fabric-cli and displays |
| 8 | +a notification to the user if an update is available. |
| 9 | +""" |
| 10 | + |
| 11 | +from typing import Optional |
| 12 | + |
| 13 | +import requests |
| 14 | + |
| 15 | +from fabric_cli import __version__ |
| 16 | +from fabric_cli.core import fab_constant, fab_logger, fab_state_config |
| 17 | +from fabric_cli.utils import fab_ui |
| 18 | + |
| 19 | + |
| 20 | +def _fetch_latest_version_from_pypi() -> Optional[str]: |
| 21 | + """ |
| 22 | + Fetch the latest version from PyPI JSON API. |
| 23 | +
|
| 24 | + Returns: |
| 25 | + Latest version string if successful, None otherwise. |
| 26 | + """ |
| 27 | + try: |
| 28 | + response = requests.get( |
| 29 | + fab_constant.VERSION_CHECK_PYPI_URL, |
| 30 | + timeout=fab_constant.VERSION_CHECK_TIMEOUT_SECONDS |
| 31 | + ) |
| 32 | + response.raise_for_status() |
| 33 | + return response.json()["info"]["version"] |
| 34 | + except (requests.RequestException, KeyError, ValueError, TypeError) as e: |
| 35 | + # Silently fail - don't interrupt user experience for version checks |
| 36 | + fab_logger.log_debug(f"Failed to fetch version from PyPI: {e}") |
| 37 | + return None |
| 38 | + |
| 39 | + |
| 40 | +def _is_pypi_version_newer(pypi_version: str) -> bool: |
| 41 | + """ |
| 42 | + Compare PyPI version with current version to determine if an update is available. |
| 43 | +
|
| 44 | + Args: |
| 45 | + pypi_version: Version string from PyPI |
| 46 | +
|
| 47 | + Returns: |
| 48 | + True if PyPI version is newer than current installed version |
| 49 | + """ |
| 50 | + try: |
| 51 | + # Parse versions as tuples (e.g., "1.3.0" -> (1, 3, 0)) |
| 52 | + current_parts = tuple(int(x) for x in __version__.split(".")) |
| 53 | + pypi_parts = tuple(int(x) for x in pypi_version.split(".")) |
| 54 | + return pypi_parts > current_parts |
| 55 | + except (ValueError, AttributeError): |
| 56 | + # Conservative: don't show notification version could not be parsed |
| 57 | + return False |
| 58 | + |
| 59 | + |
| 60 | +def check_and_notify_update() -> None: |
| 61 | + """ |
| 62 | + Check for CLI updates and display notification if a newer version is available. |
| 63 | +
|
| 64 | + This function: |
| 65 | + - Respects user's check_updates config setting |
| 66 | + - Checks PyPI on every login for the latest version |
| 67 | + - Displays notification if an update is available |
| 68 | + - Fails silently if PyPI is unreachable |
| 69 | + """ |
| 70 | + check_enabled = fab_state_config.get_config(fab_constant.FAB_CHECK_UPDATES) |
| 71 | + if check_enabled == "false": |
| 72 | + fab_logger.log_debug("Version check disabled by user configuration") |
| 73 | + return |
| 74 | + |
| 75 | + fab_logger.log_debug("Checking PyPI for latest version") |
| 76 | + latest_version = _fetch_latest_version_from_pypi() |
| 77 | + |
| 78 | + if latest_version and _is_pypi_version_newer(latest_version): |
| 79 | + msg = ( |
| 80 | + f"\n[notice] A new release of fab is available: {__version__} → {latest_version}\n" |
| 81 | + "[notice] To update, run: pip install --upgrade ms-fabric-cli\n" |
| 82 | + ) |
| 83 | + fab_ui.print_grey(msg) |
| 84 | + elif latest_version: |
| 85 | + fab_logger.log_debug(f"Already on latest version: {__version__}") |
| 86 | + else: |
| 87 | + fab_logger.log_debug("Could not fetch latest version from PyPI") |
0 commit comments