Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eufy_sync/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Sync Eufy smart scale body composition data to Garmin Connect and Strava."""

__version__ = "1.14.0"
__version__ = "1.15.0"

# Public API for programmatic use
from eufy_sync.eufy_client import EufyClient, EufyMeasurement
Expand Down
2 changes: 1 addition & 1 deletion eufy_sync/cli/doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ def _check_version(report) -> None:
if latest is None:
report("WARN", "version", "could not check")
return
if latest == __version__:
if not updater.is_newer(latest, __version__):
report("PASS", "version", f"{__version__} (up to date)")
return
report(
Expand Down
19 changes: 19 additions & 0 deletions eufy_sync/cli/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,21 @@ def _print_summary(
)


def _retry_queue_line(state, user) -> str | None:
"""'2 uploads waiting to retry (Garmin)', or None when nothing waits.
Targets no longer configured are left out: nothing will retry them."""
waiting = {
target: count
for target, count in state.waiting_upload_retries(user.name).items()
if getattr(user, target, None) is not None
}
total = sum(waiting.values())
if not total:
return None
names = ", ".join(target.capitalize() for target in sorted(waiting))
return f"{total} upload{'' if total == 1 else 's'} waiting to retry ({names})"


def _show_status(state, users: list) -> None:
"""Print detailed sync status for all users."""
for user in users:
Expand All @@ -118,6 +133,10 @@ def _show_status(state, users: list) -> None:
else:
print("Last synced measurement: never")

retry_line = _retry_queue_line(state, user)
if retry_line:
print(retry_line)

# Eufy token health
from eufy_sync.eufy_client import EufyClient
eufy_status = EufyClient(user.eufy).token_status()
Expand Down
32 changes: 20 additions & 12 deletions eufy_sync/cli/updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,23 @@
from eufy_sync.cli import shared


def _parse(v: str) -> tuple:
# Compare numeric prefix only - tolerates suffixes like "1.7.2rc1" or "1.7.2.dev0".
if not v or len(v) > 64:
raise ValueError(f"Implausible version string: {v!r}")
match = re.match(r"^\d+(?:\.\d+)*", v)
if not match:
raise ValueError(f"Implausible version string: {v!r}")
return tuple(int(x) for x in match.group(0).split("."))


def is_newer(latest: str, current: str) -> bool:
"""True when latest is a strictly newer release than current. An install
ahead of PyPI (a fresh release the index hasn't caught up with, or a dev
checkout) is never offered a downgrade."""
return _parse(latest) > _parse(current)


def _latest_pypi_version() -> str | None:
"""Return the latest eufy-sync version on PyPI, or None if unreachable."""
try:
Expand Down Expand Up @@ -44,15 +61,6 @@ def _check_for_updates() -> None:

from eufy_sync import __version__

def _parse(v: str) -> tuple:
# Compare numeric prefix only - tolerates suffixes like "1.7.2rc1" or "1.7.2.dev0".
if not v or len(v) > 64:
raise ValueError(f"Implausible version string: {v!r}")
match = re.match(r"^\d+(?:\.\d+)*", v)
if not match:
raise ValueError(f"Implausible version string: {v!r}")
return tuple(int(x) for x in match.group(0).split("."))

latest_parsed = _parse(latest)
current_parsed = _parse(__version__)

Expand Down Expand Up @@ -91,12 +99,12 @@ def _self_update() -> None:
if latest is None:
print("Could not reach PyPI. Check your connection and try again.")
return
if latest == __version__:
print(f"Already on the latest version (v{__version__}).")
return
if not re.match(r"^\d+(?:\.\d+)*", latest):
print(f"Unexpected version from PyPI ({latest!r}); update manually with pipx.")
return
if not is_newer(latest, __version__):
print(f"Already on the latest version (v{__version__}).")
return

# A uv or pipx reinstall replaces the whole tool venv, so the pin has to
# carry the browser extra along for anyone who opted into it, or the
Expand Down
Loading
Loading