From 9f8646482645356646585d7b327e116d9d367ec1 Mon Sep 17 00:00:00 2001 From: imSp4rky Date: Tue, 27 Oct 2026 13:38:57 -0600 Subject: [PATCH] fix(api): cache the health update check and fix a remote-session test fixture --- docs/dev/rest-api/endpoints.md | 2 ++ tests/conftest.py | 5 +---- tests/test_client_session_auth.py | 1 + unshackle/core/api/routes.py | 2 +- unshackle/core/update_checker.py | 26 -------------------------- 5 files changed, 5 insertions(+), 31 deletions(-) diff --git a/docs/dev/rest-api/endpoints.md b/docs/dev/rest-api/endpoints.md index 6fcdd0df..f9f9f52b 100644 --- a/docs/dev/rest-api/endpoints.md +++ b/docs/dev/rest-api/endpoints.md @@ -58,6 +58,8 @@ Health check. **This is the only route exempt from authentication**: you can cal `draining` is `true` while the server refuses new remote sessions and jobs before a restart. +The server asks GitHub for the latest release at most once per [`update_check_interval`](../../reference/configuration/misc.md#caching-updates) hours and answers from its cache between checks. + If the update check fails (for example, no network), `update_available` and `latest_version` are `null` while `current_version` still reports the installed version. | Status | Meaning | diff --git a/tests/conftest.py b/tests/conftest.py index cf15bab4..9f2f616f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -37,10 +37,7 @@ def loopback_only(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatc def no_update_check(monkeypatch: pytest.MonkeyPatch) -> None: """Keep the release lookup of the health route off the network.""" - async def no_release(*args: Any, **kwargs: Any) -> None: - return None - - monkeypatch.setattr(UpdateChecker, "check_for_updates", no_release) + monkeypatch.setattr(UpdateChecker, "check_for_updates_sync", lambda *args, **kwargs: None) @pytest.fixture(autouse=True) diff --git a/tests/test_client_session_auth.py b/tests/test_client_session_auth.py index 27b64bdc..18c3354e 100644 --- a/tests/test_client_session_auth.py +++ b/tests/test_client_session_auth.py @@ -121,6 +121,7 @@ def test_remote_get_tracks_scopes_server_cookies(): service.__dict__.update( _tracks_by_title={}, _chapters_by_title={}, + _title_alias={}, _sample_decrypt_tracks=set(), _server_cdm=None, _session=requests.Session(), diff --git a/unshackle/core/api/routes.py b/unshackle/core/api/routes.py index 8dbfa9d3..7c63cfa2 100644 --- a/unshackle/core/api/routes.py +++ b/unshackle/core/api/routes.py @@ -223,7 +223,7 @@ async def health(request: web.Request) -> web.Response: nullable: true """ try: - latest_version = await UpdateChecker.check_for_updates(__version__) + latest_version = await asyncio.to_thread(UpdateChecker.check_for_updates_sync, __version__) update_info = { "update_available": latest_version is not None, "current_version": __version__, diff --git a/unshackle/core/update_checker.py b/unshackle/core/update_checker.py index ede03f77..1d43e85e 100644 --- a/unshackle/core/update_checker.py +++ b/unshackle/core/update_checker.py @@ -1,6 +1,5 @@ from __future__ import annotations -import asyncio import json import time from pathlib import Path @@ -195,31 +194,6 @@ class UpdateChecker: except (ValueError, AttributeError): return False - @classmethod - async def check_for_updates(cls, current_version: str) -> Optional[str]: - """ - Examine GitHub for a newer version. - - Args: - current_version: The current version string (e.g., "1.1.0") - - Returns: - The latest version string if an update is available, None otherwise - """ - if not cls.is_valid_version(current_version): - return None - - try: - latest_version = await asyncio.to_thread(cls.fetch_latest_version) - - if latest_version and cls.compare_versions(current_version, latest_version): - return latest_version - - except Exception: - pass - - return None - @classmethod def get_cached_update_info(cls, current_version: str) -> Optional[str]: """