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

Qiskit / ecosystem / 26499557773

27 May 2026 08:18AM UTC coverage: 61.188%. First build
26499557773

Pull #1176

github

web-flow
Merge 225901482 into 56a2b297b
Pull Request #1176: add `__url__` for detecting redirections

0 of 5 new or added lines in 2 files covered. (0.0%)

1288 of 2105 relevant lines covered (61.19%)

0.61 hits per line

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

26.42
/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_date
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_date,
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
            try:
×
130
                self._json_dependants[package] = request_json(
×
131
                    f"github.com/{self.owner}/{self.repo}/network/dependents?"
132
                    f"dependent_type=REPOSITORY&package_id={package_id}",
133
                    parser=parse_github_dependants,
134
                )
135
            except EcosystemError:
×
NEW
136
                logger.warning("json_dependants could not be updated")
×
137

138
    def __getattr__(self, item):
1✔
139
        if self._json_repo:
×
140
            if item in GitHubData.aliases:
×
141
                item = GitHubData.aliases[item]
×
142

143
            json_elements = findall(item, self._json_repo)
×
144
            if item in GitHubData.json_types:
×
145
                json_elements = [GitHubData.json_types[item](e) for e in json_elements]
×
146

147
            if len(json_elements) == 1:
×
148
                return json_elements[0]
×
149

150
            if len(json_elements) >= 2:
×
151
                return reduce(GitHubData.reduce[item], json_elements)
×
152

153
            raise AttributeError(
×
154
                f"'{type(self).__name__}' object has no " f"attribute '{item}'"
155
            )
156

157
        if item in self._kwargs:
×
158
            return self._kwargs[item]
×
159

160
        raise AttributeError(
×
161
            f"'{type(self).__name__}' object has no attribute '{item}'"
162
        )
163

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

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

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

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

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

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

210
    @property
1✔
211
    def last_activity(self):
1✔
212
        """The creation of the last event"""
213
        if self._json_events and self._json_events["data"]:
×
214
            return parse_date(self._json_events["data"][0]["created_at"])
×
215
        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