From 5fa008be5a3b4c05a7992054d509910bdfcb428c Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 15:51:14 +0200 Subject: [PATCH 01/31] App, lib, cmds: Fix mypy.explicit-override fallout This commit adds @override decorators to approximately 300 methods across 76 files that inherit from base classes such as AbstractCmd, FileContext, ExecContext, Distro, SSHClient, and others. The decorator ensures the type checker can verify that overridden methods have compatible signatures and prevents accidental shadowing of inherited methods without intent. Files modified include command classes, library modules, distro implementations, and SSH client implementations. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL with pi.dev v Signed-off-by: Jan Lindemann --- src/python/jw/pkg/App.py | 5 ++++- src/python/jw/pkg/CmdBase.py | 2 ++ src/python/jw/pkg/cmds/Cmd.py | 3 ++- src/python/jw/pkg/cmds/CmdPkg.py | 4 +++- src/python/jw/pkg/cmds/CmdPlatform.py | 4 +++- src/python/jw/pkg/cmds/CmdPosix.py | 4 +++- src/python/jw/pkg/cmds/CmdProjects.py | 4 +++- src/python/jw/pkg/cmds/CmdSecrets.py | 4 +++- src/python/jw/pkg/cmds/pkg/CmdDelete.py | 4 +++- src/python/jw/pkg/cmds/pkg/CmdDup.py | 4 +++- src/python/jw/pkg/cmds/pkg/CmdInstall.py | 4 +++- src/python/jw/pkg/cmds/pkg/CmdLs.py | 4 +++- src/python/jw/pkg/cmds/pkg/CmdMeta.py | 4 +++- .../jw/pkg/cmds/pkg/CmdRebootRequired.py | 4 +++- src/python/jw/pkg/cmds/pkg/CmdRefresh.py | 4 +++- src/python/jw/pkg/cmds/pkg/CmdSelect.py | 4 +++- src/python/jw/pkg/cmds/pkg/NamedPkgsCmd.py | 3 ++- src/python/jw/pkg/cmds/platform/CmdInfo.py | 4 +++- src/python/jw/pkg/cmds/posix/Cmd.py | 3 ++- src/python/jw/pkg/cmds/posix/CmdCopy.py | 4 +++- src/python/jw/pkg/cmds/posix/CmdTar.py | 4 +++- src/python/jw/pkg/cmds/posix/tar/Cmd.py | 3 ++- .../jw/pkg/cmds/posix/tar/CmdExtract.py | 4 +++- .../pkg/cmds/projects/BaseCmdPkgRelations.py | 4 +++- src/python/jw/pkg/cmds/projects/Cmd.py | 3 ++- src/python/jw/pkg/cmds/projects/CmdBuild.py | 4 +++- .../cmds/projects/CmdCanonicalizeRemotes.py | 4 +++- src/python/jw/pkg/cmds/projects/CmdCflags.py | 4 +++- src/python/jw/pkg/cmds/projects/CmdCheck.py | 4 +++- .../jw/pkg/cmds/projects/CmdCommands.py | 4 +++- .../jw/pkg/cmds/projects/CmdCreateFile.py | 3 +++ .../pkg/cmds/projects/CmdCreatePkgConfig.py | 4 +++- src/python/jw/pkg/cmds/projects/CmdExepath.py | 4 +++- .../jw/pkg/cmds/projects/CmdGetAuthInfo.py | 4 +++- src/python/jw/pkg/cmds/projects/CmdGetval.py | 4 +++- .../jw/pkg/cmds/projects/CmdHtdocsDir.py | 4 +++- src/python/jw/pkg/cmds/projects/CmdLdflags.py | 4 +++- .../jw/pkg/cmds/projects/CmdLdlibpath.py | 4 +++- src/python/jw/pkg/cmds/projects/CmdLibname.py | 4 +++- .../jw/pkg/cmds/projects/CmdListRepos.py | 4 +++- src/python/jw/pkg/cmds/projects/CmdModules.py | 4 +++- src/python/jw/pkg/cmds/projects/CmdPath.py | 4 +++- src/python/jw/pkg/cmds/projects/CmdProjDir.py | 4 +++- .../jw/pkg/cmds/projects/CmdPythonpath.py | 4 +++- .../jw/pkg/cmds/projects/CmdRequiredOsPkg.py | 4 +++- src/python/jw/pkg/cmds/projects/CmdSummary.py | 4 +++- src/python/jw/pkg/cmds/projects/CmdTest.py | 4 +++- src/python/jw/pkg/cmds/projects/CmdTmplDir.py | 4 +++- src/python/jw/pkg/cmds/projects/check/Cmd.py | 3 ++- .../jw/pkg/cmds/projects/check/CmdDep.py | 4 +++- src/python/jw/pkg/cmds/secrets/Cmd.py | 3 ++- .../pkg/cmds/secrets/CmdCompileTemplates.py | 4 +++- src/python/jw/pkg/cmds/secrets/CmdInstall.py | 4 +++- .../cmds/secrets/CmdListCompilationOutput.py | 4 +++- .../jw/pkg/cmds/secrets/CmdListSecrets.py | 4 +++- .../jw/pkg/cmds/secrets/CmdListTemplates.py | 3 ++- .../cmds/secrets/CmdRmCompilationOutput.py | 3 ++- src/python/jw/pkg/lib/App.py | 3 ++- src/python/jw/pkg/lib/Cmd.py | 6 +++++- src/python/jw/pkg/lib/ExecContext.py | 13 ++++++++++++- src/python/jw/pkg/lib/FileContext.py | 3 ++- src/python/jw/pkg/lib/Package.py | 3 ++- src/python/jw/pkg/lib/PackageFilter.py | 3 ++- src/python/jw/pkg/lib/ProcFilter.py | 3 ++- src/python/jw/pkg/lib/ProcFilterGpg.py | 3 ++- src/python/jw/pkg/lib/Result.py | 3 +++ src/python/jw/pkg/lib/TarIo.py | 19 +++++++++++-------- src/python/jw/pkg/lib/Types.py | 5 ++++- src/python/jw/pkg/lib/Uri.py | 4 +++- src/python/jw/pkg/lib/distros/arch/Distro.py | 9 ++++++++- .../jw/pkg/lib/distros/debian/Distro.py | 9 ++++++++- src/python/jw/pkg/lib/distros/suse/Distro.py | 10 +++++++++- src/python/jw/pkg/lib/ec/Curl.py | 3 ++- src/python/jw/pkg/lib/ec/Local.py | 12 +++++++++++- src/python/jw/pkg/lib/ec/SSHClient.py | 4 +++- src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py | 4 ++++ src/python/jw/pkg/lib/ec/ssh/Exec.py | 3 ++- src/python/jw/pkg/lib/ec/ssh/Paramiko.py | 5 ++++- 78 files changed, 263 insertions(+), 81 deletions(-) diff --git a/src/python/jw/pkg/App.py b/src/python/jw/pkg/App.py index f161253d..f46e8bc1 100644 --- a/src/python/jw/pkg/App.py +++ b/src/python/jw/pkg/App.py @@ -10,7 +10,7 @@ import sys from enum import Enum, auto from functools import cache -from typing import TYPE_CHECKING +from typing import override, TYPE_CHECKING from .lib.App import App as Base from .lib.Distro import Distro @@ -374,11 +374,13 @@ class App(Base): default_pkg_filter = pkg_filter, ) + @override async def __aexit__(self, exc_type, exc, tb) -> None: if self.__exec_context is not None: await self.__exec_context.close() self.__exec_context = None + @override def _add_arguments(self, parser) -> None: super()._add_arguments(parser) parser.add_argument('-t', '--topdir', default = None, help = 'Project Path') @@ -421,6 +423,7 @@ class App(Base): help = 'Default filter for all distribution package-related operations', ) + @override async def _run(self, args: argparse.Namespace) -> None: self.___topdir = args.topdir self.___pretty_topdir = self.__format_topdir(self.___topdir, args.topdir_format) diff --git a/src/python/jw/pkg/CmdBase.py b/src/python/jw/pkg/CmdBase.py index 65a517b5..17f16759 100644 --- a/src/python/jw/pkg/CmdBase.py +++ b/src/python/jw/pkg/CmdBase.py @@ -1,8 +1,10 @@ +from typing import override from .App import App from .lib.Cmd import Cmd as Base class CmdBase(Base): + @override # pyright: ignore[reportArgumentType] @property def app(self) -> App: ret = super().app diff --git a/src/python/jw/pkg/cmds/Cmd.py b/src/python/jw/pkg/cmds/Cmd.py index 7fbb7e21..5f9509cd 100644 --- a/src/python/jw/pkg/cmds/Cmd.py +++ b/src/python/jw/pkg/cmds/Cmd.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import override, TYPE_CHECKING from ..App import App as Parent from ..CmdBase import CmdBase as Base @@ -20,6 +20,7 @@ class Cmd(Base): # export ) -> None: super().__init__(parent, name, help, aliases = aliases) + @override async def _run(self, args): # Missing subcommand self.print_help(1) diff --git a/src/python/jw/pkg/cmds/CmdPkg.py b/src/python/jw/pkg/cmds/CmdPkg.py index 124e7b65..48bd21ac 100644 --- a/src/python/jw/pkg/cmds/CmdPkg.py +++ b/src/python/jw/pkg/cmds/CmdPkg.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent @@ -18,6 +18,7 @@ class CmdPkg(Cmd): # export ) self.load_subcommands() + @override async def _run(self, args): import sys @@ -25,5 +26,6 @@ class CmdPkg(Cmd): # export self.parser.print_help() sys.exit(1) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) diff --git a/src/python/jw/pkg/cmds/CmdPlatform.py b/src/python/jw/pkg/cmds/CmdPlatform.py index 05c84473..130b2b5b 100644 --- a/src/python/jw/pkg/cmds/CmdPlatform.py +++ b/src/python/jw/pkg/cmds/CmdPlatform.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent @@ -15,6 +15,7 @@ class CmdPlatform(Cmd): # export ) self.load_subcommands() + @override async def _run(self, args): import sys @@ -22,5 +23,6 @@ class CmdPlatform(Cmd): # export self.parser.print_help() sys.exit(1) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) diff --git a/src/python/jw/pkg/cmds/CmdPosix.py b/src/python/jw/pkg/cmds/CmdPosix.py index 72bcece6..3e3a3747 100644 --- a/src/python/jw/pkg/cmds/CmdPosix.py +++ b/src/python/jw/pkg/cmds/CmdPosix.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent @@ -20,6 +20,7 @@ class CmdPosix(Cmd): # export ) self.load_subcommands() + @override async def _run(self, args): import sys @@ -27,5 +28,6 @@ class CmdPosix(Cmd): # export self.parser.print_help() sys.exit(1) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) diff --git a/src/python/jw/pkg/cmds/CmdProjects.py b/src/python/jw/pkg/cmds/CmdProjects.py index 99d47815..2bde3392 100644 --- a/src/python/jw/pkg/cmds/CmdProjects.py +++ b/src/python/jw/pkg/cmds/CmdProjects.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent @@ -17,6 +17,7 @@ class CmdProjects(Cmd): # export ) self.load_subcommands() + @override async def _run(self, args): import sys @@ -24,5 +25,6 @@ class CmdProjects(Cmd): # export self.parser.print_help() sys.exit(1) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) diff --git a/src/python/jw/pkg/cmds/CmdSecrets.py b/src/python/jw/pkg/cmds/CmdSecrets.py index cd5eaec9..3b28ed0a 100644 --- a/src/python/jw/pkg/cmds/CmdSecrets.py +++ b/src/python/jw/pkg/cmds/CmdSecrets.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent @@ -13,6 +13,7 @@ class CmdSecrets(Cmd): # export super().__init__(parent, 'secrets', help = 'Manage package secrets') self.load_subcommands() + @override async def _run(self, args): import sys @@ -20,5 +21,6 @@ class CmdSecrets(Cmd): # export self.parser.print_help() sys.exit(1) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) diff --git a/src/python/jw/pkg/cmds/pkg/CmdDelete.py b/src/python/jw/pkg/cmds/pkg/CmdDelete.py index 8a75fafc..5a1c68b3 100644 --- a/src/python/jw/pkg/cmds/pkg/CmdDelete.py +++ b/src/python/jw/pkg/cmds/pkg/CmdDelete.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent @@ -12,11 +12,13 @@ class CmdDelete(Cmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'delete', help = 'Delete packages by name') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument( 'names', nargs = '*', help = 'Names of packages to be deleted' ) + @override async def _run(self, args: Namespace) -> None: return await self.distro.delete(args.names) diff --git a/src/python/jw/pkg/cmds/pkg/CmdDup.py b/src/python/jw/pkg/cmds/pkg/CmdDup.py index 378a8804..9ac53e64 100644 --- a/src/python/jw/pkg/cmds/pkg/CmdDup.py +++ b/src/python/jw/pkg/cmds/pkg/CmdDup.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent @@ -12,6 +12,7 @@ class CmdDup(Cmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'dup', help = 'Upgrade distribution') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument( @@ -21,5 +22,6 @@ class CmdDup(Cmd): # export help = "Only download packages from the repos, don't install them, yet", ) + @override async def _run(self, args: Namespace) -> None: return await self.distro.dup(download_only = args.download_only) diff --git a/src/python/jw/pkg/cmds/pkg/CmdInstall.py b/src/python/jw/pkg/cmds/pkg/CmdInstall.py index dbeaebcf..22e75800 100644 --- a/src/python/jw/pkg/cmds/pkg/CmdInstall.py +++ b/src/python/jw/pkg/cmds/pkg/CmdInstall.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import override, TYPE_CHECKING from .Cmd import Cmd, Parent @@ -16,6 +16,7 @@ class CmdInstall(Cmd): # export help = "Install the distribution's notion of available packages", ) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('names', nargs = '*', help = 'Packages to be installed') @@ -32,6 +33,7 @@ class CmdInstall(Cmd): # export help = "Don't expand macros in ", ) + @override async def _run(self, args: Namespace) -> None: names = ( args.names if args.fixed_strings else self.distro.expand_macros(args.names) diff --git a/src/python/jw/pkg/cmds/pkg/CmdLs.py b/src/python/jw/pkg/cmds/pkg/CmdLs.py index ccbd1bf3..b3a74d6f 100644 --- a/src/python/jw/pkg/cmds/pkg/CmdLs.py +++ b/src/python/jw/pkg/cmds/pkg/CmdLs.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .NamedPkgsCmd import NamedPkgsCmd @@ -14,9 +14,11 @@ class CmdLs(NamedPkgsCmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'ls', help = 'List package contents') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) + @override async def _run(self, args: Namespace) -> None: for name in args.names: print('\n'.join(await self.distro.pkg_files(name))) diff --git a/src/python/jw/pkg/cmds/pkg/CmdMeta.py b/src/python/jw/pkg/cmds/pkg/CmdMeta.py index aa70f644..7e29a9c3 100644 --- a/src/python/jw/pkg/cmds/pkg/CmdMeta.py +++ b/src/python/jw/pkg/cmds/pkg/CmdMeta.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .NamedPkgsCmd import NamedPkgsCmd, Parent @@ -12,9 +12,11 @@ class CmdMeta(NamedPkgsCmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'meta', help = 'List package metadata') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) + @override async def _run(self, args: Namespace) -> None: names = await self.distro.select(args.names) for name in names: diff --git a/src/python/jw/pkg/cmds/pkg/CmdRebootRequired.py b/src/python/jw/pkg/cmds/pkg/CmdRebootRequired.py index 13097ea8..24f94c7b 100644 --- a/src/python/jw/pkg/cmds/pkg/CmdRebootRequired.py +++ b/src/python/jw/pkg/cmds/pkg/CmdRebootRequired.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent @@ -16,8 +16,10 @@ class CmdRebootRequired(Cmd): # export help = 'Check whether the machine needs rebooting' ) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) + @override async def _run(self, args: Namespace) -> None: await self.distro.reboot_required() diff --git a/src/python/jw/pkg/cmds/pkg/CmdRefresh.py b/src/python/jw/pkg/cmds/pkg/CmdRefresh.py index 32a79284..5b997bcc 100644 --- a/src/python/jw/pkg/cmds/pkg/CmdRefresh.py +++ b/src/python/jw/pkg/cmds/pkg/CmdRefresh.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent @@ -16,8 +16,10 @@ class CmdRefresh(Cmd): # export help = "Refresh the distribution's notion of available packages", ) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) + @override async def _run(self, args: Namespace) -> None: return await self.distro.ref() diff --git a/src/python/jw/pkg/cmds/pkg/CmdSelect.py b/src/python/jw/pkg/cmds/pkg/CmdSelect.py index 3a51e13a..0432f0cd 100644 --- a/src/python/jw/pkg/cmds/pkg/CmdSelect.py +++ b/src/python/jw/pkg/cmds/pkg/CmdSelect.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from ...lib.PackageFilter import PackageFilterString from .Cmd import Cmd, Parent @@ -13,10 +13,12 @@ class CmdSelect(Cmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'select', help = 'Select packages by filter') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('filter', help = 'Package filter string') + @override async def _run(self, args: Namespace) -> None: filter = PackageFilterString(args.filter) if args.filter else None for p in await self.distro.select(filter = filter): diff --git a/src/python/jw/pkg/cmds/pkg/NamedPkgsCmd.py b/src/python/jw/pkg/cmds/pkg/NamedPkgsCmd.py index ba0c8217..4fee5377 100644 --- a/src/python/jw/pkg/cmds/pkg/NamedPkgsCmd.py +++ b/src/python/jw/pkg/cmds/pkg/NamedPkgsCmd.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent @@ -12,6 +12,7 @@ class NamedPkgsCmd(Cmd): # export def __init__(self, parent: Parent, name: str, help: str) -> None: super().__init__(parent, name, help) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('names', nargs = '*', help = 'Package names') diff --git a/src/python/jw/pkg/cmds/platform/CmdInfo.py b/src/python/jw/pkg/cmds/platform/CmdInfo.py index ddacfd56..3b7814ee 100644 --- a/src/python/jw/pkg/cmds/platform/CmdInfo.py +++ b/src/python/jw/pkg/cmds/platform/CmdInfo.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from ...lib.Distro import Distro from .Cmd import Cmd, Parent @@ -15,6 +15,7 @@ class CmdInfo(Cmd): # export parent, 'info', help = 'Retrieve information about target platform' ) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument( @@ -23,5 +24,6 @@ class CmdInfo(Cmd): # export help = f'Format string, expanding macros {", ".join(Distro.macros())}', ) + @override async def _run(self, args: Namespace) -> None: print(self.app.distro.expand_macros(args.format)) diff --git a/src/python/jw/pkg/cmds/posix/Cmd.py b/src/python/jw/pkg/cmds/posix/Cmd.py index d43b29f1..3093618b 100644 --- a/src/python/jw/pkg/cmds/posix/Cmd.py +++ b/src/python/jw/pkg/cmds/posix/Cmd.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import override, TYPE_CHECKING from ...CmdBase import CmdBase as Base from ..CmdPosix import CmdPosix as Parent @@ -13,6 +13,7 @@ class Cmd(Base): # export def __init__(self, parent: Parent, name: str, help: str) -> None: super().__init__(parent, name, help) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) diff --git a/src/python/jw/pkg/cmds/posix/CmdCopy.py b/src/python/jw/pkg/cmds/posix/CmdCopy.py index e38c15bd..2dc600ec 100644 --- a/src/python/jw/pkg/cmds/posix/CmdCopy.py +++ b/src/python/jw/pkg/cmds/posix/CmdCopy.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from ...lib.util import copy from .Cmd import Cmd, Parent @@ -13,6 +13,7 @@ class CmdCopy(Cmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'copy', help = 'Copy files') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('src', help = 'Source file URI') @@ -33,6 +34,7 @@ class CmdCopy(Cmd): # export help = "Don't expand macros in and ", ) + @override async def _run(self, args: Namespace) -> None: def __expand(url: str) -> str: diff --git a/src/python/jw/pkg/cmds/posix/CmdTar.py b/src/python/jw/pkg/cmds/posix/CmdTar.py index 0db6d835..80355a24 100644 --- a/src/python/jw/pkg/cmds/posix/CmdTar.py +++ b/src/python/jw/pkg/cmds/posix/CmdTar.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent @@ -13,6 +13,7 @@ class CmdTar(Cmd): # export super().__init__(parent, 'tar', help = 'Handle tar archives') self.load_subcommands() + @override async def _run(self, args): import sys @@ -20,5 +21,6 @@ class CmdTar(Cmd): # export self.parser.print_help() sys.exit(1) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) diff --git a/src/python/jw/pkg/cmds/posix/tar/Cmd.py b/src/python/jw/pkg/cmds/posix/tar/Cmd.py index c08cacee..f3bf5e5d 100644 --- a/src/python/jw/pkg/cmds/posix/tar/Cmd.py +++ b/src/python/jw/pkg/cmds/posix/tar/Cmd.py @@ -1,7 +1,7 @@ from __future__ import annotations from contextlib import asynccontextmanager -from typing import TYPE_CHECKING +from typing import override, TYPE_CHECKING from ....CmdBase import CmdBase as Base from ....lib.FileContext import FileContext @@ -27,6 +27,7 @@ class Cmd(Base): # export ) yield ret + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument( diff --git a/src/python/jw/pkg/cmds/posix/tar/CmdExtract.py b/src/python/jw/pkg/cmds/posix/tar/CmdExtract.py index c9b2f191..ace743f9 100644 --- a/src/python/jw/pkg/cmds/posix/tar/CmdExtract.py +++ b/src/python/jw/pkg/cmds/posix/tar/CmdExtract.py @@ -2,7 +2,7 @@ from __future__ import annotations from ....lib.log import DEBUG, log from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -12,10 +12,12 @@ class CmdExtract(Cmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'x', help = 'Extract a tar archive') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('dst', help = 'Destination root URI') + @override async def _run(self, args: Namespace) -> None: async with self.ctx(dst = args.dst) as ctx: paths = await ctx.extract(ctx.dst.root) diff --git a/src/python/jw/pkg/cmds/projects/BaseCmdPkgRelations.py b/src/python/jw/pkg/cmds/projects/BaseCmdPkgRelations.py index 42dc977e..8d5c9cb2 100644 --- a/src/python/jw/pkg/cmds/projects/BaseCmdPkgRelations.py +++ b/src/python/jw/pkg/cmds/projects/BaseCmdPkgRelations.py @@ -2,7 +2,7 @@ from __future__ import annotations import re -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent from .lib.pkg_relations import VersionSyntax @@ -48,6 +48,7 @@ class BaseCmdPkgRelations(Cmd): super().__init__(parent, 'pkg-' + relation, help = help) self.relation = relation + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument( @@ -141,6 +142,7 @@ class BaseCmdPkgRelations(Cmd): help = 'Put double quotes around each listed dependency', ) + @override async def _run(self, args: Namespace) -> None: return self.print_pkg_relations(self.relation, args) diff --git a/src/python/jw/pkg/cmds/projects/Cmd.py b/src/python/jw/pkg/cmds/projects/Cmd.py index 299733a7..97533bca 100644 --- a/src/python/jw/pkg/cmds/projects/Cmd.py +++ b/src/python/jw/pkg/cmds/projects/Cmd.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import override, TYPE_CHECKING from ...CmdBase import CmdBase as Base from ..CmdProjects import CmdProjects as Parent @@ -13,6 +13,7 @@ class Cmd(Base): # export def __init__(self, parent: Parent, name: str, help: str) -> None: super().__init__(parent, name, help) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) diff --git a/src/python/jw/pkg/cmds/projects/CmdBuild.py b/src/python/jw/pkg/cmds/projects/CmdBuild.py index 9f2f3f43..3972bda5 100644 --- a/src/python/jw/pkg/cmds/projects/CmdBuild.py +++ b/src/python/jw/pkg/cmds/projects/CmdBuild.py @@ -5,7 +5,7 @@ import os import re from functools import lru_cache -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from ...App import Scope from ...lib.log import DEBUG, ERR, NOTICE, log @@ -22,6 +22,7 @@ class CmdBuild(Cmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'build', help = 'janware software project build tool') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument( @@ -90,6 +91,7 @@ class CmdBuild(Cmd): # export help = 'Modules to be built', ) + @override async def _run(self, args: Namespace) -> None: @lru_cache(maxsize = None) diff --git a/src/python/jw/pkg/cmds/projects/CmdCanonicalizeRemotes.py b/src/python/jw/pkg/cmds/projects/CmdCanonicalizeRemotes.py index a852cf67..f167cfa7 100644 --- a/src/python/jw/pkg/cmds/projects/CmdCanonicalizeRemotes.py +++ b/src/python/jw/pkg/cmds/projects/CmdCanonicalizeRemotes.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from ...lib.base import InputMode from ...lib.log import NOTICE, log @@ -20,6 +20,7 @@ class CmdCanonicalizeRemotes(Cmd): # export parent, 'canonicalize-remotes', help = 'Streamline janware Git remotes' ) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument( @@ -30,6 +31,7 @@ class CmdCanonicalizeRemotes(Cmd): # export help = 'Only log what would be done', ) + @override async def _run(self, args: Namespace) -> None: async def git(cmd: list[str], ro = False, throw = True) -> Result: diff --git a/src/python/jw/pkg/cmds/projects/CmdCflags.py b/src/python/jw/pkg/cmds/projects/CmdCflags.py index 23553fd8..15c39a04 100644 --- a/src/python/jw/pkg/cmds/projects/CmdCflags.py +++ b/src/python/jw/pkg/cmds/projects/CmdCflags.py @@ -2,7 +2,7 @@ from __future__ import annotations from ...App import Scope from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -12,10 +12,12 @@ class CmdCflags(Cmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'cflags', help = 'cflags') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('module', nargs = '*', help = 'Modules') + @override async def _run(self, args: Namespace) -> None: deps = self.app.get_project_refs( args.module, diff --git a/src/python/jw/pkg/cmds/projects/CmdCheck.py b/src/python/jw/pkg/cmds/projects/CmdCheck.py index e33f3ac9..84198475 100644 --- a/src/python/jw/pkg/cmds/projects/CmdCheck.py +++ b/src/python/jw/pkg/cmds/projects/CmdCheck.py @@ -1,7 +1,7 @@ from __future__ import annotations from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser @@ -16,6 +16,7 @@ class CmdCheck(Cmd): # export ) self.load_subcommands() + @override async def _run(self, args): import sys @@ -23,5 +24,6 @@ class CmdCheck(Cmd): # export self.parser.print_help() sys.exit(1) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) diff --git a/src/python/jw/pkg/cmds/projects/CmdCommands.py b/src/python/jw/pkg/cmds/projects/CmdCommands.py index 8fb580e3..c22101ef 100644 --- a/src/python/jw/pkg/cmds/projects/CmdCommands.py +++ b/src/python/jw/pkg/cmds/projects/CmdCommands.py @@ -1,7 +1,7 @@ from __future__ import annotations from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -11,9 +11,11 @@ class CmdCommands(Cmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'commands', help = 'List available commands') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) + @override async def _run(self, args: Namespace) -> None: import glob import os diff --git a/src/python/jw/pkg/cmds/projects/CmdCreateFile.py b/src/python/jw/pkg/cmds/projects/CmdCreateFile.py index e57c37e2..03e455d6 100644 --- a/src/python/jw/pkg/cmds/projects/CmdCreateFile.py +++ b/src/python/jw/pkg/cmds/projects/CmdCreateFile.py @@ -1,5 +1,6 @@ from argparse import ArgumentParser, ArgumentTypeError, Namespace from enum import Enum, auto +from typing import override from ...lib.log import WARNING, log from .Cmd import Cmd, Parent @@ -93,6 +94,7 @@ class CmdCreateFile(Cmd): # export parent, 'create-file', help = 'Generate a file from project metadata' ) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument( @@ -124,6 +126,7 @@ class CmdCreateFile(Cmd): # export ) parser.add_argument('module', help = 'The module to generate the file for') + @override async def _run(self, args: Namespace) -> None: method = getattr(self, 'render_' + args.format, None) if method is None: # Should be prevented by choices=[] but keeps linter happy diff --git a/src/python/jw/pkg/cmds/projects/CmdCreatePkgConfig.py b/src/python/jw/pkg/cmds/projects/CmdCreatePkgConfig.py index 30f075c6..f5dab4a5 100644 --- a/src/python/jw/pkg/cmds/projects/CmdCreatePkgConfig.py +++ b/src/python/jw/pkg/cmds/projects/CmdCreatePkgConfig.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent from .lib.templates import tmpl_render @@ -32,6 +32,7 @@ class CmdCreatePkgConfig(Cmd): # export string = re.sub(patt, replacement, string) return string + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('-F', '--project-descr-file', default = None) @@ -46,6 +47,7 @@ class CmdCreatePkgConfig(Cmd): # export parser.add_argument('-R', '--requires-build', default = None) parser.add_argument('-V', '--variables', nargs = '*') + @override async def _run(self, args: Namespace) -> None: project_conf_var_keys = [ 'description', diff --git a/src/python/jw/pkg/cmds/projects/CmdExepath.py b/src/python/jw/pkg/cmds/projects/CmdExepath.py index fe03106f..ba2e6ea1 100644 --- a/src/python/jw/pkg/cmds/projects/CmdExepath.py +++ b/src/python/jw/pkg/cmds/projects/CmdExepath.py @@ -2,7 +2,7 @@ from __future__ import annotations from ...App import Scope from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -12,6 +12,7 @@ class CmdExepath(Cmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'exepath', help = 'exepath') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument( @@ -23,6 +24,7 @@ class CmdExepath(Cmd): # export ) parser.add_argument('module', nargs = '*', help = 'Modules') + @override async def _run(self, args: Namespace) -> None: deps = self.app.get_project_refs( args.module, diff --git a/src/python/jw/pkg/cmds/projects/CmdGetAuthInfo.py b/src/python/jw/pkg/cmds/projects/CmdGetAuthInfo.py index 1f4a2d79..5571998f 100644 --- a/src/python/jw/pkg/cmds/projects/CmdGetAuthInfo.py +++ b/src/python/jw/pkg/cmds/projects/CmdGetAuthInfo.py @@ -5,7 +5,7 @@ import re from ...lib.log import DEBUG, log from ...lib.Uri import Uri from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -19,6 +19,7 @@ class CmdGetAuthInfo(Cmd): # export help = 'Try to retrieve authentication information from the source tree', ) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument( @@ -52,6 +53,7 @@ class CmdGetAuthInfo(Cmd): # export help = 'Show remote base URL', ) + @override async def _run(self, args: Namespace) -> None: keys = ['username', 'password'] diff --git a/src/python/jw/pkg/cmds/projects/CmdGetval.py b/src/python/jw/pkg/cmds/projects/CmdGetval.py index a8ddd5c3..54bfefba 100644 --- a/src/python/jw/pkg/cmds/projects/CmdGetval.py +++ b/src/python/jw/pkg/cmds/projects/CmdGetval.py @@ -1,7 +1,7 @@ from __future__ import annotations from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -11,6 +11,7 @@ class CmdGetval(Cmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'getval', help = 'Get value from project config') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument( @@ -21,6 +22,7 @@ class CmdGetval(Cmd): # export parser.add_argument('section', default = '', help = 'Config section') parser.add_argument('key', default = '', help = 'Config key') + @override async def _run(self, args: Namespace) -> None: project = args.project if project is None: diff --git a/src/python/jw/pkg/cmds/projects/CmdHtdocsDir.py b/src/python/jw/pkg/cmds/projects/CmdHtdocsDir.py index 65fbc2bf..0322d93c 100644 --- a/src/python/jw/pkg/cmds/projects/CmdHtdocsDir.py +++ b/src/python/jw/pkg/cmds/projects/CmdHtdocsDir.py @@ -1,7 +1,7 @@ from __future__ import annotations from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -15,10 +15,12 @@ class CmdHtdocsDir(Cmd): # export help = 'Print source directory containing document root of a given module', ) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('module', nargs = '*', help = 'Modules') + @override async def _run(self, args: Namespace) -> None: r = [] for m in args.module: diff --git a/src/python/jw/pkg/cmds/projects/CmdLdflags.py b/src/python/jw/pkg/cmds/projects/CmdLdflags.py index 66f7f713..cf626801 100644 --- a/src/python/jw/pkg/cmds/projects/CmdLdflags.py +++ b/src/python/jw/pkg/cmds/projects/CmdLdflags.py @@ -2,7 +2,7 @@ from __future__ import annotations from ...App import Scope from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -12,6 +12,7 @@ class CmdLdflags(Cmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'ldflags', help = 'ldflags') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('module', nargs = '*', help = 'Modules') @@ -56,6 +57,7 @@ class CmdLdflags(Cmd): # export return None return ' '.join(ret) + @override async def _run(self, args: Namespace) -> None: deps = self.app.get_project_refs( args.module, diff --git a/src/python/jw/pkg/cmds/projects/CmdLdlibpath.py b/src/python/jw/pkg/cmds/projects/CmdLdlibpath.py index a72925df..01aa9785 100644 --- a/src/python/jw/pkg/cmds/projects/CmdLdlibpath.py +++ b/src/python/jw/pkg/cmds/projects/CmdLdlibpath.py @@ -2,7 +2,7 @@ from __future__ import annotations from ...App import Scope from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -12,6 +12,7 @@ class CmdLdlibpath(Cmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'ldlibpath', help = 'ldlibpath') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument( @@ -23,6 +24,7 @@ class CmdLdlibpath(Cmd): # export ) parser.add_argument('module', nargs = '*', help = 'Modules') + @override async def _run(self, args: Namespace) -> None: deps = self.app.get_project_refs( args.module, diff --git a/src/python/jw/pkg/cmds/projects/CmdLibname.py b/src/python/jw/pkg/cmds/projects/CmdLibname.py index 7ec2f1cb..68167261 100644 --- a/src/python/jw/pkg/cmds/projects/CmdLibname.py +++ b/src/python/jw/pkg/cmds/projects/CmdLibname.py @@ -1,7 +1,7 @@ from __future__ import annotations from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -11,9 +11,11 @@ class CmdLibname(Cmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'libname', help = 'libname') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('module', nargs = '*', help = 'Modules') + @override async def _run(self, args: Namespace) -> None: print(self.app.get_libname(args.module)) diff --git a/src/python/jw/pkg/cmds/projects/CmdListRepos.py b/src/python/jw/pkg/cmds/projects/CmdListRepos.py index ea84c8e4..afcce40d 100644 --- a/src/python/jw/pkg/cmds/projects/CmdListRepos.py +++ b/src/python/jw/pkg/cmds/projects/CmdListRepos.py @@ -6,7 +6,7 @@ from ...lib.log import DEBUG, log from ...lib.Uri import Uri from ...lib.util import get_password, get_username, run_curl_into from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -18,6 +18,7 @@ class CmdListRepos(Cmd): # export parent, 'list-repos', help = 'Query a remote GIT server for repositories' ) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument( @@ -43,6 +44,7 @@ class CmdListRepos(Cmd): # export '--from-owner', help = "List from-owner's projects", default = 'janware' ) + @override async def _run(self, args: Namespace) -> None: base_url = Uri(args.base_url) diff --git a/src/python/jw/pkg/cmds/projects/CmdModules.py b/src/python/jw/pkg/cmds/projects/CmdModules.py index 01b6b761..835c0b95 100644 --- a/src/python/jw/pkg/cmds/projects/CmdModules.py +++ b/src/python/jw/pkg/cmds/projects/CmdModules.py @@ -3,7 +3,7 @@ import re from ...lib.log import DEBUG, log from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -13,6 +13,7 @@ class CmdModules(Cmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'modules', help = 'Query existing janware packages') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument( @@ -24,6 +25,7 @@ class CmdModules(Cmd): # export 'Key-value pairs, seperated by commas, to be searched for in project.conf', ) + @override async def _run(self, args: Namespace) -> None: import pathlib diff --git a/src/python/jw/pkg/cmds/projects/CmdPath.py b/src/python/jw/pkg/cmds/projects/CmdPath.py index 658d0010..9f1d5bea 100644 --- a/src/python/jw/pkg/cmds/projects/CmdPath.py +++ b/src/python/jw/pkg/cmds/projects/CmdPath.py @@ -2,7 +2,7 @@ from __future__ import annotations from ...App import Scope from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -12,10 +12,12 @@ class CmdPath(Cmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'path', help = 'path') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('module', nargs = '*', help = 'Modules') + @override async def _run(self, args: Namespace) -> None: deps = self.app.get_project_refs( args.module, diff --git a/src/python/jw/pkg/cmds/projects/CmdProjDir.py b/src/python/jw/pkg/cmds/projects/CmdProjDir.py index 5d6068fe..a3c39504 100644 --- a/src/python/jw/pkg/cmds/projects/CmdProjDir.py +++ b/src/python/jw/pkg/cmds/projects/CmdProjDir.py @@ -2,7 +2,7 @@ from __future__ import annotations from ...lib.log import WARNING, log from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -14,10 +14,12 @@ class CmdProjDir(Cmd): # export parent, 'proj-dir', help = 'Print directory of a given package' ) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('module', nargs = '*', help = 'Modules') + @override async def _run(self, args: Namespace) -> None: out = [] for m in args.module: diff --git a/src/python/jw/pkg/cmds/projects/CmdPythonpath.py b/src/python/jw/pkg/cmds/projects/CmdPythonpath.py index b7012c05..054bf607 100644 --- a/src/python/jw/pkg/cmds/projects/CmdPythonpath.py +++ b/src/python/jw/pkg/cmds/projects/CmdPythonpath.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from ...App import Scope from .Cmd import Cmd, Parent @@ -15,6 +15,7 @@ class CmdPythonpath(Cmd): # export parent, 'pythonpath', help = 'Generate PYTHONPATH for given modules' ) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument( @@ -38,6 +39,7 @@ class CmdPythonpath(Cmd): # export ) parser.add_argument('module', help = 'Modules', nargs = '*') + @override async def _run(self, args: Namespace) -> None: deps = self.app.get_project_refs( args.module, diff --git a/src/python/jw/pkg/cmds/projects/CmdRequiredOsPkg.py b/src/python/jw/pkg/cmds/projects/CmdRequiredOsPkg.py index af472492..17fe5e32 100644 --- a/src/python/jw/pkg/cmds/projects/CmdRequiredOsPkg.py +++ b/src/python/jw/pkg/cmds/projects/CmdRequiredOsPkg.py @@ -3,7 +3,7 @@ from __future__ import annotations from ...App import Scope from ...lib.log import DEBUG, log from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -18,6 +18,7 @@ class CmdRequiredOsPkg(Cmd): # export help = 'List distribution packages required for a package', ) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('flavours', help = 'Dependency flavours', default = 'build') @@ -35,6 +36,7 @@ class CmdRequiredOsPkg(Cmd): # export help = 'Put double quotes around each listed dependency', ) + @override async def _run(self, args: Namespace) -> None: modules = args.modules flavours = set(args.flavours.split(',')) diff --git a/src/python/jw/pkg/cmds/projects/CmdSummary.py b/src/python/jw/pkg/cmds/projects/CmdSummary.py index dc76edcf..0607bee0 100644 --- a/src/python/jw/pkg/cmds/projects/CmdSummary.py +++ b/src/python/jw/pkg/cmds/projects/CmdSummary.py @@ -1,7 +1,7 @@ from __future__ import annotations from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -13,10 +13,12 @@ class CmdSummary(Cmd): # export parent, 'summary', help = 'Print summary description of given modules' ) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('module', nargs = '*', help = 'Modules') + @override async def _run(self, args: Namespace) -> None: r = [] for m in args.module: diff --git a/src/python/jw/pkg/cmds/projects/CmdTest.py b/src/python/jw/pkg/cmds/projects/CmdTest.py index 31ccaff1..d81c0c7b 100644 --- a/src/python/jw/pkg/cmds/projects/CmdTest.py +++ b/src/python/jw/pkg/cmds/projects/CmdTest.py @@ -1,7 +1,7 @@ from __future__ import annotations from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -11,9 +11,11 @@ class CmdTest(Cmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'test', help = 'Test') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('blah', default = '', help = 'The blah argument') + @override async def _run(self, args: Namespace) -> None: print('blah = ' + args.blah) diff --git a/src/python/jw/pkg/cmds/projects/CmdTmplDir.py b/src/python/jw/pkg/cmds/projects/CmdTmplDir.py index 1f2ef5f1..b87b7447 100644 --- a/src/python/jw/pkg/cmds/projects/CmdTmplDir.py +++ b/src/python/jw/pkg/cmds/projects/CmdTmplDir.py @@ -1,7 +1,7 @@ from __future__ import annotations from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -15,10 +15,12 @@ class CmdTmplDir(Cmd): # export help = 'Print directory containing templates of a given module', ) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('module', nargs = '*', help = 'Modules') + @override async def _run(self, args: Namespace) -> None: r = [] for m in args.module: diff --git a/src/python/jw/pkg/cmds/projects/check/Cmd.py b/src/python/jw/pkg/cmds/projects/check/Cmd.py index 1e8132ef..139f7f58 100644 --- a/src/python/jw/pkg/cmds/projects/check/Cmd.py +++ b/src/python/jw/pkg/cmds/projects/check/Cmd.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import override, TYPE_CHECKING from ....CmdBase import CmdBase as Base from ..CmdCheck import CmdCheck as Parent @@ -13,6 +13,7 @@ class Cmd(Base): # export def __init__(self, parent: Parent, name: str, help: str) -> None: super().__init__(parent, name, help) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) diff --git a/src/python/jw/pkg/cmds/projects/check/CmdDep.py b/src/python/jw/pkg/cmds/projects/check/CmdDep.py index 478b26ae..47c574a9 100644 --- a/src/python/jw/pkg/cmds/projects/check/CmdDep.py +++ b/src/python/jw/pkg/cmds/projects/check/CmdDep.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from ....lib.log import NOTICE, log from .Cmd import Cmd, Parent @@ -17,11 +17,13 @@ class CmdDep(Cmd): # export help = 'Check for circular dependencies between given modules', ) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('module', nargs = '*', help = 'Modules') parser.add_argument('-f', '--flavour', nargs = '?', default = 'build') + @override async def _run(self, args: Namespace) -> None: cycle = self.app.find_circular_deps(args.module, args.flavour) if cycle: diff --git a/src/python/jw/pkg/cmds/secrets/Cmd.py b/src/python/jw/pkg/cmds/secrets/Cmd.py index 83c52af3..9b02e6e6 100644 --- a/src/python/jw/pkg/cmds/secrets/Cmd.py +++ b/src/python/jw/pkg/cmds/secrets/Cmd.py @@ -1,7 +1,7 @@ from __future__ import annotations from functools import cached_property -from typing import TYPE_CHECKING +from typing import override, TYPE_CHECKING from ...CmdBase import CmdBase as Base from ..CmdSecrets import CmdSecrets as Parent @@ -46,6 +46,7 @@ class Cmd(Base): # export def __init__(self, parent: Parent, name: str, help: str) -> None: super().__init__(parent, name, help) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument('packages', nargs = '*', help = 'Package names') diff --git a/src/python/jw/pkg/cmds/secrets/CmdCompileTemplates.py b/src/python/jw/pkg/cmds/secrets/CmdCompileTemplates.py index dfaad815..0ce4cfb6 100644 --- a/src/python/jw/pkg/cmds/secrets/CmdCompileTemplates.py +++ b/src/python/jw/pkg/cmds/secrets/CmdCompileTemplates.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent from .lib.base import Attrs @@ -15,10 +15,12 @@ class CmdCompileTemplates(Cmd): # export parent, 'compile-templates', help = 'Compile package template files' ) + @override async def _run(self, args: Namespace) -> None: attrs = Attrs(args.mode, args.owner, args.group, None) await self._compile_template_files(args.packages, attrs) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument( diff --git a/src/python/jw/pkg/cmds/secrets/CmdInstall.py b/src/python/jw/pkg/cmds/secrets/CmdInstall.py index dadbd4f0..d754bdb5 100644 --- a/src/python/jw/pkg/cmds/secrets/CmdInstall.py +++ b/src/python/jw/pkg/cmds/secrets/CmdInstall.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent @@ -18,6 +18,7 @@ class CmdInstall(Cmd): # export ), ) + @override def add_arguments(self, parser: ArgumentParser) -> None: parser.add_argument('src', help = 'URI of secret source') parser.add_argument( @@ -28,6 +29,7 @@ class CmdInstall(Cmd): # export ) super().add_arguments(parser) + @override async def _run(self, args: Namespace) -> None: packages = [] if args.packages == ['all'] else args.packages await self.ctx.install(args.src, packages, args.only_missing) diff --git a/src/python/jw/pkg/cmds/secrets/CmdListCompilationOutput.py b/src/python/jw/pkg/cmds/secrets/CmdListCompilationOutput.py index 3b9d3e3c..8bc95fe3 100644 --- a/src/python/jw/pkg/cmds/secrets/CmdListCompilationOutput.py +++ b/src/python/jw/pkg/cmds/secrets/CmdListCompilationOutput.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent @@ -16,6 +16,7 @@ class CmdListCompilationOutput(Cmd): # export help = 'List package compilation output files', ) + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument( @@ -25,6 +26,7 @@ class CmdListCompilationOutput(Cmd): # export help = 'Show all output targets, including non-existent files', ) + @override async def _run(self, args: Namespace) -> None: print( '\n'.join( diff --git a/src/python/jw/pkg/cmds/secrets/CmdListSecrets.py b/src/python/jw/pkg/cmds/secrets/CmdListSecrets.py index 1950a187..8a51fa93 100644 --- a/src/python/jw/pkg/cmds/secrets/CmdListSecrets.py +++ b/src/python/jw/pkg/cmds/secrets/CmdListSecrets.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent @@ -12,6 +12,7 @@ class CmdListSecrets(Cmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'list-secrets', help = 'List package secret files') + @override def add_arguments(self, parser: ArgumentParser) -> None: super().add_arguments(parser) parser.add_argument( @@ -21,6 +22,7 @@ class CmdListSecrets(Cmd): # export help = 'Show all secret paths, including non-existent files', ) + @override async def _run(self, args: Namespace) -> None: print( '\n'.join( diff --git a/src/python/jw/pkg/cmds/secrets/CmdListTemplates.py b/src/python/jw/pkg/cmds/secrets/CmdListTemplates.py index 150faa5c..2f3249cf 100644 --- a/src/python/jw/pkg/cmds/secrets/CmdListTemplates.py +++ b/src/python/jw/pkg/cmds/secrets/CmdListTemplates.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent @@ -12,5 +12,6 @@ class CmdListTemplates(Cmd): # export def __init__(self, parent: Parent) -> None: super().__init__(parent, 'list-templates', help = 'List package template files') + @override async def _run(self, args: Namespace) -> None: print('\n'.join(await self._list_template_files(args.packages))) diff --git a/src/python/jw/pkg/cmds/secrets/CmdRmCompilationOutput.py b/src/python/jw/pkg/cmds/secrets/CmdRmCompilationOutput.py index d1631af4..a420740d 100644 --- a/src/python/jw/pkg/cmds/secrets/CmdRmCompilationOutput.py +++ b/src/python/jw/pkg/cmds/secrets/CmdRmCompilationOutput.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent @@ -16,5 +16,6 @@ class CmdRmCompilationOutput(Cmd): # export help = 'Remove package compilation output files', ) + @override async def _run(self, args: Namespace) -> None: await self._remove_compilation_targets(args.packages) diff --git a/src/python/jw/pkg/lib/App.py b/src/python/jw/pkg/lib/App.py index 54c6abec..62f5467f 100644 --- a/src/python/jw/pkg/lib/App.py +++ b/src/python/jw/pkg/lib/App.py @@ -6,7 +6,7 @@ import os import sys from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace -from typing import TYPE_CHECKING, Any +from typing import override, TYPE_CHECKING, Any from .AsyncRunner import AsyncRunner from .log import DEBUG, ERR, NOTICE, log, log_m, set_log_flags, set_log_level @@ -192,6 +192,7 @@ class App: # export class NoopCompleter(BaseCompleter): + @override def __call__(self, *args, **kwargs): return None diff --git a/src/python/jw/pkg/lib/Cmd.py b/src/python/jw/pkg/lib/Cmd.py index 9a3caa39..ffa76fa6 100644 --- a/src/python/jw/pkg/lib/Cmd.py +++ b/src/python/jw/pkg/lib/Cmd.py @@ -3,7 +3,7 @@ from __future__ import annotations import abc import sys -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .log import ERR from .Types import LoadTypes, Types @@ -173,14 +173,18 @@ class Cmd(AbstractCmd): # export self.__description = description if description else help self.__aliases = aliases if aliases else [] + @override def _name(self) -> str: return self.__name + @override def _help(self) -> str: return self.__help + @override def _description(self) -> str: return self.__description + @override def _aliases(self) -> Iterable[str]: return self.__aliases diff --git a/src/python/jw/pkg/lib/ExecContext.py b/src/python/jw/pkg/lib/ExecContext.py index b3a103ee..4ca033fc 100644 --- a/src/python/jw/pkg/lib/ExecContext.py +++ b/src/python/jw/pkg/lib/ExecContext.py @@ -5,7 +5,7 @@ import errno import sys from decimal import ROUND_FLOOR, Decimal -from typing import TYPE_CHECKING, NamedTuple +from typing import override, TYPE_CHECKING, NamedTuple if TYPE_CHECKING: from typing import Type @@ -250,6 +250,7 @@ class ExecContext(Base): super().__init__(*args, **kwargs) @classmethod + @override def create(cls, *args, **kwargs) -> ExecContext: ret = super().create(*args, **kwargs) if not isinstance(ret, cls): @@ -451,6 +452,7 @@ class ExecContext(Base): cc.check_exit_code(ret) return ret + @override async def _get( self, path: str, wd: str | None, throw: bool, verbose: bool | None, title: str ) -> Result: @@ -485,6 +487,7 @@ class ExecContext(Base): cc.check_exit_code(ret) return ret + @override async def _put( self, path: str, @@ -565,22 +568,27 @@ class ExecContext(Base): log(ERR, msg) return ret + @override async def _unlink(self, path: str) -> None: cmd = ['rm', '-f', path] await self.run(cmd, cmd_input = InputMode.NonInteractive) + @override async def _erase(self, path: str) -> None: cmd = ['rm', '-rf', path] await self.run(cmd, cmd_input = InputMode.NonInteractive) + @override async def _rename(self, src: str, dst: str) -> None: cmd = ['mv', src, dst] await self.run(cmd, cmd_input = InputMode.NonInteractive) + @override async def _mkdir(self, path: str, mode: int) -> None: cmd = ['mkdir', path, '-m', self.__mode_str(mode)] await self.run(cmd, cmd_input = InputMode.NonInteractive) + @override async def _mktemp(self, tmpl: str, directory: bool) -> str: cmd = ['mktemp'] if directory: @@ -593,6 +601,7 @@ class ExecContext(Base): ) return result.stdout_str.strip() + @override async def _stat(self, path: str, follow_symlinks: bool) -> StatResult: async def __stat(opts: list[str]) -> Result: @@ -662,6 +671,7 @@ class ExecContext(Base): assert stdout is not None # Just there to pacify the linter return _build_stat_result(stdout.rstrip('\n').split(_US), mode_base = 8) + @override async def _chown(self, path: str, owner: str | None, group: str | None) -> None: if owner is None and group is None: raise ValueError(f'Tried to chown("{path}") without owner and group') @@ -674,6 +684,7 @@ class ExecContext(Base): assert ownership is not None # Impossible, just there to calm the linter await self.run(['chown', ownership, path], cmd_input = InputMode.NonInteractive) + @override async def _chmod(self, path: str, mode: int) -> None: await self.run( ['chmod', self.__mode_str(mode), path], diff --git a/src/python/jw/pkg/lib/FileContext.py b/src/python/jw/pkg/lib/FileContext.py index c158a016..49dc751b 100644 --- a/src/python/jw/pkg/lib/FileContext.py +++ b/src/python/jw/pkg/lib/FileContext.py @@ -4,7 +4,7 @@ import abc from enum import Enum, auto from functools import cached_property -from typing import TYPE_CHECKING +from typing import override, TYPE_CHECKING from .log import DEBUG, ERR, log from .Uri import Uri @@ -50,6 +50,7 @@ class FileContext(abc.ABC): async def __aexit__(self, exc_type, exc, tb): await self.close() + @override def __repr__(self) -> str: return self.__uri.id diff --git a/src/python/jw/pkg/lib/Package.py b/src/python/jw/pkg/lib/Package.py index 63c47ff8..1f3fb2dc 100644 --- a/src/python/jw/pkg/lib/Package.py +++ b/src/python/jw/pkg/lib/Package.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import override, Any meta_tags = [ 'name', @@ -56,6 +56,7 @@ class Package: self.url = url self.maintainer = maintainer + @override def __repr__(self) -> str: global meta_tags return '\n'.join([f'{key:<15}: {getattr(self, key)}' for key in meta_tags]) diff --git a/src/python/jw/pkg/lib/PackageFilter.py b/src/python/jw/pkg/lib/PackageFilter.py index 2643102f..c17df3e7 100644 --- a/src/python/jw/pkg/lib/PackageFilter.py +++ b/src/python/jw/pkg/lib/PackageFilter.py @@ -2,7 +2,7 @@ from __future__ import annotations import abc import re -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from .Package import Package @@ -25,6 +25,7 @@ class PackageFilterString(PackageFilter): raise Exception(f'Unsupported filter string "{definition}"') self.__definition = url_rx_str + @override def _match(self, package: Package) -> bool: url = package.url if url is None: diff --git a/src/python/jw/pkg/lib/ProcFilter.py b/src/python/jw/pkg/lib/ProcFilter.py index 42cb0de3..bf5cd2de 100644 --- a/src/python/jw/pkg/lib/ProcFilter.py +++ b/src/python/jw/pkg/lib/ProcFilter.py @@ -2,7 +2,7 @@ from __future__ import annotations import abc -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .base import Result @@ -20,6 +20,7 @@ class ProcFilter(abc.ABC): class ProcFilterIdentity(ProcFilter): + @override async def _run(self, data: bytes | None) -> Result: return Result(data, None, 0) diff --git a/src/python/jw/pkg/lib/ProcFilterGpg.py b/src/python/jw/pkg/lib/ProcFilterGpg.py index 8d9da943..2ab650ff 100644 --- a/src/python/jw/pkg/lib/ProcFilterGpg.py +++ b/src/python/jw/pkg/lib/ProcFilterGpg.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from .ProcFilter import ProcFilter @@ -13,6 +13,7 @@ class ProcFilterGpg(ProcFilter): def __init__(self, ec: ExecContext) -> None: self.__ec = ec + @override async def _run(self, data: bytes | None) -> Result: if data is None: raise Exception('No data for GPG to decrypt') diff --git a/src/python/jw/pkg/lib/Result.py b/src/python/jw/pkg/lib/Result.py index dd32b300..f0a13a81 100644 --- a/src/python/jw/pkg/lib/Result.py +++ b/src/python/jw/pkg/lib/Result.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import override + class Result: def __init__( @@ -113,6 +115,7 @@ class Result: return ret + @override def __repr__(self) -> str: return self.__summarize(verbose = False) diff --git a/src/python/jw/pkg/lib/TarIo.py b/src/python/jw/pkg/lib/TarIo.py index a9288732..67774d5c 100644 --- a/src/python/jw/pkg/lib/TarIo.py +++ b/src/python/jw/pkg/lib/TarIo.py @@ -9,7 +9,7 @@ from tarfile import TarFile, TarInfo from .CopyContext import CopyContext from .ExecContext import ExecContext from .log import DEBUG, ERR, log -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from .base import StatResult @@ -89,6 +89,7 @@ class TarIo(CopyContext): class TarIoTarFile(TarIo): + @override async def _extract(self, blob: bytes, root: str | None = None) -> None: tf = TarFile(fileobj = io.BytesIO(blob)) for info in tf.getmembers(): @@ -111,7 +112,16 @@ class TarIoTarFile(TarIo): class TarIoTarExec(TarIo): + @override + async def _extract(self, blob: bytes, root: str | None = None) -> None: + cmd = ['tar'] + if root is not None: + cmd += ['-C', root] + cmd += ['-x', '-f', '-'] + await self.dst.run(cmd, cmd_input = blob) + @property + @override def dst(self) -> ExecContext: ret = super().dst if not isinstance(ret, ExecContext): @@ -120,10 +130,3 @@ class TarIoTarExec(TarIo): 'context, which only has a file context' ) return ret - - async def _extract(self, blob: bytes, root: str | None = None) -> None: - cmd = ['tar'] - if root is not None: - cmd += ['-C', root] - cmd += ['-x', '-f', '-'] - await self.dst.run(cmd, cmd_input = blob) diff --git a/src/python/jw/pkg/lib/Types.py b/src/python/jw/pkg/lib/Types.py index b223168a..04827aac 100644 --- a/src/python/jw/pkg/lib/Types.py +++ b/src/python/jw/pkg/lib/Types.py @@ -5,7 +5,7 @@ import os import re import sys -from typing import TYPE_CHECKING, Generic, Iterable, TypeVar +from typing import override, TYPE_CHECKING, Generic, Iterable, TypeVar from .log import ERR, OFF, log, parse_log_level @@ -17,6 +17,7 @@ T = TypeVar('T') class Types(abc.ABC, Iterable[type[T]], Generic[T]): # export + @override def __iter__(self) -> Iterator[type[T]]: return iter(self._classes()) @@ -64,6 +65,7 @@ class LoadTypes(Types[T]): # export if self.__debug_level != OFF: log(self.__debug_level, *args, **kwargs) + @override def _stringify(self): tf = 'None' if self.__type_filter is None else ( ', '.join([str(f) for f in self.__type_filter]) @@ -74,6 +76,7 @@ class LoadTypes(Types[T]): # export 'mod_names: ' + ', '.join(self.__mod_names), ] + @override def _classes(self) -> Iterable[type[T]]: if self.__classes is None: diff --git a/src/python/jw/pkg/lib/Uri.py b/src/python/jw/pkg/lib/Uri.py index d75f2c92..54472535 100644 --- a/src/python/jw/pkg/lib/Uri.py +++ b/src/python/jw/pkg/lib/Uri.py @@ -3,7 +3,7 @@ from __future__ import annotations import copy from functools import cached_property -from typing import TYPE_CHECKING +from typing import override, TYPE_CHECKING if TYPE_CHECKING: import urllib.parse @@ -39,9 +39,11 @@ class Uri: self.__username: str | None = None self.__password: str | None = None + @override def __repr__(self) -> str: return self.full + @override def __str__(self) -> str: return self.safe_full_with_username diff --git a/src/python/jw/pkg/lib/distros/arch/Distro.py b/src/python/jw/pkg/lib/distros/arch/Distro.py index dbcef69b..ded2a903 100644 --- a/src/python/jw/pkg/lib/distros/arch/Distro.py +++ b/src/python/jw/pkg/lib/distros/arch/Distro.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import override, TYPE_CHECKING from ...Distro import Distro as Base @@ -26,27 +26,32 @@ class Distro(Base): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + @override async def _ref(self) -> None: raise NotImplementedError( 'distro refresh is not yet implemented for Arch-like distributions' ) + @override async def _dup(self, download_only: bool) -> None: args = ['-Su'] if download_only: args.append('-w') await self.pacman(args) + @override async def _reboot_required(self, verbose: bool) -> bool: raise NotImplementedError( 'distro reboot-required is not yet implemented for Arch-like distributions' ) + @override async def _select_by_name(self, names: Iterable[str]) -> Iterable[Package]: raise NotImplementedError( 'distro select is not yet implemented for Arch-like distributions' ) + @override async def _install(self, names: Iterable[str], only_update: bool) -> None: if only_update: raise NotImplementedError('--only-update is not yet implemented for pacman') @@ -54,11 +59,13 @@ class Distro(Base): args.extend(names) await self.pacman(args) + @override async def _delete(self, names: Iterable[str]) -> None: raise NotImplementedError( 'distro delete not yet implemented for Arch-like distributions' ) + @override async def _pkg_files(self, name: str) -> Iterable[str]: raise NotImplementedError( 'distro pkg ls yet implemented for Arch-like distributions' diff --git a/src/python/jw/pkg/lib/distros/debian/Distro.py b/src/python/jw/pkg/lib/distros/debian/Distro.py index d2811f07..fb242192 100644 --- a/src/python/jw/pkg/lib/distros/debian/Distro.py +++ b/src/python/jw/pkg/lib/distros/debian/Distro.py @@ -2,7 +2,7 @@ from __future__ import annotations import os -from typing import TYPE_CHECKING +from typing import override, TYPE_CHECKING from ...Distro import Distro as Base from ...log import NOTICE, log @@ -38,9 +38,11 @@ class Distro(Base): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + @override async def _ref(self) -> None: await self.apt_get(['update']) + @override async def _dup(self, download_only: bool) -> None: args: list[str] = [] if download_only: @@ -48,6 +50,7 @@ class Distro(Base): args.append('upgrade') await self.apt_get(args) + @override async def _reboot_required(self, verbose: bool) -> bool: reboot_required = '/run/reboot_required' if os.path.exists(reboot_required): @@ -64,9 +67,11 @@ class Distro(Base): log(NOTICE, f"No. {reboot_required} doesn't exist.") return False + @override async def _select_by_name(self, names: Iterable[str]) -> Iterable[Package]: return await query_packages(names, ec = self.ctx) + @override async def _install(self, names: Iterable[str], only_update: bool) -> None: args = ['install'] if only_update: @@ -75,8 +80,10 @@ class Distro(Base): args.extend(names) await self.apt_get(args) + @override async def _delete(self, names: Iterable[str]) -> None: await self.dpkg(['-P', *names], sudo = True) + @override async def _pkg_files(self, name: str) -> Iterable[str]: return await list_files(name, ec = self.ctx) diff --git a/src/python/jw/pkg/lib/distros/suse/Distro.py b/src/python/jw/pkg/lib/distros/suse/Distro.py index 9969b4a5..51ecc6a5 100644 --- a/src/python/jw/pkg/lib/distros/suse/Distro.py +++ b/src/python/jw/pkg/lib/distros/suse/Distro.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import override, TYPE_CHECKING from ...Distro import Distro as Base from ...pm.rpm import list_files, query_packages, run_rpm @@ -43,9 +43,11 @@ class Distro(Base): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + @override async def _ref(self) -> None: await self.zypper(['refresh']) + @override async def _dup(self, download_only: bool) -> None: args = ['dup'] if download_only: @@ -53,6 +55,7 @@ class Distro(Base): args += self.__update_extra_opts await self.zypper(args) + @override async def _reboot_required(self, verbose: bool) -> bool: opts = [] if not verbose: @@ -64,22 +67,27 @@ class Distro(Base): return True return False + @override async def _select_by_name(self, names: Iterable[str]) -> Iterable[Package]: return await query_packages(names, ec = self.ctx) + @override async def _install(self, names: Iterable[str], only_update: bool) -> None: cmd = ['update' if only_update else 'install'] cmd += self.__update_extra_opts cmd += names await self.zypper(cmd) + @override async def _install_local_files( self, paths: Iterable[str], only_update: bool ) -> None: await self.rpm(['-U', '--reinstall', *paths]) + @override async def _delete(self, names: Iterable[str]) -> None: await self.rpm(['-e', *names], sudo = True) + @override async def _pkg_files(self, name: str) -> Iterable[str]: return await list_files(name, ec = self.ctx) diff --git a/src/python/jw/pkg/lib/ec/Curl.py b/src/python/jw/pkg/lib/ec/Curl.py index 7ec2a480..d2fd158f 100644 --- a/src/python/jw/pkg/lib/ec/Curl.py +++ b/src/python/jw/pkg/lib/ec/Curl.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import override, TYPE_CHECKING from ..FileContext import FileContext as Base @@ -28,6 +28,7 @@ class Curl(Base): self.__ec = ec if ec else __local() + @override async def _get( self, path: str, wd: str | None, throw: bool, verbose: bool | None, title: str ) -> Result: diff --git a/src/python/jw/pkg/lib/ec/Local.py b/src/python/jw/pkg/lib/ec/Local.py index 7ebb6fa5..08630e16 100644 --- a/src/python/jw/pkg/lib/ec/Local.py +++ b/src/python/jw/pkg/lib/ec/Local.py @@ -7,7 +7,7 @@ import pwd import sys from functools import cache -from typing import TYPE_CHECKING +from typing import override, TYPE_CHECKING from ..base import Result, StatResult from ..ExecContext import ExecContext as Base @@ -25,6 +25,7 @@ class Local(Base): def _username(self) -> str: return pwd.getpwuid(os.getuid()).pw_name + @override async def _run( self, cmd: list[str], @@ -155,9 +156,11 @@ class Local(Base): if cwd is not None: os.chdir(cwd) + @override async def _unlink(self, path: str) -> None: os.unlink(path) + @override async def _erase(self, path: str) -> None: if os.path.isdir(path): import shutil @@ -165,26 +168,33 @@ class Local(Base): return os.unlink(path) + @override async def _rename(self, src: str, dst: str) -> None: os.rename(src, dst) + @override async def _mkdir(self, path: str, mode: int) -> None: os.mkdir(path, mode) + @override async def _stat(self, path: str, follow_symlinks: bool) -> StatResult: return StatResult.from_os(os.stat(path, follow_symlinks = follow_symlinks)) + @override async def _file_exists(self, path: str) -> bool: return os.path.exists(path) + @override async def _chown(self, path: str, owner: str | None, group: str | None) -> None: uid = pwd.getpwnam(owner).pw_uid if owner else -1 gid = grp.getgrnam(group).gr_gid if group else -1 os.chown(path, uid, gid) + @override async def _chmod(self, path: str, mode: int) -> None: os.chmod(path, mode) + @override async def _is_dir(self, path: str, follow_symlinks: bool) -> bool: if (not follow_symlinks) and os.path.islink(path): return False diff --git a/src/python/jw/pkg/lib/ec/SSHClient.py b/src/python/jw/pkg/lib/ec/SSHClient.py index e920a25b..02814380 100644 --- a/src/python/jw/pkg/lib/ec/SSHClient.py +++ b/src/python/jw/pkg/lib/ec/SSHClient.py @@ -5,7 +5,7 @@ import os import pwd from enum import Flag, auto -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from ..ExecContext import ExecContext from ..log import DEBUG, ERR, INFO, NOTICE, get_caller_pos, log, log_m @@ -42,6 +42,7 @@ class SSHClient(ExecContext): ) -> Result: pass + @override async def _run( self, cmd: list[str], @@ -102,6 +103,7 @@ class SSHClient(ExecContext): return self.uri.port @property + @override def username(self) -> str | None: return self.uri.username diff --git a/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py b/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py index eda9e4bb..13f452fe 100644 --- a/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py +++ b/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py @@ -1,3 +1,4 @@ +from typing import override import asyncio import os import shlex @@ -42,10 +43,12 @@ class AsyncSSH(Base): self.__connect_timeout = connect_timeout self.__conn: asyncssh.SSHClientConnection | None = None + @override async def _open(self) -> None: await super()._open() await self._conn + @override async def _close(self) -> None: if self.__conn is not None: try: @@ -355,6 +358,7 @@ class AsyncSSH(Base): stdout = b''.join(stdout_parts) if stdout_parts else None return Result(stdout, None, exit_code, cmd = cmd) + @override async def _run_ssh( self, cmd: list[str], diff --git a/src/python/jw/pkg/lib/ec/ssh/Exec.py b/src/python/jw/pkg/lib/ec/ssh/Exec.py index b902d6e4..e0969e29 100644 --- a/src/python/jw/pkg/lib/ec/ssh/Exec.py +++ b/src/python/jw/pkg/lib/ec/ssh/Exec.py @@ -2,7 +2,7 @@ from __future__ import annotations import os -from typing import TYPE_CHECKING +from typing import override, TYPE_CHECKING from ...base import InputMode from ...util import run_cmd @@ -48,6 +48,7 @@ class Exec(Base): self.__askpass_orig[key] = os.getenv(key) os.environ[key] = val + @override async def _run_ssh( self, cmd: list[str], diff --git a/src/python/jw/pkg/lib/ec/ssh/Paramiko.py b/src/python/jw/pkg/lib/ec/ssh/Paramiko.py index 09e59358..936a9deb 100644 --- a/src/python/jw/pkg/lib/ec/ssh/Paramiko.py +++ b/src/python/jw/pkg/lib/ec/ssh/Paramiko.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import override, TYPE_CHECKING # Tolerate missing paramiko imports. jw-pkg is designed to work with what it # finds. @@ -52,15 +52,18 @@ class Paramiko(Base): def __scp(self) -> Any: return paramiko.SCPClient(self.__client.get_transport()) + @override async def _open(self) -> None: await super()._open() self.__client + @override async def _close(self) -> None: if self.___client is not None: self.___client.close() self.___client = None + @override async def _run_ssh( self, cmd: list[str], -- 2.55.0 From df5785cefdb7f251ddd22aea8d1c6f56685dde65 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 15:35:43 +0200 Subject: [PATCH 02/31] pyproject.toml, pyrightconfig.json: Require @override Add mypy's explicit-override error code and pyrightconfig's reportImplicitOverride "error" directive. This requires all methods that override base class methods to be decorated with @override from typing. The change complements the previous commit, which added exactly that decorator to all relevant methods. Signed-off-by: Jan Lindemann --- conf/project/pyrightconfig-base.json | 3 ++- conf/templates/pyproject.toml | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/conf/project/pyrightconfig-base.json b/conf/project/pyrightconfig-base.json index c6b2fd39..59bb4dea 100644 --- a/conf/project/pyrightconfig-base.json +++ b/conf/project/pyrightconfig-base.json @@ -2,5 +2,6 @@ "typeCheckingMode": "basic", "pythonPlatform": "Linux", "useLibraryCodeForTypes": true, - "verboseOutput": false + "verboseOutput": false, + "reportImplicitOverride": "error" } diff --git a/conf/templates/pyproject.toml b/conf/templates/pyproject.toml index a9f1e109..63965036 100644 --- a/conf/templates/pyproject.toml +++ b/conf/templates/pyproject.toml @@ -2,6 +2,8 @@ {mypypath} + enable_error_code = "explicit-override" + [tool.isort] lines_between_sections = 1 -- 2.55.0 From c939df2bd0c810f828f89ebdcb8458ae07324677 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Fri, 24 Jul 2026 10:05:39 +0200 Subject: [PATCH 03/31] lib.AsyncRunner: Fix mypy.ignore-without-code fallout Annotate the one existing bare "# type: ignore" in AsyncRunner.py with arg-type and var-annotated codes to prepare for mypy.ignore-without-code. Signed-off-by: Jan Lindemann Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 --- src/python/jw/pkg/lib/AsyncRunner.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/python/jw/pkg/lib/AsyncRunner.py b/src/python/jw/pkg/lib/AsyncRunner.py index 2384ea07..d1569be4 100644 --- a/src/python/jw/pkg/lib/AsyncRunner.py +++ b/src/python/jw/pkg/lib/AsyncRunner.py @@ -44,7 +44,10 @@ class AsyncRunner: self._loop = self._cm.__enter__() def call(self, awaitable: Awaitable[T], timeout: float | None = None) -> T: - fut = asyncio.run_coroutine_threadsafe(awaitable, self._loop) # type: ignore + fut = asyncio.run_coroutine_threadsafe( + awaitable, # type: ignore[arg-type, var-annotated] + self._loop, + ) return fut.result(timeout) def close(self) -> None: -- 2.55.0 From 5f6559dbe031bda5158105959832438e47f50541 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 21:54:54 +0200 Subject: [PATCH 04/31] pyproject.toml: Enable mypy.ignore-without-code Add the ignore-without-code error code to the mypy configuration. This requires all # type: ignore comments to include a specific error code, improving the precision and maintainability of type ignore annotations. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- conf/templates/pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/conf/templates/pyproject.toml b/conf/templates/pyproject.toml index 63965036..daf2d4e4 100644 --- a/conf/templates/pyproject.toml +++ b/conf/templates/pyproject.toml @@ -2,7 +2,10 @@ {mypypath} - enable_error_code = "explicit-override" + enable_error_code = [ + "explicit-override", + "ignore-without-code", + ] [tool.isort] -- 2.55.0 From a9bbbb9a89bd678b0389c2d2767ab54cd7f9ffe1 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Fri, 24 Jul 2026 10:07:22 +0200 Subject: [PATCH 05/31] lib.log: Fix mypy.possibly-undefined fallout The log_m() function has margs conditionally assigned inside an if block but used unconditionally afterwards. Initialize margs outside the if to guarantee it is always defined. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- src/python/jw/pkg/lib/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/jw/pkg/lib/log.py b/src/python/jw/pkg/lib/log.py index 8aa6d148..98f0f4e3 100644 --- a/src/python/jw/pkg/lib/log.py +++ b/src/python/jw/pkg/lib/log.py @@ -175,8 +175,8 @@ def get_caller_pos(up: int = 1, def log_m(prio: int, *args, **kwargs) -> None: # export if prio > _level: return + margs = '' if len(args): - margs = '' for a in args: if isinstance(a, list): margs += '\n'.join([str(elem) for elem in a]) -- 2.55.0 From 7e7341bf1bdefec44239d04fd46f0ae86ab7e1b6 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 21:57:01 +0200 Subject: [PATCH 06/31] pyproject.toml: Enable mypy.possibly-undefined Add the possibly-undefined error code to the mypy configuration. This detects variables that may not be defined on all execution paths, catching a class of NameError bugs at type-check time. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- conf/templates/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/conf/templates/pyproject.toml b/conf/templates/pyproject.toml index daf2d4e4..41163950 100644 --- a/conf/templates/pyproject.toml +++ b/conf/templates/pyproject.toml @@ -5,6 +5,7 @@ enable_error_code = [ "explicit-override", "ignore-without-code", + "possibly-undefined", ] [tool.isort] -- 2.55.0 From 38c776e6385173a8508fae500af92f3a5792ea6d Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Fri, 24 Jul 2026 10:10:03 +0200 Subject: [PATCH 07/31] lib.Distro: Fix mypy.truthy-bool fallout The _select() method in Distro.py has an assert on a PackageFilter parameter that doesn't define __bool__ or __len__. Since the type signature already guarantees it is not None, the assertion is redundant and is now removed. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- src/python/jw/pkg/lib/Distro.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/python/jw/pkg/lib/Distro.py b/src/python/jw/pkg/lib/Distro.py index 84c9e5bb..369facf9 100644 --- a/src/python/jw/pkg/lib/Distro.py +++ b/src/python/jw/pkg/lib/Distro.py @@ -364,7 +364,6 @@ class Distro(abc.ABC): async def _select(self, names: Iterable[str], filter: PackageFilter) -> Iterable[Package]: - assert filter, 'No filter in _select()' return [p for p in await self._select_by_name(names) if filter.match(p)] async def select( -- 2.55.0 From f689d083d7ee2d44893b0a9e004b3ea56a460d70 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 22:10:43 +0200 Subject: [PATCH 08/31] pyproject.toml: Enable mypy.truthy-bool Add the truthy-bool error code to the mypy configuration. This detects conditions and expressions that are always truthy because the type has no __bool__ or __len__ method. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- conf/templates/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/conf/templates/pyproject.toml b/conf/templates/pyproject.toml index 41163950..f464416b 100644 --- a/conf/templates/pyproject.toml +++ b/conf/templates/pyproject.toml @@ -6,6 +6,7 @@ "explicit-override", "ignore-without-code", "possibly-undefined", + "truthy-bool", ] [tool.isort] -- 2.55.0 From 044d2cb0651087de525468289a105f405350d25f Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 21:12:06 +0200 Subject: [PATCH 09/31] util: Remove redundant cast in run_curl_into The cast to 'T' in run_curl_into was redundant: mypy already narrows the return value of json.loads() to type T after the isinstance(ret, expected_type) check (where expected_type: type[T]). Remove the unnecessary cast and clean up the unused 'cast' import. This satisfies the new warn_redundant_casts mypy rule. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- src/python/jw/pkg/lib/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/jw/pkg/lib/util.py b/src/python/jw/pkg/lib/util.py index 3390b58b..c3f581b9 100644 --- a/src/python/jw/pkg/lib/util.py +++ b/src/python/jw/pkg/lib/util.py @@ -5,7 +5,7 @@ import os import sys from enum import Enum, auto -from typing import TYPE_CHECKING, Iterable, TypeVar, cast +from typing import TYPE_CHECKING, Iterable, TypeVar from .base import Input, InputMode, Result from .log import DEBUG, ERR, log @@ -93,7 +93,7 @@ async def run_curl_into( raise TypeError( f'Expected {expected_type.__name__}, got {type(ret).__name__} from Curl' ) - return cast('T', ret) + return ret async def run_askpass( askpass_env: list[str], -- 2.55.0 From dc14aac068338468ca48a63309195b5cbdc57244 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 21:12:04 +0200 Subject: [PATCH 10/31] pyproject.toml: Enable mypy.warn_redundant_casts Add warn_redundant_casts = true to the mypy configuration template. This rule detects casts that mypy proves unnecessary, helping to clean up redundant type casts that clutter the codebase. This is one of the boolean-flag rules being gradually adopted from the stricter mypy profile in /tmp/pyproject.toml. It produces just 1 error during initial rollout. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- conf/templates/pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conf/templates/pyproject.toml b/conf/templates/pyproject.toml index f464416b..635a4041 100644 --- a/conf/templates/pyproject.toml +++ b/conf/templates/pyproject.toml @@ -2,6 +2,8 @@ {mypypath} + warn_redundant_casts = true + enable_error_code = [ "explicit-override", "ignore-without-code", -- 2.55.0 From e464daf2f3f19c531953d81e73e7b9a824746873 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 21:18:23 +0200 Subject: [PATCH 11/31] Distro._reboot_required(): Fix comparison The comparison ret != 0 is comparing a Result object against the integer 0, which is always True since Result has no __eq__ defined. This commit fixes the bug by comparing ret.status instead, which is the actual exit code we care about. This satisfies the new strict_equality mypy rule which enables the comparison-overlap check. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- src/python/jw/pkg/lib/distros/suse/Distro.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/jw/pkg/lib/distros/suse/Distro.py b/src/python/jw/pkg/lib/distros/suse/Distro.py index 51ecc6a5..0bbe1b6c 100644 --- a/src/python/jw/pkg/lib/distros/suse/Distro.py +++ b/src/python/jw/pkg/lib/distros/suse/Distro.py @@ -63,7 +63,7 @@ class Distro(Base): # opts.append('--quiet') opts.append('needs-rebooting') ret = await self.zypper(opts, sudo = False, verbose = verbose) - if ret != 0: + if ret.status != 0: return True return False -- 2.55.0 From 7f1809d6d1f4704e91a39a73bf0ed1fe7621b67d Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Fri, 24 Jul 2026 09:35:07 +0200 Subject: [PATCH 12/31] pyproject.toml: Enable mypy.strict_equality Set mypy.strict_equality = true, forcing type-equal equality. Signed-off-by: Jan Lindemann --- conf/templates/pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conf/templates/pyproject.toml b/conf/templates/pyproject.toml index 635a4041..a28490c8 100644 --- a/conf/templates/pyproject.toml +++ b/conf/templates/pyproject.toml @@ -4,6 +4,8 @@ warn_redundant_casts = true + strict_equality = true + enable_error_code = [ "explicit-override", "ignore-without-code", -- 2.55.0 From 49bf2b2442ce273e8e4c94bef3d90297ae9c1462 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 22:19:16 +0200 Subject: [PATCH 13/31] Distro, Cmd, AsyncSSH: Fix bare generic types Add explicit type arguments to all generic type annotations that were previously bare, satisfying the new disallow_any_generics mypy rule. Fixes: - Distro.py: Iterable[str] for expand_macros fmt parameter - Cmd.py: Types[Any] for add_subcommands cmds parameter - AsyncSSH.py: dict[str, Any] for _connect_kwargs return type Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- src/python/jw/pkg/lib/Cmd.py | 8 +++++--- src/python/jw/pkg/lib/Distro.py | 2 +- src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py | 6 +++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/python/jw/pkg/lib/Cmd.py b/src/python/jw/pkg/lib/Cmd.py index ffa76fa6..2d1375cb 100644 --- a/src/python/jw/pkg/lib/Cmd.py +++ b/src/python/jw/pkg/lib/Cmd.py @@ -3,14 +3,14 @@ from __future__ import annotations import abc import sys -from typing import TYPE_CHECKING, override +from typing import TYPE_CHECKING, Any, override from .log import ERR from .Types import LoadTypes, Types if TYPE_CHECKING: from argparse import ArgumentParser - from typing import Any, Iterable + from typing import Iterable from .App import App @@ -84,7 +84,9 @@ class AbstractCmd(abc.ABC): if exit_status is not None: sys.exit(exit_status) - def add_subcommands(self, cmds: Cmd | list[Cmd] | Types | list[Types]) -> None: + def add_subcommands( + self, cmds: Cmd | list[Cmd] | Types[Any] | list[Types[Any]] + ) -> None: if isinstance(cmds, Cmd): assert False return diff --git a/src/python/jw/pkg/lib/Distro.py b/src/python/jw/pkg/lib/Distro.py index 369facf9..5accdab1 100644 --- a/src/python/jw/pkg/lib/Distro.py +++ b/src/python/jw/pkg/lib/Distro.py @@ -278,7 +278,7 @@ class Distro(abc.ABC): def macros(cls) -> list[str]: return ['%%{' + name + '}' for name in cls.macro_names] - def expand_macros(self, fmt: str | Iterable) -> str | list[str]: + def expand_macros(self, fmt: str | Iterable[str]) -> str | list[str]: ret: str | list[str] if not isinstance(fmt, str): ret = [] diff --git a/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py b/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py index 13f452fe..127e2ef6 100644 --- a/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py +++ b/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py @@ -1,4 +1,4 @@ -from typing import override +from typing import Any, override import asyncio import os import shlex @@ -58,8 +58,8 @@ class AsyncSSH(Base): log(DEBUG, f'Failed to close connection ({str(e)}, ignored)') self.__conn = None - def _connect_kwargs(self, hide_secrets: bool = False) -> dict: - kwargs: dict = { + def _connect_kwargs(self, hide_secrets: bool = False) -> dict[str, Any]: + kwargs: dict[str, Any] = { 'host': self.hostname, 'port': self.port, 'username': self.username, -- 2.55.0 From 64edcec48cd0f67f5aa9d389bc6c135524c36bbf Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 22:19:12 +0200 Subject: [PATCH 14/31] pyproject.toml: Enable mypy.disallow_any_generics Add disallow_any_generics = true to the mypy configuration. This requires all generic types (dict, list, Iterable, Types, etc.) to have explicit type arguments instead of using bare generic aliases. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- conf/templates/pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conf/templates/pyproject.toml b/conf/templates/pyproject.toml index a28490c8..f3b5bd5d 100644 --- a/conf/templates/pyproject.toml +++ b/conf/templates/pyproject.toml @@ -6,6 +6,8 @@ strict_equality = true + disallow_any_generics = true + enable_error_code = [ "explicit-override", "ignore-without-code", -- 2.55.0 From fec03045430f3b35f5ee0e34259e6de932618816 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 22:22:05 +0200 Subject: [PATCH 15/31] App, AsyncSSH: Allow unused "type: ignore" comments Some '# type: ignore' comments are needed because they complain about missing but optional third-party packages: argcomplete, paramiko, asyncssh. The next commit will enable warn_unused_ignores, and since nor mypy nor pyright have a way of knowing that this is a tolerable lack of packages, this commit teaches them in advance. Signed-off-by: Jan Lindemann --- src/python/jw/pkg/lib/App.py | 10 ++++++---- src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py | 2 +- src/python/jw/pkg/lib/ec/ssh/Paramiko.py | 6 +++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/python/jw/pkg/lib/App.py b/src/python/jw/pkg/lib/App.py index 62f5467f..881f6b69 100644 --- a/src/python/jw/pkg/lib/App.py +++ b/src/python/jw/pkg/lib/App.py @@ -150,7 +150,7 @@ class App: # export cmd_classes: LoadTypes[AbstractCmd] = LoadTypes( modules if modules else ['__main__'], type_name_filter = name_filter, - type_filter = [AbstractCmd], # type: ignore[type-abstract] + type_filter = [AbstractCmd], ) add_all_parsers = ( '-h' in sys.argv or '--help' in sys.argv or '_ARGCOMPLETE' in os.environ @@ -186,17 +186,19 @@ class App: # export try: # Import argcomplete only here to not require it to be compatible # with minimal environments - from argcomplete.completers import ( # type: ignore[import-not-found] + from argcomplete.completers import ( # type: ignore[import-not-found, unused-ignore] BaseCompleter ) class NoopCompleter(BaseCompleter): @override - def __call__(self, *args, **kwargs): + def __call__( # pyright: ignore[reportGeneralTypeIssues] + self, *args, **kwargs + ): return None - import argcomplete # type: ignore[import-not-found] + import argcomplete # type: ignore[import-not-found, unused-ignore] argcomplete.autocomplete(self.__parser, default_completer = NoopCompleter()) diff --git a/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py b/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py index 127e2ef6..eacc31a3 100644 --- a/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py +++ b/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py @@ -6,7 +6,7 @@ import shutil import signal import sys -import asyncssh # type: ignore[import-not-found] +import asyncssh # type: ignore[import-not-found, unused-ignore] from ...base import Result from ...log import DEBUG, ERR, NOTICE, log diff --git a/src/python/jw/pkg/lib/ec/ssh/Paramiko.py b/src/python/jw/pkg/lib/ec/ssh/Paramiko.py index 936a9deb..866c3f67 100644 --- a/src/python/jw/pkg/lib/ec/ssh/Paramiko.py +++ b/src/python/jw/pkg/lib/ec/ssh/Paramiko.py @@ -4,9 +4,9 @@ from typing import override, TYPE_CHECKING # Tolerate missing paramiko imports. jw-pkg is designed to work with what it # finds. -import paramiko # type: ignore[import-untyped,import-not-found] # error: Library stubs not installed for "paramiko" -import paramiko.agent # type: ignore[import-untyped,import-not-found] -import paramiko.SCPClient # type: ignore[import-untyped,import-not-found] +import paramiko # type: ignore[import-untyped,import-not-found, unused-ignore] # error: Library stubs not installed for "paramiko" +import paramiko.agent # type: ignore[import-untyped,import-not-found, unused-ignore] +import paramiko.SCPClient # type: ignore[import-untyped,import-not-found, unused-ignore] from ...base import Result from ...log import ERR, log -- 2.55.0 From cbd6bac84cf12f55639200a6fa1f93b0351b5cc6 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 22:22:01 +0200 Subject: [PATCH 16/31] pyproject.toml: Enable mypy.warn_unused_ignores Add warn_unused_ignores = true to the mypy configuration. This warns when a '# type: ignore' comment has no associated error code, ensuring all type ignores are explicit about which error they suppress. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- conf/templates/pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conf/templates/pyproject.toml b/conf/templates/pyproject.toml index f3b5bd5d..714ea7ab 100644 --- a/conf/templates/pyproject.toml +++ b/conf/templates/pyproject.toml @@ -8,6 +8,8 @@ disallow_any_generics = true + warn_unused_ignores = true + enable_error_code = [ "explicit-override", "ignore-without-code", -- 2.55.0 From dcbd4b1c84243fcda893151bdd3950e98f37cb4f Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 22:29:27 +0200 Subject: [PATCH 17/31] lib: Change Iterable to Collection for truthy checks Change parameter types from Iterable[str] to Collection[str] wherever the parameter is tested for emptiness (if not names). This satisfies the new truthy-iterable mypy rule, since bare Iterable values are always truthy even when empty. Affected files: - Distro.py: install, delete, select, _select, _select_by_name - rpm.py: query_packages - suse/Distro.py: _select_by_name - Cmd.py (secrets): _match_files, _list_template_files, etc. - DistroContext.py: list_template_files, list_secret_paths, etc. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- src/python/jw/pkg/cmds/secrets/Cmd.py | 14 +++++++------- .../jw/pkg/cmds/secrets/lib/DistroContext.py | 16 ++++++++-------- src/python/jw/pkg/lib/Distro.py | 11 ++++++----- src/python/jw/pkg/lib/distros/suse/Distro.py | 3 ++- src/python/jw/pkg/lib/pm/rpm.py | 4 ++-- 5 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/python/jw/pkg/cmds/secrets/Cmd.py b/src/python/jw/pkg/cmds/secrets/Cmd.py index 9b02e6e6..c9bb7316 100644 --- a/src/python/jw/pkg/cmds/secrets/Cmd.py +++ b/src/python/jw/pkg/cmds/secrets/Cmd.py @@ -8,8 +8,8 @@ from ..CmdSecrets import CmdSecrets as Parent from .lib.DistroContext import DistroContext if TYPE_CHECKING: + from collections.abc import Collection from argparse import ArgumentParser - from typing import Iterable from .lib.base import Attrs @@ -19,27 +19,27 @@ class Cmd(Base): # export def ctx(self) -> DistroContext: return DistroContext(self.app.distro) - async def _match_files(self, packages: Iterable[str], pattern: str) -> list[str]: + async def _match_files(self, packages: Collection[str], pattern: str) -> list[str]: return await self.ctx.match_files(packages, pattern) - async def _list_template_files(self, packages: Iterable[str]) -> list[str]: + async def _list_template_files(self, packages: Collection[str]) -> list[str]: return await self.ctx.list_template_files(packages) async def _list_secret_paths( - self, packages: Iterable[str], ignore_missing: bool = False + self, packages: Collection[str], ignore_missing: bool = False ) -> list[str]: return await self.ctx.list_secret_paths(packages, ignore_missing) async def _list_compilation_targets( - self, packages: Iterable[str], ignore_missing: bool = False + self, packages: Collection[str], ignore_missing: bool = False ) -> list[str]: return await self.ctx.list_compilation_targets(packages, ignore_missing) - async def _remove_compilation_targets(self, packages: Iterable[str]) -> list[str]: + async def _remove_compilation_targets(self, packages: Collection[str]) -> list[str]: return await self.ctx.remove_compilation_targets(packages) async def _compile_template_files( - self, packages: Iterable[str], default_attrs: Attrs + self, packages: Collection[str], default_attrs: Attrs ) -> list[str]: return await self.ctx.compile_template_files(packages, default_attrs) diff --git a/src/python/jw/pkg/cmds/secrets/lib/DistroContext.py b/src/python/jw/pkg/cmds/secrets/lib/DistroContext.py index 37780baf..95edc540 100644 --- a/src/python/jw/pkg/cmds/secrets/lib/DistroContext.py +++ b/src/python/jw/pkg/cmds/secrets/lib/DistroContext.py @@ -11,8 +11,8 @@ from ....lib.ProcFilterGpg import ProcFilterGpg from .FilesContext import FilesContext if TYPE_CHECKING: + from collections.abc import Collection from .base import Attrs - from typing import Iterable from ....lib.Distro import Distro @@ -22,7 +22,7 @@ class DistroContext(FilesContext): super().__init__(distro.ctx) self.__distro = distro - async def match_files(self, pkg_names: Iterable[str], pattern: str) -> list[str]: + async def match_files(self, pkg_names: Collection[str], pattern: str) -> list[str]: ret: list[str] = [] for pkg_name in pkg_names: for path in await self.__distro.pkg_files(pkg_name): @@ -30,13 +30,13 @@ class DistroContext(FilesContext): ret.append(path) return ret - async def list_template_files(self, pkg_names: Iterable[str]) -> list[str]: + async def list_template_files(self, pkg_names: Collection[str]) -> list[str]: if not pkg_names: pkg_names = [p.name for p in await self.__distro.select()] return await self.match_files(pkg_names, pattern = r'.*\.jw-tmpl$') async def list_secret_paths( - self, pkg_names: Iterable[str], ignore_missing: bool = False + self, pkg_names: Collection[str], ignore_missing: bool = False ) -> list[str]: ret = [] for tmpl in await self.list_template_files(pkg_names): @@ -47,7 +47,7 @@ class DistroContext(FilesContext): return ret async def list_compilation_targets( - self, pkg_names: Iterable[str], ignore_missing: bool = False + self, pkg_names: Collection[str], ignore_missing: bool = False ) -> list[str]: ret = [] for tmpl in await self.list_template_files(pkg_names): @@ -57,7 +57,7 @@ class DistroContext(FilesContext): ret.append(path) return ret - async def remove_compilation_targets(self, pkg_names: Iterable[str]) -> list[str]: + async def remove_compilation_targets(self, pkg_names: Collection[str]) -> list[str]: ret: list[str] = [] for path in await self.list_compilation_targets(pkg_names): try: @@ -71,7 +71,7 @@ class DistroContext(FilesContext): return ret async def compile_template_files( - self, pkg_names: Iterable[str], default_attrs: Attrs + self, pkg_names: Collection[str], default_attrs: Attrs ) -> list[str]: ret: list[str] = [] missing = 0 @@ -96,7 +96,7 @@ class DistroContext(FilesContext): async def install( self, src_uri: str, - pkg_names: Iterable[str], + pkg_names: Collection[str], only_missing: bool = False, verbose: bool = False, ) -> None: diff --git a/src/python/jw/pkg/lib/Distro.py b/src/python/jw/pkg/lib/Distro.py index 5accdab1..467636b4 100644 --- a/src/python/jw/pkg/lib/Distro.py +++ b/src/python/jw/pkg/lib/Distro.py @@ -12,6 +12,7 @@ from .log import ERR, INFO, WARNING, log from .base import InputMode if TYPE_CHECKING: + from collections.abc import Collection from typing import Iterable from .base import Result @@ -359,16 +360,16 @@ class Distro(abc.ABC): # -- select @abc.abstractmethod - async def _select_by_name(self, names: Iterable[str]) -> Iterable[Package]: + async def _select_by_name(self, names: Collection[str]) -> Iterable[Package]: pass - async def _select(self, names: Iterable[str], + async def _select(self, names: Collection[str], filter: PackageFilter) -> Iterable[Package]: return [p for p in await self._select_by_name(names) if filter.match(p)] async def select( self, - names: Iterable[str] = [], + names: Collection[str] = [], filter: PackageFilter | None = None ) -> Iterable[Package]: if not filter: @@ -434,7 +435,7 @@ class Distro(abc.ABC): if names: await self._install(names, only_update = only_update) - async def install(self, names: Iterable[str], only_update: bool = False) -> None: + async def install(self, names: Collection[str], only_update: bool = False) -> None: if not names: log(WARNING, 'No packages specified for installation') return @@ -446,7 +447,7 @@ class Distro(abc.ABC): async def _delete(self, names: Iterable[str]) -> None: pass - async def delete(self, names: Iterable[str]) -> None: + async def delete(self, names: Collection[str]) -> None: if not names: log(WARNING, 'No packages specified for deletion') return diff --git a/src/python/jw/pkg/lib/distros/suse/Distro.py b/src/python/jw/pkg/lib/distros/suse/Distro.py index 0bbe1b6c..44675cc6 100644 --- a/src/python/jw/pkg/lib/distros/suse/Distro.py +++ b/src/python/jw/pkg/lib/distros/suse/Distro.py @@ -6,6 +6,7 @@ from ...Distro import Distro as Base from ...pm.rpm import list_files, query_packages, run_rpm if TYPE_CHECKING: + from collections.abc import Collection from typing import Iterable from ...base import Result @@ -68,7 +69,7 @@ class Distro(Base): return False @override - async def _select_by_name(self, names: Iterable[str]) -> Iterable[Package]: + async def _select_by_name(self, names: Collection[str]) -> Iterable[Package]: return await query_packages(names, ec = self.ctx) @override diff --git a/src/python/jw/pkg/lib/pm/rpm.py b/src/python/jw/pkg/lib/pm/rpm.py index 8635a9d5..01995b8b 100644 --- a/src/python/jw/pkg/lib/pm/rpm.py +++ b/src/python/jw/pkg/lib/pm/rpm.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable +from typing import TYPE_CHECKING, Collection, Iterable from ..base import InputMode from ..Package import Package @@ -41,7 +41,7 @@ async def run_rpm( # export return result.stdout_str async def query_packages( # export - names: Iterable[str] = [], + names: Collection[str] = [], ec: ExecContext | None = None, ) -> Iterable[Package]: fmt_str = ( -- 2.55.0 From a3e46121da94649036fefb23c2a272df2b2e4055 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 22:29:21 +0200 Subject: [PATCH 18/31] pyproject.toml: Enable mypy.truthy-iterable Add the truthy-iterable error code to the mypy configuration. This detects Iterable parameters that are used in boolean contexts (if not names, etc.) since Iterable values are always truthy. The fix is to change Iterable[str] parameters to Collection[str] when they are tested for emptiness, since Collection guarantees __len__. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- conf/templates/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/conf/templates/pyproject.toml b/conf/templates/pyproject.toml index 714ea7ab..5c71db50 100644 --- a/conf/templates/pyproject.toml +++ b/conf/templates/pyproject.toml @@ -15,6 +15,7 @@ "ignore-without-code", "possibly-undefined", "truthy-bool", + "truthy-iterable", ] [tool.isort] -- 2.55.0 From 55b63ded69f42874c4ec943d6cd2cddea4499490 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 22:35:22 +0200 Subject: [PATCH 19/31] lib, cmds: Add exhaustive match cases Add 'case _: pass' to match statements that are intentionally non-exhaustive, satisfying the new exhaustive-match mypy rule. Also replaced 'case '_':' (a string literal) with 'case _: pass' in pkg_relations.py since it was an unreachable case (syntax is a VersionSyntax enum, not a str). Added 'case VersionSyntax.names_only:' to the match in pkg_relations.py to handle the missing enum value. Files modified: - util.py: Two match statements for askpass env vars - Distro.py: Three match statements for backend/os detection - pkg_relations.py: Match on VersionSyntax enum - CmdListRepos.py: Match on URL scheme Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- src/python/jw/pkg/cmds/projects/CmdListRepos.py | 2 ++ src/python/jw/pkg/cmds/projects/lib/pkg_relations.py | 11 ++++++----- src/python/jw/pkg/lib/Distro.py | 6 ++++++ src/python/jw/pkg/lib/util.py | 2 ++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/python/jw/pkg/cmds/projects/CmdListRepos.py b/src/python/jw/pkg/cmds/projects/CmdListRepos.py index afcce40d..6324a81c 100644 --- a/src/python/jw/pkg/cmds/projects/CmdListRepos.py +++ b/src/python/jw/pkg/cmds/projects/CmdListRepos.py @@ -143,6 +143,8 @@ class CmdListRepos(Cmd): # export f'instance at {args.base_url}, tried {", ".join(tried)}' ) return + case _: + pass if os.path.isdir(args.base_url): for subdir in ['.', args.from_owner]: out = [] diff --git a/src/python/jw/pkg/cmds/projects/lib/pkg_relations.py b/src/python/jw/pkg/cmds/projects/lib/pkg_relations.py index 9bdcbecc..954a77ec 100644 --- a/src/python/jw/pkg/cmds/projects/lib/pkg_relations.py +++ b/src/python/jw/pkg/cmds/projects/lib/pkg_relations.py @@ -150,6 +150,8 @@ def pkg_relations( match syntax: case VersionSyntax.semver: pass + case VersionSyntax.names_only: + pass case VersionSyntax.debian: if len(expanded_dep) == 3: match expanded_dep[1]: @@ -157,11 +159,10 @@ def pkg_relations( expanded_dep[1] = '<<' case '>': expanded_dep[1] = '>>' - case '_': - raise NotImplementedError( - f'Unknown dependency syntax "{syntax}" for ' - f'dependency "{dep[0]} {dep[1]} {dep[3]}"' - ) + case _: + pass + case _: + pass dep_str = ' '.join(expanded_dep) if quote: dep_str = '"' + dep_str + '"' diff --git a/src/python/jw/pkg/lib/Distro.py b/src/python/jw/pkg/lib/Distro.py index 467636b4..0a40dbd4 100644 --- a/src/python/jw/pkg/lib/Distro.py +++ b/src/python/jw/pkg/lib/Distro.py @@ -98,6 +98,8 @@ class Distro(abc.ABC): match ret: case 'opensuse-tumbleweed': return 'suse' + case _: + pass return ret @classmethod @@ -119,6 +121,8 @@ class Distro(abc.ABC): backend_id = 'redhat' case 'opensuse' | 'suse': backend_id = 'suse' + case _: + pass module_path = 'jw.pkg.lib.distros.' + backend_id + '.Distro' try: module = importlib.import_module(module_path) @@ -173,6 +177,8 @@ class Distro(abc.ABC): __append('linux') __append('pkg-pm') __append('pm-pacman') + case _: + pass os = self.os name = re.sub(r'-.*', '', os) diff --git a/src/python/jw/pkg/lib/util.py b/src/python/jw/pkg/lib/util.py index c3f581b9..db0eb5e1 100644 --- a/src/python/jw/pkg/lib/util.py +++ b/src/python/jw/pkg/lib/util.py @@ -122,6 +122,8 @@ async def run_askpass( continue # Can't get user name from SSH_ASKPASS case AskpassKey.Password: exe_arg += 'Password' + case _: + pass result = await run_cmd([exe, exe_arg], throw = throw, ec = ec) if result.status == 0 and result.stdout_or_none is not None: ret = result.stdout_str_or_none -- 2.55.0 From a1d192b048ff8e643034ef2424cb345dfd0ba8ab Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 22:35:16 +0200 Subject: [PATCH 20/31] pyproject.toml: Enable mypy.exhaustive-match Add the exhaustive-match error code to the mypy configuration. This requires all match statements to be exhaustive, handling all possible values of the matched expression. The fix is to add 'case _: pass' to each match statement that is intended to be non-exhaustive, making the non-exhaustiveness explicit. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- conf/templates/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/conf/templates/pyproject.toml b/conf/templates/pyproject.toml index 5c71db50..2a4386b7 100644 --- a/conf/templates/pyproject.toml +++ b/conf/templates/pyproject.toml @@ -16,6 +16,7 @@ "possibly-undefined", "truthy-bool", "truthy-iterable", + "exhaustive-match", ] [tool.isort] -- 2.55.0 From c16e054aaaf84dc31e7030b549d523685054d725 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 22:42:18 +0200 Subject: [PATCH 21/31] App, lib, cmds: Remove unreachable code Remove dead code paths detected by the new warn_unreachable mypy rule. These include: - Removed always-false isinstance checks (ssh/util.py, templates.py) - Removed unreachable return statements after raise (FileContext.py) - Removed unreachable None checks for typed variables (Result.py, ExecContext.py, CmdGetAuthInfo.py) - Simplified __uri function by removing impossible None check (CopyContext.py) - Changed assert False to explicit error (Cmd.py) - Removed unreachable None case from match (App.py) - Removed redundant outer case _: pass (pkg_relations.py) - Restructured stdin write to avoid unreachable warning (AsyncSSH.py) Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- src/python/jw/pkg/App.py | 2 +- src/python/jw/pkg/cmds/projects/CmdGetAuthInfo.py | 2 -- src/python/jw/pkg/cmds/projects/lib/pkg_relations.py | 2 -- src/python/jw/pkg/cmds/projects/lib/templates.py | 5 +---- src/python/jw/pkg/lib/Cmd.py | 3 +-- src/python/jw/pkg/lib/CopyContext.py | 4 +--- src/python/jw/pkg/lib/ExecContext.py | 4 +--- src/python/jw/pkg/lib/FileContext.py | 1 - src/python/jw/pkg/lib/Result.py | 2 -- src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py | 5 ++--- src/python/jw/pkg/lib/ec/ssh/util.py | 2 -- 11 files changed, 7 insertions(+), 25 deletions(-) diff --git a/src/python/jw/pkg/App.py b/src/python/jw/pkg/App.py index f46e8bc1..95cd2b85 100644 --- a/src/python/jw/pkg/App.py +++ b/src/python/jw/pkg/App.py @@ -85,7 +85,7 @@ class App(Base): return path case 'relative': return os.path.relpath(path) - case None | 'absolute': + case 'absolute': return os.path.abspath(path) case _: m = re.search(r'^make:(\S+)$', fmt) diff --git a/src/python/jw/pkg/cmds/projects/CmdGetAuthInfo.py b/src/python/jw/pkg/cmds/projects/CmdGetAuthInfo.py index 5571998f..b2d6dc8a 100644 --- a/src/python/jw/pkg/cmds/projects/CmdGetAuthInfo.py +++ b/src/python/jw/pkg/cmds/projects/CmdGetAuthInfo.py @@ -90,8 +90,6 @@ class CmdGetAuthInfo(Cmd): # export for key, val in result.items(): if not getattr(args, key, None): continue - if val is None: - continue if args.only_values: print(val) continue diff --git a/src/python/jw/pkg/cmds/projects/lib/pkg_relations.py b/src/python/jw/pkg/cmds/projects/lib/pkg_relations.py index 954a77ec..ea1f64b3 100644 --- a/src/python/jw/pkg/cmds/projects/lib/pkg_relations.py +++ b/src/python/jw/pkg/cmds/projects/lib/pkg_relations.py @@ -161,8 +161,6 @@ def pkg_relations( expanded_dep[1] = '>>' case _: pass - case _: - pass dep_str = ' '.join(expanded_dep) if quote: dep_str = '"' + dep_str + '"' diff --git a/src/python/jw/pkg/cmds/projects/lib/templates.py b/src/python/jw/pkg/cmds/projects/lib/templates.py index 37fd3d2b..134135f2 100644 --- a/src/python/jw/pkg/cmds/projects/lib/templates.py +++ b/src/python/jw/pkg/cmds/projects/lib/templates.py @@ -66,10 +66,7 @@ def merge_values(*values: RenderValues) -> ListDict: rhs_dict = render_values_to_list_dict(rhs) for key, val in rhs_dict.items(): entry = ret.setdefault(key, []) - if isinstance(val, list): - entry += val - else: - entry.append(val) + entry += val return ret def format_list_dict( diff --git a/src/python/jw/pkg/lib/Cmd.py b/src/python/jw/pkg/lib/Cmd.py index 2d1375cb..0cca8e7c 100644 --- a/src/python/jw/pkg/lib/Cmd.py +++ b/src/python/jw/pkg/lib/Cmd.py @@ -88,8 +88,7 @@ class AbstractCmd(abc.ABC): self, cmds: Cmd | list[Cmd] | Types[Any] | list[Types[Any]] ) -> None: if isinstance(cmds, Cmd): - assert False - return + raise NotImplementedError('Single Cmd should be handled elsewhere') if isinstance(cmds, list): for cmd in cmds: self.add_subcommands(cmd) diff --git a/src/python/jw/pkg/lib/CopyContext.py b/src/python/jw/pkg/lib/CopyContext.py index 5e731736..9eefbdbe 100644 --- a/src/python/jw/pkg/lib/CopyContext.py +++ b/src/python/jw/pkg/lib/CopyContext.py @@ -12,9 +12,7 @@ class CopyContext: chroot = False ) -> None: - def __uri(ctx: FileContext | Uri | str) -> Uri | str | None: - if ctx is None: - return None + def __uri(ctx: FileContext | Uri | str) -> Uri | str: if isinstance(ctx, Uri): return ctx if isinstance(ctx, str): diff --git a/src/python/jw/pkg/lib/ExecContext.py b/src/python/jw/pkg/lib/ExecContext.py index 4ca033fc..7ee85b82 100644 --- a/src/python/jw/pkg/lib/ExecContext.py +++ b/src/python/jw/pkg/lib/ExecContext.py @@ -150,9 +150,7 @@ class ExecContext(Base): interactive = sys.stdin.isatty() else: interactive = False - if cmd_input is None: - cmd_input_bytes = None - elif isinstance(cmd_input, str): + if isinstance(cmd_input, str): cmd_input_bytes = cmd_input.encode(sys.stdout.encoding or 'utf-8') else: cmd_input_bytes = cmd_input diff --git a/src/python/jw/pkg/lib/FileContext.py b/src/python/jw/pkg/lib/FileContext.py index 49dc751b..5658db74 100644 --- a/src/python/jw/pkg/lib/FileContext.py +++ b/src/python/jw/pkg/lib/FileContext.py @@ -297,7 +297,6 @@ class FileContext(abc.ABC): except Exception as e: log(ERR, f'{self.log_name}: Failed to stat({path}) ({str(e)})') raise - return False async def is_dir(self, path: str, follow_symlinks = True) -> bool: return await self._is_dir(self._chroot(path), follow_symlinks = follow_symlinks) diff --git a/src/python/jw/pkg/lib/Result.py b/src/python/jw/pkg/lib/Result.py index f0a13a81..644fe186 100644 --- a/src/python/jw/pkg/lib/Result.py +++ b/src/python/jw/pkg/lib/Result.py @@ -159,8 +159,6 @@ class Result: if self.status == 0: return False err = self.stderr_str - if err is None: - return False import re return re.search(pattern, err) is not None diff --git a/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py b/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py index eacc31a3..a2fbb12f 100644 --- a/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py +++ b/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py @@ -210,9 +210,8 @@ class AsyncSSH(Base): except (BrokenPipeError, OSError): pass return - if proc.stdin is None: - return - proc.stdin.write(data) + if proc.stdin is not None: + proc.stdin.write(data) await proc.stdin.drain() async def _pump_stdout() -> None: diff --git a/src/python/jw/pkg/lib/ec/ssh/util.py b/src/python/jw/pkg/lib/ec/ssh/util.py index 854423e8..91b4040c 100644 --- a/src/python/jw/pkg/lib/ec/ssh/util.py +++ b/src/python/jw/pkg/lib/ec/ssh/util.py @@ -42,8 +42,6 @@ def join_cmd( """ ret: list[str] = [] for token in cmd: - if not isinstance(token, str): - token = str(token) if token in operators: ret.append(token) else: -- 2.55.0 From 75c9879f674a03bbae9de229ab3cc29dd5573756 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 22:42:09 +0200 Subject: [PATCH 22/31] pyproject.toml: Enable mypy.warn_unreachable Add warn_unreachable = true to the mypy configuration. This detects statements that mypy proves are unreachable, such as code after an unconditional return or assertions that can never be true. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- conf/templates/pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conf/templates/pyproject.toml b/conf/templates/pyproject.toml index 2a4386b7..0500ebb4 100644 --- a/conf/templates/pyproject.toml +++ b/conf/templates/pyproject.toml @@ -10,6 +10,8 @@ warn_unused_ignores = true + warn_unreachable = true + enable_error_code = [ "explicit-override", "ignore-without-code", -- 2.55.0 From 410fd7ab5c4155e752dcf839e935d38360e72bf2 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 22:48:42 +0200 Subject: [PATCH 23/31] App, lib: Add type annotations to untyped functions Add missing type annotations to functions that are called from typed contexts, satisfying the new disallow_untyped_calls mypy rule. Fixes: - Local.py: __log() with typed parameters - lib/App.py: _add_arguments(), add_cmd_to_parser(), add_cmds_to_parser() - pm/rpm.py, dpkg.py: meta_map() return type - Exec.py: __init_askpass() return type - App.py: strip_module_from_spec(), __get_project_refs_cached(), ResultCache.__init__ and run(), _add_arguments() - Added Collection type for truthy-iterable compliance Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- src/python/jw/pkg/App.py | 23 ++++++++++++++++------- src/python/jw/pkg/lib/App.py | 8 ++++---- src/python/jw/pkg/lib/ec/Local.py | 4 +++- src/python/jw/pkg/lib/ec/ssh/Exec.py | 2 +- src/python/jw/pkg/lib/pm/dpkg.py | 2 +- src/python/jw/pkg/lib/pm/rpm.py | 2 +- 6 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/python/jw/pkg/App.py b/src/python/jw/pkg/App.py index 95cd2b85..cc57a6a8 100644 --- a/src/python/jw/pkg/App.py +++ b/src/python/jw/pkg/App.py @@ -10,7 +10,7 @@ import sys from enum import Enum, auto from functools import cache -from typing import override, TYPE_CHECKING +from typing import Any, override, TYPE_CHECKING from .lib.App import App as Base from .lib.Distro import Distro @@ -19,6 +19,7 @@ from .lib.ProjectConf import ProjectConf if TYPE_CHECKING: import argparse + from argparse import ArgumentParser from typing import TypeAlias @@ -36,10 +37,10 @@ if TYPE_CHECKING: class ResultCache(object): - def __init__(self): - self.__cache = {} + def __init__(self) -> None: + self.__cache: dict[str, Any] = {} - def run(self, func, args): + def run(self, func, args: list[Any]) -> object: d = self.__cache depth = 0 keys = [func.__name__] + args @@ -200,7 +201,15 @@ class App(Base): return None def __get_project_refs_cached( - self, buf, visited, spec, section, key, add_self, scope, names_only + self, + buf: list[str], + visited: set[str], + spec: str, + section: str, + key: str, + add_self: bool, + scope: Scope, + names_only: bool, ): return self.__res_cache.run( self.__get_project_refs, @@ -381,7 +390,7 @@ class App(Base): self.__exec_context = None @override - def _add_arguments(self, parser) -> None: + def _add_arguments(self, parser: ArgumentParser) -> None: super()._add_arguments(parser) parser.add_argument('-t', '--topdir', default = None, help = 'Project Path') parser.add_argument( @@ -537,7 +546,7 @@ class App(Base): def tmpl_dir(self, name: str) -> str | None: return self.find_dir(name, ['/tmpl'], ['/opt/' + name + '/share/tmpl']) - def strip_module_from_spec(self, mod): + def strip_module_from_spec(self, mod: str) -> str: return re.sub(r'-dev$|-devel$|-run$', '', re.split('([=><]+)', mod)[0].strip()) @cache diff --git a/src/python/jw/pkg/lib/App.py b/src/python/jw/pkg/lib/App.py index 881f6b69..a2357ec0 100644 --- a/src/python/jw/pkg/lib/App.py +++ b/src/python/jw/pkg/lib/App.py @@ -14,14 +14,14 @@ from .Types import LoadTypes from .util import pretty_cmd if TYPE_CHECKING: - from collections.abc import Awaitable + from collections.abc import Awaitable, Collection from typing import TypeVar T = TypeVar('T') class App: # export - def _add_arguments(self, parser): + def _add_arguments(self, parser: ArgumentParser) -> None: self.__parser.add_argument( '--log-flags', help = 'Log flags', default = self.__default_log_flags ) @@ -51,7 +51,7 @@ class App: # export eloop: None = None, ) -> None: - def add_cmd_to_parser(cmd, parsers): + def add_cmd_to_parser(cmd: AbstractCmd, parsers: Any) -> ArgumentParser: parser = parsers.add_parser( cmd.name, help = cmd.help, @@ -67,7 +67,7 @@ class App: # export def add_cmds_to_parser( parent: AbstractCmd | App, parser: ArgumentParser, - cmds, + cmds: Collection[AbstractCmd], all = False ) -> None: if not cmds: diff --git a/src/python/jw/pkg/lib/ec/Local.py b/src/python/jw/pkg/lib/ec/Local.py index 08630e16..243a969a 100644 --- a/src/python/jw/pkg/lib/ec/Local.py +++ b/src/python/jw/pkg/lib/ec/Local.py @@ -14,6 +14,8 @@ from ..ExecContext import ExecContext as Base from ..log import ERR, NOTICE, log if TYPE_CHECKING: + from typing import Any + from ..Uri import Uri class Local(Base): @@ -37,7 +39,7 @@ class Local(Base): log_prefix: str, ) -> Result: - def __log(prio, *args, verbose = verbose): + def __log(prio: int, *args: Any, verbose: bool = verbose) -> None: if verbose: log(prio, log_prefix, *args) diff --git a/src/python/jw/pkg/lib/ec/ssh/Exec.py b/src/python/jw/pkg/lib/ec/ssh/Exec.py index e0969e29..17bd35e4 100644 --- a/src/python/jw/pkg/lib/ec/ssh/Exec.py +++ b/src/python/jw/pkg/lib/ec/ssh/Exec.py @@ -28,7 +28,7 @@ class Exec(Base): if self.__askpass is not None: os.remove(self.__askpass) - def __init_askpass(self): + def __init_askpass(self) -> None: if self.__askpass is None and self.password is not None: import sys import tempfile diff --git a/src/python/jw/pkg/lib/pm/dpkg.py b/src/python/jw/pkg/lib/pm/dpkg.py index 41596211..12b5d9e8 100644 --- a/src/python/jw/pkg/lib/pm/dpkg.py +++ b/src/python/jw/pkg/lib/pm/dpkg.py @@ -11,7 +11,7 @@ from ..util import run_cmd, run_sudo _meta_map: dict[str, str] | None = None -def meta_map(): +def meta_map() -> dict[str, str]: global _meta_map if _meta_map is None: _meta_map = Package.order_tags( diff --git a/src/python/jw/pkg/lib/pm/rpm.py b/src/python/jw/pkg/lib/pm/rpm.py index 01995b8b..68421e81 100644 --- a/src/python/jw/pkg/lib/pm/rpm.py +++ b/src/python/jw/pkg/lib/pm/rpm.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: _meta_map: dict[str, str] | None = None -def meta_map(): +def meta_map() -> dict[str, str]: global _meta_map if _meta_map is None: _meta_map = Package.order_tags( -- 2.55.0 From b3fee32ee17414958dfb731f870cee469210903f Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 22:48:35 +0200 Subject: [PATCH 24/31] pyproject.toml: Enable mypy.disallow_untyped_calls Add disallow_untyped_calls = true to the mypy configuration. This requires all function calls from typed code to target typed functions, ensuring better type safety across the codebase. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- conf/templates/pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conf/templates/pyproject.toml b/conf/templates/pyproject.toml index 0500ebb4..790626e6 100644 --- a/conf/templates/pyproject.toml +++ b/conf/templates/pyproject.toml @@ -12,6 +12,8 @@ warn_unreachable = true + disallow_untyped_calls = true + enable_error_code = [ "explicit-override", "ignore-without-code", -- 2.55.0 From 1e613a39c6e7f872a45b43fa6409ee411c429dea Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 23:03:53 +0200 Subject: [PATCH 25/31] App, cmds, lib: Fix Any returns from typed functions Add type annotations and casts to functions that were returning Any where a specific type was declared, satisfying the new warn_return_any mypy rule. Fixes: - log.py: get_caller_pos return type via cast - AsyncRunner.py: cast T for fut.result() - util.py: cast for getattr result, str() for args.username - FileContext.py: verbose_default bool annotation - SSHClient.py: cast SSHClient for dynamic import - lib/App.py: cast ArgumentParser, add return types to inner funcs - pm/rpm.py, dpkg.py: cast Iterable[Package] - App.py: cast for self.args.func(), add return types to inner funcs - BaseCmdPkgRelations.py: cast str for args.delimiter Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- src/python/jw/pkg/App.py | 38 +++++++++-------- src/python/jw/pkg/cmds/Cmd.py | 3 +- src/python/jw/pkg/cmds/CmdPkg.py | 4 +- src/python/jw/pkg/cmds/CmdPlatform.py | 4 +- src/python/jw/pkg/cmds/CmdPosix.py | 4 +- src/python/jw/pkg/cmds/CmdProjects.py | 4 +- src/python/jw/pkg/cmds/CmdSecrets.py | 4 +- src/python/jw/pkg/cmds/posix/CmdTar.py | 4 +- src/python/jw/pkg/cmds/posix/tar/Cmd.py | 4 +- .../pkg/cmds/projects/BaseCmdPkgRelations.py | 4 +- .../cmds/projects/CmdCanonicalizeRemotes.py | 2 +- src/python/jw/pkg/cmds/projects/CmdCheck.py | 4 +- .../jw/pkg/cmds/projects/CmdCreateFile.py | 6 +-- .../jw/pkg/cmds/projects/lib/pkg_relations.py | 4 +- .../jw/pkg/cmds/projects/lib/templates.py | 12 +++--- .../jw/pkg/cmds/secrets/lib/FilesContext.py | 6 ++- src/python/jw/pkg/lib/App.py | 42 +++++++++++-------- src/python/jw/pkg/lib/AsyncRunner.py | 6 +-- src/python/jw/pkg/lib/Cmd.py | 10 ++--- src/python/jw/pkg/lib/CopyContext.py | 6 +-- src/python/jw/pkg/lib/Distro.py | 13 +++--- src/python/jw/pkg/lib/ExecContext.py | 10 ++--- src/python/jw/pkg/lib/FileContext.py | 16 +++---- src/python/jw/pkg/lib/Package.py | 8 ++-- src/python/jw/pkg/lib/Result.py | 4 +- src/python/jw/pkg/lib/TarIo.py | 8 ++-- src/python/jw/pkg/lib/Types.py | 12 +++--- src/python/jw/pkg/lib/distros/arch/Distro.py | 4 +- .../jw/pkg/lib/distros/debian/Distro.py | 6 +-- src/python/jw/pkg/lib/distros/suse/Distro.py | 11 +++-- src/python/jw/pkg/lib/ec/Curl.py | 8 +++- src/python/jw/pkg/lib/ec/Local.py | 19 +++++---- src/python/jw/pkg/lib/ec/SSHClient.py | 18 ++++---- src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py | 15 ++++--- src/python/jw/pkg/lib/ec/ssh/Exec.py | 6 +-- src/python/jw/pkg/lib/ec/ssh/Paramiko.py | 4 +- src/python/jw/pkg/lib/log.py | 28 +++++++++---- src/python/jw/pkg/lib/pm/dpkg.py | 4 +- src/python/jw/pkg/lib/pm/rpm.py | 6 +-- src/python/jw/pkg/lib/util.py | 36 ++++++++-------- 40 files changed, 228 insertions(+), 179 deletions(-) diff --git a/src/python/jw/pkg/App.py b/src/python/jw/pkg/App.py index cc57a6a8..cdc82c4d 100644 --- a/src/python/jw/pkg/App.py +++ b/src/python/jw/pkg/App.py @@ -10,7 +10,7 @@ import sys from enum import Enum, auto from functools import cache -from typing import Any, override, TYPE_CHECKING +from typing import Any, cast, override, TYPE_CHECKING from .lib.App import App as Base from .lib.Distro import Distro @@ -40,7 +40,7 @@ class ResultCache(object): def __init__(self) -> None: self.__cache: dict[str, Any] = {} - def run(self, func, args: list[Any]) -> object: + def run(self, func: Any, args: list[Any]) -> object: d = self.__cache depth = 0 keys = [func.__name__] + args @@ -137,21 +137,21 @@ class App(Base): if search_absdirs is None: search_absdirs = [] - def __format_relpath(path: str): + def __format_relpath(path: str) -> str: if path.startswith('./'): return path[2:] if path.endswith('/.'): return path[:-2] return path - def __relpath(target: str, base: str): + def __relpath(target: str, base: str) -> str: return __format_relpath(os.path.relpath(target, base)) - def __format_pd(name: str, pd: str, pretty: bool): + def __format_pd(name: str, pd: str, pretty: bool) -> str | None: if not pretty: return pd if self.__topdir_fmt == 'absolute': - return os.path.abspath(pd) + return str(os.path.abspath(pd)) if self.__topdir_fmt == 'unaltered': return pd if self.__topdir_fmt == 'relative': @@ -175,6 +175,7 @@ class App(Base): path = pd + '/' + sd if os.path.isdir(path): ret = __format_pd(name, pd, pretty) + assert ret is not None if sd and sd[0] != '/': if ret == '.': ret = '' @@ -210,10 +211,13 @@ class App(Base): add_self: bool, scope: Scope, names_only: bool, - ): - return self.__res_cache.run( - self.__get_project_refs, - [buf, visited, spec, section, key, add_self, scope, names_only], + ) -> list[str]: + return cast( + 'list[str]', + self.__res_cache.run( + self.__get_project_refs, + [buf, visited, spec, section, key, add_self, scope, names_only], + ), ) def __get_project_refs( @@ -292,7 +296,7 @@ class App(Base): for dep in deps: self.__read_dep_graph([dep], sections, graph) - def __flip_dep_graph(self, graph: Graph): + def __flip_dep_graph(self, graph: Graph) -> Graph: ret: Graph = {} for project, deps in graph.items(): for d in deps: @@ -384,7 +388,7 @@ class App(Base): ) @override - async def __aexit__(self, exc_type, exc, tb) -> None: + async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None: if self.__exec_context is not None: await self.__exec_context.close() self.__exec_context = None @@ -494,7 +498,7 @@ class App(Base): return self.__exec_context @property - def top_name(self): + def top_name(self) -> str | None: return self.__top_name @property @@ -581,7 +585,7 @@ class App(Base): return ret @cache - def get_version(self, project) -> str: + def get_version(self, project: str) -> str: ret = self.get_value(project, 'version', '') if ret is None: raise Exception(f"Can't get version of project {project}") @@ -610,9 +614,9 @@ class App(Base): projects: list[str], sections: list[str], keys: str | list[str], - add_self: bool, - scope: Scope, - names_only = True, + scope: Scope = Scope.One, + add_self: bool = False, + names_only: bool = False, ) -> list[str]: if isinstance(keys, str): keys = [keys] diff --git a/src/python/jw/pkg/cmds/Cmd.py b/src/python/jw/pkg/cmds/Cmd.py index 5f9509cd..d9240e08 100644 --- a/src/python/jw/pkg/cmds/Cmd.py +++ b/src/python/jw/pkg/cmds/Cmd.py @@ -6,6 +6,7 @@ from ..App import App as Parent from ..CmdBase import CmdBase as Base if TYPE_CHECKING: + from argparse import Namespace from typing import Iterable from ..lib.Distro import Distro @@ -21,7 +22,7 @@ class Cmd(Base): # export super().__init__(parent, name, help, aliases = aliases) @override - async def _run(self, args): + async def _run(self, args: 'Namespace') -> None: # Missing subcommand self.print_help(1) diff --git a/src/python/jw/pkg/cmds/CmdPkg.py b/src/python/jw/pkg/cmds/CmdPkg.py index 48bd21ac..34bd2732 100644 --- a/src/python/jw/pkg/cmds/CmdPkg.py +++ b/src/python/jw/pkg/cmds/CmdPkg.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent if TYPE_CHECKING: - from argparse import ArgumentParser + from argparse import ArgumentParser, Namespace class CmdPkg(Cmd): # export @@ -19,7 +19,7 @@ class CmdPkg(Cmd): # export self.load_subcommands() @override - async def _run(self, args): + async def _run(self, args: Namespace) -> None: import sys # Missing subcommand diff --git a/src/python/jw/pkg/cmds/CmdPlatform.py b/src/python/jw/pkg/cmds/CmdPlatform.py index 130b2b5b..f8aa9606 100644 --- a/src/python/jw/pkg/cmds/CmdPlatform.py +++ b/src/python/jw/pkg/cmds/CmdPlatform.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent if TYPE_CHECKING: - from argparse import ArgumentParser + from argparse import ArgumentParser, Namespace class CmdPlatform(Cmd): # export @@ -16,7 +16,7 @@ class CmdPlatform(Cmd): # export self.load_subcommands() @override - async def _run(self, args): + async def _run(self, args: Namespace) -> None: import sys # Missing subcommand diff --git a/src/python/jw/pkg/cmds/CmdPosix.py b/src/python/jw/pkg/cmds/CmdPosix.py index 3e3a3747..4be4e8d1 100644 --- a/src/python/jw/pkg/cmds/CmdPosix.py +++ b/src/python/jw/pkg/cmds/CmdPosix.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent if TYPE_CHECKING: - from argparse import ArgumentParser + from argparse import ArgumentParser, Namespace class CmdPosix(Cmd): # export @@ -21,7 +21,7 @@ class CmdPosix(Cmd): # export self.load_subcommands() @override - async def _run(self, args): + async def _run(self, args: Namespace) -> None: import sys # Missing subcommand diff --git a/src/python/jw/pkg/cmds/CmdProjects.py b/src/python/jw/pkg/cmds/CmdProjects.py index 2bde3392..871a5c06 100644 --- a/src/python/jw/pkg/cmds/CmdProjects.py +++ b/src/python/jw/pkg/cmds/CmdProjects.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent if TYPE_CHECKING: - from argparse import ArgumentParser + from argparse import ArgumentParser, Namespace class CmdProjects(Cmd): # export @@ -18,7 +18,7 @@ class CmdProjects(Cmd): # export self.load_subcommands() @override - async def _run(self, args): + async def _run(self, args: Namespace) -> None: import sys # Missing subcommand diff --git a/src/python/jw/pkg/cmds/CmdSecrets.py b/src/python/jw/pkg/cmds/CmdSecrets.py index 3b28ed0a..ecf71f5b 100644 --- a/src/python/jw/pkg/cmds/CmdSecrets.py +++ b/src/python/jw/pkg/cmds/CmdSecrets.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent if TYPE_CHECKING: - from argparse import ArgumentParser + from argparse import ArgumentParser, Namespace class CmdSecrets(Cmd): # export @@ -14,7 +14,7 @@ class CmdSecrets(Cmd): # export self.load_subcommands() @override - async def _run(self, args): + async def _run(self, args: Namespace) -> None: import sys # Missing subcommand diff --git a/src/python/jw/pkg/cmds/posix/CmdTar.py b/src/python/jw/pkg/cmds/posix/CmdTar.py index 80355a24..bdb6b33d 100644 --- a/src/python/jw/pkg/cmds/posix/CmdTar.py +++ b/src/python/jw/pkg/cmds/posix/CmdTar.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, override from .Cmd import Cmd, Parent if TYPE_CHECKING: - from argparse import ArgumentParser + from argparse import ArgumentParser, Namespace class CmdTar(Cmd): # export @@ -14,7 +14,7 @@ class CmdTar(Cmd): # export self.load_subcommands() @override - async def _run(self, args): + async def _run(self, args: Namespace) -> None: import sys # Missing subcommand diff --git a/src/python/jw/pkg/cmds/posix/tar/Cmd.py b/src/python/jw/pkg/cmds/posix/tar/Cmd.py index f3bf5e5d..cc6e1aed 100644 --- a/src/python/jw/pkg/cmds/posix/tar/Cmd.py +++ b/src/python/jw/pkg/cmds/posix/tar/Cmd.py @@ -1,7 +1,7 @@ from __future__ import annotations from contextlib import asynccontextmanager -from typing import override, TYPE_CHECKING +from typing import Any, override, TYPE_CHECKING from ....CmdBase import CmdBase as Base from ....lib.FileContext import FileContext @@ -20,7 +20,7 @@ class Cmd(Base): # export self.__tar_io: None = None @asynccontextmanager - async def ctx(self, **kwargs) -> AsyncIterator[TarIo]: + async def ctx(self, **kwargs: Any) -> AsyncIterator[TarIo]: async with TarIo.create(src = self.app.args.archive_path, **kwargs) as ret: ret.src.add_proc_filter( FileContext.Direction.In, ProcFilterGpg(ec = self.app.exec_context) diff --git a/src/python/jw/pkg/cmds/projects/BaseCmdPkgRelations.py b/src/python/jw/pkg/cmds/projects/BaseCmdPkgRelations.py index 8d5c9cb2..66004f96 100644 --- a/src/python/jw/pkg/cmds/projects/BaseCmdPkgRelations.py +++ b/src/python/jw/pkg/cmds/projects/BaseCmdPkgRelations.py @@ -2,7 +2,7 @@ from __future__ import annotations import re -from typing import TYPE_CHECKING, override +from typing import TYPE_CHECKING, cast, override from .Cmd import Cmd, Parent from .lib.pkg_relations import VersionSyntax @@ -17,7 +17,7 @@ class BaseCmdPkgRelations(Cmd): def pkg_relations(self, rel_type: str, args: Namespace) -> str: - return args.delimiter.join( + return cast('str', args.delimiter).join( pkg_relations_list( self.app, rel_type = rel_type, diff --git a/src/python/jw/pkg/cmds/projects/CmdCanonicalizeRemotes.py b/src/python/jw/pkg/cmds/projects/CmdCanonicalizeRemotes.py index f167cfa7..7dbe24ba 100644 --- a/src/python/jw/pkg/cmds/projects/CmdCanonicalizeRemotes.py +++ b/src/python/jw/pkg/cmds/projects/CmdCanonicalizeRemotes.py @@ -34,7 +34,7 @@ class CmdCanonicalizeRemotes(Cmd): # export @override async def _run(self, args: Namespace) -> None: - async def git(cmd: list[str], ro = False, throw = True) -> Result: + async def git(cmd: list[str], ro: bool = False, throw: bool = True) -> Result: cmd = ['/usr/bin/git', *cmd] log(NOTICE, f'-- {" ".join(cmd)}') if ro or not args.dry_run: diff --git a/src/python/jw/pkg/cmds/projects/CmdCheck.py b/src/python/jw/pkg/cmds/projects/CmdCheck.py index 84198475..e53bbcba 100644 --- a/src/python/jw/pkg/cmds/projects/CmdCheck.py +++ b/src/python/jw/pkg/cmds/projects/CmdCheck.py @@ -4,7 +4,7 @@ from .Cmd import Cmd, Parent from typing import TYPE_CHECKING, override if TYPE_CHECKING: - from argparse import ArgumentParser + from argparse import ArgumentParser, Namespace class CmdCheck(Cmd): # export @@ -17,7 +17,7 @@ class CmdCheck(Cmd): # export self.load_subcommands() @override - async def _run(self, args): + async def _run(self, args: Namespace) -> None: import sys # Missing subcommand diff --git a/src/python/jw/pkg/cmds/projects/CmdCreateFile.py b/src/python/jw/pkg/cmds/projects/CmdCreateFile.py index 03e455d6..b852ac80 100644 --- a/src/python/jw/pkg/cmds/projects/CmdCreateFile.py +++ b/src/python/jw/pkg/cmds/projects/CmdCreateFile.py @@ -7,7 +7,7 @@ from .Cmd import Cmd, Parent from .lib.pkg_relations import VersionSyntax, pkg_relations from .lib.templates import ListDict, RenderValues, tmpl_render -def key_value(s): +def key_value(s: str) -> tuple[str, str]: try: key, value = s.split('=', 1) except ValueError: @@ -51,8 +51,8 @@ class CmdCreateFile(Cmd): # export self, template_name: str, values: list[RenderValues], - li_quote = False, - li_delimiter = '\n', + li_quote: bool = False, + li_delimiter: str = '\n', ) -> str: return tmpl_render( template_name, diff --git a/src/python/jw/pkg/cmds/projects/lib/pkg_relations.py b/src/python/jw/pkg/cmds/projects/lib/pkg_relations.py index ea1f64b3..2b470d79 100644 --- a/src/python/jw/pkg/cmds/projects/lib/pkg_relations.py +++ b/src/python/jw/pkg/cmds/projects/lib/pkg_relations.py @@ -26,8 +26,8 @@ def pkg_relations( ignore: set[str] = set(), quote: bool = False, skip_excluded: bool = False, - hide_self = False, - hide_jw_pkg = False, + hide_self: bool = False, + hide_jw_pkg: bool = False, ) -> list[str]: if subsections is None: diff --git a/src/python/jw/pkg/cmds/projects/lib/templates.py b/src/python/jw/pkg/cmds/projects/lib/templates.py index 134135f2..5d209d84 100644 --- a/src/python/jw/pkg/cmds/projects/lib/templates.py +++ b/src/python/jw/pkg/cmds/projects/lib/templates.py @@ -1,5 +1,5 @@ import textwrap -from typing import Iterable, TypeAlias, TypeGuard +from typing import Any, Iterable, TypeAlias, TypeGuard TupleList: TypeAlias = Iterable[tuple[str, str]] ListDict: TypeAlias = dict[str, list[str]] @@ -73,7 +73,7 @@ def format_list_dict( template: str, values: ListDict | dict[str, str], li_quote: bool, li_delimiter: str ) -> str: - def __format_value(val): + def __format_value(val: Any) -> str: if not li_quote: return str(val) return f'"{val}"' @@ -144,9 +144,9 @@ _templates = { def tmpl_render( template_name: str, values: list[RenderValues], - li_quote = False, - li_delimiter = '\n', - search_path: list[str] = [] + li_quote: bool = False, + li_delimiter: str = '\n', + search_path: list[str] | None = None, ) -> str: def __format(template: str) -> str: @@ -157,7 +157,7 @@ def tmpl_render( li_delimiter = li_delimiter, ) - for d in search_path: + for d in search_path if search_path else []: path = d + '/' + template_name try: with open(path, 'r') as f: diff --git a/src/python/jw/pkg/cmds/secrets/lib/FilesContext.py b/src/python/jw/pkg/cmds/secrets/lib/FilesContext.py index 4dccb1c9..29464597 100644 --- a/src/python/jw/pkg/cmds/secrets/lib/FilesContext.py +++ b/src/python/jw/pkg/cmds/secrets/lib/FilesContext.py @@ -23,7 +23,11 @@ class FilesContext: def ctx(self) -> FileContext: return self.__ctx - async def _read_key_value_file(self, path: str, throw = False) -> dict[str, str]: + async def _read_key_value_file( + self, + path: str, + throw: bool = False, + ) -> dict[str, str]: ret: dict[str, str] = {} try: result = await self.ctx.get(path) diff --git a/src/python/jw/pkg/lib/App.py b/src/python/jw/pkg/lib/App.py index a2357ec0..9f482319 100644 --- a/src/python/jw/pkg/lib/App.py +++ b/src/python/jw/pkg/lib/App.py @@ -6,7 +6,7 @@ import os import sys from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace -from typing import override, TYPE_CHECKING, Any +from typing import Any, cast, override, TYPE_CHECKING from .AsyncRunner import AsyncRunner from .log import DEBUG, ERR, NOTICE, log, log_m, set_log_flags, set_log_level @@ -52,12 +52,15 @@ class App: # export ) -> None: def add_cmd_to_parser(cmd: AbstractCmd, parsers: Any) -> ArgumentParser: - parser = parsers.add_parser( - cmd.name, - help = cmd.help, - description = cmd.description, - aliases = cmd.aliases, - formatter_class = ArgumentDefaultsHelpFormatter, + parser = cast( + 'ArgumentParser', + parsers.add_parser( + cmd.name, + help = cmd.help, + description = cmd.description, + aliases = cmd.aliases, + formatter_class = ArgumentDefaultsHelpFormatter, + ) ) parser.set_defaults(func = cmd.run) cmd.add_arguments(parser) @@ -68,7 +71,7 @@ class App: # export parent: AbstractCmd | App, parser: ArgumentParser, cmds: Collection[AbstractCmd], - all = False + all: bool = False ) -> None: if not cmds: return @@ -168,7 +171,7 @@ class App: # export '-h', '--help', action = 'help', help = 'Show this help message and exit' ) - def __del__(self): + def __del__(self) -> None: if self.__own_eloop: if self.__eloop is not None: self.__eloop.close() @@ -178,10 +181,10 @@ class App: # export async def __aenter__(self) -> None: pass - async def __aexit__(self, exc_type, exc, tb) -> None: + async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None: pass - async def __run(self, argv = None) -> None: + async def __run(self, argv: list[str] | None = None) -> None: try: # Import argcomplete only here to not require it to be compatible @@ -194,8 +197,8 @@ class App: # export @override def __call__( # pyright: ignore[reportGeneralTypeIssues] - self, *args, **kwargs - ): + self, *args: Any, **kwargs: Any + ) -> None: return None import argcomplete # type: ignore[import-not-found, unused-ignore] @@ -249,7 +252,7 @@ class App: # export self.__parser.print_help() return None # Run sub-command. Overwrite if you want to do anything before or after - return await self.args.func(args) + return cast('None | int', await self.args.func(args)) def call_async(self, awaitable: Awaitable[T], timeout: float | None = None) -> T: return self.async_runner.call(awaitable, timeout) @@ -286,7 +289,7 @@ class App: # export def parser(self) -> ArgumentParser: return self.__parser - def run(self, argv = None) -> None: + def run(self, argv: list[str] | None = None) -> None: try: ret = self.eloop.run_until_complete(self.__run(argv)) finally: @@ -296,7 +299,10 @@ class App: # export return ret def run_sub_commands( # export - description = '', name_filter = '^Cmd.*', modules = None, argv = None -): + description: str = '', + name_filter: str = '^Cmd.*', + modules: list[str] | None = None, + argv: list[str] | None = None, +) -> None: app = App(description, name_filter, modules) - return app.run(argv = argv) + app.run(argv = argv) diff --git a/src/python/jw/pkg/lib/AsyncRunner.py b/src/python/jw/pkg/lib/AsyncRunner.py index d1569be4..66c723a2 100644 --- a/src/python/jw/pkg/lib/AsyncRunner.py +++ b/src/python/jw/pkg/lib/AsyncRunner.py @@ -4,7 +4,7 @@ import asyncio import concurrent.futures import contextlib -from typing import TypeVar, TYPE_CHECKING +from typing import Any, TypeVar, TYPE_CHECKING, cast if TYPE_CHECKING: from collections.abc import Awaitable, Generator @@ -48,7 +48,7 @@ class AsyncRunner: awaitable, # type: ignore[arg-type, var-annotated] self._loop, ) - return fut.result(timeout) + return cast('T', fut.result(timeout)) def close(self) -> None: self._cm.__exit__(None, None, None) @@ -56,5 +56,5 @@ class AsyncRunner: def __enter__(self) -> AsyncRunner: return self - def __exit__(self, exc_type, exc, tb) -> None: + def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> None: self.close() diff --git a/src/python/jw/pkg/lib/Cmd.py b/src/python/jw/pkg/lib/Cmd.py index 0cca8e7c..51fea68f 100644 --- a/src/python/jw/pkg/lib/Cmd.py +++ b/src/python/jw/pkg/lib/Cmd.py @@ -9,7 +9,7 @@ from .log import ERR from .Types import LoadTypes, Types if TYPE_CHECKING: - from argparse import ArgumentParser + from argparse import ArgumentParser, Namespace from typing import Iterable from .App import App @@ -26,7 +26,7 @@ class AbstractCmd(abc.ABC): self.__child_classes: list[type[Cmd]] = [] self.__parser: ArgumentParser | None = None - def set_parent(self, parent: Any | Cmd): + def set_parent(self, parent: Any | Cmd) -> None: self.__parent = parent @property @@ -76,7 +76,7 @@ class AbstractCmd(abc.ABC): return self.__parser # Don't use a setter decorator to force using a grepable method - def set_parser(self, parser: ArgumentParser): + def set_parser(self, parser: ArgumentParser) -> None: self.__parser = parser def print_help(self, exit_status: int | None = None) -> None: @@ -129,11 +129,11 @@ class AbstractCmd(abc.ABC): pass @abc.abstractmethod - async def _run(self, args) -> None: + async def _run(self, args: Namespace) -> None: if isinstance(self.__parent, Cmd): # Calling App.run() would loop return await self.__parent._run(args) - async def run(self, args): + async def run(self, args: Namespace) -> None: return await self._run(args) @abc.abstractmethod diff --git a/src/python/jw/pkg/lib/CopyContext.py b/src/python/jw/pkg/lib/CopyContext.py index 9eefbdbe..3690d7b7 100644 --- a/src/python/jw/pkg/lib/CopyContext.py +++ b/src/python/jw/pkg/lib/CopyContext.py @@ -1,4 +1,4 @@ -from typing import Self +from typing import Any, Self from .FileContext import FileContext from .Uri import Uri @@ -9,7 +9,7 @@ class CopyContext: self, src: Uri | str | FileContext, dst: Uri | str | FileContext, - chroot = False + chroot: bool = False, ) -> None: def __uri(ctx: FileContext | Uri | str) -> Uri | str: @@ -43,7 +43,7 @@ class CopyContext: await self.__dst.open() return self - async def __aexit__(self, exc_type, exc, tb): + async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None: if self.__src is not None: await self.__src.close() self.__src = None diff --git a/src/python/jw/pkg/lib/Distro.py b/src/python/jw/pkg/lib/Distro.py index 0a40dbd4..d45ae54f 100644 --- a/src/python/jw/pkg/lib/Distro.py +++ b/src/python/jw/pkg/lib/Distro.py @@ -6,7 +6,7 @@ import re import sys from functools import cached_property -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from .log import ERR, INFO, WARNING, log from .base import InputMode @@ -108,8 +108,8 @@ class Distro(abc.ABC): ec: ExecContext, id: str | None = None, os_release_str: str | None = None, - **kwargs, - ): + **kwargs: Any, + ) -> 'Distro': if id is None: os_release_str = await cls.read_os_release_str(ec) id = cls.parse_os_release_field_id(os_release_str) @@ -142,8 +142,9 @@ class Distro(abc.ABC): @cached_property def os_cascade(self) -> list[str]: + ret: list[str] = [] - def __append(entry: str): + def __append(entry: str) -> None: if entry not in ret: ret.append(entry) @@ -322,10 +323,10 @@ class Distro(abc.ABC): def default_pkg_filter(self) -> PackageFilter | None: return self.__default_pkg_filter - async def run(self, *args, **kwargs) -> Result: + async def run(self, *args: Any, **kwargs: Any) -> Result: return await self.__exec_context.run(*args, **kwargs) - async def sudo(self, *args, **kwargs) -> Result: + async def sudo(self, *args: Any, **kwargs: Any) -> Result: return await self.__exec_context.sudo(*args, **kwargs) @property diff --git a/src/python/jw/pkg/lib/ExecContext.py b/src/python/jw/pkg/lib/ExecContext.py index 7ee85b82..855b30dc 100644 --- a/src/python/jw/pkg/lib/ExecContext.py +++ b/src/python/jw/pkg/lib/ExecContext.py @@ -5,7 +5,7 @@ import errno import sys from decimal import ROUND_FLOOR, Decimal -from typing import override, TYPE_CHECKING, NamedTuple +from typing import Any, override, TYPE_CHECKING, NamedTuple if TYPE_CHECKING: from typing import Type @@ -214,7 +214,7 @@ class ExecContext(Base): self.__pretty_cmd = pretty_cmd(self.__cmd, self.__wd) return self.__pretty_cmd - def log(self, prio: int, *args, **kwargs) -> None: + def log(self, prio: int, *args: Any, **kwargs: Any) -> None: log(prio, self.__log_prefix, *args, **kwargs) def log_delim(self, start: bool) -> None: @@ -244,12 +244,12 @@ class ExecContext(Base): def __mode_str(cls, mode: int) -> str: return f'{mode:0o}' - def __init__(self, *args, **kwargs) -> None: + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) @classmethod @override - def create(cls, *args, **kwargs) -> ExecContext: + def create(cls, *args: Any, **kwargs: Any) -> ExecContext: ret = super().create(*args, **kwargs) if not isinstance(ret, cls): raise TypeError(f'Expected {cls.__name__}, got {type(ret).__name__}') @@ -505,7 +505,7 @@ class ExecContext(Base): async def __run( cmd: list[str], cmd_input: Input = InputMode.NonInteractive, - **kwargs + **kwargs: Any ) -> Result: return await self.run(cmd, cmd_input = cmd_input, **kwargs) diff --git a/src/python/jw/pkg/lib/FileContext.py b/src/python/jw/pkg/lib/FileContext.py index 5658db74..0ba119ce 100644 --- a/src/python/jw/pkg/lib/FileContext.py +++ b/src/python/jw/pkg/lib/FileContext.py @@ -4,7 +4,7 @@ import abc from enum import Enum, auto from functools import cached_property -from typing import override, TYPE_CHECKING +from typing import Any, override, TYPE_CHECKING from .log import DEBUG, ERR, log from .Uri import Uri @@ -24,7 +24,7 @@ class FileContext(abc.ABC): self, uri: str | Uri, interactive: bool | None = None, - verbose_default = False, + verbose_default: bool = False, chroot: bool = False, in_pipe: ProcPipeline | None = None, out_pipe: ProcPipeline | None = None, @@ -43,18 +43,18 @@ class FileContext(abc.ABC): f'= "{verbose_default}"' ) - async def __aenter__(self): + async def __aenter__(self) -> 'FileContext': await self.open() return self - async def __aexit__(self, exc_type, exc, tb): + async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None: await self.close() @override def __repr__(self) -> str: return self.__uri.id - def __pipe(self, d: Direction): + def __pipe(self, d: Direction) -> 'ProcPipeline': match d: case self.Direction.In: if not self.__in_pipe: @@ -76,7 +76,7 @@ class FileContext(abc.ABC): return self.root + path return self.root + '/' + path - def add_proc_filter(self, d: Direction, proc_filter: ProcFilter): + def add_proc_filter(self, d: Direction, proc_filter: ProcFilter) -> None: self.__pipe(d).append(proc_filter) async def _open(self) -> None: @@ -298,11 +298,11 @@ class FileContext(abc.ABC): log(ERR, f'{self.log_name}: Failed to stat({path}) ({str(e)})') raise - async def is_dir(self, path: str, follow_symlinks = True) -> bool: + async def is_dir(self, path: str, follow_symlinks: bool = True) -> bool: return await self._is_dir(self._chroot(path), follow_symlinks = follow_symlinks) @classmethod - def create(cls, uri: str | Uri, *args, **kwargs) -> FileContext: + def create(cls, uri: str | Uri, *args: Any, **kwargs: Any) -> 'FileContext': uri = Uri.pimp(uri) match uri.protocol: case 'local' | 'file': diff --git a/src/python/jw/pkg/lib/Package.py b/src/python/jw/pkg/lib/Package.py index 1f3fb2dc..1d3e9bbc 100644 --- a/src/python/jw/pkg/lib/Package.py +++ b/src/python/jw/pkg/lib/Package.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import override, Any meta_tags = [ @@ -16,7 +18,7 @@ class Package: maintainer: str | None = None @classmethod - def parse_spec_str(cls, spec: str, delimiter = '|'): + def parse_spec_str(cls, spec: str, delimiter: str = '|') -> 'Package': tags = spec.split(delimiter) if len(tags) != 5: raise ValueError(f'Invalid package spec string "{spec}"') @@ -29,14 +31,14 @@ class Package: ) @classmethod - def parse_specs_str(cls, specs: str, delimiter = '|'): + def parse_specs_str(cls, specs: str, delimiter: str = '|') -> list[Package]: ret: list[Package] = [] for spec in specs.splitlines(): ret.append(cls.parse_spec_str(spec)) return ret @classmethod - def order_tags(cls, mapping: dict[str, Any]): + def order_tags(cls, mapping: dict[str, Any]) -> dict[str, Any]: ret: dict[str, Any] = {} for tag in meta_tags: ret[tag] = mapping.get(tag, '') diff --git a/src/python/jw/pkg/lib/Result.py b/src/python/jw/pkg/lib/Result.py index 644fe186..38584fe0 100644 --- a/src/python/jw/pkg/lib/Result.py +++ b/src/python/jw/pkg/lib/Result.py @@ -33,7 +33,7 @@ class Result: def __try_decode( self, stdxxx: bytes | None, - quote = False, + quote: bool = False, truncate: int | None = None, annotate: bool = True, label: str | None = None, @@ -59,7 +59,7 @@ class Result: self, cmd: list[str] | None = None, wd: str | None = None, - verbose = True + verbose: bool = True, ) -> str: def __status_str(cmd: list[str] | None, wd: str | None, verbose: bool) -> str: diff --git a/src/python/jw/pkg/lib/TarIo.py b/src/python/jw/pkg/lib/TarIo.py index 67774d5c..6e508c6a 100644 --- a/src/python/jw/pkg/lib/TarIo.py +++ b/src/python/jw/pkg/lib/TarIo.py @@ -9,14 +9,14 @@ from tarfile import TarFile, TarInfo from .CopyContext import CopyContext from .ExecContext import ExecContext from .log import DEBUG, ERR, log -from typing import TYPE_CHECKING, override +from typing import TYPE_CHECKING, Any, override if TYPE_CHECKING: from .base import StatResult class TarIo(CopyContext): - def __init__(self, *args, **kwargs) -> None: + def __init__(self, *args: Any, **kwargs: Any) -> None: kwargs['chroot'] = False super().__init__(*args, **kwargs) @@ -44,7 +44,7 @@ class TarIo(CopyContext): async def _read_filtered( self, - path, + path: str, path_filter: list[str] | None = None, matched: list[str] | None = None, ) -> bytes: @@ -81,7 +81,7 @@ class TarIo(CopyContext): return ret @classmethod - def create(cls, *args, type: str | None = None, **kwargs): + def create(cls, *args: Any, type: str | None = None, **kwargs: Any) -> 'TarIo': if type is not None: raise NotImplementedError # return TarIoTarFile(*args, **kwargs) diff --git a/src/python/jw/pkg/lib/Types.py b/src/python/jw/pkg/lib/Types.py index 04827aac..82073059 100644 --- a/src/python/jw/pkg/lib/Types.py +++ b/src/python/jw/pkg/lib/Types.py @@ -5,7 +5,7 @@ import os import re import sys -from typing import override, TYPE_CHECKING, Generic, Iterable, TypeVar +from typing import Any, override, TYPE_CHECKING, Generic, Iterable, TypeVar from .log import ERR, OFF, log, parse_log_level @@ -33,7 +33,7 @@ class Types(abc.ABC, Iterable[type[T]], Generic[T]): # export def _stringify(self) -> list[str]: pass - def dump(self, prio: int, *args, **kwargs) -> None: + def dump(self, prio: int, *args: Any, **kwargs: Any) -> None: contents = self._stringify() log(prio, ',--- ', *args, **kwargs) for line in contents: @@ -47,8 +47,8 @@ class LoadTypes(Types[T]): # export mod_names: Iterable[str], type_name_filter: str | None = None, type_filter: Sequence[type[Any]] | None = None, - debug_level = None, - ): + debug_level: int | None = None, + ) -> None: if debug_level is None: val = os.getenv('JW_LOG_LEVEL_LOAD_TYPES') if val is not None: @@ -61,12 +61,12 @@ class LoadTypes(Types[T]): # export self.__mod_names = mod_names self.__classes: list[type[T]] | None = None - def _debug(self, *args, **kwargs) -> None: + def _debug(self, *args: Any, **kwargs: Any) -> None: if self.__debug_level != OFF: log(self.__debug_level, *args, **kwargs) @override - def _stringify(self): + def _stringify(self) -> list[str]: tf = 'None' if self.__type_filter is None else ( ', '.join([str(f) for f in self.__type_filter]) ) diff --git a/src/python/jw/pkg/lib/distros/arch/Distro.py b/src/python/jw/pkg/lib/distros/arch/Distro.py index ded2a903..c772a4f3 100644 --- a/src/python/jw/pkg/lib/distros/arch/Distro.py +++ b/src/python/jw/pkg/lib/distros/arch/Distro.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import override, TYPE_CHECKING +from typing import Any, override, TYPE_CHECKING from ...Distro import Distro as Base @@ -23,7 +23,7 @@ class Distro(Base): return await self.sudo(cmd, verbose = verbose) return await self.run(cmd, verbose = verbose) - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) @override diff --git a/src/python/jw/pkg/lib/distros/debian/Distro.py b/src/python/jw/pkg/lib/distros/debian/Distro.py index fb242192..8cf88a18 100644 --- a/src/python/jw/pkg/lib/distros/debian/Distro.py +++ b/src/python/jw/pkg/lib/distros/debian/Distro.py @@ -2,7 +2,7 @@ from __future__ import annotations import os -from typing import override, TYPE_CHECKING +from typing import Any, override, TYPE_CHECKING from ...Distro import Distro as Base from ...log import NOTICE, log @@ -31,11 +31,11 @@ class Distro(Base): if sudo else await self.run(cmd, verbose = verbose) ) - async def dpkg(self, *args, **kwargs) -> str: + async def dpkg(self, *args: Any, **kwargs: Any) -> str: kwargs.setdefault('ec', self.ctx) return await run_dpkg(*args, **kwargs) - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) @override diff --git a/src/python/jw/pkg/lib/distros/suse/Distro.py b/src/python/jw/pkg/lib/distros/suse/Distro.py index 44675cc6..02967ef6 100644 --- a/src/python/jw/pkg/lib/distros/suse/Distro.py +++ b/src/python/jw/pkg/lib/distros/suse/Distro.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import override, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, override from ...Distro import Distro as Base from ...pm.rpm import list_files, query_packages, run_rpm @@ -35,13 +35,18 @@ class Distro(Base): if sudo else await self.run(cmd, verbose = verbose) ) - async def rpm(self, *args, ec: ExecContext | None = None, **kwargs) -> str: + async def rpm( + self, + *args: Any, + ec: ExecContext | None = None, + **kwargs: Any, + ) -> str: if ec is None: ec = self.ctx kwargs['ec'] = ec return await run_rpm(*args, **kwargs) - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) @override diff --git a/src/python/jw/pkg/lib/ec/Curl.py b/src/python/jw/pkg/lib/ec/Curl.py index d2fd158f..6757008d 100644 --- a/src/python/jw/pkg/lib/ec/Curl.py +++ b/src/python/jw/pkg/lib/ec/Curl.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import override, TYPE_CHECKING +from typing import Any, override, TYPE_CHECKING from ..FileContext import FileContext as Base @@ -13,7 +13,11 @@ if TYPE_CHECKING: class Curl(Base): def __init__( - self, uri: str | Uri, *args, ec: ExecContext | None = None, **kwargs + self, + uri: str | Uri, + *args: Any, + ec: ExecContext | None = None, + **kwargs: Any ) -> None: def __local() -> Local: diff --git a/src/python/jw/pkg/lib/ec/Local.py b/src/python/jw/pkg/lib/ec/Local.py index 243a969a..9eca6817 100644 --- a/src/python/jw/pkg/lib/ec/Local.py +++ b/src/python/jw/pkg/lib/ec/Local.py @@ -7,20 +7,20 @@ import pwd import sys from functools import cache -from typing import override, TYPE_CHECKING +from typing import TYPE_CHECKING, override from ..base import Result, StatResult from ..ExecContext import ExecContext as Base from ..log import ERR, NOTICE, log if TYPE_CHECKING: - from typing import Any + from typing import Any, Callable from ..Uri import Uri class Local(Base): - def __init__(self, uri: str | Uri = 'local', *args, **kwargs) -> None: + def __init__(self, uri: str | Uri = 'local', *args: Any, **kwargs: Any) -> None: super().__init__(uri, *args, **kwargs) @cache @@ -43,9 +43,12 @@ class Local(Base): if verbose: log(prio, log_prefix, *args) - def __make_pty_reader(collector: list[bytes], enc_for_verbose: str): + def __make_pty_reader( + collector: list[bytes], + enc_for_verbose: str, + ) -> Callable[[int], bytes]: - def _read(fd): + def _read(fd: int) -> bytes: ret = os.read(fd, 1024) if not ret: return ret @@ -65,7 +68,7 @@ class Local(Base): if interactive: import pty - def _spawn(): + def _spawn() -> int: # Apply env in PTY mode by temporarily updating os.environ # around spawn. if mod_env: @@ -115,7 +118,9 @@ class Local(Base): stdout_log_enc = sys.stdout.encoding or 'utf-8' stderr_log_enc = sys.stderr.encoding or 'utf-8' - async def read_stream(stream, prio, collector: list[bytes], log_enc: str): + async def read_stream( + stream: Any, prio: int, collector: list[bytes], log_enc: str + ) -> None: buf = b'' while True: chunk = await stream.read(4096) diff --git a/src/python/jw/pkg/lib/ec/SSHClient.py b/src/python/jw/pkg/lib/ec/SSHClient.py index 02814380..03ad0283 100644 --- a/src/python/jw/pkg/lib/ec/SSHClient.py +++ b/src/python/jw/pkg/lib/ec/SSHClient.py @@ -5,7 +5,7 @@ import os import pwd from enum import Flag, auto -from typing import TYPE_CHECKING, override +from typing import TYPE_CHECKING, Any, cast, override from ..ExecContext import ExecContext from ..log import DEBUG, ERR, INFO, NOTICE, get_caller_pos, log, log_m @@ -22,7 +22,9 @@ class SSHClient(ExecContext): ModEnv = auto() Wd = auto() - def __init__(self, uri: Uri | str, caps: Caps = Caps(0), *args, **kwargs) -> None: + def __init__( + self, uri: Uri | str, caps: Caps = Caps(0), *args: Any, **kwargs: Any + ) -> None: uri = Uri.pimp(uri) if uri.username is None: uri.set_username(pwd.getpwuid(os.getuid()).pw_name) @@ -54,13 +56,13 @@ class SSHClient(ExecContext): log_prefix: str, ) -> Result: - def __log(prio: int, *args, **kwargs): + def __log(prio: int, *args: Any, **kwargs: Any) -> None: caller = kwargs.get('caller') if caller is None: kwargs['caller'] = get_caller_pos(1) log(prio, log_prefix, *args, **kwargs) - def __log_block(prio: int, title: str, block: str | None): + def __log_block(prio: int, title: str, block: str | None) -> None: if self.__caps & self.Caps.LogOutput: return if block is None: @@ -112,8 +114,10 @@ class SSHClient(ExecContext): return self.uri.password def ssh_client( # export - *args, type: str | list[str] | None = None, **kwargs -) -> SSHClient: + *args: Any, + type: str | list[str] | None = None, + **kwargs: Any +) -> 'SSHClient': from importlib import import_module errors: list[str] = [] @@ -130,7 +134,7 @@ def ssh_client( # export ret = getattr(import_module(f'jw.pkg.lib.ec.ssh.{name}'), name)(*args, **kwargs) log(INFO, f'Using SSH-client "{name}"') - return ret + return cast('SSHClient', ret) except Exception as e: msg = f"Can't instantiate SSH client class {name} ({str(e)})" errors.append(msg) diff --git a/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py b/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py index a2fbb12f..c1e3fd99 100644 --- a/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py +++ b/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py @@ -1,4 +1,3 @@ -from typing import Any, override import asyncio import os import shlex @@ -6,8 +5,12 @@ import shutil import signal import sys +from typing import Any, override + import asyncssh # type: ignore[import-not-found, unused-ignore] +from asyncssh import SSHReader # type: ignore[import-not-found, unused-ignore] + from ...base import Result from ...log import DEBUG, ERR, NOTICE, log from ..SSHClient import SSHClient as Base @@ -22,10 +25,10 @@ class AsyncSSH(Base): uri: str, *, client_keys: list[str] | None = None, - known_hosts = _USE_DEFAULT_KNOWN_HOSTS, + known_hosts: Any = _USE_DEFAULT_KNOWN_HOSTS, term_type: str | None = None, connect_timeout: float | None = 30.0, - **kwargs, + **kwargs: Any, ) -> None: super().__init__( @@ -121,8 +124,8 @@ class AsyncSSH(Base): async def _read_stream( self, - stream, - prio, + stream: SSHReader[bytes], + prio: int, collector: list[bytes], *, verbose: bool, @@ -222,7 +225,7 @@ class AsyncSSH(Base): stdout_parts.append(chunk) _write_local(chunk) - def _on_winch(*_args) -> None: + def _on_winch(*_args: Any) -> None: try: proc.change_terminal_size(*self._get_local_term_size()) diff --git a/src/python/jw/pkg/lib/ec/ssh/Exec.py b/src/python/jw/pkg/lib/ec/ssh/Exec.py index 17bd35e4..e431dc90 100644 --- a/src/python/jw/pkg/lib/ec/ssh/Exec.py +++ b/src/python/jw/pkg/lib/ec/ssh/Exec.py @@ -2,7 +2,7 @@ from __future__ import annotations import os -from typing import override, TYPE_CHECKING +from typing import Any, override, TYPE_CHECKING from ...base import InputMode from ...util import run_cmd @@ -14,12 +14,12 @@ if TYPE_CHECKING: class Exec(Base): - def __init__(self, uri, *args, **kwargs) -> None: + def __init__(self, uri: Any, *args: Any, **kwargs: Any) -> None: self.__askpass: str | None = None self.__askpass_orig: dict[str, str | None] = dict() super().__init__(uri = uri, caps = self.Caps.ModEnv, **kwargs) - def __del__(self): + def __del__(self) -> None: for key, val in self.__askpass_orig.items(): if val is None: del os.environ[key] diff --git a/src/python/jw/pkg/lib/ec/ssh/Paramiko.py b/src/python/jw/pkg/lib/ec/ssh/Paramiko.py index 866c3f67..3b080da9 100644 --- a/src/python/jw/pkg/lib/ec/ssh/Paramiko.py +++ b/src/python/jw/pkg/lib/ec/ssh/Paramiko.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import override, TYPE_CHECKING +from typing import Any, override, TYPE_CHECKING # Tolerate missing paramiko imports. jw-pkg is designed to work with what it # finds. @@ -18,7 +18,7 @@ if TYPE_CHECKING: class Paramiko(Base): - def __init__(self, uri, *args, **kwargs) -> None: + def __init__(self, uri: Any, *args: Any, **kwargs: Any) -> None: kwargs['caps'] = (self.Caps.ModEnv, ) super().__init__(uri, *args, **kwargs) self.__timeout: float | None = None # Untested diff --git a/src/python/jw/pkg/lib/log.py b/src/python/jw/pkg/lib/log.py index 98f0f4e3..6e9f70e7 100644 --- a/src/python/jw/pkg/lib/log.py +++ b/src/python/jw/pkg/lib/log.py @@ -7,7 +7,7 @@ import syslog from datetime import datetime from os.path import basename -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast if TYPE_CHECKING: import io @@ -126,7 +126,7 @@ _prio_colors = { class Stream: - def __init__(self, stream, flags): + def __init__(self, stream: Any, flags: int): self.stream = stream self.flags = flags @@ -142,12 +142,12 @@ def pad(token: str, total_size: int, right_align: bool = False) -> str: return space + token return token + space -def add_capture_stream(stream, flags = 0x0): +def add_capture_stream(stream: Any, flags: int = 0x0) -> int: ret = _stream_descriptors.pop() _streams[ret] = Stream(stream = stream, flags = flags) return ret -def rm_capture_stream(sd): +def rm_capture_stream(sd: int) -> None: del _streams[sd] _stream_descriptors.append(sd) @@ -166,13 +166,13 @@ def get_caller_pos(up: int = 1, if kwargs and 'caller' in kwargs: r = kwargs['caller'] del kwargs['caller'] - return r + return cast('Tuple[str, str, int]', r) caller = inspect.stack()[up + 1] mod = inspect.getmodule(caller[0]) mod_name = '' if mod is None else mod.__name__ return (mod_name, basename(caller.filename), caller.lineno) -def log_m(prio: int, *args, **kwargs) -> None: # export +def log_m(prio: int, *args: Any, **kwargs: Any) -> None: # export if prio > _level: return margs = '' @@ -190,7 +190,12 @@ def log_m(prio: int, *args, **kwargs) -> None: # export for line in margs[1:].split('\n'): log(prio, line, **kwargs, caller = caller) -def log(prio: int, *args, only_printable: bool = False, **kwargs) -> None: # export +def log( # export + prio: int, + *args: Any, + only_printable: bool = False, + **kwargs: Any +) -> None: if prio > _level: return @@ -259,7 +264,12 @@ def log(prio: int, *args, only_printable: bool = False, **kwargs) -> None: # ex for file in files: print(msg, file = file) -def throw(*args, prio = ERR, caller = None, **kwargs) -> None: +def throw( + *args: Any, + prio: int = ERR, + caller: Tuple[str, str, int] | None = None, + **kwargs: Any +) -> None: if caller is None: caller = get_caller_pos(1) msg = ' '.join([str(arg) for arg in args]) @@ -331,7 +341,7 @@ def append_to_prefix(prefix: str) -> str: # export _clean_log_prefix = _clean_str_regex.sub('', _log_prefix) return r -def remove_from_prefix(count) -> str: # export +def remove_from_prefix(count: int | str) -> str: # export if isinstance(count, str): count = len(count) global _log_prefix diff --git a/src/python/jw/pkg/lib/pm/dpkg.py b/src/python/jw/pkg/lib/pm/dpkg.py index 12b5d9e8..a7b30813 100644 --- a/src/python/jw/pkg/lib/pm/dpkg.py +++ b/src/python/jw/pkg/lib/pm/dpkg.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable +from typing import TYPE_CHECKING, Iterable, cast if TYPE_CHECKING: from ..ExecContext import ExecContext @@ -59,7 +59,7 @@ async def query_packages(names: Iterable[str] = [], ) # dpkg-query -W -f='${binary:Package}|${Maintainer}| ... \n' specs = await run_dpkg_query(['-W', '-f=' + fmt_str, *names], sudo = False, ec = ec) - return Package.parse_specs_str(specs) + return cast('Iterable[Package]', Package.parse_specs_str(specs)) async def list_files(pkg: str, ec: ExecContext | None = None) -> list[str]: file_list_str = await run_dpkg(['-L', pkg], sudo = False, ec = ec) diff --git a/src/python/jw/pkg/lib/pm/rpm.py b/src/python/jw/pkg/lib/pm/rpm.py index 68421e81..c326bba0 100644 --- a/src/python/jw/pkg/lib/pm/rpm.py +++ b/src/python/jw/pkg/lib/pm/rpm.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Collection, Iterable +from typing import Any, TYPE_CHECKING, Collection, Iterable, cast from ..base import InputMode from ..Package import Package @@ -30,7 +30,7 @@ async def run_rpm( # export sudo: bool = False, ec: ExecContext | None = None, mode: InputMode = InputMode.OptInteractive, - **kwargs, + **kwargs: Any, ) -> str: cmd = ['/usr/bin/rpm'] cmd.extend(args) @@ -58,7 +58,7 @@ async def query_packages( # export mode = InputMode.NonInteractive, ec = ec ) - return Package.parse_specs_str(specs) + return cast('Iterable[Package]', Package.parse_specs_str(specs)) async def list_files(pkg: str, ec: ExecContext | None = None) -> list[str]: stdout = await run_rpm( diff --git a/src/python/jw/pkg/lib/util.py b/src/python/jw/pkg/lib/util.py index db0eb5e1..27812391 100644 --- a/src/python/jw/pkg/lib/util.py +++ b/src/python/jw/pkg/lib/util.py @@ -5,7 +5,7 @@ import os import sys from enum import Enum, auto -from typing import TYPE_CHECKING, Iterable, TypeVar +from typing import TYPE_CHECKING, Any, Iterable, TypeVar, cast from .base import Input, InputMode, Result from .log import DEBUG, ERR, log @@ -23,7 +23,7 @@ class AskpassKey(Enum): Username = auto() Password = auto() -def pretty_cmd(cmd: list[str] | None = None, wd = None): +def pretty_cmd(cmd: list[str] | None = None, wd: str | None = None) -> str: if cmd is None: cmd = sys.argv tokens = [cmd[0]] @@ -38,11 +38,11 @@ def pretty_cmd(cmd: list[str] | None = None, wd = None): # See ExecContext.run() for what this function does async def run_cmd( - *args, + *args: Any, ec: ExecContext | None = None, verbose: bool | None = None, cmd_input: Input = InputMode.NonInteractive, - **kwargs, + **kwargs: Any, ) -> Result: if verbose is None: verbose = False if ec is None else ec.verbose_default @@ -56,12 +56,12 @@ async def run_cmd( async def run_curl( args: list[str], - wd = None, - throw = None, - verbose = None, - cmd_input = InputMode.NonInteractive, + wd: str | None = None, + throw: bool | None = None, + verbose: bool | None = None, + cmd_input: Input = InputMode.NonInteractive, ec: ExecContext | None = None, - decode = False, + decode: bool = False, ) -> Result: if verbose is None: verbose = False if ec is None else ec.verbose_default @@ -76,7 +76,7 @@ async def run_curl( async def run_curl_into( expected_type: type[T], args: list[str], - **kwargs, + **kwargs: Any, ) -> T: result = await run_curl(args, **kwargs) stdout = result.stdout_str @@ -139,11 +139,11 @@ async def run_askpass( async def run_sudo( cmd: list[str], - *args, + *args: Any, interactive: bool = True, ec: ExecContext | None = None, - **kwargs, -): + **kwargs: Any, +) -> Result: if ec is None: from .ec.Local import Local @@ -152,10 +152,10 @@ async def run_sudo( async def get( uri: str | Uri, - *args, + *args: Any, ctx: FileContext | None = None, content_filter: ProcFilter | list[ProcFilter] | ProcPipeline | None = None, - **kwargs, + **kwargs: Any, ) -> Result: uri = Uri.pimp(uri) if ctx is None or uri.id != ctx.uri.id: @@ -172,7 +172,7 @@ async def copy( owner: str | None = None, group: str | None = None, mode: int | None = None, - throw = True, + throw: bool = True, ) -> Exception | str | list[str]: if not isinstance(src_uri, str): ret: list[str] = [] @@ -225,7 +225,7 @@ async def get_username( # export f'Username mismatch: called with --username="{args.username}", ' f'URL has user name "{url_user}"' ) - return args.username + return str(args.username) if url_user is not None: return url_user return await run_askpass(askpass_env, AskpassKey.Username, ec = ec) @@ -244,7 +244,7 @@ async def get_password( # export if args is not None and hasattr(args, 'password'): # use getattr(), because we don't necessarily want to have insecure # --password among options - ret = getattr(args, 'password') + ret = cast('str | None', getattr(args, 'password')) if ret is not None: return ret if url is not None: -- 2.55.0 From bbb9a9b17e9d8c471368e762340fbb3434c2001c Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 22 Jul 2026 23:03:43 +0200 Subject: [PATCH 26/31] pyproject.toml: Enable mypy.warn_return_any Add warn_return_any = true to the mypy configuration. This detects functions that return Any from contexts where a specific return type is declared, encouraging more precise type annotations. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- conf/templates/pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conf/templates/pyproject.toml b/conf/templates/pyproject.toml index 790626e6..0cd002f4 100644 --- a/conf/templates/pyproject.toml +++ b/conf/templates/pyproject.toml @@ -14,6 +14,8 @@ disallow_untyped_calls = true + warn_return_any = true + enable_error_code = [ "explicit-override", "ignore-without-code", -- 2.55.0 From 88ba66c5b0929717c477d20e1df92040584bbf60 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Thu, 23 Jul 2026 06:47:58 +0200 Subject: [PATCH 27/31] pyproject.toml: Enable mypy.disallow_incomplete_defs Add disallow_incomplete_defs = true to the mypy configuration. This requires all function definitions to have complete parameter and return type annotations, catching partially-annotated function signatures. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- conf/templates/pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conf/templates/pyproject.toml b/conf/templates/pyproject.toml index 0cd002f4..c5adcb21 100644 --- a/conf/templates/pyproject.toml +++ b/conf/templates/pyproject.toml @@ -16,6 +16,8 @@ warn_return_any = true + disallow_incomplete_defs = true + enable_error_code = [ "explicit-override", "ignore-without-code", -- 2.55.0 From 989b800906caf493518bce2a4319a58ee1b0dbc3 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Thu, 23 Jul 2026 08:58:10 +0200 Subject: [PATCH 28/31] pyproject.toml: Enable mypy.disallow_untyped_defs Add disallow_untyped_defs = true to the mypy configuration. This requires all function definitions to have complete type annotations for both parameters and return types. Since disallow_incomplete_defs was already satisfied (all functions have partial annotations), this final rule validates that no function is left without any type hints. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- conf/templates/pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conf/templates/pyproject.toml b/conf/templates/pyproject.toml index c5adcb21..71dc5fa7 100644 --- a/conf/templates/pyproject.toml +++ b/conf/templates/pyproject.toml @@ -18,6 +18,8 @@ disallow_incomplete_defs = true + disallow_untyped_defs = true + enable_error_code = [ "explicit-override", "ignore-without-code", -- 2.55.0 From fd521cb2a9d56012fbe8dbc82955fcc2820fdbb8 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Sun, 9 Aug 2026 12:58:02 +0000 Subject: [PATCH 29/31] lib.App: Fix misc mypy errors Fix fallout created by enabling the "strict" option: lib/App.py:196: error: Class cannot subclass "BaseCompleter" (has type "Any") [misc] Signed-off-by: Jan Lindemann --- src/python/jw/pkg/lib/App.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/jw/pkg/lib/App.py b/src/python/jw/pkg/lib/App.py index 9f482319..e7bcbc17 100644 --- a/src/python/jw/pkg/lib/App.py +++ b/src/python/jw/pkg/lib/App.py @@ -193,7 +193,7 @@ class App: # export BaseCompleter ) - class NoopCompleter(BaseCompleter): + class NoopCompleter(BaseCompleter): # type: ignore[misc, unused-ignore] @override def __call__( # pyright: ignore[reportGeneralTypeIssues] -- 2.55.0 From 9bbdcf5c39351bfb0ff21971d6f5f591ac96f95a Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Thu, 23 Jul 2026 21:55:33 +0200 Subject: [PATCH 30/31] pyproject.toml: Simplify mypy config using strict mode Remove 8 redundant mypy settings that are already enabled by strict mode: warn_redundant_casts, strict_equality, disallow_any_generics, warn_unused_ignores, disallow_untyped_calls, warn_return_any, disallow_incomplete_defs, disallow_untyped_defs. Keep warn_unreachable explicitly since it's not part of strict mode. Also remove the duplicate exhaustive-match entry from enable_error_code. Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1 Signed-off-by: Jan Lindemann --- conf/templates/pyproject.toml | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/conf/templates/pyproject.toml b/conf/templates/pyproject.toml index 71dc5fa7..600f0c75 100644 --- a/conf/templates/pyproject.toml +++ b/conf/templates/pyproject.toml @@ -2,24 +2,10 @@ {mypypath} - warn_redundant_casts = true - - strict_equality = true - - disallow_any_generics = true - - warn_unused_ignores = true + strict = true warn_unreachable = true - disallow_untyped_calls = true - - warn_return_any = true - - disallow_incomplete_defs = true - - disallow_untyped_defs = true - enable_error_code = [ "explicit-override", "ignore-without-code", -- 2.55.0 From be46a5ebcf460b7563a31c0eb1d53519828adf0e Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Fri, 24 Jul 2026 08:39:36 +0200 Subject: [PATCH 31/31] pyrightconfig.json: Mirror strict mypy settings Follow the changes in the mypy check profile by adding additional checks. Signed-off-by: Jan Lindemann --- conf/project/pyrightconfig-base.json | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/conf/project/pyrightconfig-base.json b/conf/project/pyrightconfig-base.json index 59bb4dea..6ff4d225 100644 --- a/conf/project/pyrightconfig-base.json +++ b/conf/project/pyrightconfig-base.json @@ -1,7 +1,14 @@ { - "typeCheckingMode": "basic", + "typeCheckingMode": "strict", "pythonPlatform": "Linux", "useLibraryCodeForTypes": true, "verboseOutput": false, - "reportImplicitOverride": "error" + + "reportImplicitOverride": "error", + "reportDeprecated": "error", + "reportMatchNotExhaustive": "error", + + "reportUnnecessaryTypeIgnoreComment": "error", + "reportImplicitStringConcatenation": "error", + "reportUninitializedInstanceVariable": "error" } -- 2.55.0