Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/devel/issues.py
169673 views
1
#!/usr/bin/env python
2
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
# Copyright (C) 2010-2025 German Aerospace Center (DLR) and others.
4
# This program and the accompanying materials are made available under the
5
# terms of the Eclipse Public License 2.0 which is available at
6
# https://www.eclipse.org/legal/epl-2.0/
7
# This Source Code may also be made available under the following Secondary
8
# Licenses when the conditions for such availability set forth in the Eclipse
9
# Public License 2.0 are satisfied: GNU General Public License, version 2
10
# or later which is available at
11
# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13
14
# @file issues.py
15
# @author Michael Behrisch
16
# @date 2025-07-31
17
18
import os
19
import sys
20
21
import requests
22
if 'SUMO_HOME' in os.environ:
23
sys.path.append(os.path.join(os.environ['SUMO_HOME'], 'tools'))
24
import sumolib # noqa
25
26
BASE_URL = "https://api.github.com"
27
28
29
def get_options():
30
op = sumolib.options.ArgumentParser()
31
op.add_argument("--repository", default='eclipse-sumo/sumo', help="repository to use")
32
op.add_argument("--token", help="use token for authentication")
33
op.add_argument("-a", "--assignee", help="filter by assignee")
34
op.add_argument("-l", "--label", help="filter by label(s)")
35
op.add_argument("-m", "--milestone", help="filter by milestone")
36
op.add_argument("-t", "--type", help="filter by type")
37
op.add_argument("-A", "--set-assignee", help="set assignee")
38
op.add_argument("-L", "--add-label", help="add label")
39
op.add_argument("-r", "--remove-label", help="remove label")
40
op.add_argument("-M", "--set-milestone", help="set milestone")
41
op.add_argument("-T", "--set-type", help="set type")
42
op.add_argument("--all", action="store_true", default=False, help="include open and closed issues")
43
return op.parse_args()
44
45
46
def get_all(s, url):
47
r = s.get(url)
48
result = r.json()
49
while "Link" in r.headers and 'rel="next"' in r.headers["Link"]:
50
r = s.get(r.headers["Link"].split('; rel="next"')[0][1:-1])
51
result += r.json()
52
return result
53
54
55
def main():
56
options = get_options()
57
s = requests.Session()
58
token = options.token
59
if not token:
60
for cred_path in (".", os.path.dirname(__file__), os.path.expanduser("~")):
61
if os.path.exists(os.path.join(cred_path, ".git-credentials")):
62
with open(os.path.join(cred_path, ".git-credentials")) as f:
63
token = f.read().split(":")[-1].split("@")[0]
64
break
65
if not token:
66
sys.exit("no authentication token found, please use the option --token or provide a .git-credentials file")
67
s.headers.update({"Accept": "application/vnd.github+json",
68
"Authorization": f"token {token}",
69
"X-GitHub-Api-Version": "2022-11-28"})
70
milestones = {}
71
if options.milestone or options.set_milestone:
72
result = get_all(s, BASE_URL + "/repos/" + options.repository + "/milestones?state=all&per_page=100")
73
milestones = {m["title"]: str(m["number"]) for m in result}
74
url = BASE_URL + "/repos/" + options.repository + "/issues?per_page=100"
75
if options.assignee:
76
url += "&assignee=" + options.assignee
77
if options.label:
78
url += "&labels=" + options.label
79
if options.milestone:
80
url += "&milestone=" + milestones.get(options.milestone, options.milestone)
81
if options.type:
82
url += "&type=" + options.type
83
if options.all:
84
url += "&state=all"
85
for issue in get_all(s, url):
86
update = {}
87
issue_type = None if issue.get("type") is None else issue["type"].get("name")
88
milestone = None if issue.get("milestone") is None else issue["milestone"].get("title")
89
if options.set_type and issue_type != options.set_type:
90
update["type"] = options.set_type
91
if options.set_assignee:
92
update["assignee"] = options.set_assignee
93
if options.set_milestone:
94
update["milestone"] = milestones.get(options.set_milestone, options.set_milestone)
95
response = None
96
if update:
97
response = s.patch(f"{BASE_URL}/repos/{options.repository}/issues/{issue['number']}", json=update)
98
if options.add_label:
99
response = s.post(f"{BASE_URL}/repos/{options.repository}/issues/{issue['number']}/labels",
100
{"labels": [options.add_label]})
101
if options.remove_label:
102
response = s.delete(
103
f"{BASE_URL}/repos/{options.repository}/issues/{issue['number']}/labels/{options.remove_label}")
104
if response:
105
print("Updated issue", issue["number"], response)
106
else:
107
print({"number": issue["number"],
108
"title": issue["title"],
109
"assignees": [assignee["login"] for assignee in issue.get("assignees", [])],
110
"labels": [label["name"] for label in issue.get("labels", [])],
111
"milestone": milestone,
112
"type": issue_type})
113
114
115
if __name__ == "__main__":
116
main()
117
118