asyncio.get_event_loop() is removed in Python 3.14 when called from
outside an async context. The current code calls it in __init__(),
which crashes on 3.14+.
To fix this, drop the eager loop creation from __init__(). Instead,
lazily create a loop in run() via asyncio.new_event_loop() when no
external loop was provided, and close it in the finally block. This
makes the lifecycle symmetric: run() owns the full create-use-close
cycle and supports re-entrant calls.
Replace __del__() with an explicit close() method, guarding against
double-close via is_closed(). close() always clears __eloop to None
so a closed loop never lingers.
Signed-off-by: Jan Lindemann <jan@janware.com>
The diff-all and diff targets diff all projects in the workspace
without filtering.
Add a target "diff-projects". It sets PGIT_SH_PROJECTS to the list of
projects from build-order, limiting the diff to the dependency
closure around $(PROJECTS).
Signed-off-by: Jan Lindemann <jan@janware.com>
If /usr/bin/isort is found, run it during "make format" to get a
defined way the imports are sorted. tool.isort in pyproject.toml is
updated to match the other fixers.
Commit the fallout of this change. Running the other fixers alone
doesn't change the formatting, so this should be safe.
Signed-off-by: Jan Lindemann <jan@janware.com>
This reverts commit 11ccaef832.
PYTHONPATH was actually produced correctly by CmdPythonpath before
this commit. This was a red herring, suggested by a buggy downstream
project, revert the change.
Signed-off-by: Jan Lindemann <jan@janware.com>
The __init__() method reads default log configuration directly
from environment variables, making it impossible for subclasses to
change defaults.
Extract the defaults into _default_log_flags(), _default_log_level(),
_default_log_file(), and _default_show_backtrace() methods, then call
them from __init__(). Subclasses can now override these methods to
customize defaults without needing to override the entire __init__()
method.
Signed-off-by: Jan Lindemann <jan@janware.com>
Add _default_*_env() helper methods that return the environment variable
names, and use them in __init__() instead of hard-coded strings. This
allows subclasses to override the names.
Signed-off-by: Jan Lindemann <jan@janware.com>
Replace the set of flag strings with a Flag enum for better type
safety and bitwise operations. Add parse_flags() to convert
comma-separated strings into a LogFlag value. Update set_flags() and
set_log_flags() to accept str | LogFlag | None.
set_flags() and set_log_flags() don't return str anylonger which is a
breaking change, but shouldn't be a problem because it's not used
anywhere.
Signed-off-by: Jan Lindemann <jan@janware.com>
The argument string builder in log() previously iterated with
enumerate and checked per-iteration whether to prepend a space.
Replace the loop with ' '.join(str(a) for a in args), which is a
single C-level operation.
Move the leading-space logic after the only_printable block so the
regex transformations see the raw joined content.
Signed-off-by: Jan Lindemann <jan@janware.com>
When prefix flags (position, prio, date) are combined with message
arguments, log() would double-space the output (e.g. '<N> Created')
or omit the separator entirely (e.g. '<N>Created'). The root cause
was that log() prepended a space to every argument, regardless of
whether the prefix already ended with one.
Fix: only prepend a space before the first argument when the prefix
doesn't already end with one. This produces exactly one separator
between prefix and content regardless of which flags are active.
Also fix log_m(): skip empty strings (the sentinel '') so it doesn't
contribute a space. And fix the early return that dropped messages
when no prefix flags were set (previous commit).
Signed-off-by: Jan Lindemann <jan@janware.com>
When position, prio, and date are all absent from the log flags, the
msg variable is empty. The early return 'if not len(msg): return'
would then skip printing the actual message in margs.
Fix by checking both msg and margs before returning. This ensures
messages are still printed even when no prefix flags are set.
Signed-off-by: Jan Lindemann <jan@janware.com>
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 <jan@janware.com>
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>
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 <jan@janware.com>
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 <jan@janware.com>
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 <jan@janware.com>
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 <jan@janware.com>
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 <jan@janware.com>
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 <jan@janware.com>
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 <jan@janware.com>
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 <jan@janware.com>
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 <jan@janware.com>
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 <jan@janware.com>
Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev 0.81.1
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 <jan@janware.com>
CmdBuild supports the targets all, clean and pkg-*. It uses builtin
dependency flavours matching those targets to resolve build order. To
make it more flexible in general, and allow it to support more
targets, e.g. "check" and "test", this commit adds a --dep-flavours
option.
Signed-off-by: Jan Lindemann <jan@janware.com>
A couple of "# export" markers after function prototypes have been
pushed along with the closing parenthesis onto the wrong line by the
code formatter, fix that.
Signed-off-by: Jan Lindemann <jan@janware.com>
local/src is a janware-specific path, remove it from App and defs.mk.
It's still in pkg.sh as a safety measure. Will have to go, too, but
is kept in for now until further audit.
Signed-off-by: Jan Lindemann <jan@janware.com>
Tolera$te missing paramiko imports. jw-pkg is designed to
work with what it finds and use plain /usr/bin/ssh if need
be.
Signed-off-by: Jan Lindemann <jan@janware.com>
CmdPythonpath, AKA pythonpath, prints a PYTHONPATH which runs from
root first to dependency leaves last. This works now but doesn't
give local overrides the advantage it should. Fix that.
Signed-off-by: Jan Lindemann <jan@janware.com>
pyrightconfig.json as generated by CmdCreateFile, doesn't contain the
repository's own Python source location in "extra_paths", fix that.
Signed-off-by: Jan Lindemann <jan@janware.com>
An Uri instance constructed from "~/some/path" returns
"file://~/some/path" as the .full property, i.e. with the "~" in the
authority part. That's not accurate and doesn't make the intended
sense in an URL context, see RFC 8089:
Common UNIX shells such as the Bourne-Again SHell (bash) and Z
SHell (zsh) provide a function known as "tilde expansion"
[Bash-Tilde] or "filename expansion" [Zsh-Tilde], where a path
that begins with a tilde character "~" can be expanded out to a
special directory name. No such facility exists using the file
URI scheme; a tilde in a file URI is always just a tilde.
The "fix" introduced by this commit makes .full return the path
without file:// prepended. That's conservative. It could also chose
to expand the tilde, which is arguably cleaner. To be introduced by a
later change after this commit has seen more coverage.
Signed-off-by: Jan Lindemann <jan@janware.com>
In non-interactive mode, "zypper dup" runs with the options
--force-resolution and --auto-agree-with-licenses duplicated, fix
that.
Signed-off-by: Jan Lindemann <jan@janware.com>
Two error messages in Distro.__init__() contain the typo 'instaniate'
instead of 'instantiate'. Fix the spelling.
Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL with pi.dev v0.80.2
Signed-off-by: Jan Lindemann <jan@janware.com>
Remove unused helper functions from install():
_matches_host_prefix(), _crop_host_prefix(), _crop_default_prefix(),
and _matches_default_prefix().
Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL with pi.dev v0.80.2
Signed-off-by: Jan Lindemann <jan@janware.com>
log() uses a regular string instead of an f-string, fix that.
Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL with pi.dev v0.80.2
Signed-off-by: Jan Lindemann <jan@janware.com>
The scheme property returns 'file://' for local paths instead of
'file', causing scheme_plus_authority to produce 'file://://' with a
triple slash.
Also fix the test to expect 'file' instead of 'file://'.
Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL with pi.dev v0.80.2
Signed-off-by: Jan Lindemann <jan@janware.com>
The CopyContext._run() calls await self._run(), which would create
infinite recursion raising RecursionError at runtime. Make it a pure
virtual.
Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL with pi.dev v0.80.2
Signed-off-by: Jan Lindemann <jan@janware.com>
get_values() splits comma-separated values and strips whitespace but
does not filter out empty strings. A value like "a, b, " produces
['a', 'b', ''], with an empty string at the end. This empty string
propagates to callers like CmdRequiredOsPkg.py and pollutes output.
Add a filter for non-empty stripped values.
Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev
Signed-off-by: Jan Lindemann <jan@janware.com>
The refactored __get_project_conf() raises FileNotFoundError when
project.conf does not exist, whereas the original read_value()
returns None. This causes get_value() to crash for projects with
missing or incomplete project.conf files.
Catch FileNotFoundError in __get_project_conf() and return None to
restore original behavior. Remove redundant @cache from
__read_project_conf() since __get_project_conf() already provides
caching.
Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev
Signed-off-by: Jan Lindemann <jan@janware.com>
The __find_circular_deps() and find_circular_deps() methods now
return list[str] instead of bool. On a cycle, the path builds up the
dependency chain in __find_circular_deps_recursive(), and
__find_circular_deps() appends the closing project to complete the
cycle. An empty list means no cycle found.
CmdDep prints the cycle as 'a -> b -> c -> a' instead of a generic
message.
Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev
Signed-off-by: Jan Lindemann <jan@janware.com>
The fd.close() call inside the `with open(...) as fd:` block is
redundant because the file handle is managed by the context manager.
Remove it.
Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev
Signed-off-by: Jan Lindemann <jan@janware.com>
Replace the hardcoded /opt in __proj_dir() with a configurable ___opt_root
member, consistent with how __projs_root is configured.
Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev
Signed-off-by: Jan Lindemann <jan@janware.com>
Mutable default arguments (list) cause unexpected shared state
between calls. Use None as default and initialise to [] inside the
function body.
Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev
Signed-off-by: Jan Lindemann <jan@janware.com>
get_project_refs() always returns a list[str], never None. The `if
deps is None: continue` check in __read_dep_graph() is dead code,
remove it.
Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev
Signed-off-by: Jan Lindemann <jan@janware.com>
The __flip_dep_graph(graph) call sits inside the while loop and
performs redundant graph flipping on every iteration. Hoist it
outside to compute once and reuse the result.
Assisted-by: unsloth/Qwen3.6-35B-A3B-GGUF:IQ4_NL and pi.dev
Signed-off-by: Jan Lindemann <jan@janware.com>
Remove App.get_section() which parses raw file sections by scanning for
section headers and accumulating lines. This method is no longer needed
since ProjectConf now handles all config file parsing.
Signed-off-by: Jan Lindemann <jan@janware.com>
Refactor App to use the new ProjectConf module for parsing
make/project.conf files.
This commit
- removes the inline ad-hoc read_value() method and its nested
helper functions, replacing them with ProjectConf's
get_str_or_none() API.
- introduces two cached helper methods (__read_project_conf() and
__get_project_conf()) to read and cache ProjectConf instances per
project.
- updates get_value() to delegate to ProjectConf and moves the
proj_dir lookup to only run in the version branch where it is
needed.
- updates the topdir init to use __read_project_conf() with a
FileNotFoundError catch for optional config files.
Signed-off-by: Jan Lindemann <jan@janware.com>