From 668e7f1420368f70e8564728fd71008f936c2eb2 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 11 Oct 2026 14:59:53 +0200 Subject: [PATCH] python: Annotate context managers as returning Generator ty 0.0.86 deprecates the overload of contextmanager() that takes a function returning Iterator, and reports every such function as an error. Annotate them as returning Generator instead. Signed-off-by: Daan De Meyer --- bin/tine.py | 4 ++-- bin/tine_test.py | 6 +++--- box/sandbox_test.py | 4 ++-- image/finalize.py | 4 ++-- image/kmod.py | 4 ++-- package/installer.py | 4 ++-- package_system/deb/deb822.py | 4 ++-- package_system/deb/install.py | 4 ++-- package_system/pacman/alpm.py | 4 ++-- package_system/rpm/build_test.py | 4 ++-- remote_cache/status.py | 4 ++-- rootfs/rootfs.py | 12 ++++++------ tools/dev_test.py | 4 ++-- util.py | 6 +++--- 14 files changed, 34 insertions(+), 34 deletions(-) diff --git a/bin/tine.py b/bin/tine.py index 9a25be35..9a41029a 100644 --- a/bin/tine.py +++ b/bin/tine.py @@ -13,7 +13,7 @@ import signal import sys import tomllib -from collections.abc import Iterator, Mapping +from collections.abc import Generator, Mapping from contextlib import ExitStack, contextmanager from dataclasses import dataclass from pathlib import Path @@ -1423,7 +1423,7 @@ def refresh_project_buckconfig(root: Path, overrides: dict[str, dict[str, str]]) @contextmanager -def mount_table_lock(root: Path) -> Iterator[None]: +def mount_table_lock(root: Path) -> Generator[None]: """Keep concurrent mount-table edits from losing each other's changes.""" path = root / MOUNT_LOCK try: diff --git a/bin/tine_test.py b/bin/tine_test.py index 5a524c76..728b9485 100644 --- a/bin/tine_test.py +++ b/bin/tine_test.py @@ -974,7 +974,7 @@ def test_the_mount_file_rejects_unrelated_local_settings(self) -> None: class TestMountUpdates(MountTestCase): @contextlib.contextmanager - def process(self, code: str, *args: str) -> collections.abc.Iterator[subprocess.Popen[str]]: + def process(self, code: str, *args: str) -> collections.abc.Generator[subprocess.Popen[str]]: prefix = f"import sys\nsys.path[:] = {sys.path!r}\nimport tine\n" with subprocess.Popen( [sys.executable, "-c", prefix + textwrap.dedent(code), str(self.root), *args], @@ -1485,7 +1485,7 @@ def setUp(self) -> None: self.addCleanup(patched.stop) @contextlib.contextmanager - def running(self) -> collections.abc.Iterator[list[object]]: + def running(self) -> collections.abc.Generator[list[object]]: execve: list[object] = [] with unittest.mock.patch.object(os, "execve", side_effect=lambda *a: execve.extend(a)): yield execve @@ -1824,7 +1824,7 @@ def setUp(self) -> None: self.addCleanup(environment.stop) @contextlib.contextmanager - def running(self) -> collections.abc.Iterator[list[object]]: + def running(self) -> collections.abc.Generator[list[object]]: execve: list[object] = [] self.served: list[object] = [] with contextlib.ExitStack() as patches: diff --git a/box/sandbox_test.py b/box/sandbox_test.py index 8c7dce70..c4cae4b1 100644 --- a/box/sandbox_test.py +++ b/box/sandbox_test.py @@ -13,7 +13,7 @@ import tempfile import unittest import unittest.mock -from collections.abc import Iterator +from collections.abc import Generator from contextlib import contextmanager, redirect_stdout, suppress from pathlib import Path @@ -36,7 +36,7 @@ def _tools(root: Path) -> Path: @contextmanager -def _project() -> Iterator[tuple[Path, Path]]: +def _project() -> Generator[tuple[Path, Path]]: """Enter a temporary project directory, with a tools tree beside it.""" with tempfile.TemporaryDirectory(prefix="sandbox-test.", dir="/var/tmp") as scratch: root = Path(scratch) diff --git a/image/finalize.py b/image/finalize.py index ec43f06b..b5c720db 100644 --- a/image/finalize.py +++ b/image/finalize.py @@ -14,7 +14,7 @@ import shutil import subprocess import sys -from collections.abc import Iterator, Sequence +from collections.abc import Generator, Sequence from pathlib import Path from typing import TypedDict @@ -55,7 +55,7 @@ def image( program: str, lowers: Sequence[str | Path] | None = None, binds: Sequence[tuple[str | Path, str | Path]] | None = None, -) -> Iterator[Path]: +) -> Generator[Path]: """Mount the image stack the spec names and yield the finalized tree.""" with rootfs.rootfs( "/buildroot", lowers=spec["lower"] if lowers is None else lowers, binds=binds diff --git a/image/kmod.py b/image/kmod.py index 58cd9676..5b94e47d 100644 --- a/image/kmod.py +++ b/image/kmod.py @@ -14,7 +14,7 @@ import fnmatch import json import os -from collections.abc import Iterable, Iterator, Mapping, Sequence +from collections.abc import Generator, Iterable, Iterator, Mapping, Sequence from contextlib import contextmanager from pathlib import Path, PurePosixPath from typing import NamedTuple, Self, cast @@ -261,7 +261,7 @@ def _each(self, head: Handle | None) -> Iterator[Handle]: entry = cast(Handle | None, self._lib.kmod_list_next(head, entry)) @contextmanager - def _lookup(self, name: str) -> Iterator[Handle | None]: + def _lookup(self, name: str) -> Generator[Handle | None]: mod = ctypes.c_void_p() if self._lib.kmod_module_new_from_name(self._ctx, name.encode(), ctypes.byref(mod)) < 0: yield None diff --git a/package/installer.py b/package/installer.py index 080b1341..e1d539e1 100644 --- a/package/installer.py +++ b/package/installer.py @@ -12,7 +12,7 @@ import os import shutil -from collections.abc import Callable, Iterator +from collections.abc import Callable, Generator from contextlib import contextmanager from pathlib import Path from typing import TypedDict @@ -72,7 +72,7 @@ class InstallSpec(TypedDict): @contextmanager -def fresh_machine_id(installroot: Path) -> Iterator[None]: +def fresh_machine_id(installroot: Path) -> Generator[None]: """Leave a fresh root the marker systemd initializes on first boot, and an existing one be.""" etc = installroot / "etc" etc.mkdir(parents=True, exist_ok=True) diff --git a/package_system/deb/deb822.py b/package_system/deb/deb822.py index 4e1ecc3c..51e4b898 100644 --- a/package_system/deb/deb822.py +++ b/package_system/deb/deb822.py @@ -4,7 +4,7 @@ """Small, strict readers for Debian's Deb822 metadata.""" import io -from collections.abc import Iterable, Iterator +from collections.abc import Generator, Iterable, Iterator from contextlib import contextmanager from pathlib import Path from typing import BinaryIO, NamedTuple, cast @@ -119,7 +119,7 @@ class Package(NamedTuple): @contextmanager -def open_text(path: Path) -> Iterator[Iterable[str]]: +def open_text(path: Path) -> Generator[Iterable[str]]: """Open a Deb822 file as text, and detect its compression from its first bytes. A byte that is not UTF-8 raises while the caller iterates inside the `with` block. This function diff --git a/package_system/deb/install.py b/package_system/deb/install.py index 4c0a5249..46263e58 100755 --- a/package_system/deb/install.py +++ b/package_system/deb/install.py @@ -20,7 +20,7 @@ import stat import sys import tempfile -from collections.abc import Iterator +from collections.abc import Generator from contextlib import contextmanager from pathlib import Path @@ -103,7 +103,7 @@ def _replace(path: Path, content: bytes, mode: int) -> None: @contextmanager -def denied_daemons(installroot: Path) -> Iterator[None]: +def denied_daemons(installroot: Path) -> Generator[None]: """Install a `policy-rc.d` that denies daemon starts, and remove it after the transaction. No package ships this file. It belongs to the administrator, so the image must not keep the diff --git a/package_system/pacman/alpm.py b/package_system/pacman/alpm.py index 06c85281..6aa83cf3 100644 --- a/package_system/pacman/alpm.py +++ b/package_system/pacman/alpm.py @@ -17,7 +17,7 @@ import gzip import io import tarfile -from collections.abc import Iterator +from collections.abc import Generator, Iterator from contextlib import ExitStack, contextmanager from pathlib import Path from typing import IO, NamedTuple, NoReturn, Self, cast @@ -406,7 +406,7 @@ def _explained(self, error: int, data: ctypes._Pointer[_List]) -> list[str]: return [] @contextmanager - def _transaction(self) -> Iterator[None]: + def _transaction(self) -> Generator[None]: if self._lib.alpm_trans_init(self._handle, _NO_FLAGS) != 0: self._fail("cannot start a transaction") try: diff --git a/package_system/rpm/build_test.py b/package_system/rpm/build_test.py index 1a8f186b..b46a8ec2 100644 --- a/package_system/rpm/build_test.py +++ b/package_system/rpm/build_test.py @@ -9,7 +9,7 @@ import subprocess import tempfile import unittest -from collections.abc import Callable, Iterator +from collections.abc import Callable, Generator from pathlib import Path from typing import cast, override from unittest import mock @@ -350,7 +350,7 @@ def run( return subprocess.CompletedProcess([], result) @contextlib.contextmanager - def mounted(trees: list[Path] = directories) -> Iterator[None]: + def mounted(trees: list[Path] = directories) -> Generator[None]: try: yield finally: diff --git a/remote_cache/status.py b/remote_cache/status.py index 91a85c7e..3aa28302 100644 --- a/remote_cache/status.py +++ b/remote_cache/status.py @@ -28,7 +28,7 @@ import struct import tempfile import threading -from collections.abc import Callable, Iterator +from collections.abc import Callable, Generator from contextlib import contextmanager from pathlib import Path from typing import Any, override @@ -114,7 +114,7 @@ def __init__(self, path: str, report: Report, attend: Attend) -> None: @contextmanager -def serving(root: Path, report: Report, attend: Attend = lambda pid: None) -> Iterator[Path]: +def serving(root: Path, report: Report, attend: Attend = lambda pid: None) -> Generator[Path]: """A socket answering for `root` for as long as the block runs. A socket file left behind by a killed shim would refuse the next bind, so it is removed first. diff --git a/rootfs/rootfs.py b/rootfs/rootfs.py index 086ed333..85d07892 100644 --- a/rootfs/rootfs.py +++ b/rootfs/rootfs.py @@ -15,7 +15,7 @@ import shutil import stat import tempfile -from collections.abc import Iterator, Mapping, Sequence +from collections.abc import Generator, Mapping, Sequence from contextlib import ExitStack, contextmanager from enum import Enum, auto from pathlib import Path @@ -170,7 +170,7 @@ def capture(tree: Path) -> None: @contextmanager -def capture_on_exit(tree: str | Path) -> Iterator[Path]: +def capture_on_exit(tree: str | Path) -> Generator[Path]: """Capture a tree after use without hiding an error from the body.""" tree = Path(tree) try: @@ -191,7 +191,7 @@ def capture_on_exit(tree: str | Path) -> Iterator[Path]: @contextmanager -def source_overlay(source: Path, target: Path) -> Iterator[Path]: +def source_overlay(source: Path, target: Path) -> Generator[Path]: """Mount `source` at `target` as an overlay, and discard every write when the context exits. `rootfs()` decodes the `.wh.` and `.esc.` marker files of a stored image layer. This function @@ -214,7 +214,7 @@ def source_overlay(source: Path, target: Path) -> Iterator[Path]: @contextmanager -def readonly_project(project: Path, outputs: Mapping[Path, Path]) -> Iterator[None]: +def readonly_project(project: Path, outputs: Mapping[Path, Path]) -> Generator[None]: """Make `project` read-only and change into it, but keep each output directory writable. `outputs` maps an output directory under `project` to a mount point outside `project`. @@ -280,7 +280,7 @@ def _apivfs(stack: ExitStack, target: Path) -> None: @contextmanager -def chroot(target: str | Path) -> Iterator[Path]: +def chroot(target: str | Path) -> Generator[Path]: """Temporarily enter an already-mounted target root.""" with _chroot(Path(target)): yield Path("/") @@ -298,7 +298,7 @@ def rootfs( apivfs: bool = False, binds: Sequence[tuple[str | Path, str | Path]] | None = None, chroot: bool = False, -) -> Iterator[Path]: +) -> Generator[Path]: """Mount a bind or overlay root, optionally chrooting and persisting its output. Persistent overlay uppers and bind sources with `capture_bind=True` are captured after all diff --git a/tools/dev_test.py b/tools/dev_test.py index 6f947dcb..d6273473 100644 --- a/tools/dev_test.py +++ b/tools/dev_test.py @@ -13,7 +13,7 @@ import time import tomllib import unittest -from collections.abc import Iterator +from collections.abc import Generator from pathlib import Path from typing import Any, cast, override from unittest import mock @@ -321,7 +321,7 @@ def test_refuses_to_overwrite_custom_zed_settings(self) -> None: self.assertEqual(settings.read_text(), '{"tab_size": 4}\n') @contextlib.contextmanager - def server(self, package: str) -> Iterator[LanguageServer]: + def server(self, package: str) -> Generator[LanguageServer]: command, env = self.launch(package) # Exercise Zed's configurationFile expansion without the CLI configuration as a fallback. env.pop("TY_CONFIG_FILE") diff --git a/util.py b/util.py index 5f46d434..2e56c7c5 100644 --- a/util.py +++ b/util.py @@ -18,7 +18,7 @@ import time import urllib.error import urllib.request -from collections.abc import Callable, Iterator +from collections.abc import Callable, Generator from contextlib import contextmanager from pathlib import Path from typing import IO, NoReturn, TextIO, cast @@ -244,7 +244,7 @@ def remove_path(path: Path, with_parents: bool = False) -> None: @contextmanager -def atomic_text_writer(path: Path) -> Iterator[TextIO]: +def atomic_text_writer(path: Path) -> Generator[TextIO]: """Yield a UTF-8 stream and atomically replace its destination on success.""" path.parent.mkdir(parents=True, exist_ok=True) mode = stat.S_IMODE(path.stat().st_mode) if path.exists() else 0o644 @@ -261,7 +261,7 @@ def atomic_text_writer(path: Path) -> Iterator[TextIO]: @contextmanager -def text_destination(path: Path) -> Iterator[TextIO]: +def text_destination(path: Path) -> Generator[TextIO]: """Yield a UTF-8 stream for a path, or for stdout when the caller named `-`. A driver published as a run target is asked for its output by a caller that has nowhere to put