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:
@@ -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.
|
||||
|
||||
|
||||
@@ -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."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user