|
1 | | -"""HTTP dispatch logic for repository_dispatch events.""" |
2 | | -import http.client |
3 | | -import json |
4 | | -import ssl |
5 | | -import sys |
6 | | -import time |
7 | | -import urllib.error |
8 | | -import urllib.request |
9 | | - |
10 | | -from . import TARGET_REPO |
| 1 | +"""Client payload builder for repository_dispatch events.""" |
11 | 2 |
|
12 | 3 | BATCH_SIZE = 200 |
13 | | -DISPATCH_URL = f"https://api.github.com/repos/{TARGET_REPO}/dispatches" |
14 | | -MAX_ATTEMPTS = 5 |
15 | | - |
16 | 4 |
|
17 | | -class DispatchError(Exception): |
18 | | - """Raised when a dispatch request fails after all retry attempts.""" |
19 | 5 |
|
20 | | - |
21 | | -def build_payload(batch: list[str], source_repo: str, ref: str, target: str) -> dict: |
22 | | - """Return the ``repository_dispatch`` payload for one batch.""" |
| 6 | +def build_client_payload(packages: list[str], source_repo: str, ref: str) -> dict: |
| 7 | + """Return the ``client_payload`` dict for one batch.""" |
23 | 8 | return { |
24 | | - "event_type": "build-wheels", |
25 | | - "client_payload": { |
26 | | - "packages": batch, |
27 | | - "source_repo": source_repo, |
28 | | - "source_repo_ref": ref, |
29 | | - "target": target, |
30 | | - }, |
| 9 | + "packages": packages, |
| 10 | + "source_repo": source_repo, |
| 11 | + "source_repo_ref": ref, |
31 | 12 | } |
32 | 13 |
|
33 | 14 |
|
34 | | -def _urlopen(req: urllib.request.Request) -> http.client.HTTPResponse: |
35 | | - """Thin urllib wrapper — exists so tests can patch it without touching stdlib.""" |
36 | | - return urllib.request.urlopen(req) |
37 | | - |
38 | | - |
39 | | -def send_dispatch( |
40 | | - payload: dict, |
41 | | - token: str, |
42 | | - *, |
43 | | - dispatch_url: str = DISPATCH_URL, |
44 | | - max_attempts: int = MAX_ATTEMPTS, |
45 | | -) -> None: |
46 | | - """POST a single ``repository_dispatch`` event to the wheels-release repo. |
47 | | -
|
48 | | - Retries up to ``max_attempts`` times on 5xx errors with exponential backoff. |
49 | | - Raises ``DispatchError`` on 4xx errors or after exhausting retries. |
50 | | - """ |
51 | | - req = urllib.request.Request( |
52 | | - dispatch_url, |
53 | | - data=json.dumps(payload).encode(), |
54 | | - headers={ |
55 | | - "Authorization": f"Bearer {token}", |
56 | | - "Accept": "application/vnd.github+json", |
57 | | - "X-GitHub-Api-Version": "2022-11-28", |
58 | | - }, |
59 | | - method="POST", |
60 | | - ) |
61 | | - for attempt in range(1, max_attempts + 1): |
62 | | - try: |
63 | | - with _urlopen(req) as resp: |
64 | | - print(f" Dispatched: HTTP {resp.status}") |
65 | | - return |
66 | | - except (urllib.error.HTTPError, urllib.error.URLError) as e: |
67 | | - if isinstance(e, urllib.error.URLError): |
68 | | - if isinstance(e.reason, ssl.SSLError): |
69 | | - raise DispatchError(f"SSL error (non-retriable): {e}") from e |
70 | | - body = str(e) |
71 | | - code = 503 |
72 | | - else: |
73 | | - body = e.read().decode() |
74 | | - code = e.code |
75 | | - if code < 500 or attempt == max_attempts: |
76 | | - print(f"HTTP {code}: {body}", file=sys.stderr) |
77 | | - raise DispatchError(f"HTTP {code}: {body}") |
78 | | - print(f" HTTP {code} on attempt {attempt}/{max_attempts}, retrying...", file=sys.stderr) |
79 | | - time.sleep(2**attempt) |
80 | | - |
81 | | - |
82 | | -def dispatch_in_batches( |
| 15 | +def build_batches( |
83 | 16 | packages: list[str], |
84 | 17 | source_repo: str, |
85 | 18 | ref: str, |
86 | | - target: str, |
87 | | - token: str, |
88 | 19 | batch_size: int = BATCH_SIZE, |
89 | | -) -> None: |
90 | | - """Dispatch all packages to the wheels-release repo, batching if needed.""" |
| 20 | +) -> list[dict]: |
| 21 | + """Split packages into batches and return a list of client_payload dicts.""" |
91 | 22 | if batch_size <= 0: |
92 | 23 | raise ValueError("batch_size must be > 0") |
93 | | - num_packages = len(packages) |
94 | | - if num_packages == 0: |
95 | | - return |
96 | | - total_batches = (num_packages + batch_size - 1) // batch_size |
97 | | - for batch_num, start in enumerate(range(0, num_packages, batch_size), 1): |
98 | | - end = min(start + batch_size, num_packages) |
99 | | - current_batch = packages[start:end] |
100 | | - print(f"\nBatch {batch_num}/{total_batches}:") |
101 | | - print("\n".join(f" - {name}" for name in current_batch)) |
102 | | - send_dispatch(build_payload(current_batch, source_repo, ref, target), token) |
| 24 | + if not packages: |
| 25 | + return [] |
| 26 | + return [ |
| 27 | + build_client_payload(packages[i : i + batch_size], source_repo, ref) |
| 28 | + for i in range(0, len(packages), batch_size) |
| 29 | + ] |
0 commit comments