[PATCH v2 07/14] Add ty checks to CI

Tamir Duberstein <[email protected]>
Newsgroups org.kernel.linux.tools
Message-ID <[email protected]>
Add ty to the development dependencies and enable all ty rules. Run ty
in the b4 CI check script so its diagnostics are surfaced with the other
checks.

Use `ty check --add-ignore` to suppress existing errors.

Signed-off-by: Tamir Duberstein <[email protected]>
---
 ci.sh                      |  1 +
 pyproject.toml             |  4 ++++
 src/liblore/node.py        |  2 +-
 tests/test_auth_headers.py | 10 +++++-----
 tests/test_node.py         | 30 +++++++++++++++---------------
 5 files changed, 26 insertions(+), 21 deletions(-)

diff --git a/ci.sh b/ci.sh
index 4b07fa2..c97c853 100755
--- a/ci.sh
+++ b/ci.sh
@@ -4,6 +4,7 @@ set -eu
 
 uv run ruff format --check
 uv run ruff check
+uv run ty check
 uv run mypy .
 uv run pyright
 uv run pytest --durations=0
diff --git a/pyproject.toml b/pyproject.toml
index a160edd..37c85a7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -44,6 +44,7 @@ dev = [
     "pytest-asyncio",
     "responses",
     "ruff",
+    "ty",
     "types-requests",
 ]
 
@@ -63,6 +64,9 @@ executionEnvironments = [
     { root = "tests", reportPrivateUsage = false },
 ]
 
+[tool.ty.rules]
+all = "error"
+
 [tool.ruff.lint]
 extend-select = ["ARG", "I"]
 
diff --git a/src/liblore/node.py b/src/liblore/node.py
index 095ba4f..1b0463e 100644
--- a/src/liblore/node.py
+++ b/src/liblore/node.py
@@ -319,7 +319,7 @@ class LoreNode:
                 except ValueError:
                     pass
 
-        node = cls(url, **kwargs)  # type: ignore[arg-type]
+        node = cls(url, **kwargs)  # type: ignore[arg-type]  # ty:ignore[invalid-argument-type]
 
         val = gitcfg.get('useragentplus')
         if isinstance(val, str) and val:
diff --git a/tests/test_auth_headers.py b/tests/test_auth_headers.py
index f1c240a..b51050c 100644
--- a/tests/test_auth_headers.py
+++ b/tests/test_auth_headers.py
@@ -29,7 +29,7 @@ class TestAuthHeadersImport:
 
     def test_ok_when_authheaders_installed(self) -> None:
         fake = ModuleType('authheaders')
-        fake.authenticate_message = MagicMock()  # type: ignore[attr-defined]
+        fake.authenticate_message = MagicMock()  # type: ignore[attr-defined]  # ty:ignore[unresolved-attribute]
         with patch.dict(sys.modules, {'authheaders': fake}):
             node = LoreNode(add_auth_headers=True)
             assert node._authheaders is not None
@@ -56,7 +56,7 @@ class TestAuthenticateMsgs:
 
     def test_adds_header_when_enabled(self) -> None:
         fake = ModuleType('authheaders')
-        fake.authenticate_message = MagicMock(  # type: ignore[attr-defined]
+        fake.authenticate_message = MagicMock(  # type: ignore[attr-defined]  # ty:ignore[unresolved-attribute]
             return_value='Authentication-Results: liblore; dkim=pass header.d=example.com',
         )
         with patch.dict(sys.modules, {'authheaders': fake}):
@@ -82,7 +82,7 @@ class TestAuthenticateMsgs:
 
     def test_skips_empty_result(self) -> None:
         fake = ModuleType('authheaders')
-        fake.authenticate_message = MagicMock(return_value='')  # type: ignore[attr-defined]
+        fake.authenticate_message = MagicMock(return_value='')  # type: ignore[attr-defined]  # ty:ignore[unresolved-attribute]
         with patch.dict(sys.modules, {'authheaders': fake}):
             node = LoreNode(add_auth_headers=True)
             msg = EmailMessage()
@@ -95,7 +95,7 @@ class TestAuthenticateMsgs:
 
     def test_multiple_messages(self) -> None:
         fake = ModuleType('authheaders')
-        fake.authenticate_message = MagicMock(  # type: ignore[attr-defined]
+        fake.authenticate_message = MagicMock(  # type: ignore[attr-defined]  # ty:ignore[unresolved-attribute]
             side_effect=[
                 'liblore; dkim=pass',
                 'Authentication-Results: liblore; dkim=fail',
@@ -126,7 +126,7 @@ class TestAuthInFetchMethods:
     @pytest.fixture()
     def auth_node(self) -> Iterator[tuple[LoreNode, responses.RequestsMock]]:
         fake = ModuleType('authheaders')
-        fake.authenticate_message = MagicMock(  # type: ignore[attr-defined]
+        fake.authenticate_message = MagicMock(  # type: ignore[attr-defined]  # ty:ignore[unresolved-attribute]
             return_value='Authentication-Results: liblore; dkim=pass',
         )
         with patch.dict(sys.modules, {'authheaders': fake}):
diff --git a/tests/test_node.py b/tests/test_node.py
index fc94d9f..f4a3495 100644
--- a/tests/test_node.py
+++ b/tests/test_node.py
@@ -500,19 +500,19 @@ class TestBatchGetThreadByMsgid:
         node = LoreNode()
         thread_a = [EmailMessage()]
         thread_b = [EmailMessage(), EmailMessage()]
-        node.get_thread_by_msgid = MagicMock(side_effect=[thread_a, thread_b])  # type: ignore[method-assign]
+        node.get_thread_by_msgid = MagicMock(side_effect=[thread_a, thread_b])  # type: ignore[method-assign]  # ty:ignore[invalid-assignment]
 
         with patch('liblore.node.time.sleep') as mock_sleep:
             results = node.batch_get_thread_by_msgid(['a@x', 'b@x'])
 
         assert results == [thread_a, thread_b]
-        assert node.get_thread_by_msgid.call_count == 2
+        assert node.get_thread_by_msgid.call_count == 2  # ty:ignore[unresolved-attribute]
         mock_sleep.assert_called_once_with(0.1)
 
     def test_no_sleep_for_single_msgid(self) -> None:
         node = LoreNode()
         thread = [EmailMessage()]
-        node.get_thread_by_msgid = MagicMock(return_value=thread)  # type: ignore[method-assign]
+        node.get_thread_by_msgid = MagicMock(return_value=thread)  # type: ignore[method-assign]  # ty:ignore[invalid-assignment]
 
         with patch('liblore.node.time.sleep') as mock_sleep:
             results = node.batch_get_thread_by_msgid(['only@x'])
@@ -522,7 +522,7 @@ class TestBatchGetThreadByMsgid:
 
     def test_passes_kwargs(self) -> None:
         node = LoreNode()
-        node.get_thread_by_msgid = MagicMock(return_value=[EmailMessage()])  # type: ignore[method-assign]
+        node.get_thread_by_msgid = MagicMock(return_value=[EmailMessage()])  # type: ignore[method-assign]  # ty:ignore[invalid-assignment]
 
         with patch('liblore.node.time.sleep'):
             node.batch_get_thread_by_msgid(
@@ -532,7 +532,7 @@ class TestBatchGetThreadByMsgid:
                 since='20240101',
             )
 
-        node.get_thread_by_msgid.assert_called_once_with(
+        node.get_thread_by_msgid.assert_called_once_with(  # ty:ignore[unresolved-attribute]
             'a@x',
             strict=False,
             sort=True,
@@ -541,7 +541,7 @@ class TestBatchGetThreadByMsgid:
 
     def test_sleep_count_matches_gaps(self) -> None:
         node = LoreNode()
-        node.get_thread_by_msgid = MagicMock(return_value=[EmailMessage()])  # type: ignore[method-assign]
+        node.get_thread_by_msgid = MagicMock(return_value=[EmailMessage()])  # type: ignore[method-assign]  # ty:ignore[invalid-assignment]
 
         with patch('liblore.node.time.sleep') as mock_sleep:
             node.batch_get_thread_by_msgid(['a@x', 'b@x', 'c@x'])
@@ -550,14 +550,14 @@ class TestBatchGetThreadByMsgid:
 
     def test_empty_list(self) -> None:
         node = LoreNode()
-        node.get_thread_by_msgid = MagicMock()  # type: ignore[method-assign]
+        node.get_thread_by_msgid = MagicMock()  # type: ignore[method-assign]  # ty:ignore[invalid-assignment]
 
         with patch('liblore.node.time.sleep') as mock_sleep:
             results = node.batch_get_thread_by_msgid([])
 
         assert results == []
         mock_sleep.assert_not_called()
-        node.get_thread_by_msgid.assert_not_called()
+        node.get_thread_by_msgid.assert_not_called()  # ty:ignore[unresolved-attribute]
 
 
 # =====================================================================
@@ -570,19 +570,19 @@ class TestBatchGetThreadByQuery:
         node = LoreNode()
         result_a = [EmailMessage()]
         result_b = [EmailMessage(), EmailMessage()]
-        node.get_thread_by_query = MagicMock(side_effect=[result_a, result_b])  # type: ignore[method-assign]
+        node.get_thread_by_query = MagicMock(side_effect=[result_a, result_b])  # type: ignore[method-assign]  # ty:ignore[invalid-assignment]
 
         with patch('liblore.node.time.sleep') as mock_sleep:
             results = node.batch_get_thread_by_query(['q1', 'q2'])
 
         assert results == [result_a, result_b]
-        assert node.get_thread_by_query.call_count == 2
+        assert node.get_thread_by_query.call_count == 2  # ty:ignore[unresolved-attribute]
         mock_sleep.assert_called_once_with(0.1)
 
     def test_no_sleep_for_single_query(self) -> None:
         node = LoreNode()
         result = [EmailMessage()]
-        node.get_thread_by_query = MagicMock(return_value=result)  # type: ignore[method-assign]
+        node.get_thread_by_query = MagicMock(return_value=result)  # type: ignore[method-assign]  # ty:ignore[invalid-assignment]
 
         with patch('liblore.node.time.sleep') as mock_sleep:
             results = node.batch_get_thread_by_query(['only_query'])
@@ -592,7 +592,7 @@ class TestBatchGetThreadByQuery:
 
     def test_sleep_count_matches_gaps(self) -> None:
         node = LoreNode()
-        node.get_thread_by_query = MagicMock(return_value=[EmailMessage()])  # type: ignore[method-assign]
+        node.get_thread_by_query = MagicMock(return_value=[EmailMessage()])  # type: ignore[method-assign]  # ty:ignore[invalid-assignment]
 
         with patch('liblore.node.time.sleep') as mock_sleep:
             node.batch_get_thread_by_query(['q1', 'q2', 'q3', 'q4'])
@@ -601,14 +601,14 @@ class TestBatchGetThreadByQuery:
 
     def test_empty_list(self) -> None:
         node = LoreNode()
-        node.get_thread_by_query = MagicMock()  # type: ignore[method-assign]
+        node.get_thread_by_query = MagicMock()  # type: ignore[method-assign]  # ty:ignore[invalid-assignment]
 
         with patch('liblore.node.time.sleep') as mock_sleep:
             results = node.batch_get_thread_by_query([])
 
         assert results == []
         mock_sleep.assert_not_called()
-        node.get_thread_by_query.assert_not_called()
+        node.get_thread_by_query.assert_not_called()  # ty:ignore[unresolved-attribute]
 
 
 # =====================================================================
@@ -1928,7 +1928,7 @@ class TestUserAgentPlusProperty:
         """Property has no setter — assignment raises AttributeError."""
         node = LoreNode()
         with pytest.raises(AttributeError):
-            node.user_agent_plus = 'nope'  # type: ignore[misc]
+            node.user_agent_plus = 'nope'  # type: ignore[misc]  # ty:ignore[invalid-assignment]
 
 
 # =====================================================================

-- 
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.