- Retry GitHub pyproject version fetch up to 3 times on failure - Fall back to Comfy Registry install endpoint when GitHub is unavailable - Avoid caching failed version checks, so stale unavailable states do not persist - Add targeted tests for registry fallback and failed-check caching behavior
197 lines
6.9 KiB
Python
197 lines
6.9 KiB
Python
"""Project version discovery and update checks."""
|
|
|
|
import os
|
|
import re
|
|
import threading
|
|
import time
|
|
from typing import Any
|
|
|
|
from .log_system import create_module_logger
|
|
|
|
PROJECT_VERSION_FILE = os.path.join(
|
|
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
|
"pyproject.toml",
|
|
)
|
|
PROJECT_GITHUB_URL = "https://github.com/Azornes/Comfyui-Model-Resolver"
|
|
PROJECT_GITHUB_PYPROJECT_URL = (
|
|
"https://raw.githubusercontent.com/Azornes/Comfyui-Model-Resolver/main/pyproject.toml"
|
|
)
|
|
PROJECT_REGISTRY_NODE_ID = "Comfyui-Model-Resolver"
|
|
PROJECT_REGISTRY_INSTALL_URL = (
|
|
f"https://api.comfy.org/nodes/{PROJECT_REGISTRY_NODE_ID}/install"
|
|
)
|
|
PROJECT_VERSION_CACHE_TTL_SECONDS = 6 * 60 * 60
|
|
PROJECT_VERSION_REQUEST_TIMEOUT_SECONDS = 5
|
|
PROJECT_VERSION_MAX_ATTEMPTS = 3
|
|
_project_version_cache = {"checked_at": 0.0, "latest_version": None}
|
|
_project_version_cache_lock = threading.Lock()
|
|
log = create_module_logger(__name__)
|
|
|
|
|
|
def _extract_project_version(text: str) -> str:
|
|
"""Extract the package version from a pyproject.toml document."""
|
|
match = re.search(r"(?m)^\s*version\s*=\s*[\"']([^\"']+)[\"']\s*$", str(text or ""))
|
|
return match.group(1).strip() if match else ""
|
|
|
|
|
|
def _get_local_project_version() -> str:
|
|
try:
|
|
with open(PROJECT_VERSION_FILE, encoding="utf-8") as version_file:
|
|
return _extract_project_version(version_file.read()) or "unknown"
|
|
except (OSError, UnicodeError):
|
|
return "unknown"
|
|
|
|
|
|
def _version_sort_key(version: str) -> tuple[int, ...]:
|
|
return tuple(int(part) for part in re.findall(r"\d+", str(version or "")))
|
|
|
|
|
|
def _fetch_github_project_version(request_source_response: Any) -> str:
|
|
"""Fetch the published version from GitHub, retrying every failed attempt."""
|
|
for attempt in range(1, PROJECT_VERSION_MAX_ATTEMPTS + 1):
|
|
response = None
|
|
try:
|
|
response = request_source_response(
|
|
PROJECT_GITHUB_PYPROJECT_URL,
|
|
timeout=PROJECT_VERSION_REQUEST_TIMEOUT_SECONDS,
|
|
max_attempts=1,
|
|
log_name="GitHub version check",
|
|
)
|
|
if response is None:
|
|
log.warning(
|
|
f"GitHub version check attempt {attempt}/{PROJECT_VERSION_MAX_ATTEMPTS} "
|
|
"failed: no response"
|
|
)
|
|
continue
|
|
|
|
status_code = getattr(response, "status_code", None)
|
|
if status_code != 200:
|
|
log.warning(
|
|
f"GitHub version check attempt {attempt}/{PROJECT_VERSION_MAX_ATTEMPTS} "
|
|
f"failed: HTTP {status_code}"
|
|
)
|
|
continue
|
|
|
|
version = _extract_project_version(response.text)
|
|
if version:
|
|
log.debug(
|
|
f"GitHub version check succeeded on attempt "
|
|
f"{attempt}/{PROJECT_VERSION_MAX_ATTEMPTS}: v{version}"
|
|
)
|
|
return version
|
|
|
|
log.warning(
|
|
f"GitHub version check attempt {attempt}/{PROJECT_VERSION_MAX_ATTEMPTS} "
|
|
"failed: response did not contain a valid version"
|
|
)
|
|
except Exception as exc:
|
|
log.warning(
|
|
f"GitHub version check attempt {attempt}/{PROJECT_VERSION_MAX_ATTEMPTS} "
|
|
f"failed: {exc}"
|
|
)
|
|
finally:
|
|
if response is not None:
|
|
try:
|
|
response.close()
|
|
except Exception:
|
|
pass
|
|
|
|
return ""
|
|
|
|
|
|
def _fetch_registry_project_version(request_source_response: Any) -> str:
|
|
"""Fetch the latest installable version from the Comfy Registry fallback."""
|
|
response = None
|
|
try:
|
|
response = request_source_response(
|
|
PROJECT_REGISTRY_INSTALL_URL,
|
|
timeout=PROJECT_VERSION_REQUEST_TIMEOUT_SECONDS,
|
|
max_attempts=1,
|
|
log_name="Comfy Registry install version check",
|
|
)
|
|
if response is None:
|
|
log.warning("Comfy Registry install version check failed: no response")
|
|
return ""
|
|
|
|
status_code = getattr(response, "status_code", None)
|
|
if status_code != 200:
|
|
log.warning(
|
|
f"Comfy Registry install version check failed: HTTP {status_code}"
|
|
)
|
|
return ""
|
|
|
|
payload = response.json()
|
|
version = payload.get("version") if isinstance(payload, dict) else ""
|
|
version = str(version or "").strip()
|
|
if version:
|
|
log.debug(f"Comfy Registry install version check succeeded: v{version}")
|
|
return version
|
|
|
|
log.warning(
|
|
"Comfy Registry install version check failed: response did not contain version"
|
|
)
|
|
except Exception as exc:
|
|
log.warning(f"Comfy Registry install version check failed: {exc}")
|
|
finally:
|
|
if response is not None:
|
|
try:
|
|
response.close()
|
|
except Exception:
|
|
pass
|
|
|
|
return ""
|
|
|
|
|
|
def _get_project_version_info() -> dict[str, Any]:
|
|
"""Return the installed version and the latest published project version."""
|
|
current_version = _get_local_project_version()
|
|
now = time.monotonic()
|
|
|
|
with _project_version_cache_lock:
|
|
cached_at = float(_project_version_cache.get("checked_at") or 0.0)
|
|
latest_version = _project_version_cache.get("latest_version")
|
|
cache_is_fresh = bool(latest_version) and now - cached_at < PROJECT_VERSION_CACHE_TTL_SECONDS
|
|
|
|
if not cache_is_fresh:
|
|
latest_version = None
|
|
try:
|
|
from .network_utils import request_source_response
|
|
|
|
latest_version = _fetch_github_project_version(request_source_response)
|
|
if not latest_version:
|
|
log.warning(
|
|
f"GitHub version check failed after {PROJECT_VERSION_MAX_ATTEMPTS} attempts; "
|
|
"trying Comfy Registry fallback"
|
|
)
|
|
latest_version = _fetch_registry_project_version(request_source_response)
|
|
except Exception:
|
|
log.exception("Project version checks failed unexpectedly")
|
|
latest_version = None
|
|
|
|
if latest_version:
|
|
with _project_version_cache_lock:
|
|
_project_version_cache.update(
|
|
{
|
|
"checked_at": now,
|
|
"latest_version": latest_version,
|
|
}
|
|
)
|
|
else:
|
|
log.warning("Could not determine the latest project version; result was not cached")
|
|
|
|
current_key = _version_sort_key(current_version)
|
|
latest_key = _version_sort_key(latest_version or "")
|
|
if not latest_version or not latest_key:
|
|
status = "unavailable"
|
|
elif current_key and latest_key > current_key:
|
|
status = "update_available"
|
|
else:
|
|
status = "current"
|
|
|
|
return {
|
|
"current_version": current_version,
|
|
"latest_version": latest_version,
|
|
"status": status,
|
|
"github_url": PROJECT_GITHUB_URL,
|
|
}
|