diff --git a/pyproject.toml b/pyproject.toml index 29bd294..bf2457a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,8 @@ dependencies = [ "peewee>=3.17.9", "peewee-migrate>=1.13.0", "platformdirs>=3.17.9", + "requests", + "packaging", ] [project.optional-dependencies] diff --git a/ra_aid/__main__.py b/ra_aid/__main__.py index 5a08a29..0aa8f95 100644 --- a/ra_aid/__main__.py +++ b/ra_aid/__main__.py @@ -30,6 +30,7 @@ from rich.text import Text from ra_aid import print_error, print_stage_header from ra_aid.__version__ import __version__ +from ra_aid.version_check import check_for_newer_version from ra_aid.agent_utils import ( create_agent, run_agent_with_retry, @@ -467,6 +468,12 @@ def main(): status = build_status(args, expert_enabled, web_research_enabled) + # Check for newer version + version_message = check_for_newer_version() + if version_message: + status.append("\n\n") + status.append(version_message, style="yellow") + console.print( Panel( status, diff --git a/ra_aid/version_check.py b/ra_aid/version_check.py new file mode 100644 index 0000000..0270f1b --- /dev/null +++ b/ra_aid/version_check.py @@ -0,0 +1,60 @@ +"""Version check module for RA.Aid.""" + +import logging +import requests +from packaging import version + +from ra_aid.__version__ import __version__ as current_version + +# URL for the latest version information +VERSION_URL = "https://docs.ra-aid.ai/version.json" + +# Set up logger +logger = logging.getLogger(__name__) + +def check_for_newer_version() -> str: + """ + Check if a newer version of RA.Aid is available. + + Makes an HTTP request to the docs site to retrieve the latest version information, + then compares it to the current version. If a newer version is available, returns + a message suggesting to upgrade. + + Returns: + str: Update message if a newer version is available, otherwise an empty string + """ + try: + # Get the latest version from the docs site + logger.debug(f"Checking for newer version at {VERSION_URL}") + response = requests.get(VERSION_URL, timeout=5) + response.raise_for_status() # Raise an exception for HTTP errors + + # Parse the response JSON + version_info = response.json() + latest_version = version_info.get("version") + + if not latest_version: + logger.warning("No version found in the version.json file") + return "" + + logger.debug(f"Current version: {current_version}, Latest version: {latest_version}") + + # Compare versions + if version.parse(latest_version) > version.parse(current_version): + logger.info(f"New version available: {latest_version}") + return (f"A new version of RA.Aid is available! Consider upgrading to {latest_version} " + "to have access to the latest features and functionality.") + + # Current version is up-to-date + logger.debug("Current version is up-to-date") + return "" + + except requests.RequestException as e: + logger.error(f"Error connecting to version check URL: {e}") + return "" + except ValueError as e: + logger.error(f"Error parsing version.json: {e}") + return "" + except Exception as e: + logger.error(f"Unexpected error during version check: {e}") + return "" \ No newline at end of file diff --git a/tests/ra_aid/test_version_check.py b/tests/ra_aid/test_version_check.py new file mode 100644 index 0000000..71fe0fa --- /dev/null +++ b/tests/ra_aid/test_version_check.py @@ -0,0 +1,94 @@ +"""Tests for version check module.""" + +from unittest.mock import Mock + +import requests +import pytest + +from ra_aid.version_check import check_for_newer_version + +def test_newer_version_available(mocker): + """Test when a newer version is available.""" + # Mock the dependencies + mocker.patch('ra_aid.version_check.current_version', '0.15.2') + mock_get = mocker.patch('ra_aid.version_check.requests.get') + + # Mock the response + mock_response = Mock() + mock_response.json.return_value = {"version": "0.16.0"} + mock_get.return_value = mock_response + + result = check_for_newer_version() + + # Check that the message contains the new version + assert "0.16.0" in result + assert "A new version of RA.Aid is available" in result + +def test_same_version(mocker): + """Test when the current version is the latest.""" + # Mock the dependencies + mocker.patch('ra_aid.version_check.current_version', '0.15.2') + mock_get = mocker.patch('ra_aid.version_check.requests.get') + + # Mock the response + mock_response = Mock() + mock_response.json.return_value = {"version": "0.15.2"} + mock_get.return_value = mock_response + + result = check_for_newer_version() + + # Check that no message is returned + assert result == "" + +def test_older_version(mocker): + """Test when the current version is newer than the latest.""" + # Mock the dependencies + mocker.patch('ra_aid.version_check.current_version', '0.15.2') + mock_get = mocker.patch('ra_aid.version_check.requests.get') + + # Mock the response + mock_response = Mock() + mock_response.json.return_value = {"version": "0.14.0"} + mock_get.return_value = mock_response + + result = check_for_newer_version() + + # Check that no message is returned + assert result == "" + +def test_connection_error(mocker): + """Test handling of connection errors.""" + # Mock a connection error + mock_get = mocker.patch('ra_aid.version_check.requests.get') + mock_get.side_effect = requests.RequestException("Connection error") + + result = check_for_newer_version() + + # Check that no message is returned + assert result == "" + +def test_json_parse_error(mocker): + """Test handling of JSON parsing errors.""" + # Mock the response with invalid JSON + mock_get = mocker.patch('ra_aid.version_check.requests.get') + mock_response = Mock() + mock_response.json.side_effect = ValueError("Invalid JSON") + mock_get.return_value = mock_response + + result = check_for_newer_version() + + # Check that no message is returned + assert result == "" + +def test_missing_version_key(mocker): + """Test handling of missing version key in JSON.""" + # Mock the response with missing version key + mock_get = mocker.patch('ra_aid.version_check.requests.get') + mock_response = Mock() + mock_response.json.return_value = {"other_key": "value"} + mock_get.return_value = mock_response + + result = check_for_newer_version() + + # Check that no message is returned + assert result == "" \ No newline at end of file