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 <jan@janware.com>
This commit is contained in:
Jan Lindemann 2026-07-22 23:03:53 +02:00
commit 1e613a39c6
40 changed files with 228 additions and 179 deletions

View file

@ -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]

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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,

View file

@ -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:

View file

@ -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

View file

@ -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,

View file

@ -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:

View file

@ -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:

View file

@ -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)

View file

@ -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)

View file

@ -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()

View file

@ -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

View file

@ -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

Some files were not shown because too many files have changed in this diff Show more