[PATCH ezgb 4/6] Add pyright checks

Tamir Duberstein <[email protected]>
Newsgroups org.kernel.linux.tools
Message-ID <[email protected]>
Add pyright to local CI with strict source checking.

Signed-off-by: Tamir Duberstein <[email protected]>
---
 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.