File size: 3,959 Bytes
f12d3b3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
106
107
108
109
110
111
112
113
114
#!/usr/bin/env python3
"""Fetch the third-party components that are not redistributed with this release.

DegradeMaster and EGFR-PROPK are published without a license, so we cannot ship
copies of them. This script clones each upstream repository at the pinned commit
used for the paper and copies the subset the pipeline imports into the layout
`scripts/evaluate_linkers.py` and `src/classifier/protac_classifier.py` expect.

Both are optional. Generation (`scripts/generate.py`) runs without either:
  - DegradeMaster is needed only for predicted-degradation scoring.
  - EGFR-PROPK is needed only for the EGFR-only PK columns of batch_report.py.

Usage:
    python scripts/fetch_third_party.py              # fetch both
    python scripts/fetch_third_party.py degrademaster
"""
from __future__ import annotations

import argparse
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]

COMPONENTS = {
    "degrademaster": {
        "url": "https://github.com/ABILiLab/DegradeMaster",
        "commit": "aa149beaf067a051e070b3b281f7dba43c2f3e90",
        "dest": ROOT / "src" / "degrademaster",
        "paths": [
            "model.py", "dataset.py", "nn_utils.py", "pdb_utils.py",
            "tools.py", "protacloader.py",
            "tokenizer", "utils",
            "checkpoint/1000/model_for_case_study.pth",
        ],
    },
    "propk": {
        "url": "https://github.com/Zhang-Ran-0119/EGFR-PROPK",
        "commit": "6940cb90c449e6c6d0678508fc2f228da796e028",
        "dest": ROOT / "src" / "propk",
        "paths": [
            "maplight.py", "maplight_gnn.py",
            "models/CL_1_finetune.pkl",
            "models/t0.5_10_finetune.pkl",
            "models/Vss_1_finetune.pkl",
        ],
    },
}


def fetch(name: str, spec: dict) -> int:
    print(f"\n=== {name} ===")
    print(f"  {spec['url']} @ {spec['commit'][:12]}")
    print("  NOTE: published without a license file. Review the upstream terms")
    print("        before using or redistributing it.")

    with tempfile.TemporaryDirectory() as tmp:
        try:
            subprocess.run(["git", "clone", "--quiet", spec["url"], tmp], check=True)
            subprocess.run(["git", "-C", tmp, "checkout", "--quiet", spec["commit"]],
                           check=True)
        except subprocess.CalledProcessError as exc:
            print(f"  FAILED to clone: {exc}")
            return 1

        missing = []
        for rel in spec["paths"]:
            src = Path(tmp) / rel
            dst = spec["dest"] / rel
            if not src.exists():
                missing.append(rel)
                continue
            dst.parent.mkdir(parents=True, exist_ok=True)
            if src.is_dir():
                shutil.copytree(src, dst, dirs_exist_ok=True)
            else:
                shutil.copy2(src, dst)
            print(f"  + {rel}")

        if missing:
            print(f"  {len(missing)} path(s) not found upstream at this commit:")
            for rel in missing:
                print(f"    - {rel}")
            print("  The upstream layout may have changed, or the checkpoint may be")
            print("  distributed outside git. Fetch those by hand.")
            return 1
    return 0


def main() -> int:
    ap = argparse.ArgumentParser(description=__doc__,
                                 formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("components", nargs="*", default=[],
                    choices=list(COMPONENTS),
                    help="which to fetch (default: all)")
    args = ap.parse_args()

    names = args.components or list(COMPONENTS)
    failures = sum(fetch(n, COMPONENTS[n]) for n in names)

    print()
    if failures:
        print(f"{failures} component(s) incomplete.")
    else:
        print("All components fetched.")
    return 1 if failures else 0


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