1*da0073e9SAndroid Build Coastguard Worker"""GitHub Utilities""" 2*da0073e9SAndroid Build Coastguard Worker 3*da0073e9SAndroid Build Coastguard Workerfrom __future__ import annotations 4*da0073e9SAndroid Build Coastguard Worker 5*da0073e9SAndroid Build Coastguard Workerimport json 6*da0073e9SAndroid Build Coastguard Workerimport os 7*da0073e9SAndroid Build Coastguard Workerfrom typing import Any, Callable, cast, Dict 8*da0073e9SAndroid Build Coastguard Workerfrom urllib.error import HTTPError 9*da0073e9SAndroid Build Coastguard Workerfrom urllib.parse import quote 10*da0073e9SAndroid Build Coastguard Workerfrom urllib.request import Request, urlopen 11*da0073e9SAndroid Build Coastguard Worker 12*da0073e9SAndroid Build Coastguard Worker 13*da0073e9SAndroid Build Coastguard Workerdef gh_fetch_url_and_headers( 14*da0073e9SAndroid Build Coastguard Worker url: str, 15*da0073e9SAndroid Build Coastguard Worker *, 16*da0073e9SAndroid Build Coastguard Worker headers: dict[str, str] | None = None, 17*da0073e9SAndroid Build Coastguard Worker data: dict[str, Any] | None = None, 18*da0073e9SAndroid Build Coastguard Worker method: str | None = None, 19*da0073e9SAndroid Build Coastguard Worker reader: Callable[[Any], Any] = lambda x: x.read(), 20*da0073e9SAndroid Build Coastguard Worker) -> tuple[Any, Any]: 21*da0073e9SAndroid Build Coastguard Worker if headers is None: 22*da0073e9SAndroid Build Coastguard Worker headers = {} 23*da0073e9SAndroid Build Coastguard Worker token = os.environ.get("GITHUB_TOKEN") 24*da0073e9SAndroid Build Coastguard Worker if token is not None and url.startswith("https://api.github.com/"): 25*da0073e9SAndroid Build Coastguard Worker headers["Authorization"] = f"token {token}" 26*da0073e9SAndroid Build Coastguard Worker data_ = json.dumps(data).encode() if data is not None else None 27*da0073e9SAndroid Build Coastguard Worker try: 28*da0073e9SAndroid Build Coastguard Worker with urlopen(Request(url, headers=headers, data=data_, method=method)) as conn: 29*da0073e9SAndroid Build Coastguard Worker return conn.headers, reader(conn) 30*da0073e9SAndroid Build Coastguard Worker except HTTPError as err: 31*da0073e9SAndroid Build Coastguard Worker if err.code == 403 and all( 32*da0073e9SAndroid Build Coastguard Worker key in err.headers for key in ["X-RateLimit-Limit", "X-RateLimit-Used"] 33*da0073e9SAndroid Build Coastguard Worker ): 34*da0073e9SAndroid Build Coastguard Worker print( 35*da0073e9SAndroid Build Coastguard Worker f"""Rate limit exceeded: 36*da0073e9SAndroid Build Coastguard Worker Used: {err.headers['X-RateLimit-Used']} 37*da0073e9SAndroid Build Coastguard Worker Limit: {err.headers['X-RateLimit-Limit']} 38*da0073e9SAndroid Build Coastguard Worker Remaining: {err.headers['X-RateLimit-Remaining']} 39*da0073e9SAndroid Build Coastguard Worker Resets at: {err.headers['x-RateLimit-Reset']}""" 40*da0073e9SAndroid Build Coastguard Worker ) 41*da0073e9SAndroid Build Coastguard Worker raise 42*da0073e9SAndroid Build Coastguard Worker 43*da0073e9SAndroid Build Coastguard Worker 44*da0073e9SAndroid Build Coastguard Workerdef gh_fetch_url( 45*da0073e9SAndroid Build Coastguard Worker url: str, 46*da0073e9SAndroid Build Coastguard Worker *, 47*da0073e9SAndroid Build Coastguard Worker headers: dict[str, str] | None = None, 48*da0073e9SAndroid Build Coastguard Worker data: dict[str, Any] | None = None, 49*da0073e9SAndroid Build Coastguard Worker method: str | None = None, 50*da0073e9SAndroid Build Coastguard Worker reader: Callable[[Any], Any] = lambda x: x.read(), 51*da0073e9SAndroid Build Coastguard Worker) -> Any: 52*da0073e9SAndroid Build Coastguard Worker return gh_fetch_url_and_headers( 53*da0073e9SAndroid Build Coastguard Worker url, headers=headers, data=data, reader=json.load, method=method 54*da0073e9SAndroid Build Coastguard Worker )[1] 55*da0073e9SAndroid Build Coastguard Worker 56*da0073e9SAndroid Build Coastguard Worker 57*da0073e9SAndroid Build Coastguard Workerdef _gh_fetch_json_any( 58*da0073e9SAndroid Build Coastguard Worker url: str, 59*da0073e9SAndroid Build Coastguard Worker params: dict[str, Any] | None = None, 60*da0073e9SAndroid Build Coastguard Worker data: dict[str, Any] | None = None, 61*da0073e9SAndroid Build Coastguard Worker) -> Any: 62*da0073e9SAndroid Build Coastguard Worker headers = {"Accept": "application/vnd.github.v3+json"} 63*da0073e9SAndroid Build Coastguard Worker if params is not None and len(params) > 0: 64*da0073e9SAndroid Build Coastguard Worker url += "?" + "&".join( 65*da0073e9SAndroid Build Coastguard Worker f"{name}={quote(str(val))}" for name, val in params.items() 66*da0073e9SAndroid Build Coastguard Worker ) 67*da0073e9SAndroid Build Coastguard Worker return gh_fetch_url(url, headers=headers, data=data, reader=json.load) 68*da0073e9SAndroid Build Coastguard Worker 69*da0073e9SAndroid Build Coastguard Worker 70*da0073e9SAndroid Build Coastguard Workerdef gh_fetch_json_dict( 71*da0073e9SAndroid Build Coastguard Worker url: str, 72*da0073e9SAndroid Build Coastguard Worker params: dict[str, Any] | None = None, 73*da0073e9SAndroid Build Coastguard Worker data: dict[str, Any] | None = None, 74*da0073e9SAndroid Build Coastguard Worker) -> dict[str, Any]: 75*da0073e9SAndroid Build Coastguard Worker return cast(Dict[str, Any], _gh_fetch_json_any(url, params, data)) 76*da0073e9SAndroid Build Coastguard Worker 77*da0073e9SAndroid Build Coastguard Worker 78*da0073e9SAndroid Build Coastguard Workerdef gh_fetch_commit(org: str, repo: str, sha: str) -> dict[str, Any]: 79*da0073e9SAndroid Build Coastguard Worker return gh_fetch_json_dict( 80*da0073e9SAndroid Build Coastguard Worker f"https://api.github.com/repos/{org}/{repo}/commits/{sha}" 81*da0073e9SAndroid Build Coastguard Worker ) 82