← Files VeraARCHIVED FILE

modules/open-item-reconciliation/scripts/check_dependencies.py

7.71 KB · Oct 3, 2026 · 06:30 UTC

↓ Download file

"""Check Python dependencies declared by this Codex plugin.

This helper is intentionally small and local to the plugin package. Codex can
run it before starting a workflow and either install the declared requirements
or explain the missing libraries to a non-technical user.
"""

from __future__ import annotations

import sys as _bootstrap_sys

_bootstrap_sys.dont_write_bytecode = True
_bootstrap_sys.pycache_prefix = (
    r"Z:\__open_item_reconciliation_no_bytecode__"
    if _bootstrap_sys.platform == "win32"
    else "/dev/null/open-item-reconciliation"
)

import os as _bootstrap_os

_BOOTSTRAP_PATH = _bootstrap_os.path.join(
    _bootstrap_os.path.dirname(_bootstrap_os.path.abspath(__file__)),
    "implementation_bootstrap.py",
)
_BOOTSTRAP_NAMESPACE = {
    "__file__": _BOOTSTRAP_PATH,
    "__name__": "_open_item_reconciliation_implementation_bootstrap",
}
_bootstrap_stat = _bootstrap_os.lstat(_BOOTSTRAP_PATH)
if _bootstrap_stat.st_mode & 0o170000 != 0o100000 or _bootstrap_stat.st_nlink != 1:
    raise RuntimeError(
        "implementation bootstrap must be an ordinary single-link regular file"
    )
_bootstrap_descriptor = _bootstrap_os.open(
    _BOOTSTRAP_PATH,
    _bootstrap_os.O_RDONLY | getattr(_bootstrap_os, "O_NOFOLLOW", 0),
)
try:
    _bootstrap_open_stat = _bootstrap_os.fstat(_bootstrap_descriptor)
    _bootstrap_identity = (
        _bootstrap_stat.st_dev,
        _bootstrap_stat.st_ino,
        _bootstrap_stat.st_size,
        _bootstrap_stat.st_mtime_ns,
        _bootstrap_stat.st_nlink,
    )
    if _bootstrap_identity != (
        _bootstrap_open_stat.st_dev,
        _bootstrap_open_stat.st_ino,
        _bootstrap_open_stat.st_size,
        _bootstrap_open_stat.st_mtime_ns,
        _bootstrap_open_stat.st_nlink,
    ):
        raise RuntimeError("implementation bootstrap changed before it was read")
    with _bootstrap_os.fdopen(
        _bootstrap_descriptor,
        "rb",
        closefd=False,
    ) as _bootstrap_handle:
        _bootstrap_source = _bootstrap_handle.read()
    _bootstrap_after_stat = _bootstrap_os.fstat(_bootstrap_descriptor)
    if (
        _bootstrap_identity
        != (
            _bootstrap_after_stat.st_dev,
            _bootstrap_after_stat.st_ino,
            _bootstrap_after_stat.st_size,
            _bootstrap_after_stat.st_mtime_ns,
            _bootstrap_after_stat.st_nlink,
        )
        or len(_bootstrap_source) != _bootstrap_after_stat.st_size
    ):
        raise RuntimeError("implementation bootstrap changed while it was read")
finally:
    _bootstrap_os.close(_bootstrap_descriptor)
# Execute only the pre-opened single-link bootstrap source.
exec(  # nosec B102
    compile(_bootstrap_source, _BOOTSTRAP_PATH, "exec"),
    _BOOTSTRAP_NAMESPACE,
)
_BOOTSTRAP_NAMESPACE["activate_implementation_boundary"](())

import argparse
import importlib.util
import re
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

__all__ = [
    "build_dependency_check",
    "declared_requirements",
    "import_name",
    "main",
    "plugin_root",
    "requirement_name",
    "selected_requirement_files",
]


PACKAGE_IMPORTS = {
    "beautifulsoup4": "bs4",
    "opencv-python": "cv2",
    "opencv-contrib-python": "cv2",
    "paddlepaddle": "paddle",
    "pillow": "PIL",
    "pymupdf": "fitz",
    "python-docx": "docx",
}


def plugin_root() -> Path:
    return Path(__file__).resolve().parents[1]


def requirement_name(line: str) -> str:
    cleaned = line.split("#", 1)[0].strip()
    if not cleaned or cleaned.startswith(("-", "git+", "http://", "https://")):
        return ""
    match = re.match(r"([A-Za-z0-9_.-]+)", cleaned)
    return match.group(1).lower() if match else ""


def import_name(package_name: str) -> str:
    return PACKAGE_IMPORTS.get(package_name.lower(), package_name.replace("-", "_"))


def declared_requirements(requirement_files: list[Path]) -> list[tuple[str, str, str]]:
    rows: list[tuple[str, str, str]] = []
    for requirements_file in requirement_files:
        for line in requirements_file.read_text(encoding="utf-8").splitlines():
            package = requirement_name(line)
            if package:
                rows.append((requirements_file.name, package, import_name(package)))
    return rows


def selected_requirement_files(
    include_optional: bool, explicit_files: list[str]
) -> list[Path]:
    root = plugin_root()
    if explicit_files:
        files = [root / name for name in explicit_files]
    elif include_optional:
        files = sorted(root.glob("requirements*.txt"))
    else:
        files = [root / "requirements.txt"]
    return [path for path in files if path.exists()]


def _utc_now() -> str:
    return datetime.now(timezone.utc).replace(microsecond=0).isoformat()


def build_dependency_check(
    *,
    include_optional: bool = False,
    explicit_files: list[str] | None = None,
) -> dict[str, Any]:
    """Return a JSON-serializable dependency-check contract for run intake."""

    requested_files = explicit_files or []
    files = selected_requirement_files(include_optional, requested_files)
    command_parts = ["python", "scripts/check_dependencies.py"]
    if include_optional:
        command_parts.append("--include-optional")
    for name in requested_files:
        command_parts.extend(["--requirements", name])
    if not files:
        return {
            "status": "missing_requirements_file",
            "checked_at": _utc_now(),
            "command": " ".join(command_parts),
            "requirement_files": requested_files,
            "missing": [],
            "note": "No selected requirements file was found in this plugin package.",
        }

    missing: list[dict[str, str]] = []
    checked: list[dict[str, str]] = []
    for source_file, package, module_name in declared_requirements(files):
        row = {
            "requirements_file": source_file,
            "package": package,
            "module": module_name,
        }
        checked.append(row)
        if importlib.util.find_spec(module_name) is None:
            missing.append(row)

    return {
        "status": "missing_dependencies" if missing else "ok",
        "checked_at": _utc_now(),
        "command": " ".join(command_parts),
        "requirement_files": [path.name for path in files],
        "checked": checked,
        "checked_count": len(checked),
        "missing": missing,
        "missing_count": len(missing),
    }


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Check this Codex plugin's Python dependencies."
    )
    parser.add_argument(
        "--include-optional",
        action="store_true",
        help="Check every requirements*.txt file, including optional OCR/browser extras.",
    )
    parser.add_argument(
        "--requirements",
        action="append",
        default=[],
        help="Specific requirements file under the plugin root. May be passed more than once.",
    )
    args = parser.parse_args()

    result = build_dependency_check(
        include_optional=args.include_optional,
        explicit_files=args.requirements,
    )
    if result["status"] == "missing_requirements_file":
        print(
            "MISSING_REQUIREMENTS_FILE: no requirements file found in this plugin package"
        )
        return 1

    if result["missing"]:
        print("MISSING_DEPENDENCIES")
        for row in result["missing"]:
            print(
                f"- {row['package']} ({row['module']}) from {row['requirements_file']}"
            )
        install_files = " ".join(f"-r {name}" for name in result["requirement_files"])
        print(
            f"Suggested install from plugin directory: python -m pip install {install_files}"
        )
        return 1

    print("OK: all selected plugin dependencies are importable")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

SHA-256: d2a0a01231ddb580a795413bb877cb816e3d12cb2dd33ed6bfaf6e7463efc1d2