• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Qiskit / ecosystem / 24730829106

21 Apr 2026 03:22PM UTC coverage: 56.68%. First build
24730829106

Pull #1104

github

web-flow
Merge fd4ac2c79 into 7baf32bfa
Pull Request #1104: If the test was successful, upload to coveralls

857 of 1512 relevant lines covered (56.68%)

0.57 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

27.18
/ecosystem/github.py
1
"""GitHub section."""
2

3
from re import match
1✔
4
from functools import reduce
1✔
5
from jsonpath import findall
1✔
6

7
from .serializable import JsonSerializable, parse_datetime
1✔
8
from .error_handling import EcosystemError, logger
1✔
9
from .request import (
1✔
10
    request_json,
11
    parse_github_package_ids,
12
    parse_github_dependants,
13
    parse_github_contributors_sidebar,
14
    URL,
15
)
16

17

18
class GitHubData(JsonSerializable):
1✔
19
    """
20
    The GitHub data related to a project
21
    """
22

23
    dict_keys = [
1✔
24
        "url",
25
        "owner",
26
        "repo",
27
        "tree",
28
        "stars",
29
        "homepage",
30
        "license",
31
        "description",
32
        "estimated_contributors",
33
        "total_dependent_repositories",
34
        "total_dependent_packages",
35
        "private",
36
        "archived",
37
        "disabled",
38
        "last_commit",
39
        "last_activity",
40
    ]
41
    aliases = {
1✔
42
        "stars": "stargazers_count",
43
        "last_commit": "pushed_at",
44
        "url": "html_url",
45
        "license": "license.name",
46
    }
47
    json_types = {
1✔
48
        "homepage": lambda x: x or None,
49
        "private": lambda x: x or None,
50
        "archived": lambda x: x or None,
51
        "disabled": lambda x: x or None,
52
        "description": lambda x: x[:131] + "..." if len(str(x)) > 135 else x,
53
        "pushed_at": parse_datetime,
54
    }
55
    reduce = {}
1✔
56

57
    def __init__(self, owner: str, repo: str, tree: str = None, **kwargs):
1✔
58
        self.owner = owner
×
59
        self.repo = repo
×
60
        self.tree = tree
×
61
        self._kwargs = kwargs or {}
×
62
        self._json_repo = None
×
63
        self._json_events = None
×
64
        self._json_package_ids = None
×
65
        self._json_dependants = None
×
66
        self._json_contributors_sidebar = None
×
67

68
    def to_dict(self) -> dict:
1✔
69
        dictionary = {}
×
70
        for key in GitHubData.dict_keys:
×
71
            value = getattr(self, key, None)
×
72
            if value is not None:
×
73
                dictionary[key] = value
×
74
        return dictionary
×
75

76
    @classmethod
1✔
77
    def from_url(cls, github_project_url: URL):
1✔
78
        """
79
        Builds a GitHubSection from an url. Returns None
80
        if the given url is not a GitHub url
81
        """
82
        if "github.com" not in github_project_url.hostname:
×
83
            # github_project_url is not a GitHub URL
84
            return None
×
85

86
        tree_path = None
×
87
        url_path = github_project_url.path.replace("github.com", "")
×
88
        if "/tree/" in url_path:
×
89
            new_path, tree_path = url_path.split("/tree/")
×
90
            logger.debug(
×
91
                "%s includes a branch or a subdirectories: %s | %s",
92
                url_path,
93
                new_path,
94
                tree_path,
95
            )
96
            url_path = new_path
×
97
        try:
×
98
            owner, repo = [
×
99
                c for c in url_path.split("/") if match(r"^[A-Za-z0-9_.-]+$", c)
100
            ]
101
        except ValueError as exc:
×
102
            raise EcosystemError(f"invalid GitHub url: {github_project_url}") from exc
×
103

104
        return GitHubData(owner=owner, repo=repo, tree=tree_path)
×
105

106
    def update_json(self):
1✔
107
        """
108
        Fetches remote data from:
109
          - api.github.com/repos/{self.owner}/{self.repo}
110
          - github.com/{self.owner}/{self.repo}/network/dependents
111
          - github.com/{self.owner}/{self.repo}
112
          - api.github.com/networks/{self.owner}/{self.repo}/events
113

114
        """
115
        self._json_repo = request_json(f"api.github.com/repos/{self.owner}/{self.repo}")
×
116
        self._json_events = request_json(
×
117
            f"api.github.com/networks/{self.owner}/{self.repo}/events"
118
        )
119
        self._json_contributors_sidebar = request_json(
×
120
            f"github.com/{self.owner}/{self.repo}/contributors_list?deferred=true",
121
            parser=parse_github_contributors_sidebar,
122
        )
123
        self._json_package_ids = request_json(
×
124
            f"github.com/{self.owner}/{self.repo}/network/dependents?dependent_type=REPOSITORY",
125
            parser=parse_github_package_ids,
126
        )
127
        self._json_dependants = {}
×
128
        for package, package_id in self._json_package_ids.items():
×
129
            self._json_dependants[package] = request_json(
×
130
                f"github.com/{self.owner}/{self.repo}/network/dependents?"
131
                f"dependent_type=REPOSITORY&package_id={package_id}",
132
                parser=parse_github_dependants,
133
            )
134

135
    def __getattr__(self, item):
1✔
136
        if self._json_repo:
×
137
            if item in GitHubData.aliases:
×
138
                item = GitHubData.aliases[item]
×
139

140
            json_elements = findall(item, self._json_repo)
×
141
            if item in GitHubData.json_types:
×
142
                json_elements = [GitHubData.json_types[item](e) for e in json_elements]
×
143

144
            if len(json_elements) == 1:
×
145
                return json_elements[0]
×
146

147
            if len(json_elements) >= 2:
×
148
                return reduce(GitHubData.reduce[item], json_elements)
×
149

150
            raise AttributeError(
×
151
                f"'{type(self).__name__}' object has no " f"attribute '{item}'"
152
            )
153

154
        if item in self._kwargs:
×
155
            return self._kwargs[item]
×
156

157
        raise AttributeError(
×
158
            f"'{type(self).__name__}' object has no attribute '{item}'"
159
        )
160

161
    def update_owner_repo(self):
1✔
162
        """
163
        Updates GitHub page when the repo was moved or renamed
164
        """
165
        if self._json_repo is None:
×
166
            self.update_json()
×
167
        owner = self._json_repo["owner"]["login"]
×
168
        repo = self._json_repo["name"]
×
169
        if self.owner != owner or self.repo != repo:
×
170
            logger.info("%s/%s moved to %s/%s", self.owner, self.repo, owner, repo)
×
171
            self.owner = owner
×
172
            self.repo = repo
×
173

174
    def dependants(self, refresh=False):
1✔
175
        """get the dependant data from (cached) JSON"""
176
        if refresh:
×
177
            self.update_json()
×
178
        return self._json_dependants
×
179

180
    def contributors_sidebar_data(self, refresh=False):
1✔
181
        """get the front page data from (cached) JSON"""
182
        if refresh:
×
183
            self.update_json()
×
184
        return self._json_contributors_sidebar
×
185

186
    @property
1✔
187
    def estimated_contributors(self):
1✔
188
        """..."""
189
        if self.contributors_sidebar_data():
×
190
            return self.contributors_sidebar_data()["estimated_contributors"]
×
191
        return self._kwargs.get("estimated_contributors")
×
192

193
    @property
1✔
194
    def total_dependent_repositories(self):
1✔
195
        """Sum of repository dependants"""
196
        if self.dependants():
×
197
            return sum(r.get("repositories", 0) for r in self.dependants().values())
×
198
        return self._kwargs.get("total_dependent_repositories")
×
199

200
    @property
1✔
201
    def total_dependent_packages(self):
1✔
202
        """Sum of package dependants"""
203
        if self.dependants():
×
204
            return sum(r.get("packages", 0) for r in self.dependants().values())
×
205
        return self._kwargs.get("total_dependent_packages")
×
206

207
    @property
1✔
208
    def last_activity(self):
1✔
209
        """The creation of the last event"""
210
        if self._json_events and self._json_events["data"]:
×
211
            return parse_datetime(self._json_events["data"][0]["created_at"])
×
212
        return self._kwargs.get("last_activity")
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc