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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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