Fix typing issues in mypy strict mode
Marco Ricci

Marco Ricci commited on 2024-07-22 13:37:03
Zeige 10 geänderte Dateien mit 141 Einfügungen und 73 Löschungen.

... ...
@@ -11,6 +11,8 @@ import collections
11 11
 import hashlib
12 12
 import math
13 13
 import unicodedata
14
+from collections.abc import Callable
15
+from typing import TypeAlias
14 16
 
15 17
 from typing_extensions import assert_type
16 18
 
... ...
@@ -23,7 +25,8 @@ __version__ = '0.1.2'
23 25
 
24 26
 class AmbiguousByteRepresentationError(ValueError):
25 27
     """The object has an ambiguous byte representation."""
26
-    def __init__(self):
28
+
29
+    def __init__(self) -> None:
27 30
         super().__init__('text string has ambiguous byte representation')
28 31
 
29 32
 
... ...
@@ -425,6 +428,8 @@ class Vault:
425 428
             a passphrase deterministically.
426 429
 
427 430
         """
431
+        TestFunc: TypeAlias = Callable[[bytes | bytearray], bool]
432
+        deterministic_signature_types: dict[str, TestFunc]
428 433
         deterministic_signature_types = {
429 434
             'ssh-ed25519': lambda k: k.startswith(
430 435
                 b'\x00\x00\x00\x0bssh-ed25519'
... ...
@@ -28,6 +28,7 @@ from typing_extensions import (
28 28
 
29 29
 import derivepassphrase as dpp
30 30
 import ssh_agent_client
31
+import ssh_agent_client.types
31 32
 from derivepassphrase import types as dpp_types
32 33
 
33 34
 if TYPE_CHECKING:
... ...
@@ -159,7 +160,7 @@ def _get_suitable_ssh_keys(
159 160
 
160 161
     """
161 162
     client: ssh_agent_client.SSHAgentClient
162
-    client_context: contextlib.AbstractContextManager
163
+    client_context: contextlib.AbstractContextManager[Any]
163 164
     match conn:
164 165
         case ssh_agent_client.SSHAgentClient():
165 166
             client = conn
... ...
@@ -324,8 +325,15 @@ def _prompt_for_passphrase() -> str:
324 325
         The user input.
325 326
 
326 327
     """
327
-    return click.prompt(
328
-        'Passphrase', default='', hide_input=True, show_default=False, err=True
328
+    return cast(
329
+        str,
330
+        click.prompt(
331
+            'Passphrase',
332
+            default='',
333
+            hide_input=True,
334
+            show_default=False,
335
+            err=True,
336
+        ),
329 337
     )
330 338
 
331 339
 
... ...
@@ -349,8 +357,8 @@ class OptionGroupOption(click.Option):
349 357
     option_group_name: str = ''
350 358
     epilog: str = ''
351 359
 
352
-    def __init__(self, *args, **kwargs):  # type: ignore
353
-        if self.__class__ == __class__:
360
+    def __init__(self, *args: Any, **kwargs: Any) -> None:
361
+        if self.__class__ == __class__:  # type: ignore[name-defined]
354 362
             raise NotImplementedError
355 363
         super().__init__(*args, **kwargs)
356 364
 
... ...
@@ -809,7 +817,7 @@ def derivepassphrase(
809 817
         for name in param.opts + param.secondary_opts:
810 818
             params_by_str[name] = param
811 819
 
812
-    def is_param_set(param: click.Parameter):
820
+    def is_param_set(param: click.Parameter) -> bool:
813 821
         return bool(ctx.params.get(param.human_readable_name))
814 822
 
815 823
     def check_incompatible_options(
... ...
@@ -89,7 +89,7 @@ class Sequin:
89 89
         """
90 90
         msg = 'sequence item out of range'
91 91
 
92
-        def uint8_to_bits(value):
92
+        def uint8_to_bits(value: int) -> Iterator[int]:
93 93
             """Yield individual bits of an 8-bit number, MSB first."""
94 94
             for i in (0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01):
95 95
                 yield 1 if value | i == value else 0
... ...
@@ -388,5 +388,5 @@ class SequinExhaustedError(Exception):
388 388
 
389 389
     """
390 390
 
391
-    def __init__(self):
391
+    def __init__(self) -> None:
392 392
         super().__init__('Sequin is exhausted')
... ...
@@ -34,7 +34,7 @@ _socket = socket
34 34
 class TrailingDataError(RuntimeError):
35 35
     """The result contained trailing data."""
36 36
 
37
-    def __init__(self):
37
+    def __init__(self) -> None:
38 38
         super().__init__('Overlong response from SSH agent')
39 39
 
40 40
 
... ...
@@ -274,7 +274,7 @@ class SSHAgentClient:
274 274
             raise EOFError(msg)
275 275
         return response[0], response[1:]
276 276
 
277
-    def list_keys(self) -> Sequence[ssh_types.KeyCommentPair]:
277
+    def list_keys(self) -> Sequence[types.KeyCommentPair]:
278 278
         """Request a list of keys known to the SSH agent.
279 279
 
280 280
         Returns:
... ...
@@ -290,9 +290,9 @@ class SSHAgentClient:
290 290
 
291 291
         """
292 292
         response_code, response = self.request(
293
-            ssh_types.SSH_AGENTC.REQUEST_IDENTITIES.value, b''
293
+            types.SSH_AGENTC.REQUEST_IDENTITIES.value, b''
294 294
         )
295
-        if response_code != ssh_types.SSH_AGENT.IDENTITIES_ANSWER.value:
295
+        if response_code != types.SSH_AGENT.IDENTITIES_ANSWER.value:
296 296
             msg = (
297 297
                 f'error return from SSH agent: '
298 298
                 f'{response_code = }, {response = }'
... ...
@@ -313,7 +313,7 @@ class SSHAgentClient:
313 313
             return bytes(buf)
314 314
 
315 315
         key_count = int.from_bytes(shift(4), 'big')
316
-        keys: collections.deque[ssh_types.KeyCommentPair]
316
+        keys: collections.deque[types.KeyCommentPair]
317 317
         keys = collections.deque()
318 318
         for _ in range(key_count):
319 319
             key_size = int.from_bytes(shift(4), 'big')
... ...
@@ -321,7 +321,7 @@ class SSHAgentClient:
321 321
             comment_size = int.from_bytes(shift(4), 'big')
322 322
             comment = shift(comment_size)
323 323
             # Both `key` and `comment` are not wrapped as SSH strings.
324
-            keys.append(ssh_types.KeyCommentPair(key, comment))
324
+            keys.append(types.KeyCommentPair(key, comment))
325 325
         if response_stream:
326 326
             raise TrailingDataError
327 327
         return keys
... ...
@@ -379,9 +379,9 @@ class SSHAgentClient:
379 379
         request_data.extend(self.string(payload))
380 380
         request_data.extend(self.uint32(flags))
381 381
         response_code, response = self.request(
382
-            ssh_types.SSH_AGENTC.SIGN_REQUEST.value, request_data
382
+            types.SSH_AGENTC.SIGN_REQUEST.value, request_data
383 383
         )
384
-        if response_code != ssh_types.SSH_AGENT.SIGN_RESPONSE.value:
384
+        if response_code != types.SSH_AGENT.SIGN_RESPONSE.value:
385 385
             msg = f'signing data failed: {response_code = }, {response = }'
386 386
             raise RuntimeError(msg)
387 387
         return self.unstring(response)
... ...
@@ -402,7 +402,7 @@ def isolated_config(
402 402
     monkeypatch: Any,
403 403
     runner: click.testing.CliRunner,
404 404
     config: Any,
405
-):
405
+) -> Iterator[None]:
406 406
     prog_name = derivepassphrase.cli.PROG_NAME
407 407
     env_name = prog_name.replace(' ', '_').upper() + '_PATH'
408 408
     with runner.isolated_filesystem():
... ...
@@ -28,31 +28,33 @@ class TestVault:
28 28
             ('twitter', twitter_phrase),
29 29
         ],
30 30
     )
31
-    def test_200_basic_configuration(self, service, expected):
31
+    def test_200_basic_configuration(
32
+        self, service: bytes | str, expected: bytes
33
+    ) -> None:
32 34
         assert Vault(phrase=self.phrase).generate(service) == expected
33 35
 
34
-    def test_201_phrase_dependence(self):
36
+    def test_201_phrase_dependence(self) -> None:
35 37
         assert (
36 38
             Vault(phrase=(self.phrase + b'X')).generate('google')
37 39
             == b'n+oIz6sL>K*lTEWYRO%7'
38 40
         )
39 41
 
40
-    def test_202_reproducibility_and_bytes_service_name(self):
42
+    def test_202_reproducibility_and_bytes_service_name(self) -> None:
41 43
         assert Vault(phrase=self.phrase).generate(b'google') == Vault(
42 44
             phrase=self.phrase
43 45
         ).generate('google')
44 46
 
45
-    def test_203_reproducibility_and_bytearray_service_name(self):
47
+    def test_203_reproducibility_and_bytearray_service_name(self) -> None:
46 48
         assert Vault(phrase=self.phrase).generate(b'google') == Vault(
47 49
             phrase=self.phrase
48 50
         ).generate(bytearray(b'google'))
49 51
 
50
-    def test_210_nonstandard_length(self):
52
+    def test_210_nonstandard_length(self) -> None:
51 53
         assert (
52 54
             Vault(phrase=self.phrase, length=4).generate('google') == b'xDFu'
53 55
         )
54 56
 
55
-    def test_211_repetition_limit(self):
57
+    def test_211_repetition_limit(self) -> None:
56 58
         assert (
57 59
             Vault(
58 60
                 phrase=b'', length=24, symbol=0, number=0, repeat=1
... ...
@@ -60,37 +62,37 @@ class TestVault:
60 62
             == b'IVTDzACftqopUXqDHPkuCIhV'
61 63
         )
62 64
 
63
-    def test_212_without_symbols(self):
65
+    def test_212_without_symbols(self) -> None:
64 66
         assert (
65 67
             Vault(phrase=self.phrase, symbol=0).generate('google')
66 68
             == b'XZ4wRe0bZCazbljCaMqR'
67 69
         )
68 70
 
69
-    def test_213_no_numbers(self):
71
+    def test_213_no_numbers(self) -> None:
70 72
         assert (
71 73
             Vault(phrase=self.phrase, number=0).generate('google')
72 74
             == b'_*$TVH.%^aZl(LUeOT?>'
73 75
         )
74 76
 
75
-    def test_214_no_lowercase_letters(self):
77
+    def test_214_no_lowercase_letters(self) -> None:
76 78
         assert (
77 79
             Vault(phrase=self.phrase, lower=0).generate('google')
78 80
             == b':{?)+7~@OA:L]!0E$)(+'
79 81
         )
80 82
 
81
-    def test_215_at_least_5_digits(self):
83
+    def test_215_at_least_5_digits(self) -> None:
82 84
         assert (
83 85
             Vault(phrase=self.phrase, length=8, number=5).generate('songkick')
84 86
             == b'i0908.7['
85 87
         )
86 88
 
87
-    def test_216_lots_of_spaces(self):
89
+    def test_216_lots_of_spaces(self) -> None:
88 90
         assert (
89 91
             Vault(phrase=self.phrase, space=12).generate('songkick')
90 92
             == b' c   6 Bq  % 5fR    '
91 93
         )
92 94
 
93
-    def test_217_all_character_classes(self):
95
+    def test_217_all_character_classes(self) -> None:
94 96
         assert (
95 97
             Vault(
96 98
                 phrase=self.phrase,
... ...
@@ -104,7 +106,7 @@ class TestVault:
104 106
             == b': : fv_wqt>a-4w1S  R'
105 107
         )
106 108
 
107
-    def test_218_only_numbers_and_very_high_repetition_limit(self):
109
+    def test_218_only_numbers_and_very_high_repetition_limit(self) -> None:
108 110
         generated = Vault(
109 111
             phrase=b'',
110 112
             length=40,
... ...
@@ -130,13 +132,13 @@ class TestVault:
130 132
         for substring in forbidden_substrings:
131 133
             assert substring not in generated
132 134
 
133
-    def test_219_very_limited_character_set(self):
135
+    def test_219_very_limited_character_set(self) -> None:
134 136
         generated = Vault(
135 137
             phrase=b'', length=24, lower=0, upper=0, space=0, symbol=0
136 138
         ).generate('testing')
137 139
         assert generated == b'763252593304946694588866'
138 140
 
139
-    def test_220_character_set_subtraction(self):
141
+    def test_220_character_set_subtraction(self) -> None:
140 142
         assert Vault._subtract(b'be', b'abcdef') == bytearray(b'acdf')
141 143
 
142 144
     @pytest.mark.parametrize(
... ...
@@ -230,13 +232,13 @@ class TestVault:
230 232
             assert binstr(s) == bytes(s)
231 233
             assert binstr(binstr(s)) == bytes(s)
232 234
 
233
-    def test_310_too_many_symbols(self):
235
+    def test_310_too_many_symbols(self) -> None:
234 236
         with pytest.raises(
235 237
             ValueError, match='requested passphrase length too short'
236 238
         ):
237 239
             Vault(phrase=self.phrase, symbol=100)
238 240
 
239
-    def test_311_no_viable_characters(self):
241
+    def test_311_no_viable_characters(self) -> None:
240 242
         with pytest.raises(ValueError, match='no allowed characters left'):
241 243
             Vault(
242 244
                 phrase=self.phrase,
... ...
@@ -248,7 +250,7 @@ class TestVault:
248 250
                 symbol=0,
249 251
             )
250 252
 
251
-    def test_320_character_set_subtraction_duplicate(self):
253
+    def test_320_character_set_subtraction_duplicate(self) -> None:
252 254
         with pytest.raises(ValueError, match='duplicate characters'):
253 255
             Vault._subtract(b'abcdef', b'aabbccddeeff')
254 256
         with pytest.raises(ValueError, match='duplicate characters'):
... ...
@@ -8,7 +8,7 @@ import contextlib
8 8
 import json
9 9
 import os
10 10
 import socket
11
-from typing import TYPE_CHECKING, cast
11
+from typing import TYPE_CHECKING
12 12
 
13 13
 import click.testing
14 14
 import pytest
... ...
@@ -196,7 +196,7 @@ for opt, config in SINGLES.items():
196 196
 
197 197
 
198 198
 class TestCLI:
199
-    def test_200_help_output(self):
199
+    def test_200_help_output(self) -> None:
200 200
         runner = click.testing.CliRunner(mix_stderr=False)
201 201
         result = runner.invoke(
202 202
             cli.derivepassphrase, ['--help'], catch_exceptions=False
... ...
@@ -390,11 +390,10 @@ class TestCLI:
390 390
     )
391 391
     def test_210_invalid_argument_range(self, option: str) -> None:
392 392
         runner = click.testing.CliRunner(mix_stderr=False)
393
-        value: str | int
394 393
         for value in '-42', 'invalid':
395 394
             result = runner.invoke(
396 395
                 cli.derivepassphrase,
397
-                [option, cast(str, value), '-p', DUMMY_SERVICE],
396
+                [option, value, '-p', DUMMY_SERVICE],
398 397
                 input=DUMMY_PASSPHRASE,
399 398
                 catch_exceptions=False,
400 399
             )
... ...
@@ -872,7 +871,7 @@ contents go here
872 871
         ):
873 872
             custom_error = 'custom error message'
874 873
 
875
-            def raiser():
874
+            def raiser() -> None:
876 875
                 raise RuntimeError(custom_error)
877 876
 
878 877
             monkeypatch.setattr(cli, '_select_ssh_key', raiser)
... ...
@@ -925,7 +924,7 @@ class TestCLIUtils:
925 924
         @click.command()
926 925
         @click.option('--heading', default='Our menu:')
927 926
         @click.argument('items', nargs=-1)
928
-        def driver(heading, items):
927
+        def driver(heading: str, items: list[str]) -> None:
929 928
             # from https://montypython.fandom.com/wiki/Spam#The_menu
930 929
             items = items or [
931 930
                 'Egg and bacon',
... ...
@@ -1001,7 +1000,7 @@ Your selection? (1-10, leave empty to abort):\x20
1001 1000
         @click.command()
1002 1001
         @click.option('--item', default='baked beans')
1003 1002
         @click.argument('prompt')
1004
-        def driver(item, prompt):
1003
+        def driver(item: str, prompt: str) -> None:
1005 1004
             try:
1006 1005
                 cli._prompt_for_selection(
1007 1006
                     [item], heading='', single_choice_prompt=prompt
... ...
@@ -27,7 +27,9 @@ class TestStaticFunctionality:
27 27
             ([1, 7, 5, 5], 8, 0o1755),
28 28
         ],
29 29
     )
30
-    def test_200_big_endian_number(self, sequence, base, expected):
30
+    def test_200_big_endian_number(
31
+        self, sequence: list[int], base: int, expected: int
32
+    ) -> None:
31 33
         assert (
32 34
             sequin.Sequin._big_endian_number(sequence, base=base)
33 35
         ) == expected
... ...
@@ -41,8 +43,12 @@ class TestStaticFunctionality:
41 43
         ],
42 44
     )
43 45
     def test_300_big_endian_number_exceptions(
44
-        self, exc_type, exc_pattern, sequence, base
45
-    ):
46
+        self,
47
+        exc_type: type[Exception],
48
+        exc_pattern: str,
49
+        sequence: list[int],
50
+        base: int,
51
+    ) -> None:
46 52
         with pytest.raises(exc_type, match=exc_pattern):
47 53
             sequin.Sequin._big_endian_number(sequence, base=base)
48 54
 
... ...
@@ -61,11 +67,16 @@ class TestSequin:
61 67
             ('OK', False, bitseq('0100111101001011')),
62 68
         ],
63 69
     )
64
-    def test_200_constructor(self, sequence, is_bitstring, expected):
70
+    def test_200_constructor(
71
+        self,
72
+        sequence: str | bytes | bytearray | list[int],
73
+        is_bitstring: bool,
74
+        expected: list[int],
75
+    ) -> None:
65 76
         seq = sequin.Sequin(sequence, is_bitstring=is_bitstring)
66 77
         assert seq.bases == {2: collections.deque(expected)}
67 78
 
68
-    def test_201_generating(self):
79
+    def test_201_generating(self) -> None:
69 80
         seq = sequin.Sequin(
70 81
             [1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1], is_bitstring=True
71 82
         )
... ...
@@ -83,7 +94,7 @@ class TestSequin:
83 94
         with pytest.raises(ValueError, match='invalid target range'):
84 95
             seq.generate(0)
85 96
 
86
-    def test_210_internal_generating(self):
97
+    def test_210_internal_generating(self) -> None:
87 98
         seq = sequin.Sequin(
88 99
             [1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1], is_bitstring=True
89 100
         )
... ...
@@ -101,7 +112,7 @@ class TestSequin:
101 112
         with pytest.raises(ValueError, match='invalid base:'):
102 113
             seq._generate_inner(16, base=1)
103 114
 
104
-    def test_211_shifting(self):
115
+    def test_211_shifting(self) -> None:
105 116
         seq = sequin.Sequin([1, 0, 1, 0, 0, 1, 0, 0, 0, 1], is_bitstring=True)
106 117
         assert seq.bases == {
107 118
             2: collections.deque([1, 0, 1, 0, 0, 1, 0, 0, 0, 1])
... ...
@@ -130,7 +141,11 @@ class TestSequin:
130 141
         ],
131 142
     )
132 143
     def test_300_constructor_exceptions(
133
-        self, sequence, is_bitstring, exc_type, exc_pattern
134
-    ):
144
+        self,
145
+        sequence: list[int] | str,
146
+        is_bitstring: bool,
147
+        exc_type: type[Exception],
148
+        exc_pattern: str,
149
+    ) -> None:
135 150
         with pytest.raises(exc_type, match=exc_pattern):
136 151
             sequin.Sequin(sequence, is_bitstring=is_bitstring)
... ...
@@ -11,6 +11,7 @@ import io
11 11
 import os
12 12
 import socket
13 13
 import subprocess
14
+from typing import TYPE_CHECKING
14 15
 
15 16
 import click
16 17
 import click.testing
... ...
@@ -22,6 +23,9 @@ import derivepassphrase.cli
22 23
 import ssh_agent_client
23 24
 import tests
24 25
 
26
+if TYPE_CHECKING:
27
+    from collections.abc import Iterator
28
+
25 29
 
26 30
 class TestStaticFunctionality:
27 31
     @pytest.mark.parametrize(
... ...
@@ -31,13 +35,15 @@ class TestStaticFunctionality:
31 35
             for val in tests.SUPPORTED_KEYS.values()
32 36
         ],
33 37
     )
34
-    def test_100_key_decoding(self, public_key, public_key_data):
38
+    def test_100_key_decoding(
39
+        self, public_key: bytes, public_key_data: bytes
40
+    ) -> None:
35 41
         keydata = base64.b64decode(public_key.split(None, 2)[1])
36 42
         assert (
37 43
             keydata == public_key_data
38 44
         ), "recorded public key data doesn't match"
39 45
 
40
-    def test_200_constructor_no_running_agent(self, monkeypatch):
46
+    def test_200_constructor_no_running_agent(self, monkeypatch: Any) -> None:
41 47
         monkeypatch.delenv('SSH_AUTH_SOCK', raising=False)
42 48
         sock = socket.socket(family=socket.AF_UNIX)
43 49
         with pytest.raises(
... ...
@@ -51,7 +57,7 @@ class TestStaticFunctionality:
51 57
             (16777216, b'\x01\x00\x00\x00'),
52 58
         ],
53 59
     )
54
-    def test_210_uint32(self, input, expected):
60
+    def test_210_uint32(self, input: int, expected: bytes | bytearray) -> None:
55 61
         uint32 = ssh_agent_client.SSHAgentClient.uint32
56 62
         assert uint32(input) == expected
57 63
 
... ...
@@ -66,7 +72,9 @@ class TestStaticFunctionality:
66 72
             ),
67 73
         ],
68 74
     )
69
-    def test_211_string(self, input, expected):
75
+    def test_211_string(
76
+        self, input: bytes | bytearray, expected: bytes | bytearray
77
+    ) -> None:
70 78
         string = ssh_agent_client.SSHAgentClient.string
71 79
         assert bytes(string(input)) == expected
72 80
 
... ...
@@ -80,7 +88,9 @@ class TestStaticFunctionality:
80 88
             ),
81 89
         ],
82 90
     )
83
-    def test_212_unstring(self, input, expected):
91
+    def test_212_unstring(
92
+        self, input: bytes | bytearray, expected: bytes | bytearray
93
+    ) -> None:
84 94
         unstring = ssh_agent_client.SSHAgentClient.unstring
85 95
         unstring_prefix = ssh_agent_client.SSHAgentClient.unstring_prefix
86 96
         assert bytes(unstring(input)) == expected
... ...
@@ -96,7 +106,9 @@ class TestStaticFunctionality:
96 106
             (-1, OverflowError, "can't convert negative int to unsigned"),
97 107
         ],
98 108
     )
99
-    def test_310_uint32_exceptions(self, value, exc_type, exc_pattern):
109
+    def test_310_uint32_exceptions(
110
+        self, value: int, exc_type: type[Exception], exc_pattern: str
111
+    ) -> None:
100 112
         uint32 = ssh_agent_client.SSHAgentClient.uint32
101 113
         with pytest.raises(exc_type, match=exc_pattern):
102 114
             uint32(value)
... ...
@@ -107,7 +119,9 @@ class TestStaticFunctionality:
107 119
             ('some string', TypeError, 'invalid payload type'),
108 120
         ],
109 121
     )
110
-    def test_311_string_exceptions(self, input, exc_type, exc_pattern):
122
+    def test_311_string_exceptions(
123
+        self, input: Any, exc_type: type[Exception], exc_pattern: str
124
+    ) -> None:
111 125
         string = ssh_agent_client.SSHAgentClient.string
112 126
         with pytest.raises(exc_type, match=exc_pattern):
113 127
             string(input)
... ...
@@ -133,8 +147,13 @@ class TestStaticFunctionality:
133 147
         ],
134 148
     )
135 149
     def test_312_unstring_exceptions(
136
-        self, input, exc_type, exc_pattern, has_trailer, parts
137
-    ):
150
+        self,
151
+        input: bytes | bytearray,
152
+        exc_type: type[Exception],
153
+        exc_pattern: str,
154
+        has_trailer: bool,
155
+        parts: tuple[bytes | bytearray, bytes | bytearray] | None,
156
+    ) -> None:
138 157
         unstring = ssh_agent_client.SSHAgentClient.unstring
139 158
         unstring_prefix = ssh_agent_client.SSHAgentClient.unstring_prefix
140 159
         with pytest.raises(exc_type, match=exc_pattern):
... ...
@@ -151,7 +170,9 @@ class TestAgentInteraction:
151 170
     @pytest.mark.parametrize(
152 171
         ['keytype', 'data_dict'], list(tests.SUPPORTED_KEYS.items())
153 172
     )
154
-    def test_200_sign_data_via_agent(self, keytype, data_dict):
173
+    def test_200_sign_data_via_agent(
174
+        self, keytype: str, data_dict: tests.SSHTestKey
175
+    ) -> None:
155 176
         del keytype  # Unused.
156 177
         private_key = data_dict['private_key']
157 178
         try:
... ...
@@ -200,7 +221,9 @@ class TestAgentInteraction:
200 221
     @pytest.mark.parametrize(
201 222
         ['keytype', 'data_dict'], list(tests.UNSUITABLE_KEYS.items())
202 223
     )
203
-    def test_201_sign_data_via_agent_unsupported(self, keytype, data_dict):
224
+    def test_201_sign_data_via_agent_unsupported(
225
+        self, keytype: str, data_dict: tests.SSHTestKey
226
+    ) -> None:
204 227
         del keytype  # Unused.
205 228
         private_key = data_dict['private_key']
206 229
         try:
... ...
@@ -243,7 +266,7 @@ class TestAgentInteraction:
243 266
                 derivepassphrase.Vault.phrase_from_key(public_key_data)
244 267
 
245 268
     @staticmethod
246
-    def _params():
269
+    def _params() -> Iterator[tuple[bytes, bool]]:
247 270
         for value in tests.SUPPORTED_KEYS.values():
248 271
             key = value['public_key_data']
249 272
             yield (key, False)
... ...
@@ -254,8 +277,10 @@ class TestAgentInteraction:
254 277
                 yield (key, True)
255 278
 
256 279
     @pytest.mark.parametrize(['key', 'single'], list(_params()))
257
-    def test_210_ssh_key_selector(self, monkeypatch, key, single):
258
-        def key_is_suitable(key: bytes):
280
+    def test_210_ssh_key_selector(
281
+        self, monkeypatch: Any, key: bytes, single: bool
282
+    ) -> None:
283
+        def key_is_suitable(key: bytes) -> bool:
259 284
             return key in {
260 285
                 v['public_key_data'] for v in tests.SUPPORTED_KEYS.values()
261 286
             }
... ...
@@ -288,7 +313,7 @@ class TestAgentInteraction:
288 313
         b64_key = base64.standard_b64encode(key).decode('ASCII')
289 314
 
290 315
         @click.command()
291
-        def driver():
316
+        def driver() -> None:
292 317
             key = derivepassphrase.cli._select_ssh_key()
293 318
             click.echo(base64.standard_b64encode(key).decode('ASCII'))
294 319
 
... ...
@@ -310,7 +335,7 @@ class TestAgentInteraction:
310 335
 
311 336
     del _params
312 337
 
313
-    def test_300_constructor_bad_running_agent(self, monkeypatch):
338
+    def test_300_constructor_bad_running_agent(self, monkeypatch: Any) -> None:
314 339
         monkeypatch.setenv('SSH_AUTH_SOCK', os.environ['SSH_AUTH_SOCK'] + '~')
315 340
         sock = socket.socket(family=socket.AF_UNIX)
316 341
         with pytest.raises(OSError):  # noqa: PT011
... ...
@@ -323,7 +348,9 @@ class TestAgentInteraction:
323 348
             b'\x00\x00\x00\x1f some bytes missing',
324 349
         ],
325 350
     )
326
-    def test_310_truncated_server_response(self, monkeypatch, response):
351
+    def test_310_truncated_server_response(
352
+        self, monkeypatch: Any, response: bytes
353
+    ) -> None:
327 354
         client = ssh_agent_client.SSHAgentClient()
328 355
         response_stream = io.BytesIO(response)
329 356
 
... ...
@@ -354,8 +381,13 @@ class TestAgentInteraction:
354 381
         ],
355 382
     )
356 383
     def test_320_list_keys_error_responses(
357
-        self, monkeypatch, response_code, response, exc_type, exc_pattern
358
-    ):
384
+        self,
385
+        monkeypatch: Any,
386
+        response_code: int,
387
+        response: bytes | bytearray,
388
+        exc_type: type[Exception],
389
+        exc_pattern: str,
390
+    ) -> None:
359 391
         client = ssh_agent_client.SSHAgentClient()
360 392
         monkeypatch.setattr(
361 393
             client,
... ...
@@ -386,8 +418,14 @@ class TestAgentInteraction:
386 418
         ],
387 419
     )
388 420
     def test_330_sign_error_responses(
389
-        self, monkeypatch, key, check, response, exc_type, exc_pattern
390
-    ):
421
+        self,
422
+        monkeypatch: Any,
423
+        key: bytes | bytearray,
424
+        check: bool,
425
+        response: tuple[int, bytes | bytearray],
426
+        exc_type: type[Exception],
427
+        exc_pattern: str,
428
+    ) -> None:
391 429
         client = ssh_agent_client.SSHAgentClient()
392 430
         monkeypatch.setattr(client, 'request', lambda a, b: response)  # noqa: ARG005
393 431
         KeyCommentPair = ssh_agent_client.types.KeyCommentPair  # noqa: N806
... ...
@@ -0,0 +1 @@
1
+Fix typing issues according to `mypy`'s strict mode.
0 2