File size: 2,798 Bytes
be19956
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Write one bucket file with a CALLER's token, in a short-lived child process.

Bucket uploads go through the Xet client, whose session is global to the
process. When a caller's token may not write the bucket, the upload fails with
a ConnectionError and the session keeps that error: every later Xet operation
in the process — the Space's own admin-token reads and writes included — then
raises ``RuntimeError: Previous task error: ...`` until a restart. A refused
caller write is an ordinary user error (a taken agent_id, a read-only token),
so it runs here instead, where a poisoned session dies with the child.

Protocol: the parent runs ``python -m app.caller_write`` with a minimal
environment (no admin ``HF_TOKEN``) and sends one JSON object on stdin:
``{"token", "bucket", "path", "text"}``. The child prints one JSON object on
stdout: ``{"result": "ok"}``, ``{"result": "forbidden", "status": 401|403}``
or ``{"result": "failed", "type": "<ExceptionType>", "status": <int|null>}``.
The token never appears in argv, files, stdout or stderr.

Kept free of ``app.*`` imports so the child starts without the Space's
settings or credentials.
"""
from __future__ import annotations

import json
import re
import sys

# The Xet client's refusal text, e.g. "HTTP status client error (403 Forbidden)".
# Matched on the explicit status phrase, never a bare 401/403 that could be
# part of a bucket name or URL.
_XET_REFUSED_RE = re.compile(r"HTTP status client error \((401|403) (Unauthorized|Forbidden)\)")


def classify(e: BaseException) -> dict:
    """Structured result for a failed caller write: "forbidden" only for an
    explicit 401/403 from the Hub; anything else is "failed" (upstream)."""
    resp = getattr(e, "response", None)
    status = getattr(resp, "status_code", None)
    if status in (401, 403):
        return {"result": "forbidden", "status": status}
    if isinstance(e, ConnectionError):
        m = _XET_REFUSED_RE.search(str(e))
        if m:
            return {"result": "forbidden", "status": int(m.group(1))}
    return {"result": "failed", "type": type(e).__name__, "status": status}


def run(request: dict) -> dict:
    from huggingface_hub import batch_bucket_files

    try:
        batch_bucket_files(
            bucket_id=request["bucket"],
            add=[(request["text"].encode("utf-8"), request["path"])],
            token=request["token"],
        )
    except Exception as e:
        return classify(e)
    return {"result": "ok"}


def main() -> int:
    try:
        request = json.loads(sys.stdin.read())
    except ValueError:
        print(json.dumps({"result": "failed", "type": "BadRequest", "status": None}))
        return 0
    print(json.dumps(run(request)), flush=True)
    return 0


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