From: Daan De Meyer Date: Sat, 14 Feb 2026 13:24:29 +0000 (+0100) Subject: Revert "Bump minimum python version to 3.10" X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=23ea86d0cfb9b9b539956b994a1f7be6a9cc9765;p=thirdparty%2Fmkosi.git Revert "Bump minimum python version to 3.10" This reverts commit 22b2f0bf18ac98f62ef92745fde5dd3f8369d4bf. Turns out using python3.12 on CentOS causes more issues than we thought it would, so let's revert the move to python 3.9. Instead, we'll conditionally import Union in sandbox.py only on python 3.9 and use the Union operator otherwise. --- diff --git a/bin/mkosi b/bin/mkosi index 930b171f3..9abee8f13 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, 10))'; then + if python3 -c 'import sys; sys.exit(sys.version_info < (3, 9))'; 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, 10))'; then + if [ -n "$candidate" ] && "$candidate" -c 'import sys; sys.exit(sys.version_info < (3, 9))'; then MKOSI_INTERPRETER="$candidate" fi fi diff --git a/docs/CODING_STYLE.md b/docs/CODING_STYLE.md index 2920437cd..ea7b7f917 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.10. +- The lowest supported Python version is CPython 3.9. ## Formatting diff --git a/kernel-install/50-mkosi.install b/kernel-install/50-mkosi.install index d51451a2e..bff9e4c76 100755 --- a/kernel-install/50-mkosi.install +++ b/kernel-install/50-mkosi.install @@ -6,6 +6,7 @@ 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 @@ -21,7 +22,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) -> Path | None: +def build_microcode_initrd(output: Path) -> Optional[Path]: vendor, ucode = identify_cpu(Path("/")) if vendor is None: diff --git a/mkosi/__init__.py b/mkosi/__init__.py index f92ec1b5e..a38ce440c 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, cast +from typing import Any, Optional, Union, 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: Path | None = None, + target: Optional[Path] = 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) -> str | None: +def kernel_get_ver_from_modules(context: Context) -> Optional[str]: # 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: str | None + kver: Optional[str] if match: kver = match.group(0) else: @@ -1426,7 +1426,7 @@ def fixup_vmlinuz_location(context: Context) -> None: copyfile2(d, vmlinuz) -def identify_cpu(root: Path) -> tuple[Path | None, Path | None]: +def identify_cpu(root: Path) -> tuple[Optional[Path], Optional[Path]]: for entry in Path("/proc/cpuinfo").read_text().split("\n\n"): vendor_id = family = model = stepping = None for line in entry.splitlines(): @@ -1872,7 +1872,7 @@ def systemd_stub_binary(context: Context) -> Path: return stub -def systemd_stub_version(context: Context, stub: Path) -> GenericVersion | None: +def systemd_stub_version(context: Context, stub: Path) -> Optional[GenericVersion]: try: sdmagic = extract_pe_section(context, stub, ".sdmagic", context.workspace / "sdmagic") except KeyError: @@ -1937,7 +1937,9 @@ def find_entry_token(context: Context) -> str: return cast(str, output["EntryToken"]) -def finalize_cmdline(context: Context, partitions: Sequence[Partition], roothash: str | None) -> list[str]: +def finalize_cmdline( + context: Context, partitions: Sequence[Partition], roothash: Optional[str] +) -> 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(): @@ -2369,7 +2371,7 @@ def maybe_compress( context: Context, compression: Compression, src: Path, - dst: Path | None = None, + dst: Optional[Path] = None, ) -> None: if not compression or src.is_dir(): if dst: @@ -2403,7 +2405,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) -> Path | None: +def get_uki_path(context: Context) -> Optional[Path]: if not want_efi(context.config) or context.config.unified_kernel_images == UnifiedKernelImage.none: return None @@ -2593,7 +2595,7 @@ def calculate_signature_sop(context: Context) -> None: ) # fmt: skip -def dir_size(path: Path | os.DirEntry[str]) -> int: +def dir_size(path: Union[Path, os.DirEntry[str]]) -> int: dir_sum = 0 for entry in os.scandir(path): if entry.is_symlink(): @@ -2608,7 +2610,7 @@ def dir_size(path: Path | os.DirEntry[str]) -> int: return dir_sum -def save_manifest(context: Context, manifest: Manifest | None) -> None: +def save_manifest(context: Context, manifest: Optional[Manifest]) -> None: if not manifest: return @@ -2787,7 +2789,7 @@ def check_inputs(config: Config) -> None: ) -def check_tool(config: Config, *tools: PathString, reason: str, hint: str | None = None) -> Path: +def check_tool(config: Config, *tools: PathString, reason: str, hint: Optional[str] = None) -> Path: tool = config.find_binary(*tools) if not tool: die(f"Could not find '{tools[0]}' which is required to {reason}.", hint=hint) @@ -2800,7 +2802,7 @@ def check_systemd_tool( *tools: PathString, version: str, reason: str, - hint: str | None = None, + hint: Optional[str] = None, ) -> None: tool = check_tool(config, *tools, reason=reason, hint=hint) @@ -2816,7 +2818,7 @@ def check_ukify( config: Config, version: str, reason: str, - hint: str | None = None, + hint: Optional[str] = None, ) -> None: ukify = check_tool(config, "ukify", "/usr/lib/systemd/ukify", reason=reason, hint=hint) @@ -3371,7 +3373,7 @@ def reuse_cache(context: Context) -> bool: def save_esp_components( context: Context, -) -> tuple[Path | None, str | None, Path | None, list[Path]]: +) -> tuple[Optional[Path], Optional[str], Optional[Path], list[Path]]: if context.config.output_format == OutputFormat.addon: stub = systemd_addon_stub_binary(context) if not stub.exists(): @@ -3467,7 +3469,7 @@ def make_image( cmdline += ["--definitions", workdir(d)] opts += ["--ro-bind", d, workdir(d)] - def can_orphan_file(distribution: Distribution | str | None, release: str | None) -> bool: + def can_orphan_file(distribution: Union[Distribution, str, None], release: Optional[str]) -> bool: if not isinstance(distribution, Distribution): return True @@ -3741,9 +3743,9 @@ def make_oci(context: Context, root_layer: Path, dst: Path) -> None: def make_esp( context: Context, - stub: Path | None, - kver: str | None, - kimg: Path | None, + stub: Optional[Path], + kver: Optional[str], + kimg: Optional[Path], microcode: list[Path], ) -> list[Partition]: if not context.config.architecture.to_efi(): @@ -3930,7 +3932,7 @@ def clamp_mtime(path: Path, mtime: int) -> None: os.utime(path, ns=updated, follow_symlinks=False) -def normalize_mtime(root: Path, mtime: int | None, directory: Path = Path("")) -> None: +def normalize_mtime(root: Path, mtime: Optional[int], directory: Path = Path("")) -> None: if mtime is None: return @@ -4475,7 +4477,7 @@ def run_coredumpctl(args: Args, config: Config) -> None: run_systemd_tool("coredumpctl", args, config) -def start_storage_target_mode(config: Config) -> AbstractContextManager[Popen | None]: +def start_storage_target_mode(config: Config) -> AbstractContextManager[Optional[Popen]]: if config.storage_target_mode == ConfigFeature.disabled: return contextlib.nullcontext() @@ -4915,7 +4917,7 @@ def run_build( resources: Path, keyring_dir: Path, metadata_dir: Path, - package_dir: Path | None = None, + package_dir: Optional[Path] = None, ) -> None: if not have_effective_cap(CAP_SYS_ADMIN): acquire_privileges() @@ -4983,7 +4985,7 @@ def ensure_tools_tree_has_etc_resolv_conf(config: Config) -> None: ) -def run_verb(args: Args, tools: Config | None, images: Sequence[Config], *, resources: Path) -> None: +def run_verb(args: Args, tools: Optional[Config], 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 fca0ce7d5..57c082b69 100644 --- a/mkosi/__main__.py +++ b/mkosi/__main__.py @@ -5,6 +5,7 @@ import faulthandler import signal import sys from types import FrameType +from typing import Optional import mkosi.resources from mkosi import run_verb @@ -16,7 +17,7 @@ from mkosi.util import resource_path INTERRUPTED = False -def onsignal(signal: int, frame: FrameType | None) -> None: +def onsignal(signal: int, frame: Optional[FrameType]) -> None: global INTERRUPTED if INTERRUPTED: return diff --git a/mkosi/archive.py b/mkosi/archive.py index 89a38bcf5..f3fbea610 100644 --- a/mkosi/archive.py +++ b/mkosi/archive.py @@ -3,6 +3,7 @@ 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 @@ -108,7 +109,7 @@ def make_cpio( src: Path, dst: Path, *, - files: Iterable[Path] | None = None, + files: Optional[Iterable[Path]] = None, sandbox: SandboxProtocol = nosandbox, ) -> None: if not files: diff --git a/mkosi/bootloader.py b/mkosi/bootloader.py index e8fafbf8a..6e22f5f00 100644 --- a/mkosi/bootloader.py +++ b/mkosi/bootloader.py @@ -10,6 +10,7 @@ import tempfile import textwrap from collections.abc import Iterator, Mapping, Sequence from pathlib import Path +from typing import Optional from mkosi.config import ( BiosBootloader, @@ -167,7 +168,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) -> Path | None: +def find_grub_directory(context: Context, *, target: str) -> Optional[Path]: for d in ("usr/lib/grub", "usr/share/grub2"): if (p := context.root / d / target).exists() and any(p.iterdir()): return p @@ -175,7 +176,7 @@ def find_grub_directory(context: Context, *, target: str) -> Path | None: return None -def find_grub_binary(config: Config, binary: str) -> Path | None: +def find_grub_binary(config: Config, binary: str) -> Optional[Path]: assert "grub" not in binary # Debian has a bespoke setup where if only grub-pc-bin is installed, grub-bios-setup is installed in @@ -184,7 +185,7 @@ def find_grub_binary(config: Config, binary: str) -> Path | None: return config.find_binary(f"grub-{binary}", f"grub2-{binary}", f"/usr/lib/grub/i386-pc/grub-{binary}") -def prepare_grub_config(context: Context) -> Path | None: +def prepare_grub_config(context: Context) -> Optional[Path]: config = context.root / "efi" / context.config.distribution.installer.grub_prefix() / "grub.cfg" with umask(~0o700): config.parent.mkdir(exist_ok=True) @@ -222,8 +223,8 @@ def grub_mkimage( *, target: str, modules: Sequence[str] = (), - output: Path | None = None, - sbat: Path | None = None, + output: Optional[Path] = None, + sbat: Optional[Path] = None, ) -> None: mkimage = find_grub_binary(context.config, "mkimage") assert mkimage @@ -292,7 +293,7 @@ def grub_mkimage( ) # fmt: skip -def find_signed_grub_image(context: Context) -> Path | None: +def find_signed_grub_image(context: Context) -> Optional[Path]: arch = context.config.architecture.to_efi() patterns = [ @@ -475,9 +476,9 @@ def run_systemd_sign_tool( *, cmdline: Sequence[PathString], options: Sequence[PathString], - certificate: Path | None, + certificate: Optional[Path], certificate_source: CertificateSource, - key: Path | None, + key: Optional[Path], key_source: KeySource, env: Mapping[str, str] = {}, stdout: _FILE = None, diff --git a/mkosi/completion.py b/mkosi/completion.py index a72421cd5..398edb2ca 100644 --- a/mkosi/completion.py +++ b/mkosi/completion.py @@ -8,6 +8,7 @@ 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 @@ -26,7 +27,7 @@ class CompGen(StrEnum): return CompGen.dirs else: return CompGen.files - # TODO: the type of action.type is Callable[[str], Any] | FileType + # TODO: the type of action.type is Union[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)): @@ -59,9 +60,9 @@ class CompGen(StrEnum): @dataclasses.dataclass(frozen=True) class CompletionItem: - short: str | None - long: str | None - help: str | None + short: Optional[str] + long: Optional[str] + help: Optional[str] choices: list[str] compgen: CompGen @@ -103,7 +104,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, str | int]) -> str: + def to_bash_hasharray(name: str, entries: Mapping[str, Union[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 a897b9a42..0916dac4e 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, Protocol, TypeVar, cast +from typing import Any, Callable, ClassVar, Generic, Optional, Protocol, TypeVar, Union, 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[[str | None, T | None], T | None] +ConfigParseCallback = Callable[[Optional[str], Optional[T]], Optional[T]] 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: Path | None + target: Optional[Path] def with_prefix(self, prefix: PathString = "/") -> tuple[Path, Path]: return ( @@ -181,8 +181,8 @@ class DriveFlag(StrEnum): class Drive: id: str size: int - directory: Path | None - options: str | None + directory: Optional[Path] + options: Optional[str] file_id: str flags: list[DriveFlag] @@ -497,7 +497,7 @@ class Architecture(StrEnum): return a - def to_efi(self) -> str | None: + def to_efi(self) -> Optional[str]: return { Architecture.x86: "ia32", Architecture.x86_64: "x64", @@ -508,7 +508,7 @@ class Architecture(StrEnum): Architecture.loongarch64: "loongarch64", }.get(self) # fmt: skip - def to_grub(self) -> str | None: + def to_grub(self) -> Optional[str]: return { Architecture.x86_64: "x86_64", Architecture.x86: "i386", @@ -686,7 +686,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) -> bool | None: +def try_parse_boolean(s: str) -> Optional[bool]: "Parse 1/true/yes/y/t/on as true and 0/false/no/n/f/off/None as false" s_l = s.lower() @@ -799,14 +799,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: str | None, old: str | None) -> Path | None: +def config_parse_key(value: Optional[str], old: Optional[str]) -> Optional[Path]: if not value: return None return parse_path(value, secret=True) if Path(value).exists() else Path(value) -def config_parse_certificate(value: str | None, old: str | None) -> Path | None: +def config_parse_certificate(value: Optional[str], old: Optional[str]) -> Optional[Path]: if not value: return None @@ -855,7 +855,7 @@ def config_make_list_matcher(parse: Callable[[str], T]) -> ConfigMatchCallback[l return config_match_list -def config_parse_string(value: str | None, old: str | None) -> str | None: +def config_parse_string(value: Optional[str], old: Optional[str]) -> Optional[str]: return value or None @@ -877,7 +877,7 @@ def config_match_key_value(match: str, value: dict[str, str]) -> bool: return value.get(k, None) == v -def config_parse_boolean(value: str | None, old: bool | None) -> bool | None: +def config_parse_boolean(value: Optional[str], old: Optional[bool]) -> Optional[bool]: if value is None: return False @@ -894,7 +894,7 @@ def parse_feature(value: str) -> ConfigFeature: return ConfigFeature.enabled if parse_boolean(value) else ConfigFeature.disabled -def config_parse_feature(value: str | None, old: ConfigFeature | None) -> ConfigFeature | None: +def config_parse_feature(value: Optional[str], old: Optional[ConfigFeature]) -> Optional[ConfigFeature]: if value is None: return ConfigFeature.auto @@ -908,7 +908,7 @@ def config_match_feature(match: str, value: ConfigFeature) -> bool: return value == parse_feature(match) -def config_parse_compression(value: str | None, old: Compression | None) -> Compression | None: +def config_parse_compression(value: Optional[str], old: Optional[Compression]) -> Optional[Compression]: if not value: return None @@ -918,7 +918,7 @@ def config_parse_compression(value: str | None, old: Compression | None) -> Comp return Compression.zstd if parse_boolean(value) else Compression.none -def config_parse_uuid(value: str | None, old: str | None) -> uuid.UUID | None: +def config_parse_uuid(value: Optional[str], old: Optional[str]) -> Optional[uuid.UUID]: if not value: return None @@ -931,7 +931,7 @@ def config_parse_uuid(value: str | None, old: str | None) -> uuid.UUID | None: die(f"{value} is not a valid UUID") -def config_parse_source_date_epoch(value: str | None, old: int | None) -> int | None: +def config_parse_source_date_epoch(value: Optional[str], old: Optional[int]) -> Optional[int]: if not value: return None @@ -946,7 +946,7 @@ def config_parse_source_date_epoch(value: str | None, old: int | None) -> int | return timestamp -def config_parse_compress_level(value: str | None, old: int | None) -> int | None: +def config_parse_compress_level(value: Optional[str], old: Optional[int]) -> Optional[int]: if not value: return None @@ -961,7 +961,7 @@ def config_parse_compress_level(value: str | None, old: int | None) -> int | Non return level -def config_parse_mode(value: str | None, old: int | None) -> int | None: +def config_parse_mode(value: Optional[str], old: Optional[int]) -> Optional[int]: if not value: return None @@ -1023,8 +1023,8 @@ def config_default_distribution(namespace: dict[str, Any]) -> Distribution: def config_default_release(namespace: dict[str, Any]) -> str: - hd: Distribution | str | None - hr: str | None + hd: Union[Distribution, str, None] + hr: Optional[str] if ( (d := os.getenv("MKOSI_HOST_DISTRIBUTION")) @@ -1070,7 +1070,7 @@ def config_default_repository_key_fetch(namespace: dict[str, Any]) -> bool: ) -def config_default_source_date_epoch(namespace: dict[str, Any]) -> int | None: +def config_default_source_date_epoch(namespace: dict[str, Any]) -> Optional[int]: for env in namespace["environment"]: if s := startswith(env, "SOURCE_DATE_EPOCH="): break @@ -1079,7 +1079,7 @@ def config_default_source_date_epoch(namespace: dict[str, Any]) -> int | None: return config_parse_source_date_epoch(s, None) -def config_default_proxy_url(namespace: dict[str, Any]) -> str | None: +def config_default_proxy_url(namespace: dict[str, Any]) -> Optional[str]: names = ("http_proxy", "https_proxy", "HTTP_PROXY", "HTTPS_PROXY") for env in namespace["environment"]: @@ -1094,7 +1094,7 @@ def config_default_proxy_url(namespace: dict[str, Any]) -> str | None: return None -def config_default_proxy_exclude(namespace: dict[str, Any]) -> list[str] | None: +def config_default_proxy_exclude(namespace: dict[str, Any]) -> Optional[list[str]]: names = ("no_proxy", "NO_PROXY") for env in namespace["environment"]: @@ -1109,7 +1109,7 @@ def config_default_proxy_exclude(namespace: dict[str, Any]) -> list[str] | None: return None -def config_default_proxy_peer_certificate(namespace: dict[str, Any]) -> Path | None: +def config_default_proxy_peer_certificate(namespace: dict[str, Any]) -> Optional[Path]: for p in (Path("/etc/pki/tls/certs/ca-bundle.crt"), Path("/etc/ssl/certs/ca-certificates.crt")): if p.exists(): return p @@ -1153,14 +1153,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: str | None, old: SE | None) -> SE | None: + def config_parse_enum(value: Optional[str], old: Optional[SE]) -> Optional[SE]: 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: str | None, old: SE | None) -> SE | None: + def config_parse_enum(value: Optional[str], old: Optional[SE]) -> Optional[SE]: if not value: return None @@ -1198,13 +1198,13 @@ def package_sort_key(package: str) -> tuple[int, str]: def config_make_list_parser( *, - delimiter: str | None = None, + delimiter: Optional[str] = None, parse: Callable[[str], T] = str, # type: ignore # see mypy#3737 unescape: bool = False, reset: bool = True, - key: Callable[[T], Any] | None = None, + key: Optional[Callable[[T], Any]] = None, ) -> ConfigParseCallback[list[T]]: - def config_parse_list(value: str | None, old: list[T] | None) -> list[T] | None: + def config_parse_list(value: Optional[str], old: Optional[list[T]]) -> Optional[list[T]]: new = old.copy() if old else [] if value is None: @@ -1266,16 +1266,16 @@ def config_match_version(match: str, value: str) -> bool: def config_make_dict_parser( *, - delimiter: str | None = None, + delimiter: Optional[str] = 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: str | None, - old: dict[str, PathString] | None, - ) -> dict[str, PathString] | None: + value: Optional[str], + old: Optional[dict[str, PathString]], + ) -> Optional[dict[str, PathString]]: new = old.copy() if old else {} if value is None: @@ -1358,7 +1358,7 @@ def config_make_path_parser( absolute: bool = False, constants: Sequence[str] = (), ) -> ConfigParseCallback[Path]: - def config_parse_path(value: str | None, old: Path | None) -> Path | None: + def config_parse_path(value: Optional[str], old: Optional[Path]) -> Optional[Path]: if not value: return None @@ -1382,7 +1382,7 @@ def is_valid_filename(s: str) -> bool: def config_make_filename_parser(hint: str) -> ConfigParseCallback[str]: - def config_parse_filename(value: str | None, old: str | None) -> str | None: + def config_parse_filename(value: Optional[str], old: Optional[str]) -> Optional[str]: if not value: return None @@ -1405,9 +1405,8 @@ def match_path_exists(image: str, value: str) -> bool: def config_parse_root_password( - value: str | None, - old: tuple[str, bool] | None, -) -> tuple[str, bool] | None: + value: Optional[str], old: Optional[tuple[str, bool]] +) -> Optional[tuple[str, bool]]: if not value: return None @@ -1458,14 +1457,14 @@ def parse_bytes(value: str) -> int: return result -def config_parse_bytes(value: str | None, old: int | None = None) -> int | None: +def config_parse_bytes(value: Optional[str], old: Optional[int] = None) -> Optional[int]: if not value: return None return parse_bytes(value) -def config_parse_number(value: str | None, old: int | None = None) -> int | None: +def config_parse_number(value: Optional[str], old: Optional[int] = None) -> Optional[int]: if not value: return None @@ -1514,7 +1513,7 @@ def parse_drive(value: str) -> Drive: ) -def config_parse_sector_size(value: str | None, old: int | None) -> int | None: +def config_parse_sector_size(value: Optional[str], old: Optional[int]) -> Optional[int]: if not value: return None @@ -1532,7 +1531,7 @@ def config_parse_sector_size(value: str | None, old: int | None) -> int | None: return size -def config_parse_vsock_cid(value: str | None, old: int | None) -> int | None: +def config_parse_vsock_cid(value: Optional[str], old: Optional[int]) -> Optional[int]: if not value: return None @@ -1553,7 +1552,7 @@ def config_parse_vsock_cid(value: str | None, old: int | None) -> int | None: return cid -def config_parse_minimum_version(value: str | None, old: str | None) -> str | None: +def config_parse_minimum_version(value: Optional[str], old: Optional[str]) -> Optional[str]: if not value: return old @@ -1630,7 +1629,7 @@ class KeySource: return f"{self.type}:{self.source}" if self.source else str(self.type) -def config_parse_key_source(value: str | None, old: KeySource | None) -> KeySource | None: +def config_parse_key_source(value: Optional[str], old: Optional[KeySource]) -> Optional[KeySource]: if not value: return KeySource(type=KeySourceType.file) @@ -1658,9 +1657,9 @@ class CertificateSource: def config_parse_certificate_source( - value: str | None, - old: CertificateSource | None, -) -> CertificateSource | None: + value: Optional[str], + old: Optional[CertificateSource], +) -> Optional[CertificateSource]: if not value: return CertificateSource(type=CertificateSourceType.file) @@ -1674,8 +1673,8 @@ def config_parse_certificate_source( def config_parse_artifact_output_list( - value: str | None, old: list[ArtifactOutput] | None -) -> list[ArtifactOutput] | None: + value: Optional[str], old: Optional[list[ArtifactOutput]] +) -> Optional[list[ArtifactOutput]]: if not value: return [] @@ -1723,10 +1722,10 @@ class ConfigSetting(Generic[T]): dest: str section: str parse: ConfigParseCallback[T] = config_parse_string # type: ignore # see mypy#3737 - match: ConfigMatchCallback[T] | None = None + match: Optional[ConfigMatchCallback[T]] = None name: str = "" - default: T | None = None - default_factory: ConfigDefaultCallback[T] | None = None + default: Optional[T] = None + default_factory: Optional[ConfigDefaultCallback[T]] = None default_factory_depends: tuple[str, ...] = tuple() path_suffixes: tuple[str, ...] = () recursive_path_suffixes: tuple[str, ...] = () @@ -1736,12 +1735,12 @@ class ConfigSetting(Generic[T]): scope: SettingScope = SettingScope.local # settings for argparse - short: str | None = None + short: Optional[str] = None long: str = "" - choices: list[str] | None = None - metavar: str | None = None - const: Any | None = None - help: str | None = None + choices: Optional[list[str]] = None + metavar: Optional[str] = None + const: Optional[Any] = None + help: Optional[str] = None # backward compatibility compat_names: tuple[str, ...] = () @@ -1794,7 +1793,7 @@ class CustomHelpFormatter(argparse.HelpFormatter): ) -def parse_chdir(path: str) -> Path | None: +def parse_chdir(path: str) -> Optional[Path]: if not path: # The current directory should be ignored return None @@ -1819,9 +1818,9 @@ class IgnoreAction(argparse.Action): self, option_strings: Sequence[str], dest: str, - nargs: int | str | None = None, + nargs: Union[int, str, None] = None, default: Any = argparse.SUPPRESS, - help: str | None = argparse.SUPPRESS, + help: Optional[str] = argparse.SUPPRESS, ) -> None: super().__init__(option_strings, dest, nargs=nargs, default=default, help=help) @@ -1829,8 +1828,8 @@ class IgnoreAction(argparse.Action): self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, - values: str | Sequence[Any] | None, - option_string: str | None = None, + values: Union[str, Sequence[Any], None], + option_string: Optional[str] = None, ) -> None: logging.warning(f"{option_string} is no longer supported") @@ -1840,8 +1839,8 @@ class PagerHelpAction(argparse._HelpAction): self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, - values: str | Sequence[Any] | None = None, - option_string: str | None = None, + values: Union[str, Sequence[Any], None] = None, + option_string: Optional[str] = None, ) -> None: page(parser.format_help(), namespace.pager) parser.exit() @@ -1861,7 +1860,7 @@ class Args: verb: Verb cmdline: list[str] force: int - directory: Path | None + directory: Optional[Path] debug: bool debug_shell: bool debug_workspace: bool @@ -1900,7 +1899,7 @@ class Args: return dataclasses.asdict(self, dict_factory=dict_with_capitalised_keys_factory) @classmethod - def from_json(cls, s: str | dict[str, Any] | SupportsRead[str] | SupportsRead[bytes]) -> "Args": + def from_json(cls, s: Union[str, dict[str, Any], SupportsRead[str], SupportsRead[bytes]]) -> "Args": """Instantiate a Args object from a (partial) JSON dump.""" if isinstance(s, str): @@ -2008,15 +2007,15 @@ class Config: profiles: list[str] files: list[Path] dependencies: list[str] - minimum_version: str | None + minimum_version: Optional[str] pass_environment: list[str] distribution: Distribution release: str architecture: Architecture - mirror: str | None - snapshot: str | None - local_mirror: str | None + mirror: Optional[str] + snapshot: Optional[str] + local_mirror: Optional[str] repository_key_check: bool repository_key_fetch: bool repositories: list[str] @@ -2025,19 +2024,19 @@ class Config: manifest_format: list[ManifestFormat] output: str output_extension: str - output_size: int | None + output_size: Optional[int] compress_output: Compression compress_level: int - output_dir: Path | None - output_mode: int | None - image_id: str | None - image_version: str | None + output_dir: Optional[Path] + output_mode: Optional[int] + image_id: Optional[str] + image_version: Optional[str] oci_labels: dict[str, str] oci_annotations: dict[str, str] split_artifacts: list[ArtifactOutput] repart_dirs: list[Path] - sysupdate_dir: Path | None - sector_size: int | None + sysupdate_dir: Optional[Path] + sector_size: Optional[int] overlay: bool seed: uuid.UUID @@ -2056,7 +2055,7 @@ class Config: remove_packages: list[str] remove_files: list[str] clean_package_metadata: ConfigFeature - source_date_epoch: int | None + source_date_epoch: Optional[int] configure_scripts: list[Path] sync_scripts: list[Path] @@ -2080,7 +2079,7 @@ class Config: initrd_volatile_packages: list[str] microcode_host: bool devicetrees: list[str] - splash: Path | None + splash: Optional[Path] kernel_command_line: list[str] kernel_modules_include: list[str] kernel_modules_exclude: list[str] @@ -2093,14 +2092,14 @@ class Config: kernel_modules_initrd_exclude: list[str] kernel_modules_initrd_include_host: bool - 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 + 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] autologin: bool make_initrd: bool @@ -2109,38 +2108,38 @@ class Config: secure_boot: bool secure_boot_auto_enroll: bool - secure_boot_key: Path | None + secure_boot_key: Optional[Path] secure_boot_key_source: KeySource - secure_boot_certificate: Path | None + secure_boot_certificate: Optional[Path] secure_boot_certificate_source: CertificateSource secure_boot_sign_tool: SecureBootSignTool verity: Verity - verity_key: Path | None + verity_key: Optional[Path] verity_key_source: KeySource - verity_certificate: Path | None + verity_certificate: Optional[Path] verity_certificate_source: CertificateSource sign_expected_pcr: ConfigFeature - sign_expected_pcr_key: Path | None + sign_expected_pcr_key: Optional[Path] sign_expected_pcr_key_source: KeySource - sign_expected_pcr_certificate: Path | None + sign_expected_pcr_certificate: Optional[Path] sign_expected_pcr_certificate_source: CertificateSource - passphrase: Path | None + passphrase: Optional[Path] checksum: bool sign: bool openpgp_tool: str - key: str | None + key: Optional[str] - tools_tree: Path | None + tools_tree: Optional[Path] tools_tree_certificates: bool extra_search_paths: list[Path] incremental: Incremental cacheonly: Cacheonly sandbox_trees: list[ConfigTree] - workspace_dir: Path | None - cache_dir: Path | None + workspace_dir: Optional[Path] + cache_dir: Optional[Path] cache_key: str - package_cache_dir: Path | None - build_dir: Path | None + package_cache_dir: Optional[Path] + build_dir: Optional[Path] build_key: str use_subvolumes: ConfigFeature repart_offline: bool @@ -2151,28 +2150,28 @@ class Config: environment_files: list[Path] with_tests: bool with_network: bool - proxy_url: str | None + proxy_url: Optional[str] proxy_exclude: list[str] - proxy_peer_certificate: Path | None - proxy_client_certificate: Path | None - proxy_client_key: Path | None + proxy_peer_certificate: Optional[Path] + proxy_client_certificate: Optional[Path] + proxy_client_key: Optional[Path] make_scripts_executable: bool - nspawn_settings: Path | None + nspawn_settings: Optional[Path] ephemeral: bool credentials: dict[str, PathString] kernel_command_line_extra: list[str] register: ConfigFeature storage_target_mode: ConfigFeature runtime_trees: list[ConfigTree] - runtime_size: int | None + runtime_size: Optional[int] runtime_network: Network runtime_build_sources: bool bind_user: bool - ssh_key: Path | None - ssh_certificate: Path | None - machine: str | None - forward_journal: Path | None + ssh_key: Optional[Path] + ssh_certificate: Optional[Path] + machine: Optional[str] + forward_journal: Optional[Path] vmm: Vmm console: ConsoleMode @@ -2186,8 +2185,8 @@ class Config: tpm: ConfigFeature removable: bool firmware: Firmware - firmware_variables: Path | None - linux: str | None + firmware_variables: Optional[Path] + linux: Optional[str] drives: list[Drive] qemu_args: list[str] @@ -2474,7 +2473,7 @@ class Config: @classmethod def from_partial_json( cls, - s: str | dict[str, Any] | SupportsRead[str] | SupportsRead[bytes], + s: Union[str, dict[str, Any], SupportsRead[str], SupportsRead[bytes]], ) -> dict[str, Any]: """Instantiate a Config object from a (partial) JSON dump.""" if isinstance(s, str): @@ -2508,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: str | dict[str, Any] | SupportsRead[str] | SupportsRead[bytes]) -> "Config": + def from_json(cls, s: Union[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) -> Path | None: + def find_binary(self, *names: PathString, tools: bool = True) -> Optional[Path]: return find_binary(*names, root=self.tools() if tools else Path("/"), extra=self.extra_search_paths) def sandbox( @@ -2523,8 +2522,8 @@ class Config: devices: bool = False, relaxed: bool = False, tools: bool = True, - scripts: Path | None = None, - overlay: Path | None = None, + scripts: Optional[Path] = None, + overlay: Optional[Path] = None, options: Sequence[PathString] = (), ) -> AbstractContextManager[list[PathString]]: opt: list[PathString] = [*options] @@ -2556,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: str | None = None - setting: str | None = None - value: str | None = None + section: Optional[str] = None + setting: Optional[str] = None + value: Optional[str] = None for line in textwrap.dedent(path.read_text()).splitlines(): comment = line.find("#") @@ -4599,7 +4598,7 @@ def create_argument_parser(chdir: bool = True) -> argparse.ArgumentParser: help=argparse.SUPPRESS, ) - last_section: str | None = None + last_section: Optional[str] = None for s in SETTINGS: if s.section != last_section: @@ -4666,8 +4665,8 @@ class ConfigAction(argparse.Action): self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, - values: str | Sequence[Any] | None, - option_string: str | None = None, + values: Union[str, Sequence[Any], None], + option_string: Optional[str] = None, ) -> None: assert option_string is not None @@ -4802,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]) -> T | None: + def finalize_value(self, setting: ConfigSetting[T]) -> Optional[T]: # 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(T | None, self.cli.get(setting.dest))) is not None: + if (v := cast(Optional[T], 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 @@ -4836,7 +4835,7 @@ class ParseContext: if ( setting.dest not in self.cli and setting.dest in self.config - and (v := cast(T | None, self.config[setting.dest])) is not None + and (v := cast(Optional[T], self.config[setting.dest])) is not None ): return v @@ -4879,8 +4878,8 @@ class ParseContext: return default def match_config(self, path: Path, asserts: bool = False) -> bool: - condition_triggered: bool | None = None - match_triggered: bool | None = None + condition_triggered: Optional[bool] = None + match_triggered: Optional[bool] = None skip = False # If the config file does not exist, we assume it matches so that we look at the other files in the @@ -4961,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: ConfigSetting[object] | None # Hint to mypy that we might assign None + s: Optional[ConfigSetting[object]] # Hint to mypy that we might assign None assert path.is_absolute() extras = path.is_dir() @@ -5147,7 +5146,7 @@ def finalize_default_tools( main: ParseContext, finalized: dict[str, Any], *, - configdir: Path | None, + configdir: Optional[Path], resources: Path, ) -> Config: context = ParseContext(resources) @@ -5198,7 +5197,7 @@ def finalize_default_initrd( main: ParseContext, finalized: dict[str, Any], *, - configdir: Path | None, + configdir: Optional[Path], resources: Path, ) -> Config: context = ParseContext(resources) @@ -5245,7 +5244,7 @@ def finalize_default_initrd( return Config.from_dict(context.finalize()) -def finalize_configdir(directory: Path | None) -> Path | None: +def finalize_configdir(directory: Optional[Path]) -> Optional[Path]: """Allow locating all mkosi configuration in a mkosi/ subdirectory instead of in the top-level directory of a git repository. """ @@ -5315,7 +5314,7 @@ def parse_config( argv: Sequence[str] = (), *, resources: Path = Path("/"), -) -> tuple[Args, Config | None, tuple[Config, ...]]: +) -> tuple[Args, Optional[Config], tuple[Config, ...]]: argv = list(argv) context = ParseContext(resources) @@ -5434,7 +5433,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: list[str] | None = ( + dependencies: Optional[list[str]] = ( None if "dependencies" in context.cli or "dependencies" in context.config else [] ) @@ -5549,7 +5548,7 @@ def finalize_term() -> str: return term if sys.stderr.isatty() else "dumb" -def finalize_git_config(proxy_url: str | None, env: dict[str, str]) -> dict[str, str]: +def finalize_git_config(proxy_url: Optional[str], env: dict[str, str]) -> dict[str, str]: if proxy_url is None: return {} @@ -5574,19 +5573,19 @@ def yes_no(b: bool) -> str: return "yes" if b else "no" -def none_to_na(s: object | None) -> str: +def none_to_na(s: Optional[object]) -> str: return "n/a" if s is None else str(s) -def none_to_random(s: object | None) -> str: +def none_to_random(s: Optional[object]) -> str: return "random" if s is None else str(s) -def none_to_none(s: object | None) -> str: +def none_to_none(s: Optional[object]) -> str: return "none" if s is None else str(s) -def none_to_default(s: object | None) -> str: +def none_to_default(s: Optional[object]) -> str: return "default" if s is None else str(s) @@ -5605,7 +5604,7 @@ def format_bytes(num_bytes: int) -> str: return f"{num_bytes}B" -def format_bytes_or_none(num_bytes: int | None) -> str: +def format_bytes_or_none(num_bytes: Optional[int]) -> str: return format_bytes(num_bytes) if num_bytes is not None else "none" @@ -5613,7 +5612,7 @@ def format_octal(oct_value: int) -> str: return f"{oct_value:>04o}" -def format_octal_or_default(oct_value: int | None) -> str: +def format_octal_or_default(oct_value: Optional[int]) -> str: return format_octal(oct_value) if oct_value is not None else "default" @@ -5892,20 +5891,20 @@ class JsonEncoder(json.JSONEncoder): return super().default(o) -def dump_json(dict: dict[str, Any], indent: int | None = 4) -> str: +def dump_json(dict: dict[str, Any], indent: Optional[int] = 4) -> str: return json.dumps(dict, cls=JsonEncoder, indent=indent, sort_keys=True) E = TypeVar("E", bound=StrEnum) -def json_type_transformer(refcls: type[Args] | type[Config]) -> Callable[[str, Any], Any]: +def json_type_transformer(refcls: Union[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: str | None, fieldtype: type[Path | None]) -> Path | None: + def optional_path_transformer(path: Optional[str], fieldtype: type[Optional[Path]]) -> Optional[Path]: return Path(path) if path is not None else None def path_list_transformer(pathlist: list[str], fieldtype: type[list[Path]]) -> list[Path]: @@ -5915,13 +5914,13 @@ def json_type_transformer(refcls: type[Args] | type[Config]) -> Callable[[str, A return uuid.UUID(uuidstr) def optional_uuid_transformer( - uuidstr: str | None, fieldtype: type[uuid.UUID | None] - ) -> uuid.UUID | None: + uuidstr: Optional[str], fieldtype: type[Optional[uuid.UUID]] + ) -> Optional[uuid.UUID]: return uuid.UUID(uuidstr) if uuidstr is not None else None def root_password_transformer( - rootpw: list[str | bool] | None, fieldtype: type[tuple[str, bool] | None] - ) -> tuple[str, bool] | None: + rootpw: Optional[list[Union[str, bool]]], fieldtype: type[Optional[tuple[str, bool]]] + ) -> Optional[tuple[str, bool]]: if rootpw is None: return None return (cast(str, rootpw[0]), cast(bool, rootpw[1])) @@ -5945,7 +5944,7 @@ def json_type_transformer(refcls: type[Args] | type[Config]) -> Callable[[str, A def enum_transformer(enumval: str, fieldtype: type[E]) -> E: return fieldtype(enumval) - def optional_enum_transformer(enumval: str | None, fieldtype: type[E | None]) -> E | None: + def optional_enum_transformer(enumval: Optional[str], fieldtype: type[Optional[E]]) -> Optional[E]: 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]: @@ -5973,9 +5972,9 @@ def json_type_transformer(refcls: type[Args] | type[Config]) -> Callable[[str, A return ret def generic_version_transformer( - version: str | None, - fieldtype: type[GenericVersion | None], - ) -> GenericVersion | None: + version: Optional[str], + fieldtype: type[Optional[GenericVersion]], + ) -> Optional[GenericVersion]: return GenericVersion(version) if version is not None else None def certificate_source_transformer( @@ -6016,11 +6015,11 @@ def json_type_transformer(refcls: type[Args] | type[Config]) -> Callable[[str, A # to shut up the type checkers and rely on the tests. transformers: dict[Any, Callable[[Any, Any], Any]] = { Path: path_transformer, - Path | None: optional_path_transformer, + Optional[Path]: optional_path_transformer, list[Path]: path_list_transformer, uuid.UUID: uuid_transformer, - uuid.UUID | None: optional_uuid_transformer, - tuple[str, bool] | None: root_password_transformer, + Optional[uuid.UUID]: optional_uuid_transformer, + Optional[tuple[str, bool]]: root_password_transformer, list[ConfigTree]: config_tree_transformer, Architecture: enum_transformer, BiosBootloader: enum_transformer, @@ -6035,7 +6034,7 @@ def json_type_transformer(refcls: type[Args] | type[Config]) -> Callable[[str, A SecureBootSignTool: enum_transformer, Incremental: enum_transformer, BuildSourcesEphemeral: enum_transformer, - Distribution | None: optional_enum_transformer, + Optional[Distribution]: optional_enum_transformer, list[ManifestFormat]: enum_list_transformer, Verb: enum_transformer, DocFormat: enum_transformer, @@ -6054,7 +6053,7 @@ def json_type_transformer(refcls: type[Args] | type[Config]) -> Callable[[str, A } def json_transformer(key: str, val: Any) -> Any: - fieldtype: dataclasses.Field[Any] | None = fields_by_name.get(key) + fieldtype: Optional[dataclasses.Field[Any]] = 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: @@ -6078,7 +6077,7 @@ def want_selinux_relabel( config: Config, root: Path, fatal: bool = True, -) -> tuple[Path, str, Path, Path] | None: +) -> Optional[tuple[Path, str, Path, Path]]: if config.selinux_relabel == ConfigFeature.disabled: return None @@ -6165,8 +6164,8 @@ def systemd_tool_version(*tool: PathString, sandbox: SandboxProtocol = nosandbox def systemd_pty_forward( config: Config, *, - background: str | None = None, - title: str | None = None, + background: Optional[str] = None, + title: Optional[str] = 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 5263bf87c..665b75ec8 100644 --- a/mkosi/context.py +++ b/mkosi/context.py @@ -4,6 +4,7 @@ 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 @@ -21,7 +22,7 @@ class Context: resources: Path, keyring_dir: Path, metadata_dir: Path, - package_dir: Path | None = None, + package_dir: Optional[Path] = None, ) -> None: self.args = args self.config = config @@ -31,8 +32,8 @@ class Context: self.metadata_dir = metadata_dir self.package_dir = package_dir or (self.workspace / "packages") self.lowerdirs: list[PathString] = [] - self.upperdir: PathString | None = None - self.workdir: PathString | None = None + self.upperdir: Optional[PathString] = None + self.workdir: Optional[PathString] = None self.package_dir.mkdir(exist_ok=True) self.staging.mkdir() @@ -86,7 +87,7 @@ class Context: *, network: bool = False, devices: bool = False, - scripts: Path | None = None, + scripts: Optional[Path] = None, options: Sequence[PathString] = (), ) -> AbstractContextManager[list[PathString]]: return self.config.sandbox( diff --git a/mkosi/curl.py b/mkosi/curl.py index 0a225a8af..0db38229a 100644 --- a/mkosi/curl.py +++ b/mkosi/curl.py @@ -3,7 +3,7 @@ import os import subprocess from pathlib import Path -from typing import overload +from typing import Optional, 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: Path | None, + output_dir: Optional[Path], log: bool = True, ) -> None: ... @@ -30,7 +30,7 @@ def curl( ) -> str: ... -def curl(config: Config, url: str, *, output_dir: Path | None = None, log: bool = True) -> str | None: +def curl(config: Config, url: str, *, output_dir: Optional[Path] = None, log: bool = True) -> Optional[str]: result = run( [ "curl", diff --git a/mkosi/distribution/__init__.py b/mkosi/distribution/__init__.py index b9cb1c4ae..5d42b8f5b 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 +from typing import TYPE_CHECKING, Optional, Union 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) -> Distribution | None: + def default_tools_tree_distribution(cls) -> Optional[Distribution]: return None @classmethod @@ -151,7 +151,7 @@ class DistributionInstaller: return False -def detect_distribution(root: Path = Path("/")) -> tuple[Distribution | str | None, str | None]: +def detect_distribution(root: Path = Path("/")) -> tuple[Union[Distribution, str, None], Optional[str]]: try: os_release = read_env_file(root / "etc/os-release") except FileNotFoundError: @@ -169,7 +169,7 @@ def detect_distribution(root: Path = Path("/")) -> tuple[Distribution | str | No "azurelinux": "azure", } - d: Distribution | None = None + d: Optional[Distribution] = 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 2a929c1b3..428c9b27a 100644 --- a/mkosi/distribution/opensuse.py +++ b/mkosi/distribution/opensuse.py @@ -4,6 +4,7 @@ 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 @@ -41,7 +42,7 @@ class Installer(DistributionInstaller, distribution=Distribution.opensuse): return "grub2" @classmethod - def package_manager(cls, config: Config) -> type[Dnf] | type[Zypper]: + def package_manager(cls, config: Config) -> Union[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 2b22b9b8e..1e7ff294b 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 +from typing import Any, Optional 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) -> Path | None: + def sslcacert(context: Context) -> Optional[Path]: if context.config.mirror: return None @@ -41,7 +41,7 @@ class Installer(centos.Installer, distribution=Distribution.rhel): return path @staticmethod - def sslclientkey(context: Context) -> Path | None: + def sslclientkey(context: Context) -> Optional[Path]: 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) -> Path | None: + def sslclientcert(context: Context) -> Optional[Path]: if context.config.mirror: return None diff --git a/mkosi/initrd.py b/mkosi/initrd.py index fbdf2056c..5343a055d 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 cast +from typing import Optional, 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: str | None - uki_generator: str | None + initrd_generator: Optional[str] + uki_generator: Optional[str] verbose: bool @staticmethod @@ -228,7 +228,7 @@ def vconsole_config() -> list[str]: ] -def initrd_finalize(staging_dir: Path, output: str, output_dir: Path | None) -> None: +def initrd_finalize(staging_dir: Path, output: str, output_dir: Optional[Path]) -> 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 0c5903e73..a6338dfd2 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 +from typing import Final, Optional 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: Path | None - snapshot: str | None = None + signedby: Optional[Path] + snapshot: Optional[str] = None def __str__(self) -> str: return textwrap.dedent( diff --git a/mkosi/installer/dnf.py b/mkosi/installer/dnf.py index 5490505ce..670a77822 100644 --- a/mkosi/installer/dnf.py +++ b/mkosi/installer/dnf.py @@ -3,6 +3,7 @@ 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 @@ -62,7 +63,7 @@ class Dnf(PackageManager): context: Context, repositories: Sequence[RpmRepository], filelists: bool = True, - metadata_expire: str | None = None, + metadata_expire: Optional[str] = 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 f2942ed66..f2ca93b63 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, overload +from typing import Literal, Optional, 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: Path | None = None - sslclientkey: Path | None = None - sslclientcert: Path | None = None - priority: int | None = None + sslcacert: Optional[Path] = None + sslclientkey: Optional[Path] = None + sslclientcert: Optional[Path] = None + priority: Optional[int] = None @overload def find_rpm_gpgkey( context: Context, key: str, - fallback: str | None = None, + fallback: Optional[str] = None, *, required: Literal[True] = True, ) -> str: ... @@ -38,19 +38,19 @@ def find_rpm_gpgkey( def find_rpm_gpgkey( context: Context, key: str, - fallback: str | None = None, + fallback: Optional[str] = None, *, required: bool, -) -> str | None: ... +) -> Optional[str]: ... def find_rpm_gpgkey( context: Context, key: str, - fallback: str | None = None, + fallback: Optional[str] = None, *, required: bool = True, -) -> str | None: +) -> Optional[str]: # 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: str | None = None, + dbbackend: Optional[str] = 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 cffa9bdb8..ddfd185b2 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 +from typing import Any, NoReturn, Optional 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: str | None = None) -> NoReturn: +def die(message: str, *, hint: Optional[str] = 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: str | None = None) -> Iterator[list[Any]]: +def complete_step(text: str, text2: Optional[str] = None) -> Iterator[list[Any]]: global LEVEL log_step(text) @@ -116,7 +116,7 @@ def complete_step(text: str, text2: str | None = None) -> Iterator[list[Any]]: class Formatter(logging.Formatter): - def __init__(self, fmt: str | None = None, *args: Any, **kwargs: Any) -> None: + def __init__(self, fmt: Optional[str] = None, *args: Any, **kwargs: Any) -> None: fmt = fmt or "%(message)s" self.formatters = { diff --git a/mkosi/manifest.py b/mkosi/manifest.py index 41e4c40c4..df3639386 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 +from typing import IO, Any, Optional from mkosi.config import ManifestFormat, OutputFormat from mkosi.context import Context @@ -43,7 +43,7 @@ class PackageManifest: @dataclasses.dataclass class SourcePackageManifest: name: str - changelog: str | None + changelog: Optional[str] 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 88ab5608a..f15791890 100644 --- a/mkosi/mounts.py +++ b/mkosi/mounts.py @@ -6,6 +6,7 @@ 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 @@ -48,7 +49,7 @@ def mount_overlay( lowerdirs: Sequence[Path], dst: Path, *, - upperdir: Path | None = None, + upperdir: Optional[Path] = None, ) -> Iterator[Path]: with contextlib.ExitStack() as stack: if upperdir is None: @@ -85,7 +86,7 @@ def mount_overlay( def finalize_source_mounts( config: Config, *, - ephemeral: BuildSourcesEphemeral | bool, + ephemeral: Union[BuildSourcesEphemeral, bool], ) -> Iterator[list[PathString]]: with contextlib.ExitStack() as stack: options: list[PathString] = [] diff --git a/mkosi/pager.py b/mkosi/pager.py index 84de95983..7077fb0a4 100644 --- a/mkosi/pager.py +++ b/mkosi/pager.py @@ -2,9 +2,10 @@ import os import pydoc +from typing import Optional -def page(text: str, enabled: bool | None) -> None: +def page(text: str, enabled: Optional[bool]) -> 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 397022179..535b6cdd5 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 +from typing import Any, Final, Optional 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: int | None - split_path: Path | None - roothash: str | None + partno: Optional[int] + split_path: Optional[Path] + roothash: Optional[str] @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]) -> str | None: - roothash: str | None = None - usrhash: str | None = None +def finalize_roothash(partitions: Sequence[Partition]) -> Optional[str]: + roothash: Optional[str] = None + usrhash: Optional[str] = None for p in partitions: if (h := p.roothash) is None: @@ -71,7 +71,7 @@ def finalize_roothash(partitions: Sequence[Partition]) -> str | None: return f"roothash={roothash}" if roothash else f"usrhash={usrhash}" if usrhash else None -def finalize_root(partitions: Sequence[Partition]) -> str | None: +def finalize_root(partitions: Sequence[Partition]) -> Optional[str]: 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 8eac9a309..6617677bf 100644 --- a/mkosi/qemu.py +++ b/mkosi/qemu.py @@ -25,6 +25,7 @@ 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 ( @@ -183,7 +184,7 @@ class OvmfConfig: vars_format: str -def find_ovmf_firmware(config: Config, firmware: Firmware) -> OvmfConfig | None: +def find_ovmf_firmware(config: Config, firmware: Firmware) -> Optional[OvmfConfig]: if not firmware.is_uefi(): return None @@ -300,7 +301,7 @@ def start_swtpm(config: Config) -> Iterator[Path]: proc.terminate() -def find_virtiofsd(*, root: Path = Path("/"), extra: Sequence[Path] = ()) -> Path | None: +def find_virtiofsd(*, root: Path = Path("/"), extra: Sequence[Path] = ()) -> Optional[Path]: if p := find_binary("virtiofsd", root=root, extra=extra): return p @@ -604,8 +605,8 @@ def qemu_version(config: Config, binary: Path) -> GenericVersion: def finalize_firmware( config: Config, - kernel: Path | None, - kerneltype: KernelType | None = None, + kernel: Optional[Path], + kerneltype: Optional[KernelType] = None, ) -> Firmware: if config.firmware != Firmware.auto: return config.firmware @@ -731,7 +732,7 @@ def finalize_drive(config: Config, drive: Drive) -> Iterator[Path]: @contextlib.contextmanager -def finalize_initrd(config: Config) -> Iterator[Path | None]: +def finalize_initrd(config: Config) -> Iterator[Optional[Path]]: 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 @@ -901,7 +902,7 @@ def finalize_register(config: Config) -> bool: return True -def register_machine(config: Config, pid: int, fname: Path, cid: int | None) -> None: +def register_machine(config: Config, pid: int, fname: Path, cid: Optional[int]) -> None: if not finalize_register(config): return @@ -1103,7 +1104,7 @@ def run_qemu(args: Args, config: Config) -> None: cmdline += ["-accel", accel] - cid: int | None = None + cid: Optional[int] = 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]) @@ -1151,8 +1152,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: socket.socket | None = None - notify: AsyncioThread[tuple[str, str]] | None = None + vsock: Optional[socket.socket] = None + notify: Optional[AsyncioThread[tuple[str, str]]] = 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 906b6abdb..cb3fcd53b 100644 --- a/mkosi/resources/man/mkosi.1.md +++ b/mkosi/resources/man/mkosi.1.md @@ -3309,7 +3309,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.10. +- The minimum required Python version is 3.9. ## Unprivileged User Namespaces diff --git a/mkosi/run.py b/mkosi/run.py index 6ddc98f5a..6a1f664f7 100644 --- a/mkosi/run.py +++ b/mkosi/run.py @@ -20,7 +20,7 @@ from collections.abc import Awaitable, Collection, Iterator, Mapping, Sequence from contextlib import AbstractContextManager from pathlib import Path from types import FrameType, TracebackType -from typing import TYPE_CHECKING, Any, Callable, Generic, NoReturn, Protocol, TypeVar, cast +from typing import TYPE_CHECKING, Any, Callable, Generic, NoReturn, Optional, Protocol, TypeVar, cast import mkosi import mkosi.sandbox @@ -229,7 +229,7 @@ def run( stdin: _FILE = None, stdout: _FILE = None, stderr: _FILE = None, - input: str | None = None, + input: Optional[str] = None, env: Mapping[str, str] = {}, log: bool = True, success_exit_status: Sequence[int] = (0,), @@ -261,7 +261,7 @@ def _preexec( cmd: list[str], env: dict[str, Any], sandbox: list[str], - preexec: Callable[[], None] | None, + preexec: Optional[Callable[[], None]], ) -> None: with uncaught_exception_handler(exit=os._exit, fork=True, proceed=True): if preexec: @@ -301,12 +301,12 @@ def spawn( stdin: _FILE = None, stdout: _FILE = None, stderr: _FILE = None, - user: int | None = None, - group: int | None = None, + user: Optional[int] = None, + group: Optional[int] = None, pass_fds: Collection[int] = (), env: Mapping[str, str] = {}, log: bool = True, - preexec: Callable[[], None] | None = None, + preexec: Optional[Callable[[], None]] = None, success_exit_status: Sequence[int] = (0,), setup: Sequence[PathString] = (), sandbox: AbstractContextManager[Sequence[PathString]] = nosandbox(), @@ -414,7 +414,7 @@ def spawn( def finalize_path( - root: Path | None = None, + root: Optional[Path] = None, extra: Sequence[Path] = (), prefix_usr: bool = False, relaxed: bool = False, @@ -446,9 +446,9 @@ def finalize_path( def find_binary( *names: PathString, - root: Path | None = None, + root: Optional[Path] = None, extra: Sequence[Path] = (), -) -> Path | None: +) -> Optional[Path]: root = root or Path("/") path = finalize_path(root=root, extra=extra, prefix_usr=True) @@ -528,9 +528,9 @@ class AsyncioThread(threading.Thread, Generic[T]): def __exit__( self, - type: type[BaseException] | None, - value: BaseException | None, - traceback: TracebackType | None, + type: Optional[type[BaseException]], + value: Optional[BaseException], + traceback: Optional[TracebackType], ) -> None: self.cancel() self.join() @@ -543,7 +543,7 @@ class AsyncioThread(threading.Thread, Generic[T]): pass -def workdir(path: Path, sandbox: SandboxProtocol | None = None) -> str: +def workdir(path: Path, sandbox: Optional[SandboxProtocol] = None) -> str: subdir = "/" if sandbox and sandbox == nosandbox else "/work" return joinpath(subdir, os.fspath(path)) @@ -606,10 +606,10 @@ def sandbox_cmd( *, network: bool = False, devices: bool = False, - scripts: Path | None = None, + scripts: Optional[Path] = None, tools: Path = Path("/"), relaxed: bool = False, - overlay: Path | None = None, + overlay: Optional[Path] = None, options: Sequence[PathString] = (), extra: Sequence[Path] = (), ) -> Iterator[list[PathString]]: @@ -715,7 +715,7 @@ def sandbox_cmd( if scripts: cmdline += ["--ro-bind", scripts, "/scripts"] - tmp: Path | None + tmp: Optional[Path] if not overlay and not relaxed: tmp = stack.enter_context(vartmpdir()) diff --git a/mkosi/util.py b/mkosi/util.py index 01bf394ac..19f0b4c79 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, Protocol, TypeVar +from typing import IO, Any, Callable, Optional, Protocol, TypeVar, Union 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 = None | int | IO[Any] -PathString = Path | str +_FILE = Union[None, int, IO[Any]] +PathString = Union[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) -> str | None: +def startswith(s: str, prefix: str) -> Optional[str]: if s.startswith(prefix): return s.removeprefix(prefix) return None diff --git a/pyproject.toml b/pyproject.toml index bbad8a586..379825e41 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.10" +requires-python = ">=3.9" 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.10" +pythonVersion = "3.9" include = [ "mkosi/**/*.py", "tests/**/*.py", @@ -61,7 +61,7 @@ include = [ ] [tool.mypy] -python_version = "3.10" +python_version = 3.9 # belonging to --strict warn_unused_configs = true disallow_any_generics = true diff --git a/tests/__init__.py b/tests/__init__.py index 96f06552f..3134a7a28 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 +from typing import Any, Optional import pytest @@ -44,9 +44,9 @@ class Image: def __exit__( self, - type: type[BaseException] | None, - value: BaseException | None, - traceback: TracebackType | None, + type: Optional[type[BaseException]], + value: Optional[BaseException], + traceback: Optional[TracebackType], ) -> None: def clean() -> None: acquire_privileges() diff --git a/tests/test_json.py b/tests/test_json.py index 6d4b9d0d8..d1f367c71 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -4,6 +4,7 @@ import os import textwrap import uuid from pathlib import Path +from typing import Optional import pytest @@ -48,7 +49,7 @@ from mkosi.distribution import Distribution @pytest.mark.parametrize("path", [None, "/baz/qux"]) -def test_args(path: Path | None) -> None: +def test_args(path: Optional[Path]) -> None: dump = textwrap.dedent( f"""\ {{