33import urllib
44import yaml
55from rsfc .utils import constants
6+ from rsfc .utils .exceptions import GithubRateLimitExceeded
67
78class GithubHarvester :
89
@@ -60,8 +61,7 @@ def get_repo_type(self):
6061
6162
6263 def get_repo_default_branch (self ):
63- res = requests .get (self .api_url )
64- res .raise_for_status ()
64+ res = self .safe_request ("GET" , self .api_url )
6565 data = res .json ()
6666 return data .get ("default_branch" , "main" )
6767
@@ -74,41 +74,43 @@ def get_codemeta_file(self):
7474 req_url = self .api_url + '/contents/codemeta.json'
7575 headers = {'Accept' : 'application/vnd.github.v3.raw' }
7676 params = {'ref' : self .repo_branch }
77- response = self .session .get (req_url , headers = headers , params = params )
78- response .raise_for_status ()
77+ response = self .safe_request ("GET" , req_url , headers = headers , params = params )
7978 return response .json ()
79+
8080 elif self .repo_type == "GITLAB" :
8181 project_path_encoded = self .api_url .split ("/projects/" )[- 1 ]
8282 branch = self .repo_branch or "main"
8383 req_url = f"https://gitlab.com/api/v4/projects/{ project_path_encoded } /repository/files/codemeta.json/raw"
8484 params = {'ref' : branch }
85- response = self .session .get (req_url , params = params )
86- response .raise_for_status ()
85+ response = self .safe_request ("GET" , req_url , params = params )
8786 return response .json ()
87+
8888 else :
8989 return None
9090
9191 except requests .RequestException :
9292 return None
9393
9494 def get_cff_file (self ):
95-
95+
9696 try :
9797 if self .repo_type == "GITHUB" :
9898 req_url = self .api_url + '/contents/CITATION.cff'
9999 headers = {'Accept' : 'application/vnd.github.v3.raw' }
100100 params = {'ref' : self .repo_branch }
101- response = self . session . get ( req_url , headers = headers , params = params )
102- response . raise_for_status ( )
101+
102+ response = self . safe_request ( "GET" , req_url , headers = headers , params = params )
103103 return yaml .safe_load (response .text )
104+
104105 elif self .repo_type == "GITLAB" :
105106 project_path_encoded = self .api_url .split ("/projects/" )[- 1 ]
106107 branch = self .repo_branch or "main"
107108 req_url = f"https://gitlab.com/api/v4/projects/{ project_path_encoded } /repository/files/CITATION.cff/raw"
108109 params = {'ref' : branch }
109- response = self . session . get ( req_url , params = params )
110- response . raise_for_status ( )
110+
111+ response = self . safe_request ( "GET" , req_url , params = params )
111112 return yaml .safe_load (response .text )
113+
112114 else :
113115 return None
114116
@@ -120,8 +122,7 @@ def get_soft_version(self):
120122 try :
121123 releases_url = f"{ self .api_url } /releases"
122124
123- response = self .session .get (releases_url )
124- response .raise_for_status ()
125+ response = self .safe_request ("GET" , releases_url )
125126 releases = response .json ()
126127
127128 latest_release = None
@@ -158,11 +159,11 @@ def get_commits(self):
158159 if self .repo_type == "GITHUB" :
159160 commits_url = f"{ self .api_url } /commits?per_page=100"
160161 headers = {'Accept' : 'application/vnd.github.v3.raw' }
161- response = self .session . get ( commits_url , headers = headers )
162+ response = self .safe_request ( "GET" , commits_url , headers = headers )
162163
163164 elif self .repo_type == "GITLAB" :
164165 commits_url = f"{ self .api_url } /repository/commits?ref_name={ self .repo_branch } &per_page=100"
165- response = self .session . get ( commits_url )
166+ response = self .safe_request ( "GET" , commits_url )
166167
167168 else :
168169 raise ValueError (f"Not supported repository: { self .repo_type } " )
@@ -181,42 +182,81 @@ def get_issues(self):
181182 if self .repo_type == "GITHUB" :
182183 issues_url = f"{ self .api_url } /issues?state=all&per_page=100"
183184 headers = {'Accept' : 'application/vnd.github.v3.raw' }
184- response = self .session . get ( issues_url , headers = headers )
185+ response = self .safe_request ( "GET" , issues_url , headers = headers )
185186
186187 elif self .repo_type == "GITLAB" :
187188 issues_url = f"{ self .api_url } /issues?state=all&per_page=100"
188- response = self .session . get ( issues_url )
189+ response = self .safe_request ( "GET" , issues_url )
189190
190191 else :
191192 raise ValueError (f"Not supported repository: { self .repo_type } " )
192193
193194 issues = []
194195 if response .status_code == 200 :
195196 data = response .json ()
196- issues = [issue for issue in data if "pull_request " not in issue ] #Filter pull requests
197+ issues = [issue for issue in data if "pullsafe_request " not in issue ]
197198 else :
198199 print (f"Error getting issues: { response .status_code } " )
199200
200201 return issues
201202
202203
203-
204204 def get_tests (self ):
205205 test_evidences = []
206206
207207 if self .repo_type == "GITHUB" :
208208 tree_url = f"{ self .api_url } /git/trees/HEAD?recursive=1"
209- resp = self .session . get ( tree_url ,headers = {'Accept' : 'application/vnd.github.v3+json' })
209+ resp = self .safe_request ( "GET" , tree_url , headers = {'Accept' : 'application/vnd.github.v3+json' })
210210 if resp .status_code == 200 :
211211 test_evidences = resp .json ().get ("tree" , [])
212212
213213 elif self .repo_type == "GITLAB" :
214214 tree_url = f"{ self .api_url } /repository/tree?recursive=true&ref={ self .repo_branch } &per_page=100"
215- resp = self .session . get ( tree_url )
215+ resp = self .safe_request ( "GET" , tree_url )
216216 if resp .status_code == 200 :
217217 test_evidences = [{"path" : item ["path" ]} for item in resp .json ()]
218218
219219 else :
220220 raise ValueError ("Unsupported repository type" )
221-
222- return test_evidences
221+
222+ return test_evidences
223+
224+
225+ #Funcion wrapper que implementa la captura de fallo por rate limit alcanzado en la API de Github/lab
226+ def safe_request (self , method , url , ** kwargs ):
227+ response = self .session .request (method , url , ** kwargs )
228+
229+ if self .repo_type == constants .REPO_TYPES [0 ] and response .status_code in (403 , 429 ):
230+ remaining = response .headers .get ("X-RateLimit-Remaining" )
231+ if remaining == "0" :
232+ reset = response .headers .get ("X-RateLimit-Reset" )
233+ if reset :
234+ reset_time = datetime .fromtimestamp (int (reset ))
235+ raise GithubRateLimitExceeded (
236+ f"GitHub rate limit exceeded. Resets at { reset_time } ."
237+ )
238+ else :
239+ raise GithubRateLimitExceeded (
240+ "GitHub rate limit exceeded."
241+ )
242+
243+ if self .repo_type == constants .REPO_TYPES [1 ] and response .status_code == 429 :
244+ retry_after = response .headers .get ("Retry-After" )
245+ reset = response .headers .get ("RateLimit-Reset" )
246+
247+ if retry_after :
248+ raise GithubRateLimitExceeded (
249+ f"GitLab rate limit exceeded. Retry after { retry_after } seconds."
250+ )
251+ elif reset :
252+ reset_time = datetime .fromtimestamp (int (reset ))
253+ raise GithubRateLimitExceeded (
254+ f"GitLab rate limit exceeded. Resets at { reset_time } ."
255+ )
256+ else :
257+ raise GithubRateLimitExceeded (
258+ "GitLab rate limit exceeded."
259+ )
260+
261+ response .raise_for_status ()
262+ return response
0 commit comments