mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-22 05:22:09 +00:00
feat(banner): check PyPI for updates when not a git install
For pip-installed hermes-agent (no .git directory), fall back to querying PyPI's JSON API to compare __version__ against the latest published release, using stdlib only (urllib + json, no packaging dep).
This commit is contained in:
parent
3215ef1609
commit
384ec9684e
2 changed files with 81 additions and 2 deletions
|
|
@ -175,6 +175,49 @@ def _check_via_local_git(repo_dir: Path) -> Optional[int]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _version_tuple(v: str) -> tuple[int, ...]:
|
||||||
|
"""Parse '0.13.0' into (0, 13, 0) for comparison. Non-numeric segments become 0."""
|
||||||
|
parts = []
|
||||||
|
for segment in v.split("."):
|
||||||
|
try:
|
||||||
|
parts.append(int(segment))
|
||||||
|
except ValueError:
|
||||||
|
parts.append(0)
|
||||||
|
return tuple(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_pypi_latest(package: str = "hermes-agent") -> Optional[str]:
|
||||||
|
"""Fetch the latest version of a package from PyPI. Returns None on failure."""
|
||||||
|
try:
|
||||||
|
import urllib.request
|
||||||
|
import json as _json
|
||||||
|
url = f"https://pypi.org/pypi/{package}/json"
|
||||||
|
req = urllib.request.Request(url, headers={"Accept": "application/json"})
|
||||||
|
with urllib.request.urlopen(req, timeout=5) as resp:
|
||||||
|
data = _json.loads(resp.read())
|
||||||
|
return data.get("info", {}).get("version")
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _check_via_pypi() -> Optional[int]:
|
||||||
|
"""Compare installed version against PyPI latest.
|
||||||
|
|
||||||
|
Returns 0 if up-to-date, 1 if behind, None on failure.
|
||||||
|
"""
|
||||||
|
latest = _fetch_pypi_latest()
|
||||||
|
if latest is None:
|
||||||
|
return None
|
||||||
|
if latest == VERSION:
|
||||||
|
return 0
|
||||||
|
try:
|
||||||
|
if _version_tuple(latest) > _version_tuple(VERSION):
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
except Exception:
|
||||||
|
return 1 if latest != VERSION else 0
|
||||||
|
|
||||||
|
|
||||||
def check_for_updates() -> Optional[int]:
|
def check_for_updates() -> Optional[int]:
|
||||||
"""Check whether a Hermes update is available.
|
"""Check whether a Hermes update is available.
|
||||||
|
|
||||||
|
|
@ -213,8 +256,9 @@ def check_for_updates() -> Optional[int]:
|
||||||
if not (repo_dir / ".git").exists():
|
if not (repo_dir / ".git").exists():
|
||||||
repo_dir = hermes_home / "hermes-agent"
|
repo_dir = hermes_home / "hermes-agent"
|
||||||
if not (repo_dir / ".git").exists():
|
if not (repo_dir / ".git").exists():
|
||||||
return None
|
behind = _check_via_pypi()
|
||||||
behind = _check_via_local_git(repo_dir)
|
else:
|
||||||
|
behind = _check_via_local_git(repo_dir)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cache_file.write_text(json.dumps({"ts": now, "behind": behind, "rev": embedded_rev}))
|
cache_file.write_text(json.dumps({"ts": now, "behind": behind, "rev": embedded_rev}))
|
||||||
|
|
|
||||||
35
tests/hermes_cli/test_banner_pip_update.py
Normal file
35
tests/hermes_cli/test_banner_pip_update.py
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_via_pypi_detects_update():
|
||||||
|
"""_check_via_pypi returns 1 when PyPI has newer version."""
|
||||||
|
from hermes_cli.banner import _check_via_pypi
|
||||||
|
with patch("hermes_cli.banner.VERSION", "0.12.0"):
|
||||||
|
with patch("hermes_cli.banner._fetch_pypi_latest", return_value="0.13.0"):
|
||||||
|
result = _check_via_pypi()
|
||||||
|
assert result == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_via_pypi_up_to_date():
|
||||||
|
"""_check_via_pypi returns 0 when versions match."""
|
||||||
|
from hermes_cli.banner import _check_via_pypi
|
||||||
|
with patch("hermes_cli.banner.VERSION", "0.13.0"):
|
||||||
|
with patch("hermes_cli.banner._fetch_pypi_latest", return_value="0.13.0"):
|
||||||
|
result = _check_via_pypi()
|
||||||
|
assert result == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_via_pypi_network_failure():
|
||||||
|
"""_check_via_pypi returns None on network error."""
|
||||||
|
from hermes_cli.banner import _check_via_pypi
|
||||||
|
with patch("hermes_cli.banner._fetch_pypi_latest", return_value=None):
|
||||||
|
result = _check_via_pypi()
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_version_tuple_comparison():
|
||||||
|
"""Version comparison works with multi-segment versions."""
|
||||||
|
from hermes_cli.banner import _version_tuple
|
||||||
|
assert _version_tuple("0.13.0") > _version_tuple("0.12.0")
|
||||||
|
assert _version_tuple("0.13.0") == _version_tuple("0.13.0")
|
||||||
|
assert _version_tuple("1.0.0") > _version_tuple("0.99.99")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue