Linux maintainer tooling and workflows
 help / color / mirror / Atom feed
From: Tamir Duberstein <tamird@kernel.org>
To: "Kernel.org Tools" <tools@kernel.org>
Cc: Konstantin Ryabitsev <konstantin@linuxfoundation.org>,
	 Tamir Duberstein <tamird@kernel.org>
Subject: [PATCH ezgb 4/6] Add pyright checks
Date: Sun, 19 Apr 2026 21:39:25 -0700	[thread overview]
Message-ID: <20260419-stronger-type-checking-v1-4-222775b987e5@kernel.org> (raw)
In-Reply-To: <20260419-stronger-type-checking-v1-0-222775b987e5@kernel.org>

Add pyright to local CI with strict source checking.

Signed-off-by: Tamir Duberstein <tamird@kernel.org>
---
 ci.sh                |  1 +
 pyproject.toml       |  7 +++++++
 src/ezgb/__init__.py |  8 +++-----
 src/ezgb/_git.py     |  2 +-
 src/ezgb/_models.py  |  4 ++--
 src/ezgb/_reader.py  | 38 +++++++++++++++++++++-----------------
 src/ezgb/_writer.py  | 14 +++++++-------
 7 files changed, 42 insertions(+), 32 deletions(-)

diff --git a/ci.sh b/ci.sh
index db7914f..fbc433d 100755
--- a/ci.sh
+++ b/ci.sh
@@ -5,4 +5,5 @@ set -eu
 uv run ruff check
 uv run ruff format --check
 uv run mypy .
+uv run pyright
 uv run pytest --durations=0
diff --git a/pyproject.toml b/pyproject.toml
index c043817..452bd6a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -26,6 +26,7 @@ classifiers = [
 [dependency-groups]
 dev = [
     "mypy",
+    "pyright",
     "pytest",
     "ruff",
 ]
@@ -50,3 +51,9 @@ select = ["E", "F", "W", "I"]
 
 [tool.ruff.format]
 quote-style = "single"
+
+[tool.pyright]
+typeCheckingMode = "strict"
+executionEnvironments = [
+    { root = "tests", reportPrivateUsage = false },
+]
diff --git a/src/ezgb/__init__.py b/src/ezgb/__init__.py
index 5cb23ae..f2f5d3d 100644
--- a/src/ezgb/__init__.py
+++ b/src/ezgb/__init__.py
@@ -23,7 +23,7 @@ from ezgb._models import (
     Status,
     UnsupportedFormatError,
 )
-from ezgb._reader import BugReader
+from ezgb._reader import BugReader, parse_since
 from ezgb._types import JsonValue
 from ezgb._writer import BugWriter
 
@@ -77,9 +77,7 @@ class GitBugRepo:
                 since = since.replace(tzinfo=timezone.utc)
             return int(since.timestamp())
         # str -- delegate to the reader's parser
-        from ezgb._reader import BugReader
-
-        return BugReader._parse_since(since)
+        return parse_since(since)
 
     def list_bugs(
         self,
@@ -206,7 +204,7 @@ class GitBugRepo:
             author_id = author.get('id', '') if isinstance(author, dict) else ''
             raw_labels = raw.get('labels') or []
             if isinstance(raw_labels, list):
-                labels = frozenset(str(lb) for lb in raw_labels)
+                labels: frozenset[str] = frozenset(str(lb) for lb in raw_labels)
             else:
                 labels = frozenset()
             comment_count = raw.get('comments', 0)
diff --git a/src/ezgb/_git.py b/src/ezgb/_git.py
index 4e29646..c1c326b 100644
--- a/src/ezgb/_git.py
+++ b/src/ezgb/_git.py
@@ -45,7 +45,7 @@ def git_run(
 
 def git_lines(repo_path: str, args: list[str]) -> list[str]:
     """Run a git command and return non-empty output lines."""
-    ecode, out = git_run(repo_path, args)
+    _ecode, out = git_run(repo_path, args)
     if not isinstance(out, str):
         out = out.decode(errors='replace')
     return [line for line in out.split('\n') if line]
diff --git a/src/ezgb/_models.py b/src/ezgb/_models.py
index b8716f0..22e45a7 100644
--- a/src/ezgb/_models.py
+++ b/src/ezgb/_models.py
@@ -96,7 +96,7 @@ class Comment:
     text: str
     created_at: datetime
     count: int
-    attachment_ids: list[str] = field(default_factory=list)
+    attachment_ids: list[str] = field(default_factory=list[str])
 
 
 @dataclass
@@ -110,7 +110,7 @@ class Bug:
     created_at: datetime
     labels: set[str]
     comments: list[Comment]
-    metadata: dict[str, str] = field(default_factory=dict)
+    metadata: dict[str, str] = field(default_factory=dict[str, str])
 
 
 @dataclass(frozen=True)
diff --git a/src/ezgb/_reader.py b/src/ezgb/_reader.py
index 735d41d..2d44478 100644
--- a/src/ezgb/_reader.py
+++ b/src/ezgb/_reader.py
@@ -57,6 +57,26 @@ def _is_dict_values(value: dict[_K, _T], value_ty: type[_U]) -> TypeGuard[dict[_
     return all(isinstance(value, value_ty) for value in value.values())
 
 
+def parse_since(since: str) -> int:
+    """Parse a datetime string to a unix timestamp.
+
+    Accepts ISO-8601 variants and ``YYYYMMDDHHMMSS``.
+    """
+    since = since.strip()
+    for fmt in (
+        '%Y-%m-%d %H:%M:%S',
+        '%Y%m%d%H%M%S',
+        '%Y-%m-%dT%H:%M:%SZ',
+        '%Y-%m-%dT%H:%M:%S',
+    ):
+        try:
+            dt = datetime.strptime(since, fmt).replace(tzinfo=timezone.utc)
+            return int(dt.timestamp())
+        except ValueError:
+            continue
+    raise ValueError('cannot parse since=%s' % since)
+
+
 def _combine_ids(primary: str, secondary: str) -> str:
     """Interleave primary and secondary IDs into a CombinedId.
 
@@ -415,23 +435,7 @@ class BugReader:
 
     @staticmethod
     def _parse_since(since: str) -> int:
-        """Parse a datetime string to a unix timestamp.
-
-        Accepts ISO-8601 variants and ``YYYYMMDDHHMMSS``.
-        """
-        since = since.strip()
-        for fmt in (
-            '%Y-%m-%d %H:%M:%S',
-            '%Y%m%d%H%M%S',
-            '%Y-%m-%dT%H:%M:%SZ',
-            '%Y-%m-%dT%H:%M:%S',
-        ):
-            try:
-                dt = datetime.strptime(since, fmt).replace(tzinfo=timezone.utc)
-                return int(dt.timestamp())
-            except ValueError:
-                continue
-        raise ValueError('cannot parse since=%s' % since)
+        return parse_since(since)
 
     def build_bug(
         self,
diff --git a/src/ezgb/_writer.py b/src/ezgb/_writer.py
index ae1d45f..adacfa1 100644
--- a/src/ezgb/_writer.py
+++ b/src/ezgb/_writer.py
@@ -77,7 +77,7 @@ class BugWriter:
             '-',
             '--non-interactive',
         ]
-        ecode, out, err = self._cli(args, stdin=text)
+        ecode, _out, err = self._cli(args, stdin=text)
         if ecode != 0:
             raise CliError('git bug comment new failed: %s' % err)
         self._reader.invalidate(bid)
@@ -100,7 +100,7 @@ class BugWriter:
             '-',
             '--non-interactive',
         ]
-        ecode, out, err = self._cli(args, stdin=text)
+        ecode, _out, err = self._cli(args, stdin=text)
         if ecode != 0:
             raise CliError('git bug comment edit failed: %s' % err)
         self._reader.invalidate(bid)
@@ -112,7 +112,7 @@ class BugWriter:
             subcmd = 'close'
         else:
             subcmd = 'open'
-        ecode, out, err = self._cli(['bug', 'status', subcmd, bid])
+        ecode, _out, err = self._cli(['bug', 'status', subcmd, bid])
         if ecode != 0:
             raise CliError('git bug status %s failed: %s' % (subcmd, err))
         self._reader.invalidate(bid)
@@ -121,7 +121,7 @@ class BugWriter:
         """Edit a bug's title."""
         bid = self._reader.resolve_bug_id(bid)
         args = ['bug', 'title', 'edit', bid, '-t', title]
-        ecode, out, err = self._cli(args)
+        ecode, _out, err = self._cli(args)
         if ecode != 0:
             raise CliError('git bug title edit failed: %s' % err)
         self._reader.invalidate(bid)
@@ -129,7 +129,7 @@ class BugWriter:
     def add_label(self, bid: str, label: str) -> None:
         """Add a label to a bug."""
         bid = self._reader.resolve_bug_id(bid)
-        ecode, out, err = self._cli(
+        ecode, _out, err = self._cli(
             ['bug', 'label', 'new', bid, label],
         )
         if ecode != 0:
@@ -139,7 +139,7 @@ class BugWriter:
     def remove_label(self, bid: str, label: str) -> None:
         """Remove a label from a bug."""
         bid = self._reader.resolve_bug_id(bid)
-        ecode, out, err = self._cli(
+        ecode, _out, err = self._cli(
             ['bug', 'label', 'rm', bid, label],
         )
         if ecode != 0:
@@ -153,7 +153,7 @@ class BugWriter:
         remote copies. This is irreversible.
         """
         bid = self._reader.resolve_bug_id(bid)
-        ecode, out, err = self._cli(['bug', 'rm', bid])
+        ecode, _out, err = self._cli(['bug', 'rm', bid])
         if ecode != 0:
             raise CliError('git bug rm failed: %s' % err)
         # Invalidate everything — the refs list has changed

-- 
2.53.0


  parent reply	other threads:[~2026-04-20  4:39 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-20  4:39 [PATCH ezgb 0/6] Harden local CI checks Tamir Duberstein
2026-04-20  4:39 ` [PATCH ezgb 1/6] Add local CI script Tamir Duberstein
2026-04-20  4:39 ` [PATCH ezgb 2/6] Add mypy checks Tamir Duberstein
2026-04-20  4:39 ` [PATCH ezgb 3/6] Add Ruff format check Tamir Duberstein
2026-04-20  4:39 ` Tamir Duberstein [this message]
2026-04-20  4:39 ` [PATCH ezgb 5/6] Add ty checks Tamir Duberstein
2026-04-20  4:39 ` [PATCH ezgb 6/6] Document local CI checks Tamir Duberstein
2026-04-27 20:20 ` [PATCH ezgb 0/6] Harden " Tamir Duberstein

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260419-stronger-type-checking-v1-4-222775b987e5@kernel.org \
    --to=tamird@kernel.org \
    --cc=konstantin@linuxfoundation.org \
    --cc=tools@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox