From: Daan De Meyer Date: Wed, 11 Feb 2026 19:39:33 +0000 (+0100) Subject: Bump minimum python version to 3.10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=22b2f0bf18ac98f62ef92745fde5dd3f8369d4bf;p=thirdparty%2Fmkosi.git Bump minimum python version to 3.10 - CentOS Stream 9 has python 3.9 by default, but python 3.12 is packaged - Ubuntu Jammy has python 3.10. So we'll require CentOS Stream 9 users to install the python3.12 package to keep using mkosi, which shouldn't be a problem. Bumping version allows us to switch to the Union operator among other improvements. This commit gets rid of Union and Optional, we'll adopt other 3.10 features later. --- diff --git a/bin/mkosi b/bin/mkosi index 9abee8f13..930b171f3 100755 --- a/bin/mkosi +++ b/bin/mkosi @@ -8,7 +8,7 @@ command="$(basename "${BASH_SOURCE[0]//-/.}")" if [ -z "$MKOSI_INTERPRETER" ]; then # Note the check seems to be inverted here because the if branch is # executed when the exit status is 0 which is equal to False in Python. - if python3 -c 'import sys; sys.exit(sys.version_info < (3, 9))'; then + if python3 -c 'import sys; sys.exit(sys.version_info < (3, 10))'; then MKOSI_INTERPRETER=python3 else # python3 is not found or too old, search $PATH for the newest interpreter. @@ -21,7 +21,7 @@ if [ -z "$MKOSI_INTERPRETER" ]; then done | sort --unique --version-sort --reverse | head --lines=1 )" - if [ -n "$candidate" ] && "$candidate" -c 'import sys; sys.exit(sys.version_info < (3, 9))'; then + if [ -n "$candidate" ] && "$candidate" -c 'import sys; sys.exit(sys.version_info < (3, 10))'; then MKOSI_INTERPRETER="$candidate" fi fi diff --git a/docs/CODING_STYLE.md b/docs/CODING_STYLE.md index ea7b7f917..2920437cd 100644 --- a/docs/CODING_STYLE.md +++ b/docs/CODING_STYLE.md @@ -9,7 +9,7 @@ SPDX-License-Identifier: LGPL-2.1-or-later ## Python Version -- The lowest supported Python version is CPython 3.9. +- The lowest supported Python version is CPython 3.10. ## Formatting diff --git a/kernel-install/50-mkosi.install b/kernel-install/50-mkosi.install index bff9e4c76..d51451a2e 100755 --- a/kernel-install/50-mkosi.install +++ b/kernel-install/50-mkosi.install @@ -6,7 +6,6 @@ import os import sys import tempfile from pathlib import Path -from typing import Optional from mkosi import identify_cpu from mkosi.archive import make_cpio @@ -22,7 +21,7 @@ def we_are_wanted(context: KernelInstallContext) -> bool: return context.uki_generator == "mkosi" or context.initrd_generator in ("mkosi", "mkosi-initrd") -def build_microcode_initrd(output: Path) -> Optional[Path]: +def build_microcode_initrd(output: Path) -> Path | None: vendor, ucode = identify_cpu(Path("/")) if vendor is None: diff --git a/mkosi/__init__.py b/mkosi/__init__.py index 2f54bfacc..ca24e703b 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -25,7 +25,7 @@ import zipapp from collections.abc import Iterator, Mapping, Sequence from contextlib import AbstractContextManager from pathlib import Path -from typing import Any, Optional, Union, cast +from typing import Any, cast from mkosi.archive import can_extract_tar, extract_tar, make_cpio, make_tar from mkosi.bootloader import ( @@ -1186,7 +1186,7 @@ def install_tree( src: Path, dst: Path, *, - target: Optional[Path] = None, + target: Path | None = None, preserve: bool = True, ) -> None: src = src.resolve() @@ -1366,7 +1366,7 @@ def gzip_binary(context: Context) -> str: return "pigz" if context.config.find_binary("pigz") else "gzip" -def kernel_get_ver_from_modules(context: Context) -> Optional[str]: +def kernel_get_ver_from_modules(context: Context) -> str | None: # Try to get version from the first dir under usr/lib/modules but fail if multiple versions are found versions = [ p.name for p in (context.root / "usr/lib/modules").glob("*") if KERNEL_VERSION_PATTERN.match(p.name) @@ -1403,7 +1403,7 @@ def fixup_vmlinuz_location(context: Context) -> None: filename = d.name.removeprefix(f"{type}-") match = KERNEL_VERSION_PATTERN.search(filename) - kver: Optional[str] + kver: str | None if match: kver = match.group(0) else: @@ -1439,7 +1439,7 @@ def want_initrd(context: Context) -> bool: return True -def identify_cpu(root: Path) -> tuple[Optional[Path], Optional[Path]]: +def identify_cpu(root: Path) -> tuple[Path | None, Path | None]: for entry in Path("/proc/cpuinfo").read_text().split("\n\n"): vendor_id = family = model = stepping = None for line in entry.splitlines(): @@ -1885,7 +1885,7 @@ def systemd_stub_binary(context: Context) -> Path: return stub -def systemd_stub_version(context: Context, stub: Path) -> Optional[GenericVersion]: +def systemd_stub_version(context: Context, stub: Path) -> GenericVersion | None: try: sdmagic = extract_pe_section(context, stub, ".sdmagic", context.workspace / "sdmagic") except KeyError: @@ -1950,9 +1950,7 @@ def find_entry_token(context: Context) -> str: return cast(str, output["EntryToken"]) -def finalize_cmdline( - context: Context, partitions: Sequence[Partition], roothash: Optional[str] -) -> list[str]: +def finalize_cmdline(context: Context, partitions: Sequence[Partition], roothash: str | None) -> list[str]: if (context.root / "etc/kernel/cmdline").exists(): cmdline = [(context.root / "etc/kernel/cmdline").read_text().strip()] elif (context.root / "usr/lib/kernel/cmdline").exists(): @@ -2384,7 +2382,7 @@ def maybe_compress( context: Context, compression: Compression, src: Path, - dst: Optional[Path] = None, + dst: Path | None = None, ) -> None: if not compression or src.is_dir(): if dst: @@ -2418,7 +2416,7 @@ def copy_nspawn_settings(context: Context) -> None: copyfile2(context.config.nspawn_settings, context.staging / context.config.output_nspawn_settings) -def get_uki_path(context: Context) -> Optional[Path]: +def get_uki_path(context: Context) -> Path | None: if not want_efi(context.config) or context.config.unified_kernel_images == UnifiedKernelImage.none: return None @@ -2607,7 +2605,7 @@ def calculate_signature_sop(context: Context) -> None: ) # fmt: skip -def dir_size(path: Union[Path, os.DirEntry[str]]) -> int: +def dir_size(path: Path | os.DirEntry[str]) -> int: dir_sum = 0 for entry in os.scandir(path): if entry.is_symlink(): @@ -2622,7 +2620,7 @@ def dir_size(path: Union[Path, os.DirEntry[str]]) -> int: return dir_sum -def save_manifest(context: Context, manifest: Optional[Manifest]) -> None: +def save_manifest(context: Context, manifest: Manifest | None) -> None: if not manifest: return @@ -2801,7 +2799,7 @@ def check_inputs(config: Config) -> None: ) -def check_tool(config: Config, *tools: PathString, reason: str, hint: Optional[str] = None) -> Path: +def check_tool(config: Config, *tools: PathString, reason: str, hint: str | None = None) -> Path: tool = config.find_binary(*tools) if not tool: die(f"Could not find '{tools[0]}' which is required to {reason}.", hint=hint) @@ -2814,7 +2812,7 @@ def check_systemd_tool( *tools: PathString, version: str, reason: str, - hint: Optional[str] = None, + hint: str | None = None, ) -> None: tool = check_tool(config, *tools, reason=reason, hint=hint) @@ -2830,7 +2828,7 @@ def check_ukify( config: Config, version: str, reason: str, - hint: Optional[str] = None, + hint: str | None = None, ) -> None: ukify = check_tool(config, "ukify", "/usr/lib/systemd/ukify", reason=reason, hint=hint) @@ -3385,7 +3383,7 @@ def reuse_cache(context: Context) -> bool: def save_esp_components( context: Context, -) -> tuple[Optional[Path], Optional[str], Optional[Path], list[Path]]: +) -> tuple[Path | None, str | None, Path | None, list[Path]]: if context.config.output_format == OutputFormat.addon: stub = systemd_addon_stub_binary(context) if not stub.exists(): @@ -3481,7 +3479,7 @@ def make_image( cmdline += ["--definitions", workdir(d)] opts += ["--ro-bind", d, workdir(d)] - def can_orphan_file(distribution: Union[Distribution, str, None], release: Optional[str]) -> bool: + def can_orphan_file(distribution: Distribution | str | None, release: str | None) -> bool: if not isinstance(distribution, Distribution): return True @@ -3755,9 +3753,9 @@ def make_oci(context: Context, root_layer: Path, dst: Path) -> None: def make_esp( context: Context, - stub: Optional[Path], - kver: Optional[str], - kimg: Optional[Path], + stub: Path | None, + kver: str | None, + kimg: Path | None, microcode: list[Path], ) -> list[Partition]: if not context.config.architecture.to_efi(): @@ -3944,7 +3942,7 @@ def clamp_mtime(path: Path, mtime: int) -> None: os.utime(path, ns=updated, follow_symlinks=False) -def normalize_mtime(root: Path, mtime: Optional[int], directory: Path = Path("")) -> None: +def normalize_mtime(root: Path, mtime: int | None, directory: Path = Path("")) -> None: if mtime is None: return @@ -4489,7 +4487,7 @@ def run_coredumpctl(args: Args, config: Config) -> None: run_systemd_tool("coredumpctl", args, config) -def start_storage_target_mode(config: Config) -> AbstractContextManager[Optional[Popen]]: +def start_storage_target_mode(config: Config) -> AbstractContextManager[Popen | None]: if config.storage_target_mode == ConfigFeature.disabled: return contextlib.nullcontext() @@ -4926,7 +4924,7 @@ def run_build( resources: Path, keyring_dir: Path, metadata_dir: Path, - package_dir: Optional[Path] = None, + package_dir: Path | None = None, ) -> None: if not have_effective_cap(CAP_SYS_ADMIN): acquire_privileges() @@ -4994,7 +4992,7 @@ def ensure_tools_tree_has_etc_resolv_conf(config: Config) -> None: ) -def run_verb(args: Args, tools: Optional[Config], images: Sequence[Config], *, resources: Path) -> None: +def run_verb(args: Args, tools: Config | None, images: Sequence[Config], *, resources: Path) -> None: images = list(images) if args.verb == Verb.init: diff --git a/mkosi/__main__.py b/mkosi/__main__.py index 57c082b69..fca0ce7d5 100644 --- a/mkosi/__main__.py +++ b/mkosi/__main__.py @@ -5,7 +5,6 @@ import faulthandler import signal import sys from types import FrameType -from typing import Optional import mkosi.resources from mkosi import run_verb @@ -17,7 +16,7 @@ from mkosi.util import resource_path INTERRUPTED = False -def onsignal(signal: int, frame: Optional[FrameType]) -> None: +def onsignal(signal: int, frame: FrameType | None) -> None: global INTERRUPTED if INTERRUPTED: return diff --git a/mkosi/archive.py b/mkosi/archive.py index f3fbea610..89a38bcf5 100644 --- a/mkosi/archive.py +++ b/mkosi/archive.py @@ -3,7 +3,6 @@ import os from collections.abc import Iterable, Sequence from pathlib import Path -from typing import Optional from mkosi.log import complete_step, log_step from mkosi.run import SandboxProtocol, finalize_passwd_symlinks, nosandbox, run, workdir @@ -109,7 +108,7 @@ def make_cpio( src: Path, dst: Path, *, - files: Optional[Iterable[Path]] = None, + files: Iterable[Path] | None = None, sandbox: SandboxProtocol = nosandbox, ) -> None: if not files: diff --git a/mkosi/bootloader.py b/mkosi/bootloader.py index 6e22f5f00..e8fafbf8a 100644 --- a/mkosi/bootloader.py +++ b/mkosi/bootloader.py @@ -10,7 +10,6 @@ import tempfile import textwrap from collections.abc import Iterator, Mapping, Sequence from pathlib import Path -from typing import Optional from mkosi.config import ( BiosBootloader, @@ -168,7 +167,7 @@ def want_grub_bios(context: Context, partitions: Sequence[Partition] = ()) -> bo return (have and bios and esp and root and installed) if partitions else have -def find_grub_directory(context: Context, *, target: str) -> Optional[Path]: +def find_grub_directory(context: Context, *, target: str) -> Path | None: for d in ("usr/lib/grub", "usr/share/grub2"): if (p := context.root / d / target).exists() and any(p.iterdir()): return p @@ -176,7 +175,7 @@ def find_grub_directory(context: Context, *, target: str) -> Optional[Path]: return None -def find_grub_binary(config: Config, binary: str) -> Optional[Path]: +def find_grub_binary(config: Config, binary: str) -> Path | None: assert "grub" not in binary # Debian has a bespoke setup where if only grub-pc-bin is installed, grub-bios-setup is installed in @@ -185,7 +184,7 @@ def find_grub_binary(config: Config, binary: str) -> Optional[Path]: return config.find_binary(f"grub-{binary}", f"grub2-{binary}", f"/usr/lib/grub/i386-pc/grub-{binary}") -def prepare_grub_config(context: Context) -> Optional[Path]: +def prepare_grub_config(context: Context) -> Path | None: config = context.root / "efi" / context.config.distribution.installer.grub_prefix() / "grub.cfg" with umask(~0o700): config.parent.mkdir(exist_ok=True) @@ -223,8 +222,8 @@ def grub_mkimage( *, target: str, modules: Sequence[str] = (), - output: Optional[Path] = None, - sbat: Optional[Path] = None, + output: Path | None = None, + sbat: Path | None = None, ) -> None: mkimage = find_grub_binary(context.config, "mkimage") assert mkimage @@ -293,7 +292,7 @@ def grub_mkimage( ) # fmt: skip -def find_signed_grub_image(context: Context) -> Optional[Path]: +def find_signed_grub_image(context: Context) -> Path | None: arch = context.config.architecture.to_efi() patterns = [ @@ -476,9 +475,9 @@ def run_systemd_sign_tool( *, cmdline: Sequence[PathString], options: Sequence[PathString], - certificate: Optional[Path], + certificate: Path | None, certificate_source: CertificateSource, - key: Optional[Path], + key: Path | None, key_source: KeySource, env: Mapping[str, str] = {}, stdout: _FILE = None, diff --git a/mkosi/completion.py b/mkosi/completion.py index 398edb2ca..a72421cd5 100644 --- a/mkosi/completion.py +++ b/mkosi/completion.py @@ -8,7 +8,6 @@ import shlex from collections.abc import Iterable, Mapping from pathlib import Path from textwrap import indent -from typing import Optional, Union from mkosi import config from mkosi.log import die @@ -27,7 +26,7 @@ class CompGen(StrEnum): return CompGen.dirs else: return CompGen.files - # TODO: the type of action.type is Union[Callable[[str], Any], FileType] + # TODO: the type of action.type is Callable[[str], Any] | FileType # the type of Path is type, but Path also works in this position, # because the constructor is a callable from str -> Path elif action.type is not None and (isinstance(action.type, type) and issubclass(action.type, Path)): @@ -60,9 +59,9 @@ class CompGen(StrEnum): @dataclasses.dataclass(frozen=True) class CompletionItem: - short: Optional[str] - long: Optional[str] - help: Optional[str] + short: str | None + long: str | None + help: str | None choices: list[str] compgen: CompGen @@ -104,7 +103,7 @@ def finalize_completion_bash(options: list[CompletionItem], resources: Path) -> def to_bash_array(name: str, entries: Iterable[str]) -> str: return f"{name.replace('-', '_')}=(" + " ".join(shlex.quote(str(e)) for e in entries) + ")" - def to_bash_hasharray(name: str, entries: Mapping[str, Union[str, int]]) -> str: + def to_bash_hasharray(name: str, entries: Mapping[str, str | int]) -> str: return ( f"{name.replace('-', '_')}=(" + " ".join(f"[{shlex.quote(str(k))}]={shlex.quote(str(v))}" for k, v in entries.items()) diff --git a/mkosi/config.py b/mkosi/config.py index fd9e9842e..f8cef3c87 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -29,7 +29,7 @@ import uuid from collections.abc import Collection, Iterable, Iterator, Sequence from contextlib import AbstractContextManager from pathlib import Path -from typing import Any, Callable, ClassVar, Generic, Optional, Protocol, TypeVar, Union, cast +from typing import Any, Callable, ClassVar, Generic, Protocol, TypeVar, cast from mkosi.distribution import Distribution, detect_distribution from mkosi.log import ARG_DEBUG, ARG_DEBUG_SANDBOX, ARG_DEBUG_SHELL, complete_step, die @@ -60,7 +60,7 @@ T = TypeVar("T") D = TypeVar("D", bound=DataclassInstance) SE = TypeVar("SE", bound=StrEnum) -ConfigParseCallback = Callable[[Optional[str], Optional[T]], Optional[T]] +ConfigParseCallback = Callable[[str | None, T | None], T | None] ConfigMatchCallback = Callable[[str, T], bool] ConfigDefaultCallback = Callable[[dict[str, Any]], T] @@ -161,7 +161,7 @@ class ConfigFeature(StrEnum): @dataclasses.dataclass(frozen=True) class ConfigTree: source: Path - target: Optional[Path] + target: Path | None def with_prefix(self, prefix: PathString = "/") -> tuple[Path, Path]: return ( @@ -181,8 +181,8 @@ class DriveFlag(StrEnum): class Drive: id: str size: int - directory: Optional[Path] - options: Optional[str] + directory: Path | None + options: str | None file_id: str flags: list[DriveFlag] @@ -496,7 +496,7 @@ class Architecture(StrEnum): return a - def to_efi(self) -> Optional[str]: + def to_efi(self) -> str | None: return { Architecture.x86: "ia32", Architecture.x86_64: "x64", @@ -507,7 +507,7 @@ class Architecture(StrEnum): Architecture.loongarch64: "loongarch64", }.get(self) # fmt: skip - def to_grub(self) -> Optional[str]: + def to_grub(self) -> str | None: return { Architecture.x86_64: "x86_64", Architecture.x86: "i386", @@ -685,7 +685,7 @@ def expand_delayed_specifiers(specifiers: dict[str, str], text: str) -> str: return re.sub(r"&(?P[&a-zA-Z])", replacer, text) -def try_parse_boolean(s: str) -> Optional[bool]: +def try_parse_boolean(s: str) -> bool | None: "Parse 1/true/yes/y/t/on as true and 0/false/no/n/f/off/None as false" s_l = s.lower() @@ -798,14 +798,14 @@ def parse_paths_from_directory( return sorted(parse_path(os.fspath(p), resolve=resolve, secret=secret) for p in path.glob(glob)) -def config_parse_key(value: Optional[str], old: Optional[str]) -> Optional[Path]: +def config_parse_key(value: str | None, old: str | None) -> Path | None: if not value: return None return parse_path(value, secret=True) if Path(value).exists() else Path(value) -def config_parse_certificate(value: Optional[str], old: Optional[str]) -> Optional[Path]: +def config_parse_certificate(value: str | None, old: str | None) -> Path | None: if not value: return None @@ -854,7 +854,7 @@ def config_make_list_matcher(parse: Callable[[str], T]) -> ConfigMatchCallback[l return config_match_list -def config_parse_string(value: Optional[str], old: Optional[str]) -> Optional[str]: +def config_parse_string(value: str | None, old: str | None) -> str | None: return value or None @@ -876,7 +876,7 @@ def config_match_key_value(match: str, value: dict[str, str]) -> bool: return value.get(k, None) == v -def config_parse_boolean(value: Optional[str], old: Optional[bool]) -> Optional[bool]: +def config_parse_boolean(value: str | None, old: bool | None) -> bool | None: if value is None: return False @@ -893,7 +893,7 @@ def parse_feature(value: str) -> ConfigFeature: return ConfigFeature.enabled if parse_boolean(value) else ConfigFeature.disabled -def config_parse_feature(value: Optional[str], old: Optional[ConfigFeature]) -> Optional[ConfigFeature]: +def config_parse_feature(value: str | None, old: ConfigFeature | None) -> ConfigFeature | None: if value is None: return ConfigFeature.auto @@ -907,7 +907,7 @@ def config_match_feature(match: str, value: ConfigFeature) -> bool: return value == parse_feature(match) -def config_parse_compression(value: Optional[str], old: Optional[Compression]) -> Optional[Compression]: +def config_parse_compression(value: str | None, old: Compression | None) -> Compression | None: if not value: return None @@ -917,7 +917,7 @@ def config_parse_compression(value: Optional[str], old: Optional[Compression]) - return Compression.zstd if parse_boolean(value) else Compression.none -def config_parse_uuid(value: Optional[str], old: Optional[str]) -> Optional[uuid.UUID]: +def config_parse_uuid(value: str | None, old: str | None) -> uuid.UUID | None: if not value: return None @@ -930,7 +930,7 @@ def config_parse_uuid(value: Optional[str], old: Optional[str]) -> Optional[uuid die(f"{value} is not a valid UUID") -def config_parse_source_date_epoch(value: Optional[str], old: Optional[int]) -> Optional[int]: +def config_parse_source_date_epoch(value: str | None, old: int | None) -> int | None: if not value: return None @@ -945,7 +945,7 @@ def config_parse_source_date_epoch(value: Optional[str], old: Optional[int]) -> return timestamp -def config_parse_compress_level(value: Optional[str], old: Optional[int]) -> Optional[int]: +def config_parse_compress_level(value: str | None, old: int | None) -> int | None: if not value: return None @@ -960,7 +960,7 @@ def config_parse_compress_level(value: Optional[str], old: Optional[int]) -> Opt return level -def config_parse_mode(value: Optional[str], old: Optional[int]) -> Optional[int]: +def config_parse_mode(value: str | None, old: int | None) -> int | None: if not value: return None @@ -1022,8 +1022,8 @@ def config_default_distribution(namespace: dict[str, Any]) -> Distribution: def config_default_release(namespace: dict[str, Any]) -> str: - hd: Union[Distribution, str, None] - hr: Optional[str] + hd: Distribution | str | None + hr: str | None if ( (d := os.getenv("MKOSI_HOST_DISTRIBUTION")) @@ -1069,7 +1069,7 @@ def config_default_repository_key_fetch(namespace: dict[str, Any]) -> bool: ) -def config_default_source_date_epoch(namespace: dict[str, Any]) -> Optional[int]: +def config_default_source_date_epoch(namespace: dict[str, Any]) -> int | None: for env in namespace["environment"]: if s := startswith(env, "SOURCE_DATE_EPOCH="): break @@ -1078,7 +1078,7 @@ def config_default_source_date_epoch(namespace: dict[str, Any]) -> Optional[int] return config_parse_source_date_epoch(s, None) -def config_default_proxy_url(namespace: dict[str, Any]) -> Optional[str]: +def config_default_proxy_url(namespace: dict[str, Any]) -> str | None: names = ("http_proxy", "https_proxy", "HTTP_PROXY", "HTTPS_PROXY") for env in namespace["environment"]: @@ -1093,7 +1093,7 @@ def config_default_proxy_url(namespace: dict[str, Any]) -> Optional[str]: return None -def config_default_proxy_exclude(namespace: dict[str, Any]) -> Optional[list[str]]: +def config_default_proxy_exclude(namespace: dict[str, Any]) -> list[str] | None: names = ("no_proxy", "NO_PROXY") for env in namespace["environment"]: @@ -1108,7 +1108,7 @@ def config_default_proxy_exclude(namespace: dict[str, Any]) -> Optional[list[str return None -def config_default_proxy_peer_certificate(namespace: dict[str, Any]) -> Optional[Path]: +def config_default_proxy_peer_certificate(namespace: dict[str, Any]) -> Path | None: for p in (Path("/etc/pki/tls/certs/ca-bundle.crt"), Path("/etc/ssl/certs/ca-certificates.crt")): if p.exists(): return p @@ -1152,14 +1152,14 @@ def make_enum_parser(type: type[SE]) -> Callable[[str], SE]: def config_make_enum_parser(type: type[SE]) -> ConfigParseCallback[SE]: - def config_parse_enum(value: Optional[str], old: Optional[SE]) -> Optional[SE]: + def config_parse_enum(value: str | None, old: SE | None) -> SE | None: return make_enum_parser(type)(value) if value else None return config_parse_enum def config_make_enum_parser_with_boolean(type: type[SE], *, yes: SE, no: SE) -> ConfigParseCallback[SE]: - def config_parse_enum(value: Optional[str], old: Optional[SE]) -> Optional[SE]: + def config_parse_enum(value: str | None, old: SE | None) -> SE | None: if not value: return None @@ -1197,13 +1197,13 @@ def package_sort_key(package: str) -> tuple[int, str]: def config_make_list_parser( *, - delimiter: Optional[str] = None, + delimiter: str | None = None, parse: Callable[[str], T] = str, # type: ignore # see mypy#3737 unescape: bool = False, reset: bool = True, - key: Optional[Callable[[T], Any]] = None, + key: Callable[[T], Any] | None = None, ) -> ConfigParseCallback[list[T]]: - def config_parse_list(value: Optional[str], old: Optional[list[T]]) -> Optional[list[T]]: + def config_parse_list(value: str | None, old: list[T] | None) -> list[T] | None: new = old.copy() if old else [] if value is None: @@ -1265,16 +1265,16 @@ def config_match_version(match: str, value: str) -> bool: def config_make_dict_parser( *, - delimiter: Optional[str] = None, + delimiter: str | None = None, parse: Callable[[str], tuple[str, PathString]], unescape: bool = False, allow_paths: bool = False, reset: bool = True, ) -> ConfigParseCallback[dict[str, PathString]]: def config_parse_dict( - value: Optional[str], - old: Optional[dict[str, PathString]], - ) -> Optional[dict[str, PathString]]: + value: str | None, + old: dict[str, PathString] | None, + ) -> dict[str, PathString] | None: new = old.copy() if old else {} if value is None: @@ -1357,7 +1357,7 @@ def config_make_path_parser( absolute: bool = False, constants: Sequence[str] = (), ) -> ConfigParseCallback[Path]: - def config_parse_path(value: Optional[str], old: Optional[Path]) -> Optional[Path]: + def config_parse_path(value: str | None, old: Path | None) -> Path | None: if not value: return None @@ -1381,7 +1381,7 @@ def is_valid_filename(s: str) -> bool: def config_make_filename_parser(hint: str) -> ConfigParseCallback[str]: - def config_parse_filename(value: Optional[str], old: Optional[str]) -> Optional[str]: + def config_parse_filename(value: str | None, old: str | None) -> str | None: if not value: return None @@ -1404,8 +1404,9 @@ def match_path_exists(image: str, value: str) -> bool: def config_parse_root_password( - value: Optional[str], old: Optional[tuple[str, bool]] -) -> Optional[tuple[str, bool]]: + value: str | None, + old: tuple[str, bool] | None, +) -> tuple[str, bool] | None: if not value: return None @@ -1456,14 +1457,14 @@ def parse_bytes(value: str) -> int: return result -def config_parse_bytes(value: Optional[str], old: Optional[int] = None) -> Optional[int]: +def config_parse_bytes(value: str | None, old: int | None = None) -> int | None: if not value: return None return parse_bytes(value) -def config_parse_number(value: Optional[str], old: Optional[int] = None) -> Optional[int]: +def config_parse_number(value: str | None, old: int | None = None) -> int | None: if not value: return None @@ -1512,7 +1513,7 @@ def parse_drive(value: str) -> Drive: ) -def config_parse_sector_size(value: Optional[str], old: Optional[int]) -> Optional[int]: +def config_parse_sector_size(value: str | None, old: int | None) -> int | None: if not value: return None @@ -1530,7 +1531,7 @@ def config_parse_sector_size(value: Optional[str], old: Optional[int]) -> Option return size -def config_parse_vsock_cid(value: Optional[str], old: Optional[int]) -> Optional[int]: +def config_parse_vsock_cid(value: str | None, old: int | None) -> int | None: if not value: return None @@ -1551,7 +1552,7 @@ def config_parse_vsock_cid(value: Optional[str], old: Optional[int]) -> Optional return cid -def config_parse_minimum_version(value: Optional[str], old: Optional[str]) -> Optional[str]: +def config_parse_minimum_version(value: str | None, old: str | None) -> str | None: if not value: return old @@ -1628,7 +1629,7 @@ class KeySource: return f"{self.type}:{self.source}" if self.source else str(self.type) -def config_parse_key_source(value: Optional[str], old: Optional[KeySource]) -> Optional[KeySource]: +def config_parse_key_source(value: str | None, old: KeySource | None) -> KeySource | None: if not value: return KeySource(type=KeySourceType.file) @@ -1656,9 +1657,9 @@ class CertificateSource: def config_parse_certificate_source( - value: Optional[str], - old: Optional[CertificateSource], -) -> Optional[CertificateSource]: + value: str | None, + old: CertificateSource | None, +) -> CertificateSource | None: if not value: return CertificateSource(type=CertificateSourceType.file) @@ -1672,8 +1673,8 @@ def config_parse_certificate_source( def config_parse_artifact_output_list( - value: Optional[str], old: Optional[list[ArtifactOutput]] -) -> Optional[list[ArtifactOutput]]: + value: str | None, old: list[ArtifactOutput] | None +) -> list[ArtifactOutput] | None: if not value: return [] @@ -1721,10 +1722,10 @@ class ConfigSetting(Generic[T]): dest: str section: str parse: ConfigParseCallback[T] = config_parse_string # type: ignore # see mypy#3737 - match: Optional[ConfigMatchCallback[T]] = None + match: ConfigMatchCallback[T] | None = None name: str = "" - default: Optional[T] = None - default_factory: Optional[ConfigDefaultCallback[T]] = None + default: T | None = None + default_factory: ConfigDefaultCallback[T] | None = None default_factory_depends: tuple[str, ...] = tuple() path_suffixes: tuple[str, ...] = () recursive_path_suffixes: tuple[str, ...] = () @@ -1734,12 +1735,12 @@ class ConfigSetting(Generic[T]): scope: SettingScope = SettingScope.local # settings for argparse - short: Optional[str] = None + short: str | None = None long: str = "" - choices: Optional[list[str]] = None - metavar: Optional[str] = None - const: Optional[Any] = None - help: Optional[str] = None + choices: list[str] | None = None + metavar: str | None = None + const: Any | None = None + help: str | None = None # backward compatibility compat_names: tuple[str, ...] = () @@ -1792,7 +1793,7 @@ class CustomHelpFormatter(argparse.HelpFormatter): ) -def parse_chdir(path: str) -> Optional[Path]: +def parse_chdir(path: str) -> Path | None: if not path: # The current directory should be ignored return None @@ -1817,9 +1818,9 @@ class IgnoreAction(argparse.Action): self, option_strings: Sequence[str], dest: str, - nargs: Union[int, str, None] = None, + nargs: int | str | None = None, default: Any = argparse.SUPPRESS, - help: Optional[str] = argparse.SUPPRESS, + help: str | None = argparse.SUPPRESS, ) -> None: super().__init__(option_strings, dest, nargs=nargs, default=default, help=help) @@ -1827,8 +1828,8 @@ class IgnoreAction(argparse.Action): self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, - values: Union[str, Sequence[Any], None], - option_string: Optional[str] = None, + values: str | Sequence[Any] | None, + option_string: str | None = None, ) -> None: logging.warning(f"{option_string} is no longer supported") @@ -1838,8 +1839,8 @@ class PagerHelpAction(argparse._HelpAction): self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, - values: Union[str, Sequence[Any], None] = None, - option_string: Optional[str] = None, + values: str | Sequence[Any] | None = None, + option_string: str | None = None, ) -> None: page(parser.format_help(), namespace.pager) parser.exit() @@ -1859,7 +1860,7 @@ class Args: verb: Verb cmdline: list[str] force: int - directory: Optional[Path] + directory: Path | None debug: bool debug_shell: bool debug_workspace: bool @@ -1898,7 +1899,7 @@ class Args: return dataclasses.asdict(self, dict_factory=dict_with_capitalised_keys_factory) @classmethod - def from_json(cls, s: Union[str, dict[str, Any], SupportsRead[str], SupportsRead[bytes]]) -> "Args": + def from_json(cls, s: str | dict[str, Any] | SupportsRead[str] | SupportsRead[bytes]) -> "Args": """Instantiate a Args object from a (partial) JSON dump.""" if isinstance(s, str): @@ -2006,15 +2007,15 @@ class Config: profiles: list[str] files: list[Path] dependencies: list[str] - minimum_version: Optional[str] + minimum_version: str | None pass_environment: list[str] distribution: Distribution release: str architecture: Architecture - mirror: Optional[str] - snapshot: Optional[str] - local_mirror: Optional[str] + mirror: str | None + snapshot: str | None + local_mirror: str | None repository_key_check: bool repository_key_fetch: bool repositories: list[str] @@ -2023,19 +2024,19 @@ class Config: manifest_format: list[ManifestFormat] output: str output_extension: str - output_size: Optional[int] + output_size: int | None compress_output: Compression compress_level: int - output_dir: Optional[Path] - output_mode: Optional[int] - image_id: Optional[str] - image_version: Optional[str] + output_dir: Path | None + output_mode: int | None + image_id: str | None + image_version: str | None oci_labels: dict[str, str] oci_annotations: dict[str, str] split_artifacts: list[ArtifactOutput] repart_dirs: list[Path] - sysupdate_dir: Optional[Path] - sector_size: Optional[int] + sysupdate_dir: Path | None + sector_size: int | None overlay: bool seed: uuid.UUID @@ -2054,7 +2055,7 @@ class Config: remove_packages: list[str] remove_files: list[str] clean_package_metadata: ConfigFeature - source_date_epoch: Optional[int] + source_date_epoch: int | None configure_scripts: list[Path] sync_scripts: list[Path] @@ -2078,7 +2079,7 @@ class Config: initrd_volatile_packages: list[str] microcode_host: bool devicetrees: list[str] - splash: Optional[Path] + splash: Path | None kernel_command_line: list[str] kernel_modules_include: list[str] kernel_modules_exclude: list[str] @@ -2091,14 +2092,14 @@ class Config: kernel_modules_initrd_exclude: list[str] kernel_modules_initrd_include_host: bool - locale: Optional[str] - locale_messages: Optional[str] - keymap: Optional[str] - timezone: Optional[str] - hostname: Optional[str] - root_password: Optional[tuple[str, bool]] - root_shell: Optional[str] - machine_id: Optional[uuid.UUID] + locale: str | None + locale_messages: str | None + keymap: str | None + timezone: str | None + hostname: str | None + root_password: tuple[str, bool] | None + root_shell: str | None + machine_id: uuid.UUID | None autologin: bool make_initrd: bool @@ -2107,38 +2108,38 @@ class Config: secure_boot: bool secure_boot_auto_enroll: bool - secure_boot_key: Optional[Path] + secure_boot_key: Path | None secure_boot_key_source: KeySource - secure_boot_certificate: Optional[Path] + secure_boot_certificate: Path | None secure_boot_certificate_source: CertificateSource secure_boot_sign_tool: SecureBootSignTool verity: Verity - verity_key: Optional[Path] + verity_key: Path | None verity_key_source: KeySource - verity_certificate: Optional[Path] + verity_certificate: Path | None verity_certificate_source: CertificateSource sign_expected_pcr: ConfigFeature - sign_expected_pcr_key: Optional[Path] + sign_expected_pcr_key: Path | None sign_expected_pcr_key_source: KeySource - sign_expected_pcr_certificate: Optional[Path] + sign_expected_pcr_certificate: Path | None sign_expected_pcr_certificate_source: CertificateSource - passphrase: Optional[Path] + passphrase: Path | None checksum: bool sign: bool openpgp_tool: str - key: Optional[str] + key: str | None - tools_tree: Optional[Path] + tools_tree: Path | None tools_tree_certificates: bool extra_search_paths: list[Path] incremental: Incremental cacheonly: Cacheonly sandbox_trees: list[ConfigTree] - workspace_dir: Optional[Path] - cache_dir: Optional[Path] + workspace_dir: Path | None + cache_dir: Path | None cache_key: str - package_cache_dir: Optional[Path] - build_dir: Optional[Path] + package_cache_dir: Path | None + build_dir: Path | None build_key: str use_subvolumes: ConfigFeature repart_offline: bool @@ -2149,28 +2150,28 @@ class Config: environment_files: list[Path] with_tests: bool with_network: bool - proxy_url: Optional[str] + proxy_url: str | None proxy_exclude: list[str] - proxy_peer_certificate: Optional[Path] - proxy_client_certificate: Optional[Path] - proxy_client_key: Optional[Path] + proxy_peer_certificate: Path | None + proxy_client_certificate: Path | None + proxy_client_key: Path | None make_scripts_executable: bool - nspawn_settings: Optional[Path] + nspawn_settings: Path | None ephemeral: bool credentials: dict[str, PathString] kernel_command_line_extra: list[str] register: ConfigFeature storage_target_mode: ConfigFeature runtime_trees: list[ConfigTree] - runtime_size: Optional[int] + runtime_size: int | None runtime_network: Network runtime_build_sources: bool bind_user: bool - ssh_key: Optional[Path] - ssh_certificate: Optional[Path] - machine: Optional[str] - forward_journal: Optional[Path] + ssh_key: Path | None + ssh_certificate: Path | None + machine: str | None + forward_journal: Path | None vmm: Vmm console: ConsoleMode @@ -2184,8 +2185,8 @@ class Config: tpm: ConfigFeature removable: bool firmware: Firmware - firmware_variables: Optional[Path] - linux: Optional[str] + firmware_variables: Path | None + linux: str | None drives: list[Drive] qemu_args: list[str] @@ -2472,7 +2473,7 @@ class Config: @classmethod def from_partial_json( cls, - s: Union[str, dict[str, Any], SupportsRead[str], SupportsRead[bytes]], + s: str | dict[str, Any] | SupportsRead[str] | SupportsRead[bytes], ) -> dict[str, Any]: """Instantiate a Config object from a (partial) JSON dump.""" if isinstance(s, str): @@ -2506,12 +2507,12 @@ class Config: return {(tk := key_transformer(k)): value_transformer(tk, v) for k, v in j.items()} @classmethod - def from_json(cls, s: Union[str, dict[str, Any], SupportsRead[str], SupportsRead[bytes]]) -> "Config": + def from_json(cls, s: str | dict[str, Any] | SupportsRead[str] | SupportsRead[bytes]) -> "Config": return dataclasses.replace( cls.default(), **{k: v for k, v in cls.from_partial_json(s).items() if k in cls.fields()} ) - def find_binary(self, *names: PathString, tools: bool = True) -> Optional[Path]: + def find_binary(self, *names: PathString, tools: bool = True) -> Path | None: return find_binary(*names, root=self.tools() if tools else Path("/"), extra=self.extra_search_paths) def sandbox( @@ -2521,8 +2522,8 @@ class Config: devices: bool = False, relaxed: bool = False, tools: bool = True, - scripts: Optional[Path] = None, - overlay: Optional[Path] = None, + scripts: Path | None = None, + overlay: Path | None = None, options: Sequence[PathString] = (), ) -> AbstractContextManager[list[PathString]]: opt: list[PathString] = [*options] @@ -2554,9 +2555,9 @@ def parse_ini(path: Path, only_sections: Collection[str] = ()) -> Iterator[tuple We have our own parser instead of using configparser as the latter does not support specifying the same setting multiple times in the same configuration file. """ - section: Optional[str] = None - setting: Optional[str] = None - value: Optional[str] = None + section: str | None = None + setting: str | None = None + value: str | None = None for line in textwrap.dedent(path.read_text()).splitlines(): comment = line.find("#") @@ -4597,7 +4598,7 @@ def create_argument_parser(chdir: bool = True) -> argparse.ArgumentParser: help=argparse.SUPPRESS, ) - last_section: Optional[str] = None + last_section: str | None = None for s in SETTINGS: if s.section != last_section: @@ -4664,8 +4665,8 @@ class ConfigAction(argparse.Action): self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, - values: Union[str, Sequence[Any], None], - option_string: Optional[str] = None, + values: str | Sequence[Any] | None, + option_string: str | None = None, ) -> None: assert option_string is not None @@ -4800,11 +4801,11 @@ class ParseContext: with chdir(path if path.is_dir() else Path.cwd()): self.parse_config_one(path if path.is_file() else Path.cwd(), parse_profiles=path.is_dir()) - def finalize_value(self, setting: ConfigSetting[T]) -> Optional[T]: + def finalize_value(self, setting: ConfigSetting[T]) -> T | None: # If a value was specified on the CLI, it always takes priority. If the setting is a collection of # values, we merge the value from the CLI with the value from the configuration, making sure that the # value from the CLI always takes priority. - if (v := cast(Optional[T], self.cli.get(setting.dest))) is not None: + if (v := cast(T | None, self.cli.get(setting.dest))) is not None: cfg_value = self.config.get(setting.dest) # We either have no corresponding value in the config files # or the values was assigned the empty string on the CLI @@ -4834,7 +4835,7 @@ class ParseContext: if ( setting.dest not in self.cli and setting.dest in self.config - and (v := cast(Optional[T], self.config[setting.dest])) is not None + and (v := cast(T | None, self.config[setting.dest])) is not None ): return v @@ -4877,8 +4878,8 @@ class ParseContext: return default def match_config(self, path: Path, asserts: bool = False) -> bool: - condition_triggered: Optional[bool] = None - match_triggered: Optional[bool] = None + condition_triggered: bool | None = None + match_triggered: bool | None = None skip = False # If the config file does not exist, we assume it matches so that we look at the other files in the @@ -4959,7 +4960,7 @@ class ParseContext: return match_triggered is not False def parse_config_one(self, path: Path, parse_profiles: bool = False, parse_local: bool = False) -> bool: - s: Optional[ConfigSetting[object]] # Hint to mypy that we might assign None + s: ConfigSetting[object] | None # Hint to mypy that we might assign None assert path.is_absolute() extras = path.is_dir() @@ -5145,7 +5146,7 @@ def finalize_default_tools( main: ParseContext, finalized: dict[str, Any], *, - configdir: Optional[Path], + configdir: Path | None, resources: Path, ) -> Config: context = ParseContext(resources) @@ -5238,7 +5239,7 @@ def finalize_default_initrd( return Config.from_dict(context.finalize()) -def finalize_configdir(directory: Optional[Path]) -> Optional[Path]: +def finalize_configdir(directory: Path | None) -> Path | None: """Allow locating all mkosi configuration in a mkosi/ subdirectory instead of in the top-level directory of a git repository. """ @@ -5308,7 +5309,7 @@ def parse_config( argv: Sequence[str] = (), *, resources: Path = Path("/"), -) -> tuple[Args, Optional[Config], tuple[Config, ...]]: +) -> tuple[Args, Config | None, tuple[Config, ...]]: argv = list(argv) context = ParseContext(resources) @@ -5421,7 +5422,7 @@ def parse_config( # To make this work, we can't use default_factory as it is evaluated too early, so # we check here to see if dependencies were explicitly provided and if not we gather # the list of default dependencies while we parse the subimages. - dependencies: Optional[list[str]] = ( + dependencies: list[str] | None = ( None if "dependencies" in context.cli or "dependencies" in context.config else [] ) @@ -5536,7 +5537,7 @@ def finalize_term() -> str: return term if sys.stderr.isatty() else "dumb" -def finalize_git_config(proxy_url: Optional[str], env: dict[str, str]) -> dict[str, str]: +def finalize_git_config(proxy_url: str | None, env: dict[str, str]) -> dict[str, str]: if proxy_url is None: return {} @@ -5561,19 +5562,19 @@ def yes_no(b: bool) -> str: return "yes" if b else "no" -def none_to_na(s: Optional[object]) -> str: +def none_to_na(s: object | None) -> str: return "n/a" if s is None else str(s) -def none_to_random(s: Optional[object]) -> str: +def none_to_random(s: object | None) -> str: return "random" if s is None else str(s) -def none_to_none(s: Optional[object]) -> str: +def none_to_none(s: object | None) -> str: return "none" if s is None else str(s) -def none_to_default(s: Optional[object]) -> str: +def none_to_default(s: object | None) -> str: return "default" if s is None else str(s) @@ -5592,7 +5593,7 @@ def format_bytes(num_bytes: int) -> str: return f"{num_bytes}B" -def format_bytes_or_none(num_bytes: Optional[int]) -> str: +def format_bytes_or_none(num_bytes: int | None) -> str: return format_bytes(num_bytes) if num_bytes is not None else "none" @@ -5600,7 +5601,7 @@ def format_octal(oct_value: int) -> str: return f"{oct_value:>04o}" -def format_octal_or_default(oct_value: Optional[int]) -> str: +def format_octal_or_default(oct_value: int | None) -> str: return format_octal(oct_value) if oct_value is not None else "default" @@ -5879,20 +5880,20 @@ class JsonEncoder(json.JSONEncoder): return super().default(o) -def dump_json(dict: dict[str, Any], indent: Optional[int] = 4) -> str: +def dump_json(dict: dict[str, Any], indent: int | None = 4) -> str: return json.dumps(dict, cls=JsonEncoder, indent=indent, sort_keys=True) E = TypeVar("E", bound=StrEnum) -def json_type_transformer(refcls: Union[type[Args], type[Config]]) -> Callable[[str, Any], Any]: +def json_type_transformer(refcls: type[Args] | type[Config]) -> Callable[[str, Any], Any]: fields_by_name = {field.name: field for field in dataclasses.fields(refcls)} def path_transformer(path: str, fieldtype: type[Path]) -> Path: return Path(path) - def optional_path_transformer(path: Optional[str], fieldtype: type[Optional[Path]]) -> Optional[Path]: + def optional_path_transformer(path: str | None, fieldtype: type[Path | None]) -> Path | None: return Path(path) if path is not None else None def path_list_transformer(pathlist: list[str], fieldtype: type[list[Path]]) -> list[Path]: @@ -5902,13 +5903,13 @@ def json_type_transformer(refcls: Union[type[Args], type[Config]]) -> Callable[[ return uuid.UUID(uuidstr) def optional_uuid_transformer( - uuidstr: Optional[str], fieldtype: type[Optional[uuid.UUID]] - ) -> Optional[uuid.UUID]: + uuidstr: str | None, fieldtype: type[uuid.UUID | None] + ) -> uuid.UUID | None: return uuid.UUID(uuidstr) if uuidstr is not None else None def root_password_transformer( - rootpw: Optional[list[Union[str, bool]]], fieldtype: type[Optional[tuple[str, bool]]] - ) -> Optional[tuple[str, bool]]: + rootpw: list[str | bool] | None, fieldtype: type[tuple[str, bool] | None] + ) -> tuple[str, bool] | None: if rootpw is None: return None return (cast(str, rootpw[0]), cast(bool, rootpw[1])) @@ -5932,7 +5933,7 @@ def json_type_transformer(refcls: Union[type[Args], type[Config]]) -> Callable[[ def enum_transformer(enumval: str, fieldtype: type[E]) -> E: return fieldtype(enumval) - def optional_enum_transformer(enumval: Optional[str], fieldtype: type[Optional[E]]) -> Optional[E]: + def optional_enum_transformer(enumval: str | None, fieldtype: type[E | None]) -> E | None: return typing.get_args(fieldtype)[0](enumval) if enumval is not None else None def enum_list_transformer(enumlist: list[str], fieldtype: type[list[E]]) -> list[E]: @@ -5960,9 +5961,9 @@ def json_type_transformer(refcls: Union[type[Args], type[Config]]) -> Callable[[ return ret def generic_version_transformer( - version: Optional[str], - fieldtype: type[Optional[GenericVersion]], - ) -> Optional[GenericVersion]: + version: str | None, + fieldtype: type[GenericVersion | None], + ) -> GenericVersion | None: return GenericVersion(version) if version is not None else None def certificate_source_transformer( @@ -6003,11 +6004,11 @@ def json_type_transformer(refcls: Union[type[Args], type[Config]]) -> Callable[[ # to shut up the type checkers and rely on the tests. transformers: dict[Any, Callable[[Any, Any], Any]] = { Path: path_transformer, - Optional[Path]: optional_path_transformer, + Path | None: optional_path_transformer, list[Path]: path_list_transformer, uuid.UUID: uuid_transformer, - Optional[uuid.UUID]: optional_uuid_transformer, - Optional[tuple[str, bool]]: root_password_transformer, + uuid.UUID | None: optional_uuid_transformer, + tuple[str, bool] | None: root_password_transformer, list[ConfigTree]: config_tree_transformer, Architecture: enum_transformer, BiosBootloader: enum_transformer, @@ -6022,7 +6023,7 @@ def json_type_transformer(refcls: Union[type[Args], type[Config]]) -> Callable[[ SecureBootSignTool: enum_transformer, Incremental: enum_transformer, BuildSourcesEphemeral: enum_transformer, - Optional[Distribution]: optional_enum_transformer, + Distribution | None: optional_enum_transformer, list[ManifestFormat]: enum_list_transformer, Verb: enum_transformer, DocFormat: enum_transformer, @@ -6041,7 +6042,7 @@ def json_type_transformer(refcls: Union[type[Args], type[Config]]) -> Callable[[ } def json_transformer(key: str, val: Any) -> Any: - fieldtype: Optional[dataclasses.Field[Any]] = fields_by_name.get(key) + fieldtype: dataclasses.Field[Any] | None = fields_by_name.get(key) # It is unlikely that the type of a field will be None only, so let's not bother with a different # sentinel value if fieldtype is None: @@ -6065,7 +6066,7 @@ def want_selinux_relabel( config: Config, root: Path, fatal: bool = True, -) -> Optional[tuple[Path, str, Path, Path]]: +) -> tuple[Path, str, Path, Path] | None: if config.selinux_relabel == ConfigFeature.disabled: return None @@ -6152,8 +6153,8 @@ def systemd_tool_version(*tool: PathString, sandbox: SandboxProtocol = nosandbox def systemd_pty_forward( config: Config, *, - background: Optional[str] = None, - title: Optional[str] = None, + background: str | None = None, + title: str | None = None, ) -> list[str]: tint_bg = parse_boolean(config.environment.get("SYSTEMD_TINT_BACKGROUND", "1")) and parse_boolean( os.environ.get("SYSTEMD_TINT_BACKGROUND", "1") diff --git a/mkosi/context.py b/mkosi/context.py index 665b75ec8..5263bf87c 100644 --- a/mkosi/context.py +++ b/mkosi/context.py @@ -4,7 +4,6 @@ import os from collections.abc import Sequence from contextlib import AbstractContextManager from pathlib import Path -from typing import Optional from mkosi.config import Args, Config from mkosi.util import PathString, flatten @@ -22,7 +21,7 @@ class Context: resources: Path, keyring_dir: Path, metadata_dir: Path, - package_dir: Optional[Path] = None, + package_dir: Path | None = None, ) -> None: self.args = args self.config = config @@ -32,8 +31,8 @@ class Context: self.metadata_dir = metadata_dir self.package_dir = package_dir or (self.workspace / "packages") self.lowerdirs: list[PathString] = [] - self.upperdir: Optional[PathString] = None - self.workdir: Optional[PathString] = None + self.upperdir: PathString | None = None + self.workdir: PathString | None = None self.package_dir.mkdir(exist_ok=True) self.staging.mkdir() @@ -87,7 +86,7 @@ class Context: *, network: bool = False, devices: bool = False, - scripts: Optional[Path] = None, + scripts: Path | None = None, options: Sequence[PathString] = (), ) -> AbstractContextManager[list[PathString]]: return self.config.sandbox( diff --git a/mkosi/curl.py b/mkosi/curl.py index 0db38229a..0a225a8af 100644 --- a/mkosi/curl.py +++ b/mkosi/curl.py @@ -3,7 +3,7 @@ import os import subprocess from pathlib import Path -from typing import Optional, overload +from typing import overload from mkosi.config import Config from mkosi.mounts import finalize_certificate_mounts @@ -15,7 +15,7 @@ def curl( config: Config, url: str, *, - output_dir: Optional[Path], + output_dir: Path | None, log: bool = True, ) -> None: ... @@ -30,7 +30,7 @@ def curl( ) -> str: ... -def curl(config: Config, url: str, *, output_dir: Optional[Path] = None, log: bool = True) -> Optional[str]: +def curl(config: Config, url: str, *, output_dir: Path | None = None, log: bool = True) -> str | None: result = run( [ "curl", diff --git a/mkosi/distribution/__init__.py b/mkosi/distribution/__init__.py index 5d42b8f5b..b9cb1c4ae 100644 --- a/mkosi/distribution/__init__.py +++ b/mkosi/distribution/__init__.py @@ -5,7 +5,7 @@ import importlib import urllib.parse from collections.abc import Sequence from pathlib import Path -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING from mkosi.log import die from mkosi.util import StrEnum, read_env_file @@ -135,7 +135,7 @@ class DistributionInstaller: return "" @classmethod - def default_tools_tree_distribution(cls) -> Optional[Distribution]: + def default_tools_tree_distribution(cls) -> Distribution | None: return None @classmethod @@ -151,7 +151,7 @@ class DistributionInstaller: return False -def detect_distribution(root: Path = Path("/")) -> tuple[Union[Distribution, str, None], Optional[str]]: +def detect_distribution(root: Path = Path("/")) -> tuple[Distribution | str | None, str | None]: try: os_release = read_env_file(root / "etc/os-release") except FileNotFoundError: @@ -169,7 +169,7 @@ def detect_distribution(root: Path = Path("/")) -> tuple[Union[Distribution, str "azurelinux": "azure", } - d: Optional[Distribution] = None + d: Distribution | None = None for the_id in [dist_id, *dist_id_like]: if not the_id: continue diff --git a/mkosi/distribution/opensuse.py b/mkosi/distribution/opensuse.py index 428c9b27a..2a929c1b3 100644 --- a/mkosi/distribution/opensuse.py +++ b/mkosi/distribution/opensuse.py @@ -4,7 +4,6 @@ import os import tempfile from collections.abc import Iterable, Sequence from pathlib import Path -from typing import Union from xml.etree import ElementTree from mkosi.config import Architecture, Config, parse_ini @@ -42,7 +41,7 @@ class Installer(DistributionInstaller, distribution=Distribution.opensuse): return "grub2" @classmethod - def package_manager(cls, config: Config) -> Union[type[Dnf], type[Zypper]]: + def package_manager(cls, config: Config) -> type[Dnf] | type[Zypper]: if config.find_binary("zypper"): return Zypper else: diff --git a/mkosi/distribution/rhel.py b/mkosi/distribution/rhel.py index cf7aa7fa4..752fd37d0 100644 --- a/mkosi/distribution/rhel.py +++ b/mkosi/distribution/rhel.py @@ -2,7 +2,7 @@ from collections.abc import Iterable from pathlib import Path -from typing import Any, Optional +from typing import Any from mkosi.context import Context from mkosi.distribution import Distribution, centos, join_mirror @@ -27,7 +27,7 @@ class Installer(centos.Installer, distribution=Distribution.rhel): ) @staticmethod - def sslcacert(context: Context) -> Optional[Path]: + def sslcacert(context: Context) -> Path | None: if context.config.mirror: return None @@ -41,7 +41,7 @@ class Installer(centos.Installer, distribution=Distribution.rhel): return path @staticmethod - def sslclientkey(context: Context) -> Optional[Path]: + def sslclientkey(context: Context) -> Path | None: if context.config.mirror: return None @@ -56,7 +56,7 @@ class Installer(centos.Installer, distribution=Distribution.rhel): return paths[0] @staticmethod - def sslclientcert(context: Context) -> Optional[Path]: + def sslclientcert(context: Context) -> Path | None: if context.config.mirror: return None diff --git a/mkosi/initrd.py b/mkosi/initrd.py index 5343a055d..fbdf2056c 100644 --- a/mkosi/initrd.py +++ b/mkosi/initrd.py @@ -11,7 +11,7 @@ import subprocess import sys import tempfile from pathlib import Path -from typing import Optional, cast +from typing import cast import mkosi.resources from mkosi.config import DocFormat, InitrdProfile, OutputFormat @@ -33,8 +33,8 @@ class KernelInstallContext: staging_area: Path layout: str image_type: str - initrd_generator: Optional[str] - uki_generator: Optional[str] + initrd_generator: str | None + uki_generator: str | None verbose: bool @staticmethod @@ -228,7 +228,7 @@ def vconsole_config() -> list[str]: ] -def initrd_finalize(staging_dir: Path, output: str, output_dir: Optional[Path]) -> None: +def initrd_finalize(staging_dir: Path, output: str, output_dir: Path | None) -> None: if output_dir: with umask(~0o700) if os.getuid() == 0 else cast(umask, contextlib.nullcontext()): Path(output_dir).mkdir(parents=True, exist_ok=True) diff --git a/mkosi/installer/apt.py b/mkosi/installer/apt.py index a6338dfd2..0c5903e73 100644 --- a/mkosi/installer/apt.py +++ b/mkosi/installer/apt.py @@ -4,7 +4,7 @@ import dataclasses import textwrap from collections.abc import Sequence from pathlib import Path -from typing import Final, Optional +from typing import Final from mkosi.config import Config, ConfigFeature from mkosi.context import Context @@ -21,8 +21,8 @@ class AptRepository: url: str suite: str components: tuple[str, ...] - signedby: Optional[Path] - snapshot: Optional[str] = None + signedby: Path | None + snapshot: str | None = None def __str__(self) -> str: return textwrap.dedent( diff --git a/mkosi/installer/dnf.py b/mkosi/installer/dnf.py index 670a77822..5490505ce 100644 --- a/mkosi/installer/dnf.py +++ b/mkosi/installer/dnf.py @@ -3,7 +3,6 @@ import textwrap from collections.abc import Sequence from pathlib import Path -from typing import Optional from mkosi.config import Cacheonly, Config from mkosi.context import Context @@ -63,7 +62,7 @@ class Dnf(PackageManager): context: Context, repositories: Sequence[RpmRepository], filelists: bool = True, - metadata_expire: Optional[str] = None, + metadata_expire: str | None = None, ) -> None: (context.sandbox_tree / "etc/dnf/vars").mkdir(parents=True, exist_ok=True) (context.sandbox_tree / "etc/yum.repos.d").mkdir(parents=True, exist_ok=True) diff --git a/mkosi/installer/rpm.py b/mkosi/installer/rpm.py index a90c59cdf..d67927aa2 100644 --- a/mkosi/installer/rpm.py +++ b/mkosi/installer/rpm.py @@ -3,7 +3,7 @@ import dataclasses import textwrap from pathlib import Path -from typing import Literal, Optional, overload +from typing import Literal, overload from mkosi.context import Context from mkosi.distribution import Distribution @@ -18,17 +18,17 @@ class RpmRepository: url: str gpgurls: tuple[str, ...] enabled: bool = True - sslcacert: Optional[Path] = None - sslclientkey: Optional[Path] = None - sslclientcert: Optional[Path] = None - priority: Optional[int] = None + sslcacert: Path | None = None + sslclientkey: Path | None = None + sslclientcert: Path | None = None + priority: int | None = None @overload def find_rpm_gpgkey( context: Context, key: str, - fallback: Optional[str] = None, + fallback: str | None = None, *, required: Literal[True] = True, ) -> str: ... @@ -38,19 +38,19 @@ def find_rpm_gpgkey( def find_rpm_gpgkey( context: Context, key: str, - fallback: Optional[str] = None, + fallback: str | None = None, *, required: bool, -) -> Optional[str]: ... +) -> str | None: ... def find_rpm_gpgkey( context: Context, key: str, - fallback: Optional[str] = None, + fallback: str | None = None, *, required: bool = True, -) -> Optional[str]: +) -> str | None: # We assume here that GPG keys will only ever be relative symlinks and never absolute symlinks. paths = glob_in_sandbox( @@ -78,7 +78,7 @@ def setup_rpm( context: Context, *, dbpath: str = "/usr/lib/sysimage/rpm", - dbbackend: Optional[str] = None, + dbbackend: str | None = None, ) -> None: confdir = context.sandbox_tree / "etc/rpm" confdir.mkdir(parents=True, exist_ok=True) diff --git a/mkosi/log.py b/mkosi/log.py index ddfd185b2..cffa9bdb8 100644 --- a/mkosi/log.py +++ b/mkosi/log.py @@ -7,7 +7,7 @@ import os import sys import time from collections.abc import Iterator -from typing import Any, NoReturn, Optional +from typing import Any, NoReturn from mkosi.sandbox import ANSI_BOLD, ANSI_GRAY, ANSI_RED, ANSI_RESET, ANSI_YELLOW, terminal_is_dumb @@ -18,7 +18,7 @@ ARG_DEBUG_SANDBOX = contextvars.ContextVar("debug-sandbox", default=False) LEVEL = 0 -def die(message: str, *, hint: Optional[str] = None) -> NoReturn: +def die(message: str, *, hint: str | None = None) -> NoReturn: logging.error(f"{message}") if hint: logging.info(f"({hint})") @@ -95,7 +95,7 @@ def log_notice(text: str) -> None: @contextlib.contextmanager -def complete_step(text: str, text2: Optional[str] = None) -> Iterator[list[Any]]: +def complete_step(text: str, text2: str | None = None) -> Iterator[list[Any]]: global LEVEL log_step(text) @@ -116,7 +116,7 @@ def complete_step(text: str, text2: Optional[str] = None) -> Iterator[list[Any]] class Formatter(logging.Formatter): - def __init__(self, fmt: Optional[str] = None, *args: Any, **kwargs: Any) -> None: + def __init__(self, fmt: str | None = None, *args: Any, **kwargs: Any) -> None: fmt = fmt or "%(message)s" self.formatters = { diff --git a/mkosi/manifest.py b/mkosi/manifest.py index df3639386..41e4c40c4 100644 --- a/mkosi/manifest.py +++ b/mkosi/manifest.py @@ -5,7 +5,7 @@ import datetime import json import subprocess import textwrap -from typing import IO, Any, Optional +from typing import IO, Any from mkosi.config import ManifestFormat, OutputFormat from mkosi.context import Context @@ -43,7 +43,7 @@ class PackageManifest: @dataclasses.dataclass class SourcePackageManifest: name: str - changelog: Optional[str] + changelog: str | None packages: list[PackageManifest] = dataclasses.field(default_factory=list) def add(self, package: PackageManifest) -> None: diff --git a/mkosi/mounts.py b/mkosi/mounts.py index f15791890..88ab5608a 100644 --- a/mkosi/mounts.py +++ b/mkosi/mounts.py @@ -6,7 +6,6 @@ import stat import tempfile from collections.abc import Iterator, Sequence from pathlib import Path -from typing import Optional, Union from mkosi.config import BuildSourcesEphemeral, Config from mkosi.log import die @@ -49,7 +48,7 @@ def mount_overlay( lowerdirs: Sequence[Path], dst: Path, *, - upperdir: Optional[Path] = None, + upperdir: Path | None = None, ) -> Iterator[Path]: with contextlib.ExitStack() as stack: if upperdir is None: @@ -86,7 +85,7 @@ def mount_overlay( def finalize_source_mounts( config: Config, *, - ephemeral: Union[BuildSourcesEphemeral, bool], + ephemeral: BuildSourcesEphemeral | bool, ) -> Iterator[list[PathString]]: with contextlib.ExitStack() as stack: options: list[PathString] = [] diff --git a/mkosi/pager.py b/mkosi/pager.py index 7077fb0a4..84de95983 100644 --- a/mkosi/pager.py +++ b/mkosi/pager.py @@ -2,10 +2,9 @@ import os import pydoc -from typing import Optional -def page(text: str, enabled: Optional[bool]) -> None: +def page(text: str, enabled: bool | None) -> None: if enabled: # Initialize less options from $MKOSI_LESS or provide a suitable fallback. # F: don't page if one screen diff --git a/mkosi/partition.py b/mkosi/partition.py index 535b6cdd5..397022179 100644 --- a/mkosi/partition.py +++ b/mkosi/partition.py @@ -5,7 +5,7 @@ import json import subprocess from collections.abc import Mapping, Sequence from pathlib import Path -from typing import Any, Final, Optional +from typing import Any, Final from mkosi.log import die from mkosi.run import SandboxProtocol, nosandbox, run, workdir @@ -15,9 +15,9 @@ from mkosi.run import SandboxProtocol, nosandbox, run, workdir class Partition: type: str uuid: str - partno: Optional[int] - split_path: Optional[Path] - roothash: Optional[str] + partno: int | None + split_path: Path | None + roothash: str | None @classmethod def from_dict(cls, dict: Mapping[str, Any]) -> "Partition": @@ -47,9 +47,9 @@ def find_partitions(image: Path, *, sandbox: SandboxProtocol = nosandbox) -> lis return [Partition.from_dict(d) for d in output] -def finalize_roothash(partitions: Sequence[Partition]) -> Optional[str]: - roothash: Optional[str] = None - usrhash: Optional[str] = None +def finalize_roothash(partitions: Sequence[Partition]) -> str | None: + roothash: str | None = None + usrhash: str | None = None for p in partitions: if (h := p.roothash) is None: @@ -71,7 +71,7 @@ def finalize_roothash(partitions: Sequence[Partition]) -> Optional[str]: return f"roothash={roothash}" if roothash else f"usrhash={usrhash}" if usrhash else None -def finalize_root(partitions: Sequence[Partition]) -> Optional[str]: +def finalize_root(partitions: Sequence[Partition]) -> str | None: root = finalize_roothash(partitions) if not root: root = next((f"root=PARTUUID={p.uuid}" for p in partitions if p.type.startswith("root")), None) diff --git a/mkosi/qemu.py b/mkosi/qemu.py index 6617677bf..8eac9a309 100644 --- a/mkosi/qemu.py +++ b/mkosi/qemu.py @@ -25,7 +25,6 @@ import textwrap import uuid from collections.abc import Iterator, Sequence from pathlib import Path -from typing import Optional from mkosi.bootloader import KernelType from mkosi.config import ( @@ -184,7 +183,7 @@ class OvmfConfig: vars_format: str -def find_ovmf_firmware(config: Config, firmware: Firmware) -> Optional[OvmfConfig]: +def find_ovmf_firmware(config: Config, firmware: Firmware) -> OvmfConfig | None: if not firmware.is_uefi(): return None @@ -301,7 +300,7 @@ def start_swtpm(config: Config) -> Iterator[Path]: proc.terminate() -def find_virtiofsd(*, root: Path = Path("/"), extra: Sequence[Path] = ()) -> Optional[Path]: +def find_virtiofsd(*, root: Path = Path("/"), extra: Sequence[Path] = ()) -> Path | None: if p := find_binary("virtiofsd", root=root, extra=extra): return p @@ -605,8 +604,8 @@ def qemu_version(config: Config, binary: Path) -> GenericVersion: def finalize_firmware( config: Config, - kernel: Optional[Path], - kerneltype: Optional[KernelType] = None, + kernel: Path | None, + kerneltype: KernelType | None = None, ) -> Firmware: if config.firmware != Firmware.auto: return config.firmware @@ -732,7 +731,7 @@ def finalize_drive(config: Config, drive: Drive) -> Iterator[Path]: @contextlib.contextmanager -def finalize_initrd(config: Config) -> Iterator[Optional[Path]]: +def finalize_initrd(config: Config) -> Iterator[Path | None]: with contextlib.ExitStack() as stack: if (config.output_dir_or_cwd() / config.output_split_initrd).exists(): yield config.output_dir_or_cwd() / config.output_split_initrd @@ -902,7 +901,7 @@ def finalize_register(config: Config) -> bool: return True -def register_machine(config: Config, pid: int, fname: Path, cid: Optional[int]) -> None: +def register_machine(config: Config, pid: int, fname: Path, cid: int | None) -> None: if not finalize_register(config): return @@ -1104,7 +1103,7 @@ def run_qemu(args: Args, config: Config) -> None: cmdline += ["-accel", accel] - cid: Optional[int] = None + cid: int | None = None if QemuDeviceNode.vhost_vsock in qemu_device_fds: if config.vsock_cid == VsockCID.auto: cid = find_unused_vsock_cid(config, qemu_device_fds[QemuDeviceNode.vhost_vsock]) @@ -1152,8 +1151,8 @@ def run_qemu(args: Args, config: Config) -> None: assert ovmf cmdline += ["-drive", f"if=pflash,format={ovmf.format},readonly=on,file={ovmf.firmware}"] - vsock: Optional[socket.socket] = None - notify: Optional[AsyncioThread[tuple[str, str]]] = None + vsock: socket.socket | None = None + notify: AsyncioThread[tuple[str, str]] | None = None with contextlib.ExitStack() as stack: if firmware.is_uefi(): diff --git a/mkosi/resources/man/mkosi.1.md b/mkosi/resources/man/mkosi.1.md index 2081b5211..405f17c0c 100644 --- a/mkosi/resources/man/mkosi.1.md +++ b/mkosi/resources/man/mkosi.1.md @@ -3301,7 +3301,7 @@ In this scenario, the kernel is loaded from the ESP in the image by **systemd-bo `ubuntu-archive-keyring`, `kali-archive-keyring` and/or `debian-archive-keyring` packages explicitly, in addition to **apt**, depending on what kind of distribution images you want to build. -- The minimum required Python version is 3.9. +- The minimum required Python version is 3.10. ## Unprivileged User Namespaces diff --git a/mkosi/run.py b/mkosi/run.py index 26598b724..187016752 100644 --- a/mkosi/run.py +++ b/mkosi/run.py @@ -17,7 +17,7 @@ from collections.abc import Awaitable, Collection, Iterator, Mapping, Sequence from contextlib import AbstractContextManager from pathlib import Path from types import TracebackType -from typing import TYPE_CHECKING, Any, Callable, Generic, NoReturn, Optional, Protocol, TypeVar +from typing import TYPE_CHECKING, Any, Callable, Generic, NoReturn, Protocol, TypeVar import mkosi.sandbox from mkosi.log import ARG_DEBUG, ARG_DEBUG_SANDBOX, ARG_DEBUG_SHELL, die @@ -156,7 +156,7 @@ def run( stdin: _FILE = None, stdout: _FILE = None, stderr: _FILE = None, - input: Optional[str] = None, + input: str | None = None, env: Mapping[str, str] = {}, log: bool = True, success_exit_status: Sequence[int] = (0,), @@ -188,7 +188,7 @@ def _preexec( cmd: list[str], env: dict[str, Any], sandbox: list[str], - preexec: Optional[Callable[[], None]], + preexec: Callable[[], None] | None, ) -> None: if preexec: preexec() @@ -231,12 +231,12 @@ def spawn( stdin: _FILE = None, stdout: _FILE = None, stderr: _FILE = None, - user: Optional[int] = None, - group: Optional[int] = None, + user: int | None = None, + group: int | None = None, pass_fds: Collection[int] = (), env: Mapping[str, str] = {}, log: bool = True, - preexec: Optional[Callable[[], None]] = None, + preexec: Callable[[], None] | None = None, success_exit_status: Sequence[int] = (0,), setup: Sequence[PathString] = (), sandbox: AbstractContextManager[Sequence[PathString]] = nosandbox(), @@ -344,7 +344,7 @@ def spawn( def finalize_path( - root: Optional[Path] = None, + root: Path | None = None, extra: Sequence[Path] = (), prefix_usr: bool = False, relaxed: bool = False, @@ -376,9 +376,9 @@ def finalize_path( def find_binary( *names: PathString, - root: Optional[Path] = None, + root: Path | None = None, extra: Sequence[Path] = (), -) -> Optional[Path]: +) -> Path | None: root = root or Path("/") path = finalize_path(root=root, extra=extra, prefix_usr=True) @@ -458,9 +458,9 @@ class AsyncioThread(threading.Thread, Generic[T]): def __exit__( self, - type: Optional[type[BaseException]], - value: Optional[BaseException], - traceback: Optional[TracebackType], + type: type[BaseException] | None, + value: BaseException | None, + traceback: TracebackType | None, ) -> None: self.cancel() self.join() @@ -473,7 +473,7 @@ class AsyncioThread(threading.Thread, Generic[T]): pass -def workdir(path: Path, sandbox: Optional[SandboxProtocol] = None) -> str: +def workdir(path: Path, sandbox: SandboxProtocol | None = None) -> str: subdir = "/" if sandbox and sandbox == nosandbox else "/work" return joinpath(subdir, os.fspath(path)) @@ -536,10 +536,10 @@ def sandbox_cmd( *, network: bool = False, devices: bool = False, - scripts: Optional[Path] = None, + scripts: Path | None = None, tools: Path = Path("/"), relaxed: bool = False, - overlay: Optional[Path] = None, + overlay: Path | None = None, options: Sequence[PathString] = (), extra: Sequence[Path] = (), ) -> Iterator[list[PathString]]: @@ -645,7 +645,7 @@ def sandbox_cmd( if scripts: cmdline += ["--ro-bind", scripts, "/scripts"] - tmp: Optional[Path] + tmp: Path | None if not overlay and not relaxed: tmp = stack.enter_context(vartmpdir()) diff --git a/mkosi/util.py b/mkosi/util.py index 19f0b4c79..01bf394ac 100644 --- a/mkosi/util.py +++ b/mkosi/util.py @@ -21,7 +21,7 @@ import tempfile from collections.abc import Hashable, Iterable, Iterator, Mapping, Sequence from pathlib import Path from types import ModuleType -from typing import IO, Any, Callable, Optional, Protocol, TypeVar, Union +from typing import IO, Any, Callable, Protocol, TypeVar from mkosi.log import die from mkosi.resources import as_file @@ -32,8 +32,8 @@ V = TypeVar("V") S = TypeVar("S", bound=Hashable) # Borrowed from https://github.com/python/typeshed/blob/3d14016085aed8bcf0cf67e9e5a70790ce1ad8ea/stdlib/3/subprocess.pyi#L24 -_FILE = Union[None, int, IO[Any]] -PathString = Union[Path, str] +_FILE = None | int | IO[Any] +PathString = Path | str # Borrowed from # https://github.com/python/typeshed/blob/ec52bf1adde1d3183d0595d2ba982589df48dff1/stdlib/_typeshed/__init__.pyi#L19 @@ -72,7 +72,7 @@ def round_up(x: int, blocksize: int = 4096) -> int: return (x + blocksize - 1) // blocksize * blocksize -def startswith(s: str, prefix: str) -> Optional[str]: +def startswith(s: str, prefix: str) -> str | None: if s.startswith(prefix): return s.removeprefix(prefix) return None diff --git a/pyproject.toml b/pyproject.toml index 379825e41..bbad8a586 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ authors = [ version = "26" description = "Build Bespoke OS Images" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" license = {text = "LGPL-2.1-or-later"} [project.optional-dependencies] @@ -53,7 +53,7 @@ multi_line_output = 3 py_version = "39" [tool.pyright] -pythonVersion = "3.9" +pythonVersion = "3.10" include = [ "mkosi/**/*.py", "tests/**/*.py", @@ -61,7 +61,7 @@ include = [ ] [tool.mypy] -python_version = 3.9 +python_version = "3.10" # belonging to --strict warn_unused_configs = true disallow_any_generics = true diff --git a/tests/__init__.py b/tests/__init__.py index 3134a7a28..96f06552f 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -9,7 +9,7 @@ import uuid from collections.abc import Iterator, Mapping, Sequence from pathlib import Path from types import TracebackType -from typing import Any, Optional +from typing import Any import pytest @@ -44,9 +44,9 @@ class Image: def __exit__( self, - type: Optional[type[BaseException]], - value: Optional[BaseException], - traceback: Optional[TracebackType], + type: type[BaseException] | None, + value: BaseException | None, + traceback: TracebackType | None, ) -> None: def clean() -> None: acquire_privileges() diff --git a/tests/test_json.py b/tests/test_json.py index d1f367c71..6d4b9d0d8 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -4,7 +4,6 @@ import os import textwrap import uuid from pathlib import Path -from typing import Optional import pytest @@ -49,7 +48,7 @@ from mkosi.distribution import Distribution @pytest.mark.parametrize("path", [None, "/baz/qux"]) -def test_args(path: Optional[Path]) -> None: +def test_args(path: Path | None) -> None: dump = textwrap.dedent( f"""\ {{