From 92c214d20808dfb8bfbfb1a6383724236a6c1f1d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 6 Aug 2026 13:01:21 +0000 Subject: [PATCH] Remove duplicate update_checker from src/jabs/utils src/jabs/utils/update_checker.py was a byte-for-byte duplicate of packages/jabs-core/src/jabs/core/utils/update_checker.py apart from how it resolved the version string (jabs.version.version_str() vs. version_str("jabs-behavior-classifier"), which are equivalent). The jabs-core copy is the one exported by jabs.core.utils, but the GUI was still importing the stale root copy via jabs.utils. Delete the root copy and re-export the two helpers from jabs-core in src/jabs/utils/__init__.py, so jabs.utils.check_for_update and jabs.utils.is_pypi_install (used by menu_handlers and update_check_dialog) keep working unchanged. Move tests/utils/test_update_checker.py to packages/jabs-core/tests/ and retarget its patch paths, so the surviving implementation stays covered by the jabs-core suite that CI runs. Also switch the two log calls in the surviving module to lazy %s formatting, per the logging conventions in CLAUDE.md. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01MhEg7sSojc4dxBh7FbtZjW --- .../src/jabs/core/utils/update_checker.py | 4 +- .../jabs-core/tests}/test_update_checker.py | 22 ++++---- src/jabs/utils/__init__.py | 8 ++- src/jabs/utils/update_checker.py | 53 ------------------- 4 files changed, 19 insertions(+), 68 deletions(-) rename {tests/utils => packages/jabs-core/tests}/test_update_checker.py (91%) delete mode 100644 src/jabs/utils/update_checker.py diff --git a/packages/jabs-core/src/jabs/core/utils/update_checker.py b/packages/jabs-core/src/jabs/core/utils/update_checker.py index 71700765..7ef71ca1 100644 --- a/packages/jabs-core/src/jabs/core/utils/update_checker.py +++ b/packages/jabs-core/src/jabs/core/utils/update_checker.py @@ -33,7 +33,7 @@ def check_for_update() -> tuple[bool, str | None, str]: has_update = parse_version(latest_version) > parse_version(current_version) return has_update, latest_version, current_version except Exception as e: - logger.warning(f"Failed to check for updates: {e}") + logger.warning("Failed to check for updates: %s", e) return False, None, version_str() @@ -49,5 +49,5 @@ def is_pypi_install() -> bool: installer = dist.read_text("INSTALLER") return installer is not None and installer.strip() in ("pip", "uv") except Exception as e: - logger.debug(f"Could not determine installation method: {e}") + logger.debug("Could not determine installation method: %s", e) return False diff --git a/tests/utils/test_update_checker.py b/packages/jabs-core/tests/test_update_checker.py similarity index 91% rename from tests/utils/test_update_checker.py rename to packages/jabs-core/tests/test_update_checker.py index dc462c1e..284fc606 100644 --- a/tests/utils/test_update_checker.py +++ b/packages/jabs-core/tests/test_update_checker.py @@ -1,10 +1,10 @@ -"""Unit tests for jabs.utils.update_checker module.""" +"""Unit tests for jabs.core.utils.update_checker module.""" import json from importlib import metadata from unittest.mock import MagicMock, Mock, patch -from jabs.utils.update_checker import check_for_update, is_pypi_install +from jabs.core.utils.update_checker import check_for_update, is_pypi_install class TestCheckForUpdate: @@ -18,7 +18,7 @@ def test_update_available(self): mock_response.__exit__ = Mock(return_value=False) with ( - patch("jabs.utils.update_checker.version_str", return_value="0.9.0"), + patch("jabs.core.utils.update_checker.version_str", return_value="0.9.0"), patch("urllib.request.urlopen", return_value=mock_response), ): has_update, latest, current = check_for_update() @@ -35,7 +35,7 @@ def test_no_update_available(self): mock_response.__exit__ = Mock(return_value=False) with ( - patch("jabs.utils.update_checker.version_str", return_value="1.0.0"), + patch("jabs.core.utils.update_checker.version_str", return_value="1.0.0"), patch("urllib.request.urlopen", return_value=mock_response), ): has_update, latest, current = check_for_update() @@ -52,7 +52,7 @@ def test_newer_local_version(self): mock_response.__exit__ = Mock(return_value=False) with ( - patch("jabs.utils.update_checker.version_str", return_value="2.0.0"), + patch("jabs.core.utils.update_checker.version_str", return_value="2.0.0"), patch("urllib.request.urlopen", return_value=mock_response), ): has_update, latest, current = check_for_update() @@ -64,7 +64,7 @@ def test_newer_local_version(self): def test_network_error(self): """Test handling of network errors when checking for updates.""" with ( - patch("jabs.utils.update_checker.version_str", return_value="1.0.0"), + patch("jabs.core.utils.update_checker.version_str", return_value="1.0.0"), patch( "urllib.request.urlopen", side_effect=Exception("Network error"), @@ -84,7 +84,7 @@ def test_invalid_json_response(self): mock_response.__exit__ = Mock(return_value=False) with ( - patch("jabs.utils.update_checker.version_str", return_value="1.0.0"), + patch("jabs.core.utils.update_checker.version_str", return_value="1.0.0"), patch("urllib.request.urlopen", return_value=mock_response), ): has_update, latest, current = check_for_update() @@ -101,7 +101,7 @@ def test_missing_version_in_response(self): mock_response.__exit__ = Mock(return_value=False) with ( - patch("jabs.utils.update_checker.version_str", return_value="1.0.0"), + patch("jabs.core.utils.update_checker.version_str", return_value="1.0.0"), patch("urllib.request.urlopen", return_value=mock_response), ): has_update, latest, current = check_for_update() @@ -118,7 +118,7 @@ def test_timeout_handling(self): mock_response.__exit__ = Mock(return_value=False) with ( - patch("jabs.utils.update_checker.version_str", return_value="1.0.0"), + patch("jabs.core.utils.update_checker.version_str", return_value="1.0.0"), patch("urllib.request.urlopen", return_value=mock_response) as mock_urlopen, ): check_for_update() @@ -136,7 +136,7 @@ def test_version_comparison_with_prereleases(self): mock_response.__exit__ = Mock(return_value=False) with ( - patch("jabs.utils.update_checker.version_str", return_value="0.9.0"), + patch("jabs.core.utils.update_checker.version_str", return_value="0.9.0"), patch("urllib.request.urlopen", return_value=mock_response), ): has_update, latest, current = check_for_update() @@ -153,7 +153,7 @@ def test_pypi_api_url(self): mock_response.__exit__ = Mock(return_value=False) with ( - patch("jabs.utils.update_checker.version_str", return_value="1.0.0"), + patch("jabs.core.utils.update_checker.version_str", return_value="1.0.0"), patch("urllib.request.urlopen", return_value=mock_response) as mock_urlopen, ): check_for_update() diff --git a/src/jabs/utils/__init__.py b/src/jabs/utils/__init__.py index 91a408c0..4385f351 100644 --- a/src/jabs/utils/__init__.py +++ b/src/jabs/utils/__init__.py @@ -1,6 +1,10 @@ -"""JABS utilities""" +"""JABS utilities. -from .update_checker import check_for_update, is_pypi_install +The update-check helpers live in ``jabs-core`` and are re-exported here so that +``from jabs.utils import check_for_update`` keeps working for the GUI. +""" + +from jabs.core.utils import check_for_update, is_pypi_install # a hard coded random seed used for the final training done with all # training data before saving the classifier diff --git a/src/jabs/utils/update_checker.py b/src/jabs/utils/update_checker.py deleted file mode 100644 index 285de2fe..00000000 --- a/src/jabs/utils/update_checker.py +++ /dev/null @@ -1,53 +0,0 @@ -"""Utilities for checking PyPI for JABS updates.""" - -import json -import logging -import urllib.request -from importlib import metadata - -from packaging.version import parse as parse_version - -from jabs.version import version_str - -logger = logging.getLogger(__name__) - - -def check_for_update() -> tuple[bool, str | None, str]: - """Check PyPI for newer version of jabs-behavior-classifier. - - Returns: - tuple: (has_update: bool, latest_version: str | None, current_version: str) - - has_update: True if a newer version is available - - latest_version: Latest version string from PyPI, or None if check failed - - current_version: Current installed version string - """ - try: - current_version = version_str() - - with urllib.request.urlopen( - "https://pypi.org/pypi/jabs-behavior-classifier/json", timeout=5 - ) as response: - data = json.loads(response.read()) - latest_version = data["info"]["version"] - - has_update = parse_version(latest_version) > parse_version(current_version) - return has_update, latest_version, current_version - except Exception as e: - logger.warning(f"Failed to check for updates: {e}") - return False, None, version_str() - - -def is_pypi_install() -> bool: - """Check if jabs-behavior-classifier was installed from PyPI. - - Returns: - bool: True if installed via pip from PyPI, False otherwise - """ - try: - dist = metadata.distribution("jabs-behavior-classifier") - # Check if installer was pip - installer = dist.read_text("INSTALLER") - return installer is not None and installer.strip() in ("pip", "uv") - except Exception as e: - logger.debug(f"Could not determine installation method: {e}") - return False