Type generators explicitly as generators, not iterators
Marco Ricci

Marco Ricci commited on 2026-06-19 22:40:11
Zeige 20 geänderte Dateien mit 86 Einfügungen und 65 Löschungen.


For compatibility with `contextlib.contextmanager`, and because it is
the more specific type, we annotate generators as `Generator[T, None,
None]`, not as `Iterator[T]`.

In January 2026, typeshed started shipping type annotations for the
Python standard library that deprecate the use of
`contextlib.contextmanager` with general functions returning
`Iterator[T]`.  `contextlib.contextmanager` actually requires
a `Generator[T, None, None]` to work, but the `contextlib` and the
`typing` documentation has been erroneously suggesting the `Iterator[T]`
type as a "shorthand" notation for years.  For
`contextlib.contextmanager`, the difference matters.  So, re-annotate
everything explicitly with generators (when producing) or iterables
(when consuming), whichever is appropriate.
... ...
@@ -47,7 +47,7 @@ else:
47 47
 if TYPE_CHECKING:
48 48
     import types
49 49
     from collections.abc import (
50
-        Iterator,
50
+        Generator,
51 51
         Mapping,
52 52
     )
53 53
     from contextlib import AbstractContextManager
... ...
@@ -564,7 +564,7 @@ def get_suitable_ssh_keys(
564 564
     | Sequence[str]
565 565
     | None = None,
566 566
     /,
567
-) -> Iterator[_types.SSHKeyCommentPair]:
567
+) -> Generator[_types.SSHKeyCommentPair, None, None]:
568 568
     """Yield all SSH keys suitable for passphrase derivation.
569 569
 
570 570
     Suitable SSH keys are queried from the running SSH agent (see
... ...
@@ -36,7 +36,7 @@ from typing_extensions import TypeAlias, override
36 36
 from derivepassphrase import _internals
37 37
 
38 38
 if TYPE_CHECKING:
39
-    from collections.abc import Iterable, Iterator, Mapping, Sequence
39
+    from collections.abc import Generator, Iterable, Mapping, Sequence
40 40
 
41 41
     from typing_extensions import Any, Self
42 42
 
... ...
@@ -2589,7 +2589,7 @@ def _write_po_file(  # noqa: C901,PLR0912
2589 2589
 def _format_po_info(
2590 2590
     data: Mapping[str, Any],
2591 2591
     /,
2592
-) -> Iterator[str]:  # pragma: no cover [internal]
2592
+) -> Generator[str, None, None]:  # pragma: no cover [internal]
2593 2593
     """"""  # noqa: D419
2594 2594
     sortorder = [
2595 2595
         "project-id-version",
... ...
@@ -26,7 +26,7 @@ from typing_extensions import (
26 26
 )
27 27
 
28 28
 if TYPE_CHECKING:
29
-    from collections.abc import Callable, Iterator, Sequence
29
+    from collections.abc import Callable, Generator, Sequence
30 30
     from typing import Literal
31 31
 
32 32
     from typing_extensions import (
... ...
@@ -253,7 +253,7 @@ class _VaultConfigValidator:
253 253
 
254 254
     def walk_subconfigs(
255 255
         self,
256
-    ) -> Iterator[tuple[tuple[str] | tuple[str, str], str, Any]]:
256
+    ) -> Generator[tuple[tuple[str] | tuple[str, str], str, Any], None, None]:
257 257
         obj = cast("dict[str, dict[str, Any]]", self.maybe_config)
258 258
         if isinstance(obj.get("global", False), dict):
259 259
             for k, v in list(obj["global"].items()):
... ...
@@ -338,7 +338,7 @@ class _VaultConfigValidator:
338 338
             elif not allow_unknown_settings:
339 339
                 raise ValueError(err_unknown_setting.format(**kwargs))
340 340
 
341
-    def clean_up_falsy_values(self) -> Iterator[CleanupStep]:  # noqa: C901
341
+    def clean_up_falsy_values(self) -> Generator[CleanupStep, None, None]:  # noqa: C901
342 342
         obj = self.maybe_config
343 343
         if (
344 344
             not isinstance(obj, dict)
... ...
@@ -40,7 +40,7 @@ if TYPE_CHECKING:
40 40
     from typing_extensions import Buffer
41 41
 
42 42
 if TYPE_CHECKING:
43
-    from collections.abc import Iterator
43
+    from collections.abc import Generator
44 44
 
45 45
     from cryptography.hazmat.primitives import ciphers, hashes, hmac, padding
46 46
     from cryptography.hazmat.primitives.ciphers import algorithms, modes
... ...
@@ -666,7 +666,7 @@ def _decrypt_bucket_file(
666 666
     master_keys: _types.StoreroomMasterKeys,
667 667
     *,
668 668
     root_dir: str | bytes | os.PathLike = ".",
669
-) -> Iterator[Buffer]:
669
+) -> Generator[Buffer, None, None]:
670 670
     """Decrypt a complete bucket.
671 671
 
672 672
     Args:
... ...
@@ -29,7 +29,7 @@ from typing import TYPE_CHECKING
29 29
 from typing_extensions import assert_type
30 30
 
31 31
 if TYPE_CHECKING:
32
-    from collections.abc import Iterator, Sequence
32
+    from collections.abc import Generator, Sequence
33 33
 
34 34
 __all__ = ("Sequin", "SequinExhaustedError")
35 35
 
... ...
@@ -87,7 +87,7 @@ class Sequin:
87 87
         """
88 88
         msg = "sequence item out of range"
89 89
 
90
-        def uint8_to_bits(value: int) -> Iterator[int]:
90
+        def uint8_to_bits(value: int) -> Generator[int, None, None]:
91 91
             """Yield individual bits of an 8-bit number, MSB first."""
92 92
             for i in (0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01):
93 93
                 yield 1 if value | i == value else 0
... ...
@@ -20,7 +20,7 @@ if sys.version_info < (3, 11):
20 20
     from exceptiongroup import ExceptionGroup
21 21
 
22 22
 if TYPE_CHECKING:
23
-    from collections.abc import Iterable, Iterator
23
+    from collections.abc import Generator, Iterable
24 24
     from collections.abc import Set as AbstractSet
25 25
     from types import TracebackType
26 26
 
... ...
@@ -442,7 +442,7 @@ class SSHAgentClient:
442 442
         | _types.SSHAgentSocket
443 443
         | Sequence[str]
444 444
         | None = None,
445
-    ) -> Iterator[SSHAgentClient]:
445
+    ) -> Generator[SSHAgentClient, None, None]:
446 446
         """Return an SSH agent client subcontext.
447 447
 
448 448
         If necessary, construct an SSH agent client first using the
... ...
@@ -25,7 +25,7 @@ from tests.machinery import hypothesis as hypothesis_machinery
25 25
 from tests.machinery import pytest as pytest_machinery
26 26
 
27 27
 if TYPE_CHECKING:
28
-    from collections.abc import Iterator, Sequence
28
+    from collections.abc import Generator, Sequence
29 29
 
30 30
 startup_ssh_auth_sock = os.environ.get("SSH_AUTH_SOCK", None)
31 31
 
... ...
@@ -55,7 +55,9 @@ def pytest_configure(config: pytest.Config) -> None:
55 55
 # https://docs.pytest.org/en/stable/explanation/fixtures.html#a-note-about-fixture-cleanup
56 56
 # https://github.com/pytest-dev/pytest/issues/5243#issuecomment-491522595
57 57
 @pytest.fixture(scope="session", autouse=False)
58
-def term_handler() -> Iterator[None]:  # pragma: no cover [external]
58
+def term_handler() -> Generator[
59
+    None, None, None
60
+]:  # pragma: no cover [external]
59 61
     try:
60 62
         import signal  # noqa: PLC0415
61 63
 
... ...
@@ -581,7 +583,7 @@ Popen = TypeVar("Popen", bound=subprocess.Popen)
581 583
 @contextlib.contextmanager
582 584
 def terminate_on_exit(
583 585
     proc: Popen,
584
-) -> Iterator[Popen]:  # pragma: unless posix no cover [unused]
586
+) -> Generator[Popen, None, None]:  # pragma: unless posix no cover [unused]
585 587
     """Terminate and wait for the subprocess upon exiting the context.
586 588
 
587 589
     Args:
... ...
@@ -610,7 +612,9 @@ def spawn_named_agent(  # noqa: C901
610 612
     spawn_func: SSHAgentSpawnFunc | SSHAgentInterfaceFunc,
611 613
     agent_type: data.KnownSSHAgentType,
612 614
     agent_name: str,
613
-) -> Iterator[data.SpawnedSSHAgentInfo]:  # pragma: no cover [external]
615
+) -> Generator[
616
+    data.SpawnedSSHAgentInfo, None, None
617
+]:  # pragma: no cover [external]
614 618
     """Spawn the named SSH agent and check that it is operational.
615 619
 
616 620
     Using the correct agent-specific spawn function from the
... ...
@@ -792,7 +796,7 @@ def spawn_named_agent(  # noqa: C901
792 796
 )
793 797
 def running_ssh_agent(  # pragma: no cover [external]
794 798
     request: pytest.FixtureRequest,
795
-) -> Iterator[data.RunningSSHAgentInfo]:
799
+) -> Generator[data.RunningSSHAgentInfo, None, None]:
796 800
     """Ensure a running SSH agent, if possible, as a pytest fixture.
797 801
 
798 802
     Check for a running SSH agent, or spawn/interface a new one if
... ...
@@ -818,7 +822,7 @@ def running_ssh_agent(  # pragma: no cover [external]
818 822
 
819 823
     def prepare_environment(
820 824
         agent_type: data.KnownSSHAgentType,
821
-    ) -> Iterator[data.RunningSSHAgentInfo]:
825
+    ) -> Generator[data.RunningSSHAgentInfo, None, None]:
822 826
         with pytest.MonkeyPatch.context() as monkeypatch:
823 827
             if agent_type == data.KnownSSHAgentType.StubbedSSHAgent:
824 828
                 monkeypatch.setattr(
... ...
@@ -874,7 +878,9 @@ def running_ssh_agent(  # pragma: no cover [external]
874 878
 @pytest.fixture(params=spawn_handlers_params)
875 879
 def spawn_ssh_agent(
876 880
     request: pytest.FixtureRequest,
877
-) -> Iterator[data.SpawnedSSHAgentInfo]:  # pragma: no cover [external]
881
+) -> Generator[
882
+    data.SpawnedSSHAgentInfo, None, None
883
+]:  # pragma: no cover [external]
878 884
     """Spawn or interface an SSH agent, as a pytest fixture.
879 885
 
880 886
     Spawn a new SSH agent (isolated from other SSH use by other
... ...
@@ -1053,7 +1059,7 @@ def _load_key_optimistically(
1053 1059
 @pytest.fixture
1054 1060
 def ssh_agent_client_with_test_keys_loaded(
1055 1061
     spawn_ssh_agent: data.SpawnedSSHAgentInfo,
1056
-) -> Iterator[ssh_agent.SSHAgentClient]:
1062
+) -> Generator[ssh_agent.SSHAgentClient, None, None]:
1057 1063
     """Provide an SSH agent with loaded test keys, as a pytest fixture.
1058 1064
 
1059 1065
     Use the `spawn_ssh_agent` fixture to acquire a usable SSH agent,
... ...
@@ -27,7 +27,7 @@ __all__ = ()
27 27
 
28 28
 if TYPE_CHECKING:
29 29
     import socket
30
-    from collections.abc import Iterator
30
+    from collections.abc import Generator
31 31
 
32 32
     from typing_extensions import Any
33 33
 
... ...
@@ -99,7 +99,9 @@ def list_keys_singleton(self: Any = None) -> list[_types.SSHKeyCommentPair]:
99 99
 # -------------
100 100
 
101 101
 
102
-def suitable_ssh_keys(conn: Any) -> Iterator[_types.SSHKeyCommentPair]:
102
+def suitable_ssh_keys(
103
+    conn: Any,
104
+) -> Generator[_types.SSHKeyCommentPair, None, None]:
103 105
     """Return a two-item list of SSH test keys (key/comment pairs).
104 106
 
105 107
     Intended as a monkeypatching replacement for
... ...
@@ -32,7 +32,13 @@ __all__ = ()
32 32
 
33 33
 if TYPE_CHECKING:
34 34
     import types
35
-    from collections.abc import Callable, Iterator, Mapping, Sequence
35
+    from collections.abc import (
36
+        Callable,
37
+        Generator,
38
+        Iterable,
39
+        Mapping,
40
+        Sequence,
41
+    )
36 42
     from contextlib import AbstractContextManager
37 43
     from typing import IO, Literal, NotRequired
38 44
 
... ...
@@ -500,7 +506,7 @@ class StubbedSSHAgentSocket:
500 506
         self._check_for_io_on_closed_connection()
501 507
         self.receive_from_client.extend(memoryview(data))
502 508
         while self.receive_from_client:
503
-            result: Buffer | Iterator[int]
509
+            result: Buffer | Iterable[int]
504 510
             if len(self.receive_from_client) < self.HEADER_SIZE:
505 511
                 break
506 512
             count = int.from_bytes(
... ...
@@ -599,7 +605,7 @@ class StubbedSSHAgentSocket:
599 605
         extension_marker = b"\x1b" + string(extension.encode("ascii"))
600 606
         return self.receive_from_client.startswith(extension_marker, 4)
601 607
 
602
-    def query_extensions(self) -> Iterator[int]:
608
+    def query_extensions(self) -> Generator[int, None, None]:
603 609
         """Answer an `SSH_AGENTC_EXTENSION` request.
604 610
 
605 611
         Yields:
... ...
@@ -619,7 +625,7 @@ class StubbedSSHAgentSocket:
619 625
 
620 626
     def request_identities(
621 627
         self, *, list_extended: bool = False
622
-    ) -> Iterator[int]:
628
+    ) -> Generator[int, None, None]:
623 629
         """Answer an `SSH_AGENTC_REQUEST_IDENTITIES` request.
624 630
 
625 631
         Args:
... ...
@@ -41,7 +41,7 @@ from derivepassphrase.ssh_agent import socketprovider
41 41
 __all__ = ()
42 42
 
43 43
 if TYPE_CHECKING:
44
-    from collections.abc import Callable, Iterator, Sequence
44
+    from collections.abc import Callable, Generator, Sequence
45 45
     from contextlib import AbstractContextManager
46 46
 
47 47
     from typing_extensions import Any
... ...
@@ -225,7 +225,7 @@ class Parametrize(types.SimpleNamespace):
225 225
 def faked_entry_point_list(  # noqa: C901
226 226
     additional_entry_points: Sequence[importlib.metadata.EntryPoint],
227 227
     remove_conflicting_entries: bool = False,
228
-) -> Iterator[Sequence[str]]:
228
+) -> Generator[Sequence[str], None, None]:
229 229
     """Yield a context where additional entry points are visible.
230 230
 
231 231
     Args:
... ...
@@ -371,7 +371,7 @@ def isolated_config(
371 371
     monkeypatch: pytest.MonkeyPatch,
372 372
     runner: tests.machinery.CliRunner,
373 373
     main_config_str: str | None = None,
374
-) -> Iterator[None]:
374
+) -> Generator[None, None, None]:
375 375
     """Provide an isolated configuration setup, as a context.
376 376
 
377 377
     This context manager sets up (and changes into) a temporary
... ...
@@ -429,7 +429,7 @@ def isolated_vault_config(
429 429
     runner: tests.machinery.CliRunner,
430 430
     vault_config: Any,
431 431
     main_config_str: str | None = None,
432
-) -> Iterator[None]:
432
+) -> Generator[None, None, None]:
433 433
     """Provide an isolated vault configuration setup, as a context.
434 434
 
435 435
     Uses [`isolated_config`][] internally.  Beyond those actions, this
... ...
@@ -467,7 +467,7 @@ def isolated_vault_exporter_config(
467 467
     runner: tests.machinery.CliRunner,
468 468
     vault_config: str | bytes | None = None,
469 469
     vault_key: str | None = None,
470
-) -> Iterator[None]:
470
+) -> Generator[None, None, None]:
471 471
     """Provide an isolated vault configuration setup, as a context.
472 472
 
473 473
     Works similarly to [`isolated_config`][], except that no user
... ...
@@ -509,7 +509,7 @@ def isolated_vault_exporter_config(
509 509
             @contextlib.contextmanager
510 510
             def chdir(
511 511
                 newpath: str | bytes | os.PathLike,
512
-            ) -> Iterator[None]:  # pragma: no branch
512
+            ) -> Generator[None, None, None]:  # pragma: no branch
513 513
                 oldpath = pathlib.Path.cwd().resolve()
514 514
                 os.chdir(newpath)
515 515
                 try:
... ...
@@ -29,7 +29,7 @@ from derivepassphrase import _types, ssh_agent, vault
29 29
 from tests import data, machinery
30 30
 
31 31
 if TYPE_CHECKING:
32
-    from collections.abc import Iterator
32
+    from collections.abc import Generator
33 33
 
34 34
     from typing_extensions import Buffer
35 35
 
... ...
@@ -498,7 +498,7 @@ class TestStubbedSSHAgentSocket:
498 498
     @contextlib.contextmanager
499 499
     def _get_agent(
500 500
         self, *, extended_agent: bool | None = False
501
-    ) -> Iterator[machinery.StubbedSSHAgentSocket]:
501
+    ) -> Generator[machinery.StubbedSSHAgentSocket, None, None]:
502 502
         agent_class: type[machinery.StubbedSSHAgentSocket] = (
503 503
             machinery.StubbedSSHAgentSocketWithAddressAndDeterministicDSA
504 504
             if extended_agent
... ...
@@ -29,7 +29,7 @@ from tests import data, machinery
29 29
 from tests.machinery import pytest as pytest_machinery
30 30
 
31 31
 if TYPE_CHECKING:
32
-    from collections.abc import Iterator
32
+    from collections.abc import Generator
33 33
 
34 34
 DUMMY_SERVICE = data.DUMMY_SERVICE
35 35
 DUMMY_PASSPHRASE = data.DUMMY_PASSPHRASE
... ...
@@ -315,7 +315,7 @@ class TestUserConfigurationFileOther:
315 315
         self,
316 316
         *,
317 317
         main_config: str = "",
318
-    ) -> Iterator[pytest.MonkeyPatch]:
318
+    ) -> Generator[pytest.MonkeyPatch, None, None]:
319 319
         runner = machinery.CliRunner(mix_stderr=False)
320 320
         # TODO(the-13th-letter): Rewrite using parenthesized
321 321
         # with-statements.
... ...
@@ -27,7 +27,7 @@ from tests import machinery
27 27
 from tests.machinery import pytest as pytest_machinery
28 28
 
29 29
 if TYPE_CHECKING:
30
-    from collections.abc import Iterator
30
+    from collections.abc import Generator
31 31
 
32 32
 
33 33
 class VersionOutputData(NamedTuple):
... ...
@@ -334,7 +334,7 @@ def tokenize_version_output_item_listing(
334 334
     /,
335 335
     *,
336 336
     is_alias_annotation: bool = False,
337
-) -> Iterator[str]:
337
+) -> Generator[str, None, None]:
338 338
     """Yield the next feature in a `--version` feature listing.
339 339
 
340 340
     This is a regular expression-based parser (alluded to in
... ...
@@ -42,7 +42,7 @@ from tests.machinery import hypothesis as hypothesis_machinery
42 42
 from tests.machinery import pytest as pytest_machinery
43 43
 
44 44
 if TYPE_CHECKING:
45
-    from collections.abc import Callable, Iterable, Iterator
45
+    from collections.abc import Callable, Generator, Iterable
46 46
     from typing import NoReturn
47 47
 
48 48
     from typing_extensions import Any
... ...
@@ -83,7 +83,7 @@ def vault_config_exporter_shell_interpreter(  # noqa: C901
83 83
     # about here.
84 84
     command: click.Command | click.Group | None = None,
85 85
     runner: machinery.CliRunner | None = None,
86
-) -> Iterator[machinery.ReadableResult]:
86
+) -> Generator[machinery.ReadableResult, None, None]:
87 87
     """A rudimentary sh(1) interpreter for `--export-as=sh` output.
88 88
 
89 89
     Assumes a script as emitted by `derivepassphrase vault
... ...
@@ -1265,7 +1265,7 @@ class TestTempdir:
1265 1265
         @contextlib.contextmanager
1266 1266
         def make_temporary_directory(
1267 1267
             path: pathlib.Path,
1268
-        ) -> Iterator[pathlib.Path]:
1268
+        ) -> Generator[pathlib.Path, None, None]:
1269 1269
             try:
1270 1270
                 path.mkdir()
1271 1271
                 yield path
... ...
@@ -25,7 +25,7 @@ from tests.data import callables
25 25
 from tests.machinery import pytest as pytest_machinery
26 26
 
27 27
 if TYPE_CHECKING:
28
-    from collections.abc import Iterator
28
+    from collections.abc import Generator
29 29
     from typing import NoReturn
30 30
 
31 31
 DUMMY_SERVICE = data.DUMMY_SERVICE
... ...
@@ -568,7 +568,7 @@ class TestKeyBasic:
568 568
         main_config_str: str | None = None,
569 569
         registry: dict[str, _types.SSHAgentSocketProvider | str | None]
570 570
         | None = None,
571
-    ) -> Iterator[machinery.CliRunner]:
571
+    ) -> Generator[machinery.CliRunner, None, None]:
572 572
         runner = machinery.CliRunner(mix_stderr=False)
573 573
         # TODO(the-13th-letter): Rewrite using parenthesized
574 574
         # with-statements.
... ...
@@ -901,7 +901,7 @@ class TestInvalidCommandLines:
901 901
                 DUMMY_SERVICE: {**DUMMY_CONFIG_SETTINGS},
902 902
             },
903 903
         },
904
-    ) -> Iterator[machinery.CliRunner]:
904
+    ) -> Generator[machinery.CliRunner, None, None]:
905 905
         runner = machinery.CliRunner(mix_stderr=False)
906 906
         # TODO(the-13th-letter): Rewrite using parenthesized
907 907
         # with-statements.
... ...
@@ -39,7 +39,7 @@ from tests.machinery import hypothesis as hypothesis_machinery
39 39
 from tests.machinery import pytest as pytest_machinery
40 40
 
41 41
 if TYPE_CHECKING:
42
-    from collections.abc import Iterator
42
+    from collections.abc import Generator
43 43
     from typing import NoReturn
44 44
 
45 45
     from typing_extensions import Any
... ...
@@ -306,7 +306,7 @@ class TestImportConfigInvalid:
306 306
     """Tests concerning `vault` configuration imports: invalid imports."""
307 307
 
308 308
     @contextlib.contextmanager
309
-    def _setup_environment(self) -> Iterator[machinery.CliRunner]:
309
+    def _setup_environment(self) -> Generator[machinery.CliRunner, None, None]:
310 310
         runner = machinery.CliRunner(mix_stderr=False)
311 311
         # TODO(the-13th-letter): Rewrite using parenthesized
312 312
         # with-statements.
... ...
@@ -527,7 +527,7 @@ class TestExportConfigInvalid:
527 527
         *,
528 528
         config: _types.VaultConfig = {"services": {}},  # noqa: B006
529 529
         error_messages: tuple[str, ...] = (),
530
-    ) -> Iterator[list[str]]:
530
+    ) -> Generator[list[str], None, None]:
531 531
         runner = machinery.CliRunner(mix_stderr=False)
532 532
         # TODO(the-13th-letter): Rewrite using parenthesized
533 533
         # with-statements.
... ...
@@ -719,7 +719,7 @@ class TestStoringConfigurationFailures:
719 719
             "services": {},
720 720
         },
721 721
         patch_suitable_ssh_keys: bool = True,
722
-    ) -> Iterator[pytest.MonkeyPatch]:
722
+    ) -> Generator[pytest.MonkeyPatch, None, None]:
723 723
         runner = machinery.CliRunner(mix_stderr=False)
724 724
         # TODO(the-13th-letter): Rewrite using parenthesized
725 725
         # with-statements.
... ...
@@ -36,7 +36,7 @@ from cryptography.hazmat.primitives.ciphers import (  # noqa: E402
36 36
 )
37 37
 
38 38
 if TYPE_CHECKING:
39
-    from collections.abc import Callable, Iterator
39
+    from collections.abc import Callable, Generator
40 40
     from typing import Any
41 41
 
42 42
     from typing_extensions import Buffer, Literal
... ...
@@ -256,7 +256,7 @@ class TestCLIFailures:
256 256
         *,
257 257
         vault_config: str | bytes = data.VAULT_V03_CONFIG,
258 258
         vault_key: str = data.VAULT_MASTER_KEY,
259
-    ) -> Iterator[pytest.MonkeyPatch]:
259
+    ) -> Generator[pytest.MonkeyPatch, None, None]:
260 260
         runner = machinery.CliRunner(mix_stderr=False)
261 261
         # TODO(the-13th-letter): Rewrite using parenthesized
262 262
         # with-statements.
... ...
@@ -354,7 +354,7 @@ class TestStoreroom:
354 354
         *,
355 355
         vault_config: str | bytes = data.VAULT_STOREROOM_CONFIG_ZIPPED,
356 356
         vault_key: str = data.VAULT_MASTER_KEY,
357
-    ) -> Iterator[tuple[pytest.MonkeyPatch, machinery.CliRunner]]:
357
+    ) -> Generator[tuple[pytest.MonkeyPatch, machinery.CliRunner], None, None]:
358 358
         runner = machinery.CliRunner(mix_stderr=False)
359 359
         # TODO(the-13th-letter): Rewrite using parenthesized
360 360
         # with-statements.
... ...
@@ -575,7 +575,7 @@ class TestVaultNativeConfig:
575 575
         *,
576 576
         vault_config: str | bytes = data.VAULT_V03_CONFIG,
577 577
         vault_key: str = data.VAULT_MASTER_KEY,
578
-    ) -> Iterator[tuple[pytest.MonkeyPatch, machinery.CliRunner]]:
578
+    ) -> Generator[tuple[pytest.MonkeyPatch, machinery.CliRunner], None, None]:
579 579
         runner = machinery.CliRunner(mix_stderr=False)
580 580
         # TODO(the-13th-letter): Rewrite using parenthesized
581 581
         # with-statements.
... ...
@@ -23,7 +23,7 @@ from tests import data, machinery
23 23
 from tests.machinery import pytest as pytest_machinery
24 24
 
25 25
 if TYPE_CHECKING:
26
-    from collections.abc import Callable, Iterator
26
+    from collections.abc import Callable, Generator
27 27
 
28 28
     from typing_extensions import Any, Buffer
29 29
 
... ...
@@ -179,7 +179,7 @@ class TestCLIUtilities:
179 179
             return TestCLIUtilities.VaultKeyEnvironment(expected, *env_vars)
180 180
 
181 181
     @contextlib.contextmanager
182
-    def _setup_environment(self) -> Iterator[pytest.MonkeyPatch]:
182
+    def _setup_environment(self) -> Generator[pytest.MonkeyPatch, None, None]:
183 183
         runner = machinery.CliRunner(mix_stderr=False)
184 184
         # TODO(the-13th-letter): Rewrite using parenthesized
185 185
         # with-statements.
... ...
@@ -353,7 +353,7 @@ class TestExportVaultConfigDataHandlerRegistry:
353 353
         *,
354 354
         registry: dict[str, Any] | None = None,
355 355
         find_handlers: Callable | None = None,
356
-    ) -> Iterator[pytest.MonkeyPatch]:
356
+    ) -> Generator[pytest.MonkeyPatch, None, None]:
357 357
         with pytest.MonkeyPatch.context() as monkeypatch:
358 358
             if registry is not None:  # pragma: no branch
359 359
                 monkeypatch.setattr(
... ...
@@ -435,7 +435,7 @@ class TestGenericVaultCLIErrors:
435 435
     @contextlib.contextmanager
436 436
     def _setup_environment(
437 437
         self, *, vault_config: str | bytes = data.VAULT_V03_CONFIG
438
-    ) -> Iterator[tuple[pytest.MonkeyPatch, machinery.CliRunner]]:
438
+    ) -> Generator[tuple[pytest.MonkeyPatch, machinery.CliRunner], None, None]:
439 439
         runner = machinery.CliRunner(mix_stderr=False)
440 440
         # TODO(the-13th-letter): Rewrite using parenthesized
441 441
         # with-statements.
... ...
@@ -30,7 +30,12 @@ from tests.data import callables
30 30
 from tests.machinery import pytest as pytest_machinery
31 31
 
32 32
 if TYPE_CHECKING:
33
-    from collections.abc import Callable, Iterator, Mapping, Sequence
33
+    from collections.abc import (
34
+        Callable,
35
+        Generator,
36
+        Mapping,
37
+        Sequence,
38
+    )
34 39
 
35 40
     from typing_extensions import Any, Literal
36 41
 
... ...
@@ -39,7 +44,7 @@ if sys.version_info < (3, 11):
39 44
 
40 45
 
41 46
 @pytest.fixture
42
-def use_stub_agent() -> Iterator[None]:
47
+def use_stub_agent() -> Generator[None, None, None]:
43 48
     """Enforce use of the stubbed SSH agent."""
44 49
     with pytest.MonkeyPatch.context() as monkeypatch:
45 50
         monkeypatch.setattr(
... ...
@@ -848,7 +853,7 @@ class TestSSHAgentSocketProviderRegistry:
848 853
         resolve = socketprovider.SocketProvider.resolve
849 854
         register = socketprovider.SocketProvider.register
850 855
 
851
-        def ancestry_chain(name: str) -> Iterator[str]:
856
+        def ancestry_chain(name: str) -> Generator[str, None, None]:
852 857
             current: _types.SSHAgentSocketProvider | str | None = name
853 858
             seen: set[_types.SSHAgentSocketProvider | str | None] = set()
854 859
             while isinstance(current, str) and current not in seen:
... ...
@@ -1204,7 +1209,9 @@ class TestAgentErrorResponses:
1204 1209
             data.AgentProtocolRequest, Sequence[data.AgentProtocolResponse]
1205 1210
         ],
1206 1211
         /,
1207
-    ) -> Iterator[tuple[pytest.MonkeyPatch, ssh_agent.SSHAgentClient]]:
1212
+    ) -> Generator[
1213
+        tuple[pytest.MonkeyPatch, ssh_agent.SSHAgentClient], None, None
1214
+    ]:
1208 1215
         # TODO(the-13th-letter): Rewrite using parenthesized
1209 1216
         # with-statements.
1210 1217
         # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9
... ...
@@ -1558,7 +1565,7 @@ class TestWindowsNamedPipeHandle:
1558 1565
     @contextlib.contextmanager
1559 1566
     def stub_createfile_and_closehandle(
1560 1567
         self,
1561
-    ) -> Iterator[None]:
1568
+    ) -> Generator[None, None, None]:
1562 1569
         singleton = object()
1563 1570
 
1564 1571
         def create_file(*_args: Any, **_kwargs: Any) -> object:
... ...
@@ -21,7 +21,7 @@ from hypothesis import strategies
21 21
 from derivepassphrase._internals import cli_messages as msg
22 22
 
23 23
 if TYPE_CHECKING:
24
-    from collections.abc import Iterator
24
+    from collections.abc import Generator
25 25
     from typing import ClassVar
26 26
 
27 27
 
... ...
@@ -32,7 +32,7 @@ class Parametrize(types.SimpleNamespace):
32 32
 
33 33
 
34 34
 @pytest.fixture(scope="class")
35
-def use_debug_translations() -> Iterator[None]:
35
+def use_debug_translations() -> Generator[None, None, None]:
36 36
     """Force the use of debug translations (pytest class fixture)."""
37 37
     with pytest.MonkeyPatch.context() as monkeypatch:
38 38
         monkeypatch.setattr(msg, "translation", msg.DebugTranslations())
... ...
@@ -40,7 +40,7 @@ def use_debug_translations() -> Iterator[None]:
40 40
 
41 41
 
42 42
 @contextlib.contextmanager
43
-def monkeypatched_null_translations() -> Iterator[None]:
43
+def monkeypatched_null_translations() -> Generator[None, None, None]:
44 44
     """Force the use of no-op translations in this context."""
45 45
     with pytest.MonkeyPatch.context() as monkeypatch:
46 46
         monkeypatch.setattr(msg, "translation", gettext.NullTranslations())
47 47