Skip to content

Commit 738397a

Browse files
authored
Merge pull request #82 from andriumon/main
Update to 0.1.3
2 parents 86ad0c2 + b370be8 commit 738397a

13 files changed

Lines changed: 112 additions & 45 deletions

File tree

CITATION.cff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ keywords:
2626
- indicators
2727
- fairness
2828
license: MIT
29-
version: 0.1.2
29+
version: 0.1.3

codemeta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@
3434
"operatingSystem": "Linux",
3535
"programmingLanguage": "Python",
3636
"relatedLink": "https://github.com/EVERSE-ResearchSoftware/indicators",
37-
"version": "0.1.2",
37+
"version": "0.1.3",
3838
"developmentStatus": "wip"
3939
}

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "rsfc"
3-
version = "0.1.2"
3+
version = "0.1.3"
44
description = "EVERSE Research Software Fairness Checks"
55
authors = ["Andres Montero <andres.montero.martin@upm.es>"]
66
license = "MIT"

requirements.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ click-option-group==0.5.7
1414
contourpy==1.3.2
1515
contractions==0.1.73
1616
cycler==0.12.1
17+
docker==7.1.0
1718
docutils==0.21.2
1819
duckdb==1.3.1
1920
elementpath==4.8.0
@@ -32,7 +33,7 @@ jsonschema==4.24.0
3233
jsonschema-specifications==2025.4.1
3334
jupyter_core==5.8.1
3435
kiwisolver==1.4.8
35-
lxml==5.4.0
36+
lxml==5.1.0
3637
Markdown==3.8.1
3738
MarkupSafe==3.0.2
3839
matplotlib==3.10.3
@@ -72,6 +73,7 @@ six==1.17.0
7273
snowballstemmer==3.0.1
7374
somef==0.9.11
7475
soupsieve==2.7
76+
tabulate==0.9.0
7577
textblob==0.17.1
7678
textsearch==0.0.24
7779
threadpoolctl==3.6.0
@@ -84,4 +86,4 @@ typing_extensions==4.14.0
8486
tzdata==2025.2
8587
urllib3==2.5.0
8688
validators==0.22.0
87-
xgboost==2.1.4
89+
xgboost==2.1.4

src/rsfc/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: utf-8 -*-
22

33

4-
__version__ = "0.1.2"
4+
__version__ = "0.1.3"
55

src/rsfc/harvesters/github_harvester.py

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import urllib
44
import yaml
55
from rsfc.utils import constants
6+
from rsfc.utils.exceptions import GithubRateLimitExceeded
67

78
class 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

src/rsfc/main.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,23 @@ def main():
1313

1414
from rsfc.rsfc_core import start_assessment
1515
from rsfc.utils.rsfc_helpers import resolve_w3id, remove_git_from_url
16+
from rsfc.utils.exceptions import GithubRateLimitExceeded
1617
import os
1718
import json
19+
import sys
20+
1821

1922
print("Checking if url is w3id")
2023

2124
repo_url = resolve_w3id(args.repo)
2225
repo_url = remove_git_from_url(repo_url)
2326

24-
rsfc_asmt, table = start_assessment(repo_url, args.ftr, args.id, args.t)
27+
try:
28+
rsfc_asmt, table = start_assessment(repo_url, args.ftr, args.id, args.t)
29+
30+
except GithubRateLimitExceeded as e:
31+
print(f"\nERROR: {e}")
32+
sys.exit(1)
2533

2634
output_dir = './rsfc_output/'
2735
output_file = "rsfc_assessment.json"

src/rsfc/model/assessment.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ class Assessment:
1010
def __init__(self, checks):
1111
self.checks = checks
1212

13-
1413
def render_template(self, sw, ftr, test_id):
1514

1615
print("Rendering assessment...")
@@ -49,7 +48,7 @@ def render_template(self, sw, ftr, test_id):
4948
return json.loads(rendered)
5049

5150

52-
def to_terminal_table(self, test_id):
51+
def to_terminal_table(self, test_id, badge_url):
5352
rows = []
5453

5554
for check in self.checks:
@@ -74,6 +73,6 @@ def to_terminal_table(self, test_id):
7473
headers = ["TEST ID", "Short Description", "Output"]
7574
table = tabulate(rows, headers, tablefmt="grid")
7675
info = "\n\nFor rationale on the tests performed, please check the assessment file created in the outputs folder.\n"
77-
table = table + info
76+
badge = f"\n\nRSFC badge for your README file:, {badge_url}\n"
7877

79-
return table
78+
return table + info + badge

src/rsfc/rsfc_core.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from rsfc.harvesters import codemeta_harvester as cm
66
from rsfc.harvesters import cff_harvester as cf
77
from rsfc.harvesters import github_harvester as gt
8+
from rsfc.utils import rsfc_helpers
89

910

1011
def start_assessment(repo_url, ftr, test_id, token):
@@ -23,6 +24,7 @@ def start_assessment(repo_url, ftr, test_id, token):
2324
assess = asmt.Assessment(checks)
2425

2526
rsfc_asmt = assess.render_template(sw, ftr, test_id)
26-
table = assess.to_terminal_table(test_id)
27+
badge_url = rsfc_helpers.generate_badge(checks)
28+
table = assess.to_terminal_table(test_id, badge_url)
2729

2830
return rsfc_asmt, table

src/rsfc/rsfc_tests/rsfc_tests.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -843,14 +843,12 @@ def test_dependencies_have_version(somef_data):
843843
evidence = constants.EVIDENCE_DEPENDENCIES_VERSION
844844
suggest = "No suggestions"
845845
for item in somef_data['requirements']:
846-
if 'README' not in item['source'] and "version" in item["result"]:
847-
if item["result"]["version"]:
848-
continue
849-
else:
850-
output = "false"
851-
evidence = constants.EVIDENCE_NO_DEPENDENCIES_VERSION
852-
suggest = constants.SUGGEST_NO_DEPENDENCIES_VERSION
853-
break
846+
if 'README' not in item['source']:
847+
if not item["result"].get("version"):
848+
output = "false"
849+
evidence = constants.EVIDENCE_NO_DEPENDENCIES_VERSION
850+
suggest = constants.SUGGEST_NO_DEPENDENCIES_VERSION
851+
break
854852

855853
check = ch.Check(constants.INDICATORS_DICT['requirements_specified'], 'RSFC-13-3', "Dependencies have version numbers", constants.PROCESS_DEPENDENCIES_VERSION, output, evidence, suggest)
856854

0 commit comments

Comments
 (0)