proj/pkgcore/pkgcore:master commit in: examples/
"Arthur Zamarin" <[email protected]>
| Newsgroups | gmane.linux.gentoo.cvs |
|---|---|
| Message-ID | <1786214878.65e01698b887827a69b68e295c02b414187bf922.arthurzam@gentoo> |
commit: 65e01698b887827a69b68e295c02b414187bf922
Author: Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
AuthorDate: Sat Aug 8 10:35:40 2026 +0000
Commit: Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
CommitDate: Sat Aug 8 18:47:58 2026 +0000
URL: https://gitweb.gentoo.org/proj/pkgcore/pkgcore.git/commit/?id=65e01698
examples: migrate existing scripts to new bugzilla module
Signed-off-by: Arthur Zamarin <arthurzam <AT> gentoo.org>
examples/destable_arch_bugs.py | 103 ++++++++------------------
examples/set_maintainers.py | 116 +++++++++---------------------
examples/verify_at_done.py | 160 ++++++++++++++++++-----------------------
3 files changed, 133 insertions(+), 246 deletions(-)
diff --git a/examples/destable_arch_bugs.py b/examples/destable_arch_bugs.py
index 00ca0290e..c8607343c 100755
--- a/examples/destable_arch_bugs.py
+++ b/examples/destable_arch_bugs.py
@@ -2,26 +2,21 @@
"""Go over all open stabilization bugs for that arch, and drop the arch."""
-import json
import sys
-import urllib.request as urllib
-from typing import TypedDict
-from urllib.parse import urlencode
+from pkgcore.bugzilla import (
+ BugQuery,
+ BugUpdate,
+ Component,
+ ListChange,
+ NewComment,
+ Status,
+)
+from pkgcore.bugzilla.apikey import BugzillaClientArgs
from pkgcore.util import commandline
argparser = commandline.ArgumentParser(version=False, description=__doc__)
-argparser.add_argument(
- "--api-key",
- metavar="KEY",
- required=True,
- help="Bugzilla API key",
- docs="""
- The Bugzilla API key to use for authentication. Used mainly to overcome
- rate limiting done by bugzilla server. This tool doesn't perform any
- bug editing, just fetching info for the bug.
- """,
-)
+BugzillaClientArgs.mangle_argparser(argparser)
argparser.add_argument(
"--arch",
metavar="ARCH",
@@ -35,80 +30,40 @@ argparser.add_argument(
)
-class BugInfo(TypedDict):
- id: int
- cc: list[str]
-
-
@argparser.bind_final_check
def check_args(parser, namespace):
- repo = namespace.domain.ebuild_repos
+ repo = namespace.domain.ebuild_repos_raw
namespace.known_arches = frozenset().union(*(pkg.known_arches for pkg in repo))
if namespace.arch not in namespace.known_arches:
parser.error(f"unknown arch: {namespace.arch}")
-def fetch_bugs(arch: str, api_key: str) -> tuple[BugInfo, ...]:
- params = urlencode(
- (
- ("Bugzilla_api_key", api_key),
- ("component", "Stabilization"),
- ("include_fields", ",".join(BugInfo.__annotations__)),
- ("bug_status", "UNCONFIRMED"),
- ("bug_status", "CONFIRMED"),
- ("bug_status", "IN_PROGRESS"),
- ("cc", f"{arch}@gentoo.org"),
- )
- )
- with urllib.urlopen(
- "https://bugs.gentoo.org/rest/bug?" + params, timeout=30
- ) as response:
- return tuple(json.loads(response.read().decode("utf-8")).get("bugs", []))
-
-
-def update_bug(arch: str, api_key: str, bug_id: int, to_close: bool):
- req = {
- "Bugzilla_api_key": api_key,
- "ids": [bug_id],
- "cc": {"remove": [f"{arch}@gentoo.org"]},
- }
-
+def destable(arch: str, last: bool) -> BugUpdate:
+ """Drop the arch from CC, closing the bug if it was the only one left"""
+ uncc = ListChange.removing(f"{arch}@gentoo.org")
comment = f"Arch {arch} is destabled, removing."
- if to_close:
- req["status"] = "RESOLVED"
- req["resolution"] = "FIXED"
- comment += "\n\nNo remaining arches, closing the bug."
- else:
- req["status"] = "IN_PROGRESS"
- req["comment"] = {"body": comment}
-
- data = json.dumps(req).encode("utf-8")
- url = f"https://bugs.gentoo.org/rest/bug/{bug_id}"
- request = urllib.Request(url, data=data, method="PUT")
- request.add_header("Content-Type", "application/json")
- with urllib.urlopen(request, timeout=30) as response:
- return json.loads(response.read().decode("utf-8"))
+ if last:
+ return BugUpdate.resolve(
+ comment=f"{comment}\n\nNo remaining arches, closing the bug.", cc=uncc
+ )
+ return BugUpdate(status=Status.IN_PROGRESS, cc=uncc, comment=NewComment(comment))
@argparser.bind_main_func
def main(options, out, err):
- for i, bug in enumerate(bugs := fetch_bugs(options.arch, options.api_key), start=1):
- cc = frozenset(bug["cc"])
- cc_names = frozenset(
- x.split("@", 1)[0] for x in cc if x.endswith("@gentoo.org") or "@" not in x
- )
- bug_arches = cc_names.intersection(options.known_arches)
-
- out.write(f"[{i}/{len(bugs)}] https://bugs.gentoo.org/{bug['id']}")
+ query = (
+ BugQuery.component(Component.STABILIZATION)
+ & BugQuery.unresolved()
+ & BugQuery.cc(f"{options.arch}@gentoo.org")
+ )
+ bugs = options.bugzilla.search(query)
+ for i, bug in enumerate(bugs.values(), start=1):
+ out.write(f"[{i}/{len(bugs)}] {bug.url}")
out.flush()
- update_bug(
- arch=options.arch,
- api_key=options.api_key,
- bug_id=bug["id"],
- to_close=len(bug_arches) == 1,
- )
+ remaining = set(bug.arches(options.known_arches)) - {options.arch}
+ options.bugzilla.update(bug.id, destable(options.arch, not remaining))
if __name__ == "__main__":
diff --git a/examples/set_maintainers.py b/examples/set_maintainers.py
index ce5317046..b20205f3b 100755
--- a/examples/set_maintainers.py
+++ b/examples/set_maintainers.py
@@ -1,108 +1,60 @@
#!/usr/bin/env python3
-import json
+"""Assign bug-wrangler owned keywording and stabilization bugs to maintainers."""
+
import sys
-import urllib.request as urllib
-from urllib.parse import urlencode
-from pkgcore.ebuild.atom import atom
-from pkgcore.ebuild.errors import MalformedAtom
+from pkgcore.bugzilla import (
+ BugCategory,
+ BugQuery,
+ BugUpdate,
+ BugzillaError,
+ ListChange,
+)
+from pkgcore.bugzilla.apikey import BugzillaClientArgs
+from pkgcore.bugzilla.changes import MAINTAINER_NEEDED
from pkgcore.util import commandline
-argparser = commandline.ArgumentParser(color=False, version=False)
-argparser.add_argument(
- "--api-key",
- metavar="KEY",
- required=True,
- help="Bugzilla API key",
- docs="""
- The Bugzilla API key to use for authentication. Used mainly to overcome
- rate limiting done by bugzilla server. This tool doesn't perform any
- bug editing, just fetching info for the bug.
- """,
+argparser = commandline.ArgumentParser(color=False, version=False, description=__doc__)
+BugzillaClientArgs.mangle_argparser(argparser)
+
+QUERY = (
+ BugQuery.assigned_to("bug-wranglers")
+ & BugQuery.category(BugCategory.STABLEREQ, BugCategory.KEYWORDREQ)
+ & BugQuery.unresolved()
)
@argparser.bind_final_check
def check_args(parser, namespace):
- namespace.repo = namespace.domain.ebuild_repos
+ # raw, so packages the profile filters out still yield their maintainers
+ namespace.repo = namespace.domain.ebuild_repos_raw
-def fetch_bugs():
- params = urlencode(
- (
- ("assigned_to", "bug-wranglers"),
- ("component", "Stabilization"),
- ("component", "Keywording"),
- (
- "include_fields",
- "id,cf_stabilisation_atoms",
- ),
- ("bug_status", "UNCONFIRMED"),
- ("bug_status", "CONFIRMED"),
- ("bug_status", "IN_PROGRESS"),
- )
- )
- with urllib.urlopen(
- "https://bugs.gentoo.org/rest/bug?" + params, timeout=30
- ) as response:
- reply = json.loads(response.read().decode("utf-8")).get("bugs", [])
- return {
- bug["id"]: bug["cf_stabilisation_atoms"].splitlines()
- for bug in reply
- if bug["cf_stabilisation_atoms"].strip()
- }
-
-
-def parse_atom(pkg: str):
- try:
- return atom(pkg)
- except MalformedAtom as exc:
- try:
- return atom(f"={pkg}")
- except MalformedAtom:
- raise exc
-
-
-def collect_maintainers(repo, atoms):
- for a in atoms:
- for pkg in repo.itermatch(parse_atom(a.split(" ", 1)[0]).unversioned_atom):
+def collect_maintainers(repo, bug):
+ for a in bug.package_list.atoms:
+ for pkg in repo.itermatch(a.unversioned_atom):
for maintainer in pkg.maintainers:
yield maintainer.email
@argparser.bind_main_func
def main(options, out, err):
- for bug_id, atoms in fetch_bugs().items():
+ for bug in options.bugzilla.search(QUERY).values():
+ if not bug.package_list:
+ continue
try:
- maintainers = dict.fromkeys(collect_maintainers(options.repo, atoms)) or (
- "[email protected]",
+ maintainers = dict.fromkeys(collect_maintainers(options.repo, bug)) or (
+ MAINTAINER_NEEDED,
)
assignee, *add_cc = maintainers
-
- request_data = dict(
- Bugzilla_api_key=options.api_key,
- cc_add=add_cc,
- assigned_to=assignee,
- )
- request = urllib.Request(
- url=f"https://bugs.gentoo.org/rest/bug/{bug_id}",
- data=json.dumps(request_data).encode("utf-8"),
- method="PUT",
- headers={
- "Content-Type": "application/json",
- "Accept": "application/json",
- },
- )
- with urllib.urlopen(request, timeout=30) as response:
- reply = response.read().decode("utf-8")
- out.write(f"Bug: {bug_id}, replied: {reply}")
- except MalformedAtom:
- err.write(
- err.fg("red"),
- f"Malformed bug {bug_id} with atoms: {', '.join(atoms)}",
- err.reset,
+ changes = options.bugzilla.update(
+ bug.id,
+ BugUpdate(assigned_to=assignee, cc=ListChange.adding(*add_cc)),
)
+ out.write(f"Bug: {bug.id}, assigned to {assignee}, changed: {changes!r}")
+ except BugzillaError as exc:
+ err.write(err.fg("red"), f"Bug {bug.id}: {exc}", err.reset)
if __name__ == "__main__":
diff --git a/examples/verify_at_done.py b/examples/verify_at_done.py
index 125933731..e8df79500 100755
--- a/examples/verify_at_done.py
+++ b/examples/verify_at_done.py
@@ -2,118 +2,98 @@
"""Go over all open stabilization or keywording bugs, and check for done bugs."""
-import json
import sys
-import urllib.request as urllib
-from typing import TypedDict
-from urllib.parse import urlencode
-from pkgcore.ebuild.atom import atom
-from pkgcore.ebuild.errors import MalformedAtom
+from pkgcore.bugzilla import (
+ BugCategory,
+ BugQuery,
+ BugzillaError,
+ Component,
+ FlagStatus,
+)
+from pkgcore.bugzilla.apikey import BugzillaClientArgs
+from pkgcore.bugzilla.pkglist import ALL_KEYWORDS, NO_KEYWORDS, SAME_KEYWORDS
from pkgcore.util import commandline
argparser = commandline.ArgumentParser(version=False, description=__doc__)
-argparser.add_argument(
- "--api-key",
- metavar="KEY",
- required=True,
- help="Bugzilla API key",
- docs="""
- The Bugzilla API key to use for authentication. Used mainly to overcome
- rate limiting done by bugzilla server. This tool doesn't perform any
- bug editing, just fetching info for the bug.
- """,
-)
+BugzillaClientArgs.mangle_argparser(argparser)
+QUERY = (
+ BugQuery.component(Component.STABILIZATION, Component.KEYWORDING)
+ & BugQuery.unresolved()
+ & BugQuery.flag("sanity-check", FlagStatus.GRANTED)
+)
-class BugInfo(TypedDict):
- id: int
- cf_stabilisation_atoms: str
- component: str
- cc: list[str]
+UNEXPANDED = frozenset((ALL_KEYWORDS, SAME_KEYWORDS))
@argparser.bind_final_check
def check_args(parser, namespace):
- namespace.repo = namespace.domain.ebuild_repos
-
-
-def fetch_bugs(api_key: str) -> tuple[BugInfo, ...]:
- params = urlencode(
- (
- ("Bugzilla_api_key", api_key),
- ("component", "Stabilization"),
- ("component", "Keywording"),
- ("include_fields", ",".join(BugInfo.__annotations__)),
- ("bug_status", "UNCONFIRMED"),
- ("bug_status", "CONFIRMED"),
- ("bug_status", "IN_PROGRESS"),
- ("f1", "flagtypes.name"),
- ("o1", "anywords"),
- ("v1", "sanity-check+"),
- )
+ namespace.repo = namespace.domain.ebuild_repos_raw
+ namespace.known_arches = frozenset().union(
+ *(repo.known_arches for repo in namespace.repo)
)
- with urllib.urlopen(
- "https://bugs.gentoo.org/rest/bug?" + params, timeout=30
- ) as response:
- return tuple(json.loads(response.read().decode("utf-8")).get("bugs", []))
-def parse_atom(pkg: str):
- try:
- return atom(pkg)
- except MalformedAtom as exc:
- try:
- return atom(f"={pkg}")
- except MalformedAtom:
- raise exc
+def requested_arches(entry, cc_arches):
+ """The arches a package list line asks for.
+ A line carrying no keywords of its own inherits the whole CC list, which is
+ how nattka reads it too.
+ """
+ keywords = frozenset(x.lstrip("~") for x in entry.keywords)
+ if NO_KEYWORDS in keywords:
+ return frozenset()
+ return keywords or frozenset(cc_arches)
-def collect_packages(repo, bug: BugInfo):
- return tuple(
- pkg
- for a in bug["cf_stabilisation_atoms"].splitlines()
- if (b := " ".join(a.split()))
- for pkg in repo.itermatch(parse_atom(b.split(" ", 1)[0]))
- )
+
+def pending_packages(repo, bug, cc_arches):
+ """Map each requested arch to the packages it still has to stabilize.
+
+ Returns None when the bug can't be judged, either because an atom matches
+ nothing in the repo or because the package list still holds unexpanded
+ keyword shorthands. Concluding from a partial view is how an arch gets told
+ it is done while a package the repo never matched is still waiting on it.
+ """
+ pending: dict[str, list] = {}
+ for entry in bug.package_list.entries:
+ if entry.pkg is None:
+ continue
+ if UNEXPANDED & frozenset(entry.keywords):
+ return None
+ if not (pkgs := tuple(repo.itermatch(entry.pkg))):
+ return None
+ for arch in requested_arches(entry, cc_arches):
+ pending.setdefault(arch, []).extend(pkgs)
+ return pending
@argparser.bind_main_func
def main(options, out, err):
- for bug in fetch_bugs(options.api_key):
+ for bug in options.bugzilla.search(QUERY).values():
+ # the heuristic for keywording is wrong, skip those for now
+ if bug.category is BugCategory.KEYWORDREQ:
+ continue
+ cc_arches = bug.arches(options.known_arches)
try:
- pkgs = collect_packages(options.repo, bug)
- if not pkgs:
+ pending = pending_packages(options.repo, bug, cc_arches)
+ except BugzillaError as exc:
+ err.write(err.fg("red"), f">>> {exc}", err.reset)
+ continue
+ if not pending:
+ continue
+
+ for arch in cc_arches:
+ if not (pkgs := pending.get(arch)):
continue
- for cc in bug["cc"]:
- cc = cc.removesuffix("@gentoo.org")
- if bug["component"] == "Keywording":
- continue # skip keywording for now, the heuristic is wrong
- if all(cc in pkg.keywords for pkg in pkgs):
- out.write(
- out.fg("yellow"),
- f"https://bugs.gentoo.org/{bug['id']}, cc: {cc}, all packages are done",
- out.reset,
- " -> ",
- f"nattka resolve -a {cc} {bug['id']}",
- )
- if bug["component"] == "Keywording" and all(
- f"~{cc}" in pkg.keywords for pkg in pkgs
- ):
- out.write(
- out.fg("yellow"),
- f"https://bugs.gentoo.org/{bug['id']}, cc: ~{cc}, all packages are done",
- out.reset,
- " -> ",
- f"nattka resolve -a {cc} {bug['id']}",
- )
- except MalformedAtom as exc:
- err.write(
- err.fg("red"),
- f">>> Malformed bug {bug['id']} with atoms: {', '.join(bug['cf_stabilisation_atoms'].splitlines())}",
- err.reset,
- str(exc),
- )
+ if all(arch in pkg.keywords for pkg in pkgs):
+ out.write(
+ out.fg("yellow"),
+ f"{bug.url}, cc: {arch}, all packages are done",
+ out.reset,
+ " -> ",
+ f"nattka resolve -a {arch} {bug.id}",
+ )
if __name__ == "__main__":