File size: 3,992 Bytes
7f153d6
74acbf7
 
 
7f153d6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74acbf7
 
 
 
 
 
 
 
 
 
7f153d6
74acbf7
7f153d6
 
74acbf7
7f153d6
 
 
 
 
74acbf7
 
 
 
7f153d6
 
 
74acbf7
 
 
7f153d6
 
74acbf7
7f153d6
74acbf7
 
 
 
 
 
7f153d6
74acbf7
 
7f153d6
74acbf7
7f153d6
74acbf7
7f153d6
74acbf7
 
 
 
7f153d6
74acbf7
7f153d6
 
 
74acbf7
7f153d6
74acbf7
7f153d6
 
 
 
 
74acbf7
7f153d6
74acbf7
7f153d6
 
 
 
 
 
 
 
74acbf7
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
"""Run the parsing pipeline without setting six environment variables by hand.

    py scripts/parse.py                      # Windows
    python3 scripts/parse.py                 # Linux
    py scripts/parse.py data/other           # a different folder
    py scripts/parse.py --backend vlm        # force a backend

Why Python rather than shell. This wrapper was written twice first — `parse.sh`
for bash and `parse.ps1` for PowerShell — and inside twenty minutes hit two traps
that are peculiar to shells:

  - CRLF. `core.autocrlf=true` rewrites `.sh` on Windows checkout, and bash then
    dies on line one with `set: pipefail`, a message that names nothing.
  - A trailing backslash. A path containing spaces gets quoted by PowerShell, the
    final backslash escapes the closing quote, and the NEXT argument is swallowed
    — `--backend pipeline` ended up inside the value of `--input`.

Neither is possible here: `subprocess` takes an argument list and never goes
through a shell. One file, the same on every platform, and readable by anyone on
the team without knowing both shells.

This is a development aid. Production calls the module directly
(`python -m src.knowledge_parsing.run`) — parsing is an offline, admin-triggered
batch job, not something a person runs from a terminal.
"""

from __future__ import annotations

import os
import shutil
import subprocess
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent

# Model weights and temp files are kept OFF C: — it has under 2 GB free.
MODELS = Path("D:/mineru-models")

ENVIRONMENT = {
    "HF_HOME": str(MODELS / "hf"),
    "MODELSCOPE_CACHE": str(MODELS / "modelscope"),
    "TMP": str(MODELS / "tmp"),
    "TEMP": str(MODELS / "tmp"),
    "PYTHONIOENCODING": "utf-8",
    "UV_CACHE_DIR": "D:/uv-cache",
}

# `--no-sync` is MANDATORY, not a preference. Plain `uv run` syncs the venv to
# uv.lock first, and `mineru` is not locked there — so it would UNINSTALL MinerU
# before running anything. This is also the repo's standard form; see CLAUDE.md.
UV = ["uv", "run", "--no-sync", "python"]


def _environment() -> dict[str, str]:
    """Variables the caller already set are NOT overridden."""
    env = os.environ.copy()
    for k, v in ENVIRONMENT.items():
        env.setdefault(k, v)
    return env


def main() -> int:
    if shutil.which("uv") is None:
        print("uv is not on PATH.", file=sys.stderr)
        return 1

    env = _environment()

    if subprocess.run([*UV, "-c", "import mineru"], cwd=ROOT, env=env,
                      capture_output=True).returncode != 0:
        print("MinerU is not installed in this repo's venv.", file=sys.stderr)
        print("  UV_CACHE_DIR=D:/uv-cache uv pip install --python .venv "
              "mineru==3.4.4", file=sys.stderr)
        return 1

    args = list(sys.argv[1:])

    # A first argument that is not a flag is taken as the input folder.
    if args and not args[0].startswith("-"):
        input_dir = args.pop(0)
    else:
        input_dir = str(ROOT / "data" / "knowledge_docs")

    # The backend is chosen from whether a GPU is present, and ALWAYS printed —
    # no artifact should ever carry a backend nobody noticed.
    if "--backend" not in args:
        # flush is required: without it the parent's message appears AFTER the
        # child's output, and the reader assumes the backend was chosen late.
        if shutil.which("nvidia-smi"):
            print(">> GPU detected - using the default hybrid/high", flush=True)
        else:
            print(">> NO GPU detected - falling back to pipeline")
            print("   (no image descriptions; multiplication copied as the "
                  "letter 'x')", flush=True)
            args += ["--backend", "pipeline"]

    command = [*UV, "-m", "src.knowledge_parsing.run",
               "--input", input_dir, *args]
    return subprocess.run(command, cwd=ROOT, env=env).returncode


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