33import json
44import logging
55import re
6+ import time
67from typing import Any , Dict , Optional , Sequence , Tuple , Union
78
89import requests
910
1011import ghstack .github
1112
13+ MAX_RETRIES = 5
14+ INITIAL_BACKOFF_SECONDS = 60
15+
1216
1317class RealGitHubEndpoint (ghstack .github .GitHubEndpoint ):
1418 """
@@ -155,32 +159,63 @@ def rest(self, method: str, path: str, **kwargs: Any) -> Any:
155159 }
156160
157161 url = self .rest_endpoint .format (github_url = self .github_url ) + "/" + path
158- logging .debug ("# {} {}" .format (method , url ))
159- logging .debug ("Request body:\n {}" .format (json .dumps (kwargs , indent = 1 )))
160162
161- resp : requests .Response = getattr (requests , method )(
162- url ,
163- json = kwargs ,
164- headers = headers ,
165- proxies = self ._proxies (),
166- verify = self .verify ,
167- cert = self .cert ,
168- )
163+ backoff_seconds = INITIAL_BACKOFF_SECONDS
164+ for attempt in range (0 , MAX_RETRIES ):
165+ logging .debug ("# {} {}" .format (method , url ))
166+ logging .debug ("Request body:\n {}" .format (json .dumps (kwargs , indent = 1 )))
169167
170- logging .debug ("Response status: {}" .format (resp .status_code ))
168+ resp : requests .Response = getattr (requests , method )(
169+ url ,
170+ json = kwargs ,
171+ headers = headers ,
172+ proxies = self ._proxies (),
173+ verify = self .verify ,
174+ cert = self .cert ,
175+ )
171176
172- try :
173- r = resp .json ()
174- except ValueError :
175- logging .debug ("Response body:\n {}" .format (r .text ))
176- raise
177- else :
178- pretty_json = json .dumps (r , indent = 1 )
179- logging .debug ("Response JSON:\n {}" .format (pretty_json ))
177+ logging .debug ("Response status: {}" .format (resp .status_code ))
180178
181- if resp .status_code == 404 :
182- raise ghstack .github .NotFoundError (
183- """\
179+ try :
180+ r = resp .json ()
181+ except ValueError :
182+ logging .debug ("Response body:\n {}" .format (r .text ))
183+ raise
184+ else :
185+ pretty_json = json .dumps (r , indent = 1 )
186+ logging .debug ("Response JSON:\n {}" .format (pretty_json ))
187+
188+ # Per Github rate limiting: https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#exceeding-the-rate-limit
189+ if resp .status_code in (403 , 429 ):
190+ remaining_count = resp .headers .get ("x-ratelimit-remaining" )
191+ reset_time = resp .headers .get ("x-ratelimit-reset" )
192+
193+ if remaining_count == "0" and reset_time :
194+ sleep_time = int (reset_time ) - int (time .time ())
195+ logging .warning (
196+ f"Rate limit exceeded. Sleeping until reset in { sleep_time } seconds."
197+ )
198+ time .sleep (sleep_time )
199+ continue
200+ else :
201+ retry_after_seconds = resp .headers .get ("retry-after" )
202+ if retry_after_seconds :
203+ sleep_time = int (retry_after_seconds )
204+ logging .warning (
205+ f"Secondary rate limit hit. Sleeping for { sleep_time } seconds."
206+ )
207+ else :
208+ sleep_time = backoff_seconds
209+ logging .warning (
210+ f"Secondary rate limit hit. Sleeping for { sleep_time } seconds (exponential backoff)."
211+ )
212+ backoff_seconds *= 2
213+ time .sleep (sleep_time )
214+ continue
215+
216+ if resp .status_code == 404 :
217+ raise ghstack .github .NotFoundError (
218+ """\
184219 GitHub raised a 404 error on the request for
185220{url}.
186221Usually, this doesn't actually mean the page doesn't exist; instead, it
@@ -190,13 +225,15 @@ def rest(self, method: str, path: str, **kwargs: Any) -> Any:
190225"public_repo" for permissions, and update ~/.ghstackrc with your new
191226value.
192227""" .format (
193- url = url , github_url = self .github_url
228+ url = url , github_url = self .github_url
229+ )
194230 )
195- )
196231
197- try :
198- resp .raise_for_status ()
199- except requests .HTTPError :
200- raise RuntimeError (pretty_json )
232+ try :
233+ resp .raise_for_status ()
234+ except requests .HTTPError :
235+ raise RuntimeError (pretty_json )
201236
202- return r
237+ return r
238+
239+ raise RuntimeError ("Exceeded maximum retries due to GitHub rate limiting" )
0 commit comments