Overriding the _run() method entirely in App subclasses is currently only possible if the application supports a subcommand structure. Make it possible to use it as an abstraction for a single-command application.
The .strip property of class Result defaults to True, and its name isn't very clear. Rename it to .strip_output, and default it to False to avoid surprising contents for unsuspecting callers.
The command line jw-pkg.py is run with is logged with level "debug", and reconstructed with ' '.join(sys.argv). Use pretty_cmd() instead, this adds quotes around spaces.
Commands executed by ExecContext and its derived classes don't populate the "cmd" parameter of "Result"'s constructor. Fixing that makes for nicer error messages.
In LoadTypes' constructor, allow the type_filter parameter to be of type Sequence[type[Any]] instead of list[type[T]]. a) Sequence is more generic than list, and b) with T instead of Any, trying to instantiate with an abstract class has mypy complain:
# E: Only concrete class can be given where "type[MyClass]" is
expected [type-abstract]
OpenSUSE leaves installing local packages to the default implementation in lib.Distro._install_local_files(), which passes the package path to the package manager, i.e. zypper in OpenSUSE's case. That has advantages, namely automatic installation of dependencies, but also disadvantages, namely the attempt to install dependencies even if the package manager is disfunctional, possibly because an installed package containing installation sources is broken.
That could lead to a deadlock when trying to install a fixed package. I see two ways out: Support an additional flag to jw-pkg's install command which selects whether or not dependencies shall be resolved along, or just use rpm directly for all local install attempts.
The latter is the less fancy way to handle this, so as a first step make it the default by overriding suse.Distro._install_local_files().
Define default parameter values for Result's constructor, namely None for exit status, stdout and stderr.
Instantiating a Result object without parameters signifies "this object doesn't contain data from a real process's exit event". Up to now, similar meaning has been hand-crafted by ExecContext's run() and friends by using an error exit status (1) to make sure it wasn't mistaken for success. This commit formalizes that into the Result structure itself, but uses None instead for the exit status.
Controlling default values in Result itself also means that the Result class gets better awareness of what it contains, and its log messages and stdin / stdout can be more fitting:
- If a real process failed, make stdout return at least b'' - If a real process succeeded, make stdout return at least b''
Returning something from .stdout on success fixes a real bug: An attempt to access what "rpm -U somepackage.rpm" returns, namely nothing, raises a bogus exception, because stdout is None.
Make the log delimiter look more consistent: Whether a CallContext was constructed with a title parameter or without, prefix its .log_delimiter property with a "----".
Ignore newline at the end of Result.stdout_str if only one line of output is wanted from an executed shell command. The output of both uname and mktemp are used wrongly in that regard.
- Remove package_name and package_path from the prototype of
detect_modules(). They can and should be deduced from
namespace['__name__'] and namespace['__path__'], respectively.
- Make prefix default to None, which signifies "Don't filter by
prefix".
- Add an optional extend_namespace parameter, which will make the
function append the module's __name__ to its __path__. This
defaults to True, thereby adding a side effect to the function.
Which is always wanted in the case for all callers of this
function.
Accept if AsyncSSH is missing. The package would be nice to have, i.e. a good candidate for a "recommends" section, but until there's support for that, better be able to do without and fall back to command-line ssh.
Accept if argcomplete is missing. The package would be nice to have, i.e. a good candidate for a "recommends" section, but until there's support for that, better be able to do without.
Fix another regression of commit 6db73873e7: lib.ExecContext.CallContext.__exit__() returns True, which swallows all exceptions thrown in the context of _run() and _sudo(). Fix that.
,---- file://local: Running /usr/bin/zypper --non-interactive --gpg-auto-import-keys --no-gpg-checks install make time xdg-utils coreutils cpio git-core bash python3 sudo gawk pkg-config python3-isort python3-yapf python3-ruff python3-pyright rpmbuild python3-base - > | Loading repository data... | Reading installed packages... | 'sudo' is already installed. | No update candidate for 'sudo-1.9.17p2-2.2.x86_64'. The highest available version is already installed. | 'bash' is already installed. | No update candidate for 'bash-5.3.9-6.4.x86_64'. The highest available version is already installed. | 'python3' not found in package names. Trying capabilities. | 'python313' providing 'python3' is already installed. | 'coreutils' is already installed. | No update candidate for 'coreutils-9.11-3.1.x86_64'. The highest available version is already installed. | 'pkg-config' not found in package names. Trying capabilities. | 'make' is already installed. | No update candidate for 'make-4.4.1-3.5.x86_64'. The highest available version is already installed. | 'python3-base' not found in package names. Trying capabilities. | 'python313-base' providing 'python3-base' is already installed. | 'rpmbuild' not found in package names. Trying capabilities. | 'git-core' is already installed. | No update candidate for 'git-core-2.54.0-2.1.x86_64'. The highest available version is already installed. | 'python3-pyright' not found in package names. Trying capabilities. | 'python3-ruff' not found in package names. Trying capabilities. | 'python3-isort' not found in package names. Trying capabilities. | 'python3-yapf' not found in package names. Trying capabilities. | Resolving package dependencies... | | Problem: 1: the installed busybox-gawk-1.37.0-41.4.noarch conflicts with 'gawk' provided by the to be installed gawk-5.4.0-1.1.x86_64 | Solution 1: Following actions will be done: | do not install gawk-5.4.0-1.1.x86_64 | do not ask to install a solvable providing rpmbuild | Solution 2: deinstallation of busybox-gawk-1.37.0-41.4.noarch | | Choose from above solutions by number or cancel [1/2/c/d/?] (c): c `---- file://local: Running /usr/bin/zypper --non-interactive --gpg-auto-import-keys --no-gpg-checks install make time xdg-utils coreutils cpio git-core bash python3 sudo gawk pkg-config python3-isort python3-yapf python3-ruff python3-pyright rpmbuild python3-base - <
Fix a regregression breaking run_curl() / run_curl_into(), introduced by commit 6db73873e7. A missing indentation raises a non-existing Error after successful JSON parsing, fix that.
The previous commits have put rules for linting and formatting via ruff, yapf, mypy and pyright into place. They are checked with the make check target, and this commit adds the fixes for the target to succeed.
It does some refactoring where type checking dug up dirty bits, and also adds lots of churn in the Python code. To a good deal, that's owed to mere formatting changes. It would have been better to seperate those from syntax and refactoring fixes into multiple commits, so that the interesting changes don't drown in the formatting nose. However, that would have been a lot of additional work only to be thrown away by later commits, hence this commit has a big diff in one piece. The size of the diff is regrettable but hopefully a one-off: What it buys is automatic format checking for CI and predictble formats for smaller diffs in the future.
Rules that "make check" enforces are, in the following order
- Syntax checkers:
- ruff check .
- mypy .
- pyright
- Format check:
- yapf --diff --recursive .
The refactoring includes:
- Turn the Result class into a more elaborate object, capable of
doing more heavy lifting around stderr and stdout decoding,
summarizing outcome, and matching error strings.
Aside from fixing broken type checks, this also removes lots of
boilerplate calling code which is currently used for handling
possible call outcome scenarios. Trying to access an inexistent,
decoded string should raise a meaningful exception by itself now,
which removes lots of code with case distinctions.
- Fix Cmd type hierarchy:
- Add the AbstractCmd class above Cmd. This is necessary because
the checker rightfully complains it can't instantiate a Cmd
instance where constructor arguments were needed. They never
were, but the type used at the instantiating code's location in
jw.pkg.App so claims.
- Lots of sub- and sub-subcommands are derived from the base
class of the invoking command. That provides some properties
shared across the ancestor hierarchy of a command, but is
semantically unsound. Fix that by introducing jw.pkg.BaseCmd
class as a place to provide basic helpers shared across all
commands used in a jw.pkg.App's context, and derive all command
classes from that afresh. The parent command is still reachable
via a common parent property.
Formatting changes are conforming to PEP-8, mostly, with minor tweaks. All in all they include the following changes.
- Remove # -*- coding: utf-8 -*-
The line was needed by Python 2 which is not supported anylonger.
For Python 3, the default encoding is UTF-8, anyway.
- Allow to run "make py-format" without having it produce any
changes. It's basically "yapf --in-place --recursive ." with some
code style settings, see conf/topdir/pyproject.toml. The settings
may be debatable. I've had custom tweaks in place on that target,
too, but then again, IDEs would have more hassle to integrate
that.
- Introduce a 88 character line length limit
- One import per line, reshuffle them semantically, see
[tool.isort] in pyproject.toml.
- Hide imports needed for type-checking only behind
if TYPE_CHECKING
- Spaces around assignments accounts for much churn. Having having
no spaces in inline parameter list assignments and default
parameter values would arguably be more compact where it's
useful. On the other hand, I have not found a code formatter
which allows spaces around assignments in parameter lists broken
into one per line and that's often better than a wall of text.
- Add two spaces before # export, as this seems to be mandated by
PEP-8
The __init__.py files as gnerated by python-tools.sh contain multiple issues, fix them:
- Make the machinery fail if the same type name is imported from
different modules
- Support relative imports from .Module import Module instead of
having to use the entire module path as import source
- Import types explicitly re-exported with "as":
from .Module import Module as Module
Otherwise ruff will regard the type as "imported but not used"
- Add "# ruff: noqa: E501" near the top. The import lines can get
long and are beyond manual control (except for renaming the
modules themselves, that is). This can cause ruff to fail, so get
it to accept long lines in __init__.py. The style violation
doesn't make much of a difference in generated code, anyway,
because nobody reads that. Plus what's happening in the code
isn't rocket science, so good style wouldn't help much with
understanding, either.
This promptly digs up two symbol name conflicts lib.pm.dpkg and lib.pm.rpm. Fix them along with this commit to keep it from breaking the build.
Add get(), which does pretty much what FileContext.get() does, but with auto-instantiating a FileContext instance. Input processing filters can be passed, too, all *args and **kwargs are passed unchanged to the FileContext's constructor.
lib.Distro._install()'s default implementation allows to install packages specified as direct links, but only to the local machine. Implement installation to arbitrary hosts specified with --target.
Enclose ExecContext._run() in an open() / close() - pair. This is convenient for the caller in that it doesn't need to take care of opening and closing for one call only, and inconvenient in that it forces the caller to conciously add an open() / close() - pair around multiple run() calls where it wants the context to stay open in between. Or use the ExecContext as a context manager.
There are a couple of assert statements in the codebase which can make jw-pkg fail without any detail whatsoever if --backtrace is not specified, fix that.