File size: 5,054 Bytes
e93bfbd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
"""Push this project to the Hugging Face Hub.

A bare `hf upload <repo> .` would push .venv (1.4 GB), 9,688 dataset images and
__pycache__ to the Hub -- `hf upload` does not read .gitignore. This script runs
the upload with the right excludes and publishes MODEL_CARD.md as the Hub's
README.md, leaving the GitHub README.md alone.

    python push_to_hub.py                 # dry run: list what would be uploaded
    python push_to_hub.py --push          # do it
    python push_to_hub.py --push --data   # include ./data (~181 MB, 9.7k images)
    python push_to_hub.py --push --create-pr
"""

import argparse
import os
import subprocess
import sys

REPO_ID = "GAD-Research-Lab/MedicalAI-Light-Weight"
ROOT = os.path.dirname(os.path.abspath(__file__))

# `hf upload` matches these with fnmatch against repo-relative POSIX paths.
EXCLUDE = [
    ".venv/*",
    ".git/*",
    "**/__pycache__/*",
    "*.pyc",
    "build/*",
    "dist/*",
    "*.spec",
    "update_config.json",
    "results.csv",
    "analyzing_images_for_ai.md",          # local scratch notes (gitignored)
    "blip-xray-finetuned/xray_blip.pth",   # {'epoch': N} stub, no weights -- misleading on the Hub
    "MODEL_CARD.md",   # uploaded separately, as README.md
    "README.md",       # GitHub README; the model card takes its place on the Hub
]
DATA_EXCLUDE = ["data/*"]


def hf_executable():
    """Prefer the `hf` next to the running interpreter, so a venv run stays in its venv."""
    bindir = os.path.dirname(sys.executable)
    for candidate in (os.path.join(bindir, "hf.exe"), os.path.join(bindir, "hf")):
        if os.path.exists(candidate):
            return candidate
    return "hf"


def files_to_upload(exclude):
    """Mirror hf upload's own filtering so the dry run is accurate."""
    from huggingface_hub.utils import filter_repo_objects

    paths = []
    for dirpath, dirnames, filenames in os.walk(ROOT):
        dirnames[:] = [d for d in dirnames if d not in (".git", ".venv", "__pycache__")]
        for name in filenames:
            full = os.path.join(dirpath, name)
            paths.append(os.path.relpath(full, ROOT).replace(os.sep, "/"))
    return sorted(filter_repo_objects(paths, allow_patterns=None, ignore_patterns=exclude))


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--push", action="store_true", help="Actually upload (default is a dry run)")
    ap.add_argument("--data", action="store_true", help="Include ./data (~181 MB of images)")
    ap.add_argument("--create-pr", action="store_true", help="Open a PR instead of committing to main")
    ap.add_argument("--private", action="store_true", help="Create the repo private if it does not exist")
    ap.add_argument("--repo-id", default=REPO_ID)
    ap.add_argument("--message", default="Upload models, ONNX exports and application code")
    args = ap.parse_args()

    exclude = list(EXCLUDE) + ([] if args.data else DATA_EXCLUDE)

    files = files_to_upload(exclude)
    total = sum(os.path.getsize(os.path.join(ROOT, f)) for f in files)
    print(f"{len(files)} files, {total / 1e9:.2f} GB -> {args.repo_id}\n")
    for f in files:
        size = os.path.getsize(os.path.join(ROOT, f))
        print(f"  {size / 1e6:>9.2f} MB  {f}" if size > 1e6 else f"  {'':>9}     {f}")

    if not args.push:
        print("\nDry run. Re-run with --push to upload.")
        return 0

    hf = hf_executable()
    common = ["--repo-type", "model"]
    if args.create_pr:
        common.append("--create-pr")
    if args.private:
        common.append("--private")

    # 1. Model card first, so the repo is never briefly published without one.
    print("\n-> Uploading MODEL_CARD.md as README.md")
    card = subprocess.run(
        [hf, "upload", args.repo_id, os.path.join(ROOT, "MODEL_CARD.md"), "README.md",
         "--commit-message", "Add model card", *common],
        cwd=ROOT,
    )
    if card.returncode != 0:
        print("Model card upload failed; stopping before the bulk upload.", file=sys.stderr)
        return card.returncode

    # 2. Everything else.
    print("\n-> Uploading project files")
    bulk = subprocess.run(
        [hf, "upload", args.repo_id, ".", "--commit-message", args.message,
         "--exclude", *exclude, *common],
        cwd=ROOT,
    )
    if bulk.returncode != 0:
        print("Bulk upload FAILED.", file=sys.stderr)
        return bulk.returncode

    # Never trust the exit code alone -- confirm against the Hub. A 403 on the LFS
    # endpoint can still leave small files committed, which looks like success.
    from huggingface_hub import HfApi

    remote = set(HfApi().list_repo_files(args.repo_id))
    missing = [f for f in files if f not in remote and f != "MODEL_CARD.md"]
    if missing:
        print(f"\n{len(missing)} file(s) did NOT land on the Hub:", file=sys.stderr)
        for f in missing:
            print(f"  {f}", file=sys.stderr)
        return 1

    print(f"\nVerified {len(files)} files: https://huggingface.co/{args.repo_id}")
    return 0


if __name__ == "__main__":
    sys.exit(main())