Refine autoupdate config handling

This commit is contained in:
delete 2026-06-11 00:23:14 +03:00
parent bcef2d7b52
commit 71598cda7d
3 changed files with 109 additions and 10 deletions

19
.gitignore vendored
View file

@ -1,2 +1,21 @@
.bin/
data-dev/
# Local deployment/config secrets
.env
docker-compose.override.yml
example/autoupdate.json
# Python
__pycache__/
*.py[cod]
# macOS
.DS_Store
.AppleDouble
.LSOverride
Icon?
._*
.Spotlight-V100
.Trashes
.fseventsd

View file

@ -21,6 +21,12 @@ class UpdateError(RuntimeError):
ReleaseHandler = Callable[[Path, dict[str, Any]], None]
DEFAULT_CONFIG_NAME = "autoupdate.json"
DEFAULT_CONFIG = {
"base_url": "https://updates.example.com",
"api_key": "upsk_replace_me",
"project_slug": "example-project",
}
def ensure_updated(
@ -37,7 +43,17 @@ def ensure_updated(
If `apply_update` is provided, it receives `(downloaded_artifact_path, release_dict)`
and is responsible for applying the downloaded artifact before restart.
"""
if not api_key or api_key == "upsk_replace_me":
if _is_placeholder_config(
{
"base_url": base_url,
"api_key": api_key,
"project_slug": project_slug,
}
):
print("autoupdate: configure autoupdate.json to enable updates")
return False
if not api_key:
raise UpdateError("set a real API key before calling ensure_updated")
script_path = _current_script_path()
@ -51,6 +67,8 @@ def ensure_updated(
if not latest_version or latest_version == current_version:
return False
print(f"autoupdate: updating {current_version} -> {latest_version}")
download_url = urljoin(base_url.rstrip("/") + "/", str(release.get("download_url") or ""))
if not download_url:
raise UpdateError("latest release does not contain a download URL")
@ -75,6 +93,27 @@ def ensure_updated(
return True
def ensure_updated_from_config(
current_version: str,
config_path: str | Path | None = None,
apply_update: ReleaseHandler | None = None,
) -> bool:
"""
Load update settings from JSON and run the update check.
If `config_path` is omitted, `autoupdate.json` is loaded from the same
directory as the currently running script.
"""
config = _load_config(config_path)
return ensure_updated(
base_url=config["base_url"],
api_key=config["api_key"],
project_slug=config["project_slug"],
current_version=current_version,
apply_update=apply_update,
)
def _current_script_path() -> Path:
main_module = sys.modules.get("__main__")
main_file = getattr(main_module, "__file__", None)
@ -83,6 +122,52 @@ def _current_script_path() -> Path:
return Path(main_file).resolve()
def _default_config_path() -> Path:
return _current_script_path().with_name(DEFAULT_CONFIG_NAME)
def _load_config(config_path: str | Path | None) -> dict[str, str]:
path = (
Path(config_path).expanduser()
if config_path is not None
else _default_config_path()
)
try:
with path.open("r", encoding="utf-8") as config_file:
payload = json.load(config_file)
except FileNotFoundError:
_write_default_config(path)
return DEFAULT_CONFIG.copy()
except json.JSONDecodeError as exc:
raise UpdateError(f"update config file is not valid JSON: {path}") from exc
if not isinstance(payload, dict):
raise UpdateError(f"update config must be a JSON object: {path}")
config: dict[str, str] = {}
for key in ("base_url", "api_key", "project_slug"):
value = payload.get(key)
if not isinstance(value, str) or not value.strip():
raise UpdateError(f"update config field {key!r} is required: {path}")
config[key] = value.strip()
return config
def _write_default_config(path: Path) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8") as config_file:
json.dump(DEFAULT_CONFIG, config_file, indent=2)
config_file.write("\n")
def _is_placeholder_config(config: dict[str, str]) -> bool:
return any(
config.get(key, "").strip() == value
for key, value in DEFAULT_CONFIG.items()
)
def _make_request(url: str, api_key: str, accept_json: bool = False) -> Request:
headers = {
"Authorization": f"Bearer {api_key}",

View file

@ -1,12 +1,7 @@
from autoupdate import ensure_updated
from autoupdate import ensure_updated_from_config
ensure_updated(
base_url="http://127.0.0.1:8080",
api_key="upsk_zdQu4gosIJjvIaQfn5A_ux9msFyDfEnc8c29F0ZCTmk",
project_slug="test",
current_version="0.1.3",
)
ver = "0.1.1"
ensure_updated_from_config(current_version=ver)
print("TEst")
print(f"TEst {ver}")