AccessPath / tools /update_accessibility_inference_progress.py
anonymous-accesspath's picture
Audited anonymous-review AccessPath release
2f382c4 verified
Raw
History Blame Contribute Delete
3.3 kB
#!/usr/bin/env python3
"""Atomically update the Accessibility3R inference status file.
This helper deliberately uses only the Python standard library so it can run
inside the existing Accessibility3D environment without adding a dependency.
"""
from __future__ import annotations
import argparse
import json
import os
from datetime import datetime
from pathlib import Path
def now_iso() -> str:
return datetime.now().astimezone().isoformat(timespec="seconds")
def load_existing(path: Path) -> dict:
try:
value = json.loads(path.read_text(encoding="utf-8"))
except (FileNotFoundError, json.JSONDecodeError, OSError):
return {}
return value if isinstance(value, dict) else {}
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--path", type=Path, required=True)
parser.add_argument("--status", choices=("running", "completed", "failed"), required=True)
parser.add_argument("--stage-key")
parser.add_argument("--stage-label")
parser.add_argument("--percent", type=float)
parser.add_argument("--stage-end-percent", type=float)
parser.add_argument("--stage-expected-seconds", type=float)
parser.add_argument("--total-estimated-seconds", type=float)
parser.add_argument("--message")
parser.add_argument("--exit-code", type=int)
args = parser.parse_args()
path = args.path.expanduser().resolve()
path.parent.mkdir(parents=True, exist_ok=True)
current = load_existing(path)
timestamp = now_iso()
current.setdefault("started_at", timestamp)
current["updated_at"] = timestamp
current["status"] = args.status
if args.stage_key is not None:
current["stage_key"] = args.stage_key
current["stage_started_at"] = timestamp
if args.stage_label is not None:
current["stage_label"] = args.stage_label
if args.percent is not None:
current["percent"] = max(0.0, min(100.0, args.percent))
if args.stage_end_percent is not None:
current["stage_end_percent"] = max(0.0, min(100.0, args.stage_end_percent))
if args.stage_expected_seconds is not None:
current["stage_expected_seconds"] = max(0.0, args.stage_expected_seconds)
if args.total_estimated_seconds is not None:
current["total_estimated_seconds"] = max(1.0, args.total_estimated_seconds)
if args.message is not None:
current["message"] = args.message
if args.exit_code is not None:
current["exit_code"] = args.exit_code
if args.status == "completed":
current.pop("failed_at", None)
current.pop("exit_code", None)
current["percent"] = 100.0
current["stage_end_percent"] = 100.0
current["completed_at"] = timestamp
elif args.status == "failed":
current.pop("completed_at", None)
current["failed_at"] = timestamp
else:
current.pop("completed_at", None)
current.pop("failed_at", None)
current.pop("exit_code", None)
temporary = path.with_name(f".{path.name}.{os.getpid()}.tmp")
temporary.write_text(
json.dumps(current, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
os.replace(temporary, path)
return 0
if __name__ == "__main__":
raise SystemExit(main())