Praneshrajan15's picture
Deploy DataForge playground API
13fe504 verified
Raw
History Blame Contribute Delete
13.3 kB
"""Release doctor checks for DataForge public-surface gates."""
from __future__ import annotations
import json
import shutil
import subprocess
import sys
import tempfile
import tomllib
from dataclasses import asdict, dataclass
from importlib import metadata as importlib_metadata
from pathlib import Path
from typing import Any
EXPECTED_HF_USER = "Praneshrajan15"
DEFAULT_KAGGLE_CREDENTIALS = Path.home() / ".kaggle" / "credentials.json"
STALE_KAGGLE_JSON = Path.home() / ".kaggle" / "kaggle.json"
CORE_DISTRIBUTION = "dataforge_07"
@dataclass(frozen=True)
class DoctorCheck:
"""One release doctor check result."""
name: str
ok: bool
detail: str
metadata: dict[str, Any]
@dataclass(frozen=True)
class DoctorReport:
"""Machine-readable release doctor report."""
ok: bool
checks: list[DoctorCheck]
scopes: list[str]
secrets_printed: bool = False
def to_dict(self) -> dict[str, Any]:
"""Return a JSON-serializable report."""
return asdict(self)
def _check_hugging_face() -> DoctorCheck:
try:
from huggingface_hub import HfApi, get_token
except ImportError as exc:
return DoctorCheck("hugging_face", False, f"huggingface_hub missing: {exc}", {})
token_present = bool(get_token())
if not token_present:
return DoctorCheck(
"hugging_face",
False,
"No cached Hugging Face token found.",
{"expected_user": EXPECTED_HF_USER, "token_present": False},
)
try:
info = HfApi(token=get_token()).whoami()
except Exception as exc:
return DoctorCheck(
"hugging_face",
False,
f"Could not resolve Hugging Face identity: {exc}",
{"expected_user": EXPECTED_HF_USER, "token_present": True},
)
user = str(info.get("name", ""))
return DoctorCheck(
"hugging_face",
user == EXPECTED_HF_USER,
"Authenticated with expected Hugging Face user."
if user == EXPECTED_HF_USER
else f"Authenticated as {user!r}, expected {EXPECTED_HF_USER!r}.",
{"user": user, "expected_user": EXPECTED_HF_USER, "token_present": True},
)
def _load_kaggle_oauth(path: Path) -> dict[str, Any]:
if path.name == "kaggle.json":
raise RuntimeError(
f"Refusing to read stale legacy Kaggle API key file: {path}. "
f"Use OAuth credentials at {DEFAULT_KAGGLE_CREDENTIALS}."
)
if not path.exists():
raise RuntimeError(f"Missing Kaggle OAuth credentials: {path}")
payload = json.loads(path.read_text(encoding="utf-8-sig"))
if not isinstance(payload, dict):
raise RuntimeError("Kaggle OAuth credentials must be a JSON object.")
required = {"refresh_token", "access_token", "access_token_expiration", "username", "scopes"}
missing = sorted(required - set(payload))
if missing:
raise RuntimeError("Kaggle OAuth credentials missing fields: " + ", ".join(missing))
if not isinstance(payload.get("username"), str) or not payload["username"]:
raise RuntimeError("Kaggle OAuth credentials are missing username.")
scopes = payload.get("scopes")
if not isinstance(scopes, list) or not scopes:
raise RuntimeError("Kaggle OAuth credentials are missing scopes.")
return payload
def _check_kaggle_oauth(credentials_path: Path = DEFAULT_KAGGLE_CREDENTIALS) -> DoctorCheck:
try:
payload = _load_kaggle_oauth(credentials_path)
except Exception as exc:
return DoctorCheck(
"kaggle_oauth", False, str(exc), {"credential_path": str(credentials_path)}
)
return DoctorCheck(
"kaggle_oauth",
True,
"Kaggle OAuth credentials are present and legacy key is ignored.",
{
"credential_path": str(credentials_path),
"username": payload["username"],
"scopes_count": len(payload["scopes"]),
"legacy_kaggle_json_exists": STALE_KAGGLE_JSON.exists(),
"legacy_kaggle_json_used": False,
"tokens_printed": False,
},
)
def _check_kaggle_cli_clean_config(
credentials_path: Path = DEFAULT_KAGGLE_CREDENTIALS,
) -> DoctorCheck:
"""Verify Kaggle CLI auth through OAuth under a clean config directory."""
script = _project_root() / "scripts" / "preflight" / "check_kaggle_auth.py"
if not script.exists():
return DoctorCheck(
"kaggle_cli_clean_config",
False,
f"Kaggle auth preflight script not found: {script}",
{"credential_path": str(credentials_path), "tokens_printed": False},
)
command = [
sys.executable,
str(script),
"--kaggle-json",
str(credentials_path),
"--check-cli",
]
try:
result = subprocess.run(
command,
cwd=_project_root(),
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=90,
check=False,
)
except subprocess.TimeoutExpired:
return DoctorCheck(
"kaggle_cli_clean_config",
False,
"Kaggle CLI clean-config OAuth preflight timed out.",
{"credential_path": str(credentials_path), "tokens_printed": False},
)
ok = result.returncode == 0
detail = (
"Kaggle CLI read-only command succeeded with clean KAGGLE_CONFIG_DIR."
if ok
else "Kaggle CLI clean-config OAuth preflight failed."
)
return DoctorCheck(
"kaggle_cli_clean_config",
ok,
detail,
{
"credential_path": str(credentials_path),
"command": " ".join(command),
"tokens_printed": False,
},
)
def _check_cloudflare() -> DoctorCheck:
npx = shutil.which("npx") or shutil.which("npx.cmd")
if npx is None:
return DoctorCheck("cloudflare", False, "npx/wrangler not available on PATH.", {})
env_file_path = ""
try:
with tempfile.NamedTemporaryFile(delete=False) as env_file:
env_file_path = env_file.name
command = [npx, "wrangler", "whoami", "--env-file", env_file_path]
try:
result = subprocess.run(
command,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=60,
check=False,
)
finally:
if env_file_path:
Path(env_file_path).unlink(missing_ok=True)
except FileNotFoundError:
return DoctorCheck("cloudflare", False, "npx/wrangler not available on PATH.", {})
except subprocess.TimeoutExpired:
return DoctorCheck("cloudflare", False, "wrangler whoami timed out.", {})
output = result.stdout + result.stderr
ok = result.returncode == 0 and "logged in" in output.lower()
has_route_scope = "workers_routes (write)" in output
return DoctorCheck(
"cloudflare",
ok and has_route_scope,
"Wrangler OAuth is logged in with Workers route scope."
if ok and has_route_scope
else "Wrangler is not logged in with the required Workers route scope.",
{
"wrangler_available": result.returncode == 0,
"logged_in": ok,
"workers_routes_write": has_route_scope,
"command": f"{npx} wrangler whoami --env-file <empty-env-file>",
"env_file_override": "empty",
},
)
def _project_root() -> Path:
"""Return the repository root for local core release checks."""
return Path(__file__).resolve().parents[2]
def _check_package_boundary() -> DoctorCheck:
"""Verify the core wheel only includes the public DataForge namespace."""
pyproject_path = _project_root() / "pyproject.toml"
if not pyproject_path.exists():
try:
distribution = importlib_metadata.distribution(CORE_DISTRIBUTION)
top_level = distribution.read_text("top_level.txt") or ""
except importlib_metadata.PackageNotFoundError as exc:
return DoctorCheck(
"core_package_boundary",
False,
f"Could not read installed package metadata: {exc}",
{"distribution": CORE_DISTRIBUTION},
)
top_level_packages = sorted(line.strip() for line in top_level.splitlines() if line.strip())
expected_top_level = ["dataforge"]
return DoctorCheck(
"core_package_boundary",
top_level_packages == expected_top_level,
"Installed wheel exposes only the dataforge top-level package."
if top_level_packages == expected_top_level
else (
"Installed wheel exposes top-level packages "
f"{top_level_packages!r}, expected {expected_top_level!r}."
),
{"top_level": top_level_packages, "expected": expected_top_level},
)
try:
pyproject = tomllib.loads(pyproject_path.read_text(encoding="utf-8"))
include = pyproject["tool"]["setuptools"]["packages"]["find"]["include"]
except Exception as exc:
return DoctorCheck(
"core_package_boundary",
False,
f"Could not read package include contract: {exc}",
{"pyproject": str(pyproject_path)},
)
expected = ["dataforge", "dataforge.*"]
return DoctorCheck(
"core_package_boundary",
include == expected,
"Core wheel includes only the dataforge namespace."
if include == expected
else f"Core wheel package include list is {include!r}, expected {expected!r}.",
{"include": include, "expected": expected},
)
def _check_packaged_core_files() -> DoctorCheck:
"""Verify files required by source-install and wheel smoke commands exist."""
root = _project_root()
required = [
root / "dataforge" / "py.typed",
root / "dataforge" / "fixtures" / "hospital_10rows.csv",
root / "dataforge" / "fixtures" / "hospital_schema.yaml",
root / "dataforge" / "safety" / "constitutions" / "default.yaml",
]
missing = [str(path.relative_to(root)) for path in required if not path.exists()]
return DoctorCheck(
"core_packaged_files",
not missing,
"Core package data files required by smoke commands are present."
if not missing
else "Missing core package data files: " + ", ".join(missing),
{"missing": missing},
)
def _core_checks() -> list[DoctorCheck]:
"""Return local OSS release checks that do not require personal accounts."""
return [_check_package_boundary(), _check_packaged_core_files()]
def _maintainer_deploy_checks(kaggle_credentials: Path) -> list[DoctorCheck]:
"""Return maintainer-specific deploy/auth checks."""
return [
_check_hugging_face(),
_check_kaggle_oauth(kaggle_credentials),
_check_kaggle_cli_clean_config(kaggle_credentials),
_check_cloudflare(),
]
def run_doctor(
*,
kaggle_credentials: Path = DEFAULT_KAGGLE_CREDENTIALS,
core: bool = True,
maintainer_deploy: bool = False,
) -> DoctorReport:
"""Run selected release doctor checks."""
checks: list[DoctorCheck] = []
scopes: list[str] = []
if core:
checks.extend(_core_checks())
scopes.append("core")
if maintainer_deploy:
checks.extend(_maintainer_deploy_checks(kaggle_credentials))
scopes.append("maintainer_deploy")
return DoctorReport(
ok=all(check.ok for check in checks),
checks=checks,
scopes=scopes,
)
def main(argv: list[str] | None = None) -> int:
"""Script entrypoint used by CI and local release work."""
import argparse
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--json", action="store_true", help="Print JSON instead of text.")
parser.add_argument("--kaggle-credentials", type=Path, default=DEFAULT_KAGGLE_CREDENTIALS)
parser.add_argument("--core", action="store_true", help="Run OSS core release checks.")
parser.add_argument(
"--maintainer-deploy",
action="store_true",
help="Run maintainer-specific deploy/auth checks.",
)
args = parser.parse_args(argv)
core = args.core or not args.maintainer_deploy
report = run_doctor(
kaggle_credentials=args.kaggle_credentials,
core=core,
maintainer_deploy=args.maintainer_deploy,
)
if args.json:
print(json.dumps(report.to_dict(), indent=2, sort_keys=True))
else:
for check in report.checks:
status = "ok" if check.ok else "fail"
print(f"{status:4} {check.name}: {check.detail}")
return 0 if report.ok else 2
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))