File size: 5,092 Bytes
22de727
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Prepare the public demo (MINIWEB_DEMO=1, src/miniweb/app/demo.py) from the Hugging Face release. The Space runs it on
every start, before the server (deploy/space/start.sh); everything goes under one folder, data/demo/ by default.

    python scripts/demo_setup.py                              # downloads from the Hub
    python scripts/demo_setup.py --no-media                   # without the 8 GB of site images
    python scripts/demo_setup.py --src data/release/miniweb-v1-hf   # from a local copy of the release

Each step is skipped when its output exists:
1. the site database, static images and media (scripts/restore_site_db.py): miniweb.db, static/, media/
2. the human tasks with their recordings (scripts/fetch_tasks.py): annotations/, which the annotation tool shows
3. translation_replay.json: each translation the recordings requested, with the translation the site returned, so
   the demo's translation site answers those requests exactly as recorded
4. the session tables, so the server starts on a ready database
Then the task folders are made read-only. Serve with the variables this prints (start.sh sets them).
"""
import argparse, glob, json, os, stat, subprocess, sys

ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
TRANSLATE = "/sites/translation/api/translate"


def run(*args):
    print("+", " ".join(args), flush=True)
    subprocess.run([sys.executable, *args], check=True, cwd=ROOT)


def recorded_translations(annotations):
    """{(source, target, text): translation} from the tasks' server logs and verification walks."""
    out, conflicts = {}, 0

    def walk(o):
        nonlocal conflicts
        if isinstance(o, dict):
            body, resp = o.get("body"), o.get("response")
            if (o.get("path") == TRANSLATE and isinstance(body, dict) and isinstance(resp, dict)
                    and body.get("text") and resp.get("translated_text")):
                key = (body.get("source", "").strip(), body.get("target", "").strip(), body["text"].strip())
                if out.setdefault(key, resp["translated_text"]) != resp["translated_text"]:
                    conflicts += 1                  # two recordings disagree: the first one read stays
            for v in o.values():
                walk(v)
        elif isinstance(o, list):
            for v in o:
                walk(v)

    for f in sorted(glob.glob(os.path.join(annotations, "*", "*", "server_log.json"))
                    + glob.glob(os.path.join(annotations, "*", "*", "verification_walk.json"))):
        with open(f) as fh:
            walk(json.load(fh))
    if conflicts:
        print(f"warning: {conflicts} recorded translations disagree with an earlier recording of the same text")
    return out


def read_only(folder):
    for d, _, files in os.walk(folder):
        for f in files:
            os.chmod(os.path.join(d, f), stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
        os.chmod(d, stat.S_IRUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--src", help="a local copy of the release (default: download from the Hub)")
    ap.add_argument("--out", default=os.path.join(ROOT, "data", "demo"))
    ap.add_argument("--no-media", action="store_true")
    a = ap.parse_args()
    out = os.path.abspath(a.out)
    paths = {"MINIWEB_DB": os.path.join(out, "miniweb.db"), "MINIWEB_DATA_STATIC": os.path.join(out, "static"),
             "MINIWEB_MEDIA_DIR": os.path.join(out, "media"), "MINIWEB_ANNOTATIONS_DIR": os.path.join(out, "annotations"),
             "MINIWEB_TRANSLATION_REPLAY": os.path.join(out, "translation_replay.json")}
    os.makedirs(out, exist_ok=True)

    if not os.path.exists(paths["MINIWEB_DB"]):
        run("scripts/restore_site_db.py", "--db", paths["MINIWEB_DB"], "--static", paths["MINIWEB_DATA_STATIC"],
            "--media", paths["MINIWEB_MEDIA_DIR"], *(["--src", os.path.join(a.src, "site_db")] if a.src else []),
            *(["--no-media"] if a.no_media else []))
    if not os.path.isdir(paths["MINIWEB_ANNOTATIONS_DIR"]):
        run("scripts/fetch_tasks.py", "--out", paths["MINIWEB_ANNOTATIONS_DIR"], "--valset", os.devnull,
            *(["--src", a.src] if a.src else []))
    if not os.path.exists(paths["MINIWEB_TRANSLATION_REPLAY"]):
        rows = [{"source": s, "target": t, "text": x, "translated": y}
                for (s, t, x), y in sorted(recorded_translations(paths["MINIWEB_ANNOTATIONS_DIR"]).items())]
        with open(paths["MINIWEB_TRANSLATION_REPLAY"], "w") as fh:
            json.dump(rows, fh, indent=1, ensure_ascii=False)
        print(f"{len(rows)} recorded translations -> {paths['MINIWEB_TRANSLATION_REPLAY']}")

    os.environ["MINIWEB_DB"] = paths["MINIWEB_DB"]
    sys.path.insert(0, os.path.join(ROOT, "src"))
    from miniweb.app import db
    db.init_db()
    db.close()
    read_only(paths["MINIWEB_ANNOTATIONS_DIR"])

    print("serve with:\n" + "\n".join(f"  {k}={v}" for k, v in paths.items()) + "\n  MINIWEB_DEMO=1")


if __name__ == "__main__":
    main()