Skip to content

Commit fda1d97

Browse files
authored
add update_checksup (#1072)
1 parent 348da1d commit fda1d97

File tree

14 files changed

+270
-85
lines changed

14 files changed

+270
-85
lines changed

.github/workflows/weekly-badge-and-stats-update.yml renamed to .github/workflows/weekly-update.yml

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Weekly | Update stats and badges
1+
name: Weekly | Update member data
22

33
on:
44
workflow_dispatch:
@@ -7,6 +7,7 @@ on:
77

88
jobs:
99
batch_update_badges:
10+
name: "Update members data"
1011
runs-on: ubuntu-latest
1112
steps:
1213
- name: Setup variables
@@ -24,27 +25,30 @@ jobs:
2425
python -m pip install --upgrade pip
2526
pip install -r requirements.txt
2627
27-
- name: Update GitHub stats
28+
- name: Update GitHub data
2829
run: python manager.py members update_github
2930
env:
3031
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3132

32-
- name: Update PyPI stats
33+
- name: Update PyPI data
3334
run: python manager.py members update_pypi
3435

35-
- name: Update Julia stats
36+
- name: Update Julia data
3637
run: python manager.py members update_julia
3738

39+
- name: Update checkups data
40+
run: python manager.py members update_checkups
41+
3842
- name: Create PR for stats and badges update
3943
id: cpr
4044
uses: peter-evans/create-pull-request@v7
4145
with:
4246
author: GitHub <noreply@github.com>
43-
commit-message: Badges and stats update for ${{ env.dateIseconds }}
44-
title: Badges and stats update for ${{ env.date_bdY }}
47+
commit-message: Member data update for ${{ env.dateIseconds }}
48+
title: Member data update for ${{ env.date_bdY }}
4549
body: |
46-
Badges and stats update
47-
Time: ${{ env.dateIseconds }}
50+
Member data update
51+
when: ${{ env.dateIseconds }}
4852
${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
4953
branch: ${{ env.pr_branch_name }}
5054
labels: member update

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ dmypy.json
140140

141141
# Generated files
142142
website/
143+
badges/
143144
docs/assets/
144145
all_qiskit_versions.json
145146

ecosystem/check.py

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Checks/Validations section."""
22

3+
import datetime
34
from pathlib import Path
45

56
import tomllib
@@ -22,17 +23,25 @@ def __init__(self, toml_filename: str = None, resources_dir: str = None):
2223
data = tomllib.load(f)
2324
self._data = data
2425

25-
def check(self, checkid):
26+
def checkup(self, checkup_id):
2627
"""Given an ID for a check, the details"""
27-
return self._data[checkid]
28+
return self._data[checkup_id]
29+
30+
def id_by_pytest_node(self, node_id):
31+
"""Given a PyTest node ID, find the test ID"""
32+
for id_, checkup in self._data.items():
33+
if not isinstance(checkup, dict):
34+
continue
35+
if checkup.get("checker") == node_id:
36+
return id_
37+
raise AttributeError(f"nodeid {node_id} not found")
2838

2939

3040
class CheckData(JsonSerializable):
3141
"""
3242
The validation data related to a project
3343
"""
3444

35-
dict_keys = ["id", "xfailed", "since", "details"]
3645
labels_toml = ChecksToml()
3746

3847
def __init__(self, id_: str, xfailed=None, since=None, details=None, **_):
@@ -42,20 +51,43 @@ def __init__(self, id_: str, xfailed=None, since=None, details=None, **_):
4251
self.details = details
4352

4453
def to_dict(self) -> dict:
45-
ret = {}
46-
if self.xfailed:
47-
ret["xfailed"] = self.xfailed
48-
if self.since:
49-
ret["since"] = self.since
50-
if self.details:
51-
ret["details"] = self.details
52-
54+
ret = super().to_dict()
55+
del ret["id"]
56+
ret["importance"] = self.importance
5357
return ret
5458

59+
@property
60+
def importance(self):
61+
"""get the importance of the check"""
62+
if "importance" in self.labels_toml.checkup(self.id):
63+
return self.labels_toml.checkup(self.id)["importance"]
64+
return None
65+
66+
def __repr__(self):
67+
return str(self.to_dict())
68+
5569
def __getattr__(self, name):
56-
return self.labels_toml.check(self.id)[name]
70+
return self.labels_toml.checkup(self.id)[name]
5771

58-
def importance(self):
72+
@classmethod
73+
def from_report(cls, pytest_report):
74+
"""creates a CheckData instance based on a PyTest report"""
75+
assertion_msg = (
76+
pytest_report.longreprtext.partition("\n")[0].split(":", 1)[1].strip()
77+
)
78+
test_id = cls.labels_toml.id_by_pytest_node(pytest_report.nodeid)
79+
if hasattr(pytest_report, "wasxfail") and pytest_report.wasxfail:
80+
return CheckData(
81+
test_id, details=assertion_msg, xfailed=pytest_report.wasxfail
82+
)
83+
since = (
84+
pytest_report.previously_failed.kwargs["since"]
85+
if hasattr(pytest_report, "previously_failed")
86+
else datetime.datetime.today()
87+
)
88+
return CheckData(test_id, details=assertion_msg, since=since)
89+
90+
def importances(self):
5991
"""Returns dict name->description with the possible importance values"""
6092
return {i["name"]: i["description"] for i in self.labels_toml["importance"]}
6193

ecosystem/cli/members.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""CliMembers class for controlling all CLI functions."""
22

3+
import datetime
34
import json
45
import tomllib
56
import os
@@ -161,6 +162,44 @@ def update_julia(self, name=None):
161162
project.update_julia()
162163
self.dao.update(project.name_id, julia=project.julia)
163164

165+
def update_checkups(self, name=None):
166+
"""
167+
Updates checkups data.
168+
If <name> is not given, runs on all the members.
169+
Otherwise, all the members with name_id that contains <name>
170+
as substring are checked.
171+
"""
172+
for project in self.dao.get_all(name):
173+
project.update_checkups()
174+
if project.checks:
175+
today = datetime.datetime.today()
176+
for checkup_id, checkup in project.checks.items():
177+
if checkup.xfailed:
178+
self.logger.info(
179+
"☑️ %s expected to fail checkup %s: %s ",
180+
project.name,
181+
checkup_id,
182+
checkup.xfailed,
183+
)
184+
continue
185+
days_since = (today - checkup.since).days
186+
for_x_days = (
187+
f"for {days_since} days" if days_since else "since today"
188+
)
189+
self.logger.info(
190+
"❌ %s failed checkup %s (%s)",
191+
project.name,
192+
checkup_id,
193+
for_x_days,
194+
)
195+
else:
196+
self.logger.info(
197+
"✅ %s (%s) passed all the checkups",
198+
project.name,
199+
project.name_id,
200+
)
201+
self.dao.update(project.name_id, checks=project.checks)
202+
164203
@staticmethod
165204
def filter_data(
166205
member_dict, data_map, forced_addition=False

ecosystem/member.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
from uuid import uuid4
55
import re
66

7+
from .error_handling import EcosystemError
78
from .julia import JuliaData
89
from .serializable import JsonSerializable, parse_datetime
910
from .github import GitHubData
1011
from .pypi import PyPIData
1112
from .check import CheckData
1213
from .request import URL, request_json
14+
from .validation import validate_member
1315

1416

1517
class Member(JsonSerializable): # pylint: disable=too-many-instance-attributes
@@ -168,8 +170,23 @@ def _create_qisk_dot_it_link_for_badge(self):
168170
response = request_json("https://api-ssl.bitly.com/v4/bitlinks", post=data)
169171
return response["link"]
170172

173+
def _qisk_dot_it_link_exists(self):
174+
try:
175+
request_json(
176+
f"https://api-ssl.bitly.com/v4/bitlinks/qisk.it/e-{self.short_uuid}",
177+
parser=lambda x: {},
178+
)
179+
except EcosystemError as error:
180+
if "Not Found (404)" in error.message:
181+
return None
182+
if "Unauthorized (401)" in error.message:
183+
return "UNAUTHORIZED"
184+
raise error
185+
return f"https://qisk.it/e-{self.short_uuid}"
186+
171187
def update_badge(self):
172188
"""If not there yet, creates a new Bitly link for the badge"""
189+
self.badge = self._qisk_dot_it_link_exists()
173190
if self.badge is None:
174191
self.badge = self._create_qisk_dot_it_link_for_badge()
175192

@@ -252,4 +269,17 @@ def from_submission(cls, submission, issue_number: str = None):
252269
@property
253270
def xfails(self):
254271
"""list of xfails for a self member"""
255-
return [check for check in self.checks if check.xfailed]
272+
return [
273+
check
274+
for checkid, check in self.checks.items()
275+
if hasattr(check, "xfailed") and check.xfailed
276+
]
277+
278+
def update_checkups(self):
279+
"""Runs validation tests and updates the check-ups sections"""
280+
checkups = {}
281+
report = validate_member(self, verbose_level="-q")
282+
for test in report.xfailed + report.failed:
283+
checkup_data = CheckData.from_report(test)
284+
checkups[checkup_data.id] = checkup_data
285+
self.checks = checkups

ecosystem/resources/checks.toml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,16 @@ importance = "STRONG-RECOMMENDATION"
2626
title = "URLs must exists"
2727
description = "None of the URLs must not return 404s or other forms of permanent errors. Rediractions are accepted."
2828
applies_to = "all"
29-
affects = ["member.website", "member.url", "member.documentation", "member.packages.*"]
29+
affects = ["member.website", "member.url", "member.documentation", "member.packages.*", "member.pypi.*.url"]
30+
category = "SUBMISSION"
31+
importance = "IMPORTANT"
32+
33+
[013]
34+
title = "URLs use a secure schema"
35+
description = "All the URLs must use HTTTPS (HyperText Transfer Protocol Secure) schema."
36+
applies_to = "all"
37+
affects = ["member.website", "member.url", "member.documentation", "member.packages.*", "member.pypi.*.url"]
38+
checker = "test_url.py::TestURLs::test_http"
3039
category = "SUBMISSION"
3140
importance = "IMPORTANT"
3241

@@ -35,6 +44,7 @@ title="Adhere to the Qiskit CoC"
3544
applies_to = "all"
3645
category = "SUBMISSION"
3746
description = "Project submitters should adhere to the Qiskit code of conduct. If the submitter is the project maintainer, they can enforce your their code of conduct in addition to the one coming from Qiskit."
47+
importance = "CRITICAL"
3848

3949
[Q20]
4050
title = "Be compatible with the Qiskit SDK v2 or newer"

ecosystem/resources/members/arvaklite_7fcadf8b.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ website = "https://arvak.io"
88
group = "Transpiler plugin"
99
packages = []
1010
uuid = "7fcadf8b-f043-4eac-a491-9783a1c00cfe"
11+
badge = "https://qisk.it/e-7fcadf8b"
1112

1213
[github]
1314
url = "https://github.com/hiq-lab/arvak-lite"

ecosystem/resources/members/azurequant_8abf631e.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ labels = [ "provider", "cloud service",]
66
created_at = 1715084759.598566
77
group = "Compute provider"
88
uuid = "8abf631e-bbf0-4624-b9c7-60073981f5b0"
9-
badge = "https://qisk.it/qe-8abf631e"
9+
badge = "https://qisk.it/e-8abf631e"
1010

1111
[github]
1212
url = "https://github.com/microsoft/azure-quantum-python"

ecosystem/resources/members/qiskitinsp_1513e7fe.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ url = "https://github.com/neuralsorcerer/qiskit-inspect"
1313
owner = "neuralsorcerer"
1414
repo = "qiskit-inspect"
1515
stars = 1
16-
homepage = "http://pypi.org/project/qiskit-inspect/"
16+
homepage = "https://pypi.org/project/qiskit-inspect/"
1717
license = "Apache License 2.0"
1818
description = "Debugger, backend prefix tracing, and assertions for Qiskit"
1919
total_dependent_repositories = 0

ecosystem/validation/__init__.py

Lines changed: 1 addition & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
"""Validation module"""
22

33
import pytest
4+
from ecosystem.validation.conftest import ValidationReport
45

56
# pylint: disable=pointless-string-statement
67
"""
7-
BIG TODO:
8-
MOVE THE VALIDATIONS TO REGULAR PYTHON-BASED TESTS.
9-
THIS CUSTOM VALIDATION DISCOVERY LOOKS TOO MUCH TO UNITTEST DISCOVERY
108
119
TODO json:
1210
- check no member repetition
@@ -34,59 +32,6 @@
3432
"""
3533

3634

37-
class ValidationReport:
38-
# pylint: disable=missing-function-docstring, missing-class-docstring
39-
40-
def __init__(self, member):
41-
self._member = member
42-
self.collected = 0
43-
self.exitcode = 0
44-
self.passed = []
45-
self.failed = []
46-
self.xfailed = []
47-
self.skipped = []
48-
49-
@property
50-
def xfails(self):
51-
return {
52-
checkdata.checker: checkdata.xfailed for checkdata in self._member.xfails
53-
}
54-
55-
def pytest_itemcollected(self, item):
56-
# pylint: disable=protected-access
57-
item._nodeid = "/".join(item.nodeid.split("/")[2:])
58-
59-
@pytest.hookimpl(hookwrapper=True)
60-
def pytest_runtest_makereport(self, item, call): # pylint: disable=unused-argument
61-
62-
outcome = yield
63-
report = outcome.get_result()
64-
if report.when == "call":
65-
if report.passed:
66-
self.passed.append(report)
67-
elif report.failed:
68-
self.failed.append(report)
69-
elif report.wasxfail:
70-
self.xfailed.append(report)
71-
else:
72-
self.skipped.append(report)
73-
74-
def pytest_collection_modifyitems(self, items):
75-
self.collected = len(items)
76-
for item in items:
77-
if item.nodeid in self.xfails:
78-
item.add_marker(pytest.mark.xfail(reason=self.xfails[item.nodeid]))
79-
80-
def pytest_terminal_summary(
81-
self, terminalreporter, exitstatus
82-
): # pylint: disable=unused-argument
83-
self.exitcode = exitstatus.value if hasattr(exitstatus, "value") else exitstatus
84-
85-
@pytest.fixture
86-
def member(self):
87-
return self._member
88-
89-
9035
def validate_member(member, verbose_level=None):
9136
"""Runs all the validation for a member
9237
verbose_level: -v, -vv, -q

0 commit comments

Comments
 (0)