Fix linter problems

- Fix ruff check warnings
- Fix mypy warnings
- Move mypy config from setup.cfg to pyproject.toml and update it

Fixes: #306

Assisted-by: Claude Code
Co-authored-by: Kamil Páral <kparal@redhat.com>
This commit is contained in:
Jaroslav Groman 2026-06-10 14:30:36 +02:00 committed by Kamil Páral
commit f79993230f
23 changed files with 56 additions and 68 deletions

View file

@ -516,6 +516,4 @@ def main() -> None:
if __name__ == "__main__":
exit = main()
if exit:
sys.exit(exit)
main()

View file

@ -22,7 +22,7 @@
import logging
from flask import flash, request, redirect, url_for
from flask import flash
from flask_admin.babel import gettext
import flask_admin
from blockerbugs import app, db

View file

@ -143,7 +143,7 @@ class ChoicesValidator(BaseValidator):
super(ChoicesValidator, self).configure(args, kwargs)
def _check_data(self):
if not self.raw_data in self.choices:
if self.raw_data not in self.choices:
raise ValidationError("%s must be one of %s" % (self.name, str(self.choices)))

View file

@ -121,7 +121,7 @@ def get_milestone_updates(milestone: Milestone) -> list[Update]:
.filter(
Bug.milestone == milestone,
Bug.active.is_(True),
Bug.is_proposed_accepted.is_(True),
Bug.is_proposed_accepted.is_(True), # type: ignore[attr-defined]
)
.all()
)
@ -562,7 +562,7 @@ def bugzilla_sync_proposal(bugid, milestone, blocker, fe):
@main.route("/propose_bug", methods=["GET", "POST"])
@oidc.require_login
@oidc.require_login # type: ignore[has-type]
def propose_bug():
current_milestone = get_current_milestone()
bugform = BugProposeForm()

View file

@ -110,7 +110,7 @@ class Bug(db.Model):
_depends_on: db.Mapped[str] = db.mapped_column("depends_on", db.Text, default="[]")
"""A JSON list of bug numbers which this bug depends on"""
updates: db.Mapped[List["Update"]] = db.relationship( # noqa: F821
secondary=models.update_fixes,
secondary=models.update_fixes, # type: ignore[has-type]
back_populates="bugs",
lazy="dynamic",
order_by="[Update.status.desc(), Update.request.desc()]",
@ -129,7 +129,7 @@ class Bug(db.Model):
needinfo: Optional[bool],
needinfo_requestee: Optional[str],
last_whiteboard_change: Optional[datetime.datetime] = datetime.datetime.now(datetime.UTC),
last_bug_sync: Optional[datetime] = datetime.datetime.now(datetime.UTC),
last_bug_sync: Optional[datetime.datetime] = datetime.datetime.now(datetime.UTC),
depends_on: Optional[list[int]] = None,
) -> None:
self.bugid = bugid

View file

@ -84,7 +84,9 @@ class Update(db.Model):
stable_karma: db.Mapped[Optional[int]]
"""Target karma value for allowing the update to go stable (via auto or manual push)."""
bugs: db.Mapped[List["Bug"]] = db.relationship( # noqa: F821
secondary=models.update_fixes, back_populates="updates", cascade_backrefs=False
secondary=models.update_fixes, # type: ignore[has-type]
back_populates="updates",
cascade_backrefs=False,
)
"""A list of bugs this update claims to fix *and* we track them"""

View file

@ -98,7 +98,7 @@ class BlockerBugs:
# &bug_status=POST&bug_status=MODIFIED&classification=Fedora&component=anaconda&f1=component
# &o1=changedafter&product=Fedora&query_format=advanced&v1=2013-03-21%2012%3A25&version=19
def get_bz_query(
self, tracker: int, last_update: datetime.datetime = None, offset: int = 0
self, tracker: int, last_update: Optional[datetime.datetime] = None, offset: int = 0
) -> dict[str, Any]:
"""Build a Bugzilla query to retrieve all necessary info about all bugs which block the
`tracker` bug.

View file

@ -82,7 +82,7 @@ def agreed_revote_parser(line: str) -> List[Union[AgreedCommand, RevoteCommand]]
if not words:
return []
out = []
out: List[Union[AgreedCommand, RevoteCommand]] = []
command = words[0]
for word in words[1:]:
if command == "agreed":
@ -200,7 +200,7 @@ class ForgejoComment:
Returns:
list: List of VoteCommand/AgreedCommand/RevoteCommand instances
"""
out = []
out: List[Union[VoteCommand, AgreedCommand, RevoteCommand]] = []
for line in self.text.lower().split("\n"):
line = line.strip()
if line.startswith("agreed") or line.startswith("revote"):
@ -286,15 +286,18 @@ class BugVoteTracker:
continue
if agreed:
assert isinstance(command, AgreedCommand)
self.open = False
self.outcome = command.outcome
self.need_summary_post = True
elif revote:
assert isinstance(command, RevoteCommand)
self.open = True
self.outcome = None
self.need_summary_post = False
self.votes = {}
elif vote:
assert isinstance(command, VoteCommand)
if not self.open:
continue
self.votes[comment.user] = {"vote": command.vote, "comment_id": comment.id}
@ -313,7 +316,7 @@ class BugVoteTracker:
Returns:
Dict mapping vote values to lists of (user, comment_id) tuples
"""
out = {vote: [] for vote in VOTES}
out: Dict[str, List[Tuple[str, int]]] = {vote: [] for vote in VOTES}
for user, vote_data in self.votes.items():
out[vote_data["vote"]].append((user, vote_data["comment_id"]))
@ -456,7 +459,7 @@ def voting_info(
"""
# Find users who commented but didn't vote
non_voting_users = {}
all_voters = set()
all_voters: set[str] = set()
for tracker in trackers.values():
all_voters.update(tracker.votes.keys())

View file

@ -24,7 +24,7 @@ from flask import g, request, abort
from blockerbugs import app, oidc
@oidc.require_login
@oidc.require_login # type: ignore[has-type]
def check_admin_rights():
if app.config["FAS_ADMIN_GROUP"] in g.oidc_user.groups:
return None

View file

@ -181,7 +181,7 @@ class UpdateSync(object):
buglist.extend(
milestone.bugs.filter( # type: ignore[attr-defined]
Bug.active.is_(True),
Bug.is_proposed_accepted.is_(True),
Bug.is_proposed_accepted.is_(True), # type: ignore[attr-defined]
).all()
)

View file

@ -38,3 +38,17 @@ log_file = "pytest.log"
log_file_level = "INFO"
log_file_format = "%(asctime)s [%(levelname)8s] (%(filename)s:%(lineno)s) %(message)s"
log_file_date_format = "%Y-%m-%d %H:%M:%S"
[tool.mypy]
# what directory/files to check
files = "blockerbugs/, testing/, scripts/, *.py"
# Target environment Python version, keep in sync with builder defined at:
# https://forge.fedoraproject.org/infra/ansible/src/branch/main/roles/openshift-apps/blockerbugs/templates/buildconfig.yml.j2
python_version = "3.11"
# Ignore missing stubs/types for third-party libraries that don't provide them
ignore_missing_imports = true
# Exclude auto-generated and third-party vendored files
exclude = [
"^blockerbugs/_version\\.py$",
"^versioneer\\.py$",
]

View file

@ -1,32 +1,3 @@
[mypy]
files = blockerbugs/, testing/, scripts/, setup.py, wsgi.py
# not our code ↓
exclude = ^blockerbugs/_version\.py$
# There seem to be no typing stubs available for these libraries:
[mypy-flask_sqlalchemy.*]
ignore_missing_imports = True
[mypy-flask_wtf.*]
ignore_missing_imports = True
[mypy-flask_oidc.*]
ignore_missing_imports = True
[mypy-flask_admin.*]
ignore_missing_imports = True
[mypy-alembic.*]
ignore_missing_imports = True
[mypy-wtforms.*]
ignore_missing_imports = True
[mypy-iso8601.*]
ignore_missing_imports = True
[mypy-koji.*]
ignore_missing_imports = True
[mypy-bugzilla.*]
ignore_missing_imports = True
[mypy-fedora.*]
ignore_missing_imports = True
[mypy-bodhi.*]
ignore_missing_imports = True
[coverage:run]
source = blockerbugs

View file

@ -3,7 +3,7 @@ import json
import copy
import http.client as httplib
import mock
from unittest import mock
import pytest
from blockerbugs import db

View file

@ -1,4 +1,4 @@
import mock
from unittest import mock
import datetime
from xmlrpc.client import Fault
@ -150,7 +150,7 @@ class TestBugProposalTrackerCheck:
test_bz = BlockerProposal(stubbz, self.ref_proposed, self.ref_trackers, is_blocker=True)
test_result = test_bz.check_blocker_proposal()
assert test_result
assert test_result is True
def test_check_propose_blocker_already(self):
refbug = mock.MagicMock()
@ -161,7 +161,7 @@ class TestBugProposalTrackerCheck:
test_bz = BlockerProposal(stubbz, self.ref_proposed, self.ref_trackers, is_blocker=True)
test_result = test_bz.check_blocker_proposal()
assert test_result == False
assert test_result is False
def test_check_bothpropose_blocker_already(self):
self.ref_tracker_type = "Blocker and Freeze Exception"
@ -176,7 +176,7 @@ class TestBugProposalTrackerCheck:
)
test_result = test_bz.check_blocker_proposal()
assert test_result == False
assert test_result is False
def test_check_bothpropose_blocker_already_fe_ok(self):
self.ref_tracker_type = "Blocker and Freeze Exception"
@ -191,7 +191,7 @@ class TestBugProposalTrackerCheck:
)
test_result = test_bz.check_fe_proposal()
assert test_result == True
assert test_result is True
def test_check_bothpropose_fe_already(self):
refbug = mock.MagicMock()
@ -204,7 +204,7 @@ class TestBugProposalTrackerCheck:
)
test_result = test_bz.check_fe_proposal()
assert test_result == False
assert test_result is False
def test_check_bothpropose_fe_already_blocker_ok(self):
refbug = mock.MagicMock()
@ -217,7 +217,7 @@ class TestBugProposalTrackerCheck:
)
test_result = test_bz.check_blocker_proposal()
assert test_result == True
assert test_result is True
def test_proposed_bug_notexist(self):
stubbz = mock.MagicMock()

View file

@ -2,10 +2,10 @@ import pytest
from munch import Munch
from copy import deepcopy
from blockerbugs.util.bug_sync import BugSync
from mock import MagicMock
from unittest.mock import MagicMock
import datetime
basicbug = Munch(
basicbug: Munch = Munch(
{
"bug_id": 123456,
"weburl": "https://bugzilla.redhat.com/show_bug.cgi?id=123456",
@ -104,7 +104,7 @@ class TestSyncExtractInformation:
buginfo = self.testsync.extract_information(self.testbug, "Blocker")
assert buginfo["proposed"] == False
assert buginfo["proposed"] is False
def test_bugid(self):
buginfo = self.testsync.extract_information(self.testbug, "Blocker")
@ -177,7 +177,7 @@ class TestSyncExtractInformation:
self.testbug.flags = []
buginfo = self.testsync.extract_information(self.testbug, "Blocker")
assert buginfo["needinfo"] == False
assert buginfo["needinfo"] is False
assert buginfo["needinfo_requestee"] == ""
def test_dependson(self):

View file

@ -1,4 +1,4 @@
import mock
from unittest import mock
import pytest
from blockerbugs import db

View file

@ -2,11 +2,11 @@ import pytest
from munch import Munch
from copy import deepcopy
from blockerbugs.util.update_sync import UpdateSync
from mock import MagicMock
from unittest.mock import MagicMock
from datetime import datetime
basicupdate = Munch(
basicupdate: Munch = Munch(
approved=None,
close_bugs=True,
critpath=False,

View file

@ -10,7 +10,7 @@ from blockerbugs.models.bug import Bug
from blockerbugs import db
from blockerbugs.util.bug_sync import BugSync
templatebug = Munch(
templatebug: Munch = Munch(
{
"bug_id": 123456,
"weburl": "https://bugzilla.redhat.com/show_bug.cgi?id=123456",

View file

@ -4,7 +4,7 @@ from copy import copy
import pytest
from munch import Munch
from mock import patch, Mock
from unittest.mock import patch, Mock
from blockerbugs import db
from blockerbugs.models.milestone import Milestone
@ -15,7 +15,7 @@ from testing.test_controllers import add_bug
# only required part of a Bodhi response
base_update = Munch(
base_update: Munch = Munch(
title="anaconda-18.6.8-1.fc18",
date_pushed="2012-09-13 16:40:28",
date_submitted="2012-09-12 23:43:04",

View file

@ -1,7 +1,7 @@
"""Tests for configuration validation"""
import pytest
import mock
from unittest import mock
from werkzeug.datastructures import Headers
from blockerbugs.controllers.api.utils import check_forgejo_signature

View file

@ -4,7 +4,7 @@
from typing import Any
from unittest.mock import Mock
import mock
from unittest import mock
from blockerbugs.util import forgejo_bot

View file

@ -4,7 +4,7 @@ from typing import Any
from unittest.mock import Mock, MagicMock
import pytest
import mock
from unittest import mock
from blockerbugs.util import forgejo_interface

View file

@ -1 +1 @@
from blockerbugs import app as application
from blockerbugs import app as application # noqa: F401