main
py 81 lines 2.63 KB
Raw
1 from __future__ import annotations
2
3 import importlib
4 import importlib.metadata
5 import importlib.util
6 import shutil
7 import subprocess
8 import sys
9 import threading
10 from pathlib import Path
11
12 from helpers.errors import format_error
13 from helpers.print_style import PrintStyle
14
15
16 _LOCK = threading.Lock()
17 _PLUGIN_DIR = Path(__file__).resolve().parent
18 _ROOT_REQUIREMENTS_FILE = _PLUGIN_DIR.parents[1] / "requirements.txt"
19
20
21 def ensure_dependencies(raise_on_error: bool = True) -> bool:
22 """Install the pinned framework-runtime dependency when needed."""
23 with _LOCK:
24 try:
25 requirement = _json_repair_requirement()
26 if _json_repair_is_current(requirement):
27 return True
28
29 uv = shutil.which("uv")
30 if not uv:
31 raise RuntimeError(
32 "Context Doctor plugin requires 'uv' to install json_repair automatically"
33 )
34
35 PrintStyle.info(
36 "Context Doctor: installing pinned json_repair dependency"
37 )
38 subprocess.check_call(
39 [uv, "pip", "install", "--python", sys.executable, requirement],
40 cwd=str(_PLUGIN_DIR),
41 )
42 importlib.invalidate_caches()
43 if not _json_repair_is_current(requirement):
44 raise RuntimeError(
45 f"Context Doctor dependency {requirement!r} is unavailable after installation"
46 )
47 return True
48 except Exception as exc:
49 message = (
50 "Context Doctor: failed to install json_repair dependency: "
51 f"{format_error(exc)}"
52 )
53 if raise_on_error:
54 raise RuntimeError(message) from exc
55 PrintStyle.error(message)
56 return False
57
58
59 def install() -> bool:
60 return ensure_dependencies(raise_on_error=True)
61
62
63 def _json_repair_requirement() -> str:
64 if _ROOT_REQUIREMENTS_FILE.is_file():
65 for line in _ROOT_REQUIREMENTS_FILE.read_text(encoding="utf-8").splitlines():
66 requirement = line.strip()
67 if requirement.startswith("json_repair=="):
68 return requirement
69 raise RuntimeError(
70 f"Context Doctor pinned json_repair requirement not found in {_ROOT_REQUIREMENTS_FILE}"
71 )
72
73
74 def _json_repair_is_current(requirement: str) -> bool:
75 expected_version = requirement.partition("==")[2]
76 if not expected_version or importlib.util.find_spec("json_repair") is None:
77 return False
78 try:
79 return importlib.metadata.version("json-repair") == expected_version
80 except importlib.metadata.PackageNotFoundError:
81 return False