← Files VeraARCHIVED FILE
modules/bilancio-xbrl-it/scripts/managed_ocr_runtime.py
14.8 KB · Oct 2, 2026 · 00:29 UTC
#!/usr/bin/env python3
"""Manage the persistent PaddleOCR runtime shared by Vera workflows."""
from __future__ import annotations
import argparse
import hashlib
import importlib.machinery
import importlib.metadata
import json
import os
import subprocess
import sys
import tempfile
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Callable
__all__ = [
"INSTALL_FAILURE_MESSAGE",
"INSTALL_SUCCESS_MESSAGE",
"OCR_SETUP_PROMPT",
"SetupResult",
"activate_ocr_runtime",
"install_ocr_runtime",
"main",
]
OCR_SETUP_PROMPT = (
"PaddleOCR is required to read this document. Shall Codex install it now? "
"The download is about 500 MB."
)
INSTALL_SUCCESS_MESSAGE = "PaddleOCR is ready. Retrying the document now."
INSTALL_FAILURE_MESSAGE = (
"I couldn't install PaddleOCR right now. Shall I try the installation again?"
)
REQUIRED_MODULES = ("PIL", "cv2", "numpy", "paddleocr", "paddle")
OCR_MODEL_NAMES = (
"PP-OCRv5_mobile_det",
"latin_PP-OCRv5_mobile_rec",
"en_PP-OCRv5_mobile_rec",
)
RUNTIME_ROOT_ENV = "MPARANZA_RUNTIME_ROOT"
_RUNTIME_LEASES = {}
RUNTIME_DIR_NAME = "paddleocr"
READY_MARKER = ".mparanza-ocr-ready.json"
READY_SCHEMA_VERSION = 2
MODEL_FILES = ("inference.json", "inference.pdiparams")
Runner = Callable[..., subprocess.CompletedProcess[str]]
@dataclass(frozen=True)
class SetupResult:
"""One managed setup result suitable for the host orchestration layer."""
status: str
message: str
runtime_path: str
reused: bool
detail: str = ""
def _manager(requirements_path: Path):
"""Find the owning product without using a client-project dependency path."""
import importlib.util
roots = [requirements_path.parent, *requirements_path.parents]
roots.append(requirements_path.parent.parent / "vera")
owner = next(
(root for root in roots if (root / "requirements-shared-core.txt").is_file()),
None,
)
if owner is None:
raise ValueError(
"Install the current Vera, Clara or Lucia package to use shared OCR."
)
source = owner / "scripts" / "_managed_python_runtime.py"
spec = importlib.util.spec_from_file_location("mparanza_ocr_manager", source)
if spec is None or spec.loader is None:
raise RuntimeError("Shared Python runtime manager unavailable")
manager = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = manager
spec.loader.exec_module(manager)
return owner, manager
def runtime_target(requirements_path: Path) -> Path:
"""Return OCR's site-packages in the sole shared environment."""
owner, manager = _manager(requirements_path)
selection = manager.select_runtime(
owner, requirements=["requirements-shared-ocr.txt"]
)
environment = manager.dependency_target(selection)
return environment / (
"Lib/site-packages"
if sys.platform == "win32"
else "lib/python3.12/site-packages"
)
def _hold_lease(environment: Path) -> bool:
"""Keep externally activated native OCR packages unchanged until process exit."""
if environment in _RUNTIME_LEASES:
return True
lock = environment.parent / "runtime.lock"
if lock.is_symlink():
return False
try:
handle = lock.open("rb")
except OSError:
return False
try:
if sys.platform == "win32":
import msvcrt
msvcrt.locking(handle.fileno(), msvcrt.LK_NBRLCK, 1)
else:
import fcntl
fcntl.flock(handle.fileno(), fcntl.LOCK_SH | fcntl.LOCK_NB)
except OSError:
handle.close()
return False
_RUNTIME_LEASES[environment] = handle
return True
def _normalize_package_name(value: str) -> str:
return "-".join(filter(None, value.lower().replace("_", "-").split("-")))
def _locked_requirements(requirements_path: Path) -> dict[str, str]:
"""Return exact package pins; managed runtimes must not resolve version ranges."""
locked: dict[str, str] = {}
for raw_line in requirements_path.read_text(encoding="utf-8").splitlines():
line = raw_line.split("#", 1)[0].strip()
if not line:
continue
if line.count("==") != 1 or any(
marker in line for marker in (";", "[", "]", "<", ">", "~", ",", "@")
):
raise ValueError(
"Managed OCR requirements must use exact package==version pins"
)
package, version = (part.strip() for part in line.split("==", 1))
normalized = _normalize_package_name(package)
if not normalized or not version or normalized in locked:
raise ValueError("Managed OCR requirements contain an invalid package pin")
locked[normalized] = version
if not locked:
raise ValueError("Managed OCR requirements contain no package pins")
return locked
def _requirements_digest(requirements_path: Path) -> str:
locked = _locked_requirements(requirements_path)
content = "\n".join(f"{name}=={locked[name]}" for name in sorted(locked))
return hashlib.sha256(content.encode("utf-8")).hexdigest()
def _requirements_fingerprint(requirements_path: Path) -> str:
return _requirements_digest(requirements_path)[:16]
def _runtime_target(requirements_path: Path) -> Path:
_locked_requirements(requirements_path)
return runtime_target(requirements_path)
def _modules_present(target: Path) -> bool:
"""Check a mechanical installation contract without importing OCR engines."""
return target.is_dir() and all(
importlib.machinery.PathFinder.find_spec(module, [str(target)]) is not None
for module in REQUIRED_MODULES
)
def _model_cache(target: Path) -> Path:
return target / "model-cache"
def _models_present(target: Path) -> bool:
official_models = _model_cache(target) / "official_models"
return all(
all(
(official_models / model / file_name).is_file() for file_name in MODEL_FILES
)
for model in OCR_MODEL_NAMES
)
def _installed_package_versions(target: Path) -> dict[str, str]:
versions: dict[str, str] = {}
for distribution in importlib.metadata.distributions(path=[str(target)]):
name = distribution.metadata.get("Name")
if name:
versions[_normalize_package_name(str(name))] = distribution.version
return versions
def _package_receipt(target: Path, requirements_path: Path) -> dict[str, str]:
locked = _locked_requirements(requirements_path)
installed = _installed_package_versions(target)
if any(installed.get(name) != version for name, version in locked.items()):
raise ValueError("Managed OCR package versions do not match the lock")
# Preserve the complete resolved environment, not only the direct pins, so
# transitive dependency drift is visible and invalidates later reuse.
return {name: installed[name] for name in sorted(installed)}
def _file_receipt(path: Path) -> dict[str, object]:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return {
"size_bytes": path.stat().st_size,
"sha256": digest.hexdigest(),
}
def _model_receipt(target: Path) -> dict[str, dict[str, dict[str, object]]]:
official_models = _model_cache(target) / "official_models"
receipt: dict[str, dict[str, dict[str, object]]] = {}
for model in OCR_MODEL_NAMES:
directory = official_models / model
files = {
file_name: _file_receipt(directory / file_name) for file_name in MODEL_FILES
}
receipt[model] = files
return receipt
def _runtime_ready(target: Path, requirements_path: Path) -> bool:
marker = target / READY_MARKER
if (
not marker.is_file()
or not _modules_present(target)
or not _models_present(target)
):
return False
try:
receipt = json.loads(marker.read_text(encoding="utf-8"))
return (
receipt.get("schema_version") == READY_SCHEMA_VERSION
and receipt.get("requirements_sha256")
== _requirements_digest(requirements_path)
and receipt.get("packages") == _package_receipt(target, requirements_path)
and receipt.get("models") == _model_receipt(target)
)
except (OSError, KeyError, TypeError, ValueError, json.JSONDecodeError):
return False
def _activate_path(target: Path) -> None:
target_text = str(target)
if target_text not in sys.path:
sys.path.insert(0, target_text)
existing = [
part for part in os.environ.get("PYTHONPATH", "").split(os.pathsep) if part
]
if target_text not in existing:
os.environ["PYTHONPATH"] = os.pathsep.join([target_text, *existing])
os.environ["PADDLE_PDX_CACHE_HOME"] = str(_model_cache(target))
def _prefetch_models(target: Path, runner: Runner, python: Path) -> None:
"""Download the declared public OCR models inside the approved install step."""
_activate_path(target)
code = (
"from paddleocr import PaddleOCR\n"
f"models = {OCR_MODEL_NAMES[1:]!r}\n"
"for model in models:\n"
" PaddleOCR(text_detection_model_name='PP-OCRv5_mobile_det', "
"text_recognition_model_name=model, use_doc_orientation_classify=False, "
"use_doc_unwarping=False, use_textline_orientation=False, "
"enable_mkldnn=False)\n"
)
completed = runner(
[str(python), "-c", code],
cwd=target,
env={**os.environ, "MPARANZA_RUNTIME_INSTALLING": "1"},
capture_output=True,
check=False,
text=True,
)
if completed.returncode != 0 or not _models_present(target):
detail = (completed.stderr or completed.stdout).strip()
raise RuntimeError(detail or "The OCR model download was incomplete")
def activate_ocr_runtime(requirements_path: Path) -> Path | None:
"""Verify the shared packages and XBRL model integrity before activation."""
source = requirements_path.resolve()
target = _runtime_target(source)
owner, manager = _manager(source)
environment = manager.activate_runtime(
owner, requirements=["requirements-shared-ocr.txt"]
)
if environment is None or not _runtime_ready(target, source):
return None
if not _hold_lease(environment):
return None
if (
not _runtime_ready(target, source)
or manager.activate_runtime(owner, requirements=["requirements-shared-ocr.txt"])
is None
):
_RUNTIME_LEASES.pop(environment).close()
return None
_activate_path(target)
return target
def install_ocr_runtime(
requirements_path: Path,
*,
runner: Runner = subprocess.run,
model_runner: Runner = subprocess.run,
) -> SetupResult:
"""Enable shared OCR and retain the XBRL model integrity contract."""
source = requirements_path.expanduser().resolve()
target = _runtime_target(source)
if activate_ocr_runtime(source) is not None:
return SetupResult("ready", INSTALL_SUCCESS_MESSAGE, str(target), True)
owner, manager = _manager(source)
ready, environment, detail = manager.ensure_runtime(
owner,
requirements=["requirements-shared-ocr.txt"],
runner=runner,
)
if not ready:
return SetupResult(
"failed", INSTALL_FAILURE_MESSAGE, str(target), False, detail
)
if (
Path(sys.prefix).resolve() == environment.resolve()
or environment in _RUNTIME_LEASES
):
return SetupResult(
"failed",
INSTALL_FAILURE_MESSAGE,
str(target),
False,
"Exit running OCR workflows and run model setup from base Python.",
)
shared = manager._shared_runtime()
try:
with shared._writer(environment.parent / "runtime.lock"):
(target / READY_MARKER).unlink(missing_ok=True)
# Only model assets are staged; no second Python environment is created.
with tempfile.TemporaryDirectory(
prefix="ocr-models-", dir=environment.parent
) as temporary:
staging = Path(temporary)
_prefetch_models(
staging, model_runner, manager.runtime_python(environment)
)
models = _model_receipt(staging)
receipt = {
"schema_version": READY_SCHEMA_VERSION,
"requirements_fingerprint": _requirements_fingerprint(source),
"requirements_sha256": _requirements_digest(source),
"modules": list(REQUIRED_MODULES),
"packages": _package_receipt(target, source),
"models": models,
}
cache = _model_cache(target)
if cache.is_symlink():
raise OSError("OCR model cache cannot be a symlink")
if cache.exists():
cache.replace(staging / "previous-model-cache")
_model_cache(staging).replace(cache)
shared._write(target / READY_MARKER, receipt)
except (OSError, RuntimeError, ValueError, TypeError) as error:
return SetupResult(
"failed", INSTALL_FAILURE_MESSAGE, str(target), False, str(error)
)
if activate_ocr_runtime(source) is None:
return SetupResult(
"failed",
INSTALL_FAILURE_MESSAGE,
str(target),
False,
"The managed OCR integrity receipt could not be verified.",
)
return SetupResult("ready", INSTALL_SUCCESS_MESSAGE, str(target), False)
def main(argv: list[str] | None = None) -> int:
"""Report or install the optional runtime through one machine-readable CLI."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("action", choices=("status", "install"))
parser.add_argument("--requirements", type=Path)
args = parser.parse_args(argv)
requirements_path = (
args.requirements.expanduser().resolve()
if args.requirements is not None
else Path(__file__).resolve().parents[1] / "requirements-ocr.txt"
)
if args.action == "status":
target = activate_ocr_runtime(requirements_path)
result = (
SetupResult("ready", INSTALL_SUCCESS_MESSAGE, str(target), True)
if target is not None
else SetupResult(
"requires_install",
OCR_SETUP_PROMPT,
str(_runtime_target(requirements_path)),
False,
)
)
sys.stdout.write(json.dumps(asdict(result)) + "\n")
return 0 if result.status == "ready" else 1
result = install_ocr_runtime(requirements_path)
sys.stdout.write(json.dumps(asdict(result)) + "\n")
return 0 if result.status == "ready" else 1
if __name__ == "__main__":
raise SystemExit(main())
SHA-256: 52216b53d3501ebe79b537a5d671ae989731a874d8a7a272307c6b1443f7c42c