fix(cli): suppress matching release notices for dev builds (#4628)

## Related issue

N/A

## Summary

- Avoid prompting development builds such as `0.9.0.dev0` to install the matching `0.9.0` final release.
- Continue notifying development builds about later release lines and post-releases.

## Test Plan

- `uv run pytest tests/cli/test_update_check.py::test_wheel_check_no_nag_for_matching_dev_release tests/cli/test_update_check.py::test_wheel_check_nags_when_newer_release_available tests/cli/test_update_check.py::test_is_newer_pep440_ordering tests/cli/test_update_check.py::test_is_newer_tolerates_garbage tests/cli/test_update_check.py::test_should_notify_release_treats_dev_build_as_current_release`
- `uv run ruff format --check omnigent/update_check.py tests/cli/test_update_check.py`
- `uv run ruff check omnigent/update_check.py tests/cli/test_update_check.py`

## Demo

N/A

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] UI / frontend change
- [ ] Refactor / chore
- [ ] Docs
- [ ] Test / CI
- [ ] Breaking change

## Test coverage

- [x] Unit tests added / updated
- [ ] Integration tests added / updated
- [ ] E2E tests added / updated
- [ ] Manual verification completed
- [ ] Existing tests cover this change
- [ ] Not applicable

## Coverage notes

The focused wheel-notice test reproduces the `0.9.0.dev0` versus `0.9.0` scenario, while helper coverage verifies later releases still notify.

## Changelog

Development builds no longer show an update reminder for the matching final release.

Signed-off-by: Zeyi (Rice) Fan <zeyi.f@databricks.com>
This commit is contained in:
Zeyi (Rice) Fan
2026-08-11 16:40:30 -07:00
committed by GitHub
parent 4dbe1dce76
commit 20fa47c8ac
2 changed files with 65 additions and 3 deletions
+26 -1
View File
@@ -296,7 +296,7 @@ def _run_installed_wheel_check() -> None:
cache is not None
and cache.kind == "wheel"
and cache.latest_version
and _is_newer(cache.latest_version, info.package_version)
and _should_notify_release(cache.latest_version, info.package_version)
and cache.latest_version != cache.last_notified_version
):
_print_pypi_notice(info.package_version, cache.latest_version)
@@ -339,6 +339,31 @@ def _is_newer(latest: str, current: str) -> bool:
return latest != current and bool(latest)
def _should_notify_release(latest: str, current: str) -> bool:
"""Return whether the passive update notice should report *latest*.
A development build is already on its corresponding release line, so
the notice stays quiet for that line's final release. Later releases and
post-releases still produce a notice.
"""
from packaging.version import InvalidVersion, parse
try:
latest_version = parse(latest)
current_version = parse(current)
except InvalidVersion:
return _is_newer(latest, current)
if (
current_version.is_devrelease
and latest_version.epoch == current_version.epoch
and latest_version.release == current_version.release
and not latest_version.is_postrelease
):
return False
return latest_version > current_version
def _resolve_index_url() -> str:
"""Resolve the package index to query, honoring uv/pip config.
+39 -2
View File
@@ -598,6 +598,7 @@ def _write_fake_dist_info(
direct_url: dict[str, object] | None = None,
uv_cache: dict[str, object] | None = None,
dir_mtime_epoch: float | None = None,
version: str = "0.1.0",
) -> importlib.metadata.PathDistribution:
"""Build a real ``.dist-info/`` on disk and return a PathDistribution.
@@ -619,11 +620,14 @@ def _write_fake_dist_info(
:param dir_mtime_epoch: When provided, ``os.utime`` is used to
backdate the dist-info dir's mtime to this Unix timestamp —
this is the fallback signal when ``uv_cache.json`` is absent.
:param version: Installed package version written to ``METADATA``.
:returns: A ``PathDistribution`` constructed against the dir.
"""
dist_info = tmp_path / "omnigent-0.1.0.dist-info"
dist_info = tmp_path / f"omnigent-{version}.dist-info"
dist_info.mkdir()
(dist_info / "METADATA").write_text("Metadata-Version: 2.1\nName: omnigent\nVersion: 0.1.0\n")
(dist_info / "METADATA").write_text(
f"Metadata-Version: 2.1\nName: omnigent\nVersion: {version}\n"
)
if installer is not None:
(dist_info / "INSTALLER").write_text(installer + "\n")
if direct_url is not None:
@@ -1002,6 +1006,30 @@ def test_wheel_check_no_nag_when_up_to_date(
assert capsys.readouterr().err == ""
def test_wheel_check_no_nag_for_matching_dev_release(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
"""A dev build from the latest release line is already current."""
monkeypatch.delenv("OMNIGENT_NO_UPDATE_CHECK", raising=False)
_point_cache_at(tmp_path, monkeypatch)
_write_cache(
_CacheEntry(
last_check_epoch=time.time(),
commits_behind=0,
kind="wheel",
latest_version="0.9.0",
)
)
dist = _write_fake_dist_info(tmp_path, installer="uv", version="0.9.0.dev0")
monkeypatch.setattr("omnigent.update_check._get_distribution", lambda: dist)
_run_installed_wheel_check()
assert capsys.readouterr().err == ""
def test_wheel_check_nags_when_newer_release_available(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
@@ -1234,6 +1262,15 @@ def test_is_newer_tolerates_garbage() -> None:
assert _is_newer("", "0.1.0") is False
def test_should_notify_release_treats_dev_build_as_current_release() -> None:
"""Matching finals stay quiet without hiding later release lines."""
from omnigent.update_check import _should_notify_release
assert _should_notify_release("0.9.0", "0.9.0.dev0") is False
assert _should_notify_release("0.9.1", "0.9.0.dev0") is True
assert _should_notify_release("0.9.0.post1", "0.9.0.dev0") is True
class _FakeResp:
"""Minimal httpx.Response stand-in for the Simple-API parser."""