lib.App: Rebuild parser from run() argv
__init__() builds the parser and the lazy subcommand registration inside it decides which subcommands to register by re-parsing sys.argv. run() then parses a different argv, so if the caller passes an argv that is deeper than the one in sys.argv, the required subparsers have not been registered and the invocation fails with an "unrecognized arguments" error. run_sub_commands() passes argv to run(), so the mismatch is reachable from the public API. Move the parser construction from __init__() into _build_parser() and call it from run() when an argv is given, so that registration and parsing are driven by the same command line. The top-level command instances are created once in __init__() and reused when the parser is rebuilt. Assisted-by: unsloth/Qwen3.8-27B-GGUF:Q4_K_M with pi.dev v0.84.2 Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
parent
845601ff10
commit
3fce4b27f8
1 changed files with 53 additions and 44 deletions
|
|
@ -105,6 +105,47 @@ class App: # export
|
|||
eloop: asyncio.AbstractEventLoop | None = None,
|
||||
) -> None:
|
||||
|
||||
from .Cmd import AbstractCmd
|
||||
|
||||
self.__args: Namespace | None = None
|
||||
self.__cmdline: str | None = None
|
||||
self.__description = description
|
||||
|
||||
self.__default_log_flags = self._default_log_flags(
|
||||
LogFlag.STDERR | LogFlag.POSITION | LogFlag.PRIO | LogFlag.COLOR
|
||||
)
|
||||
if (env := os.getenv(self._default_log_flags_env(), None)) is not None:
|
||||
self.__default_log_flags = parse_log_flags(env)
|
||||
|
||||
self.__default_log_level = self._default_log_level(NOTICE)
|
||||
if (env := os.getenv(self._default_log_level_env(), None)) is not None:
|
||||
self.__default_log_level = parse_log_level(env)
|
||||
|
||||
self.__default_log_file = self._default_log_file(None)
|
||||
if (env := os.getenv(self._default_log_file_env(), None)) is not None:
|
||||
self.__default_log_file = env
|
||||
|
||||
self.__back_trace = self._default_show_backtrace(False)
|
||||
if (env := os.getenv(self._default_show_backtrace_env())) is not None:
|
||||
self.__back_trace = env.lower() in ['1', 'true']
|
||||
|
||||
set_log_flags(self.__default_log_flags)
|
||||
set_log_level(self.__default_log_level)
|
||||
|
||||
self.__async_runner: AsyncRunner | None = None
|
||||
self.__eloop = eloop
|
||||
self.__own_eloop = False
|
||||
|
||||
cmd_classes: LoadTypes[AbstractCmd] = LoadTypes(
|
||||
modules if modules else ['__main__'],
|
||||
type_name_filter = name_filter,
|
||||
type_filter = [AbstractCmd],
|
||||
)
|
||||
self.__cmds: list[AbstractCmd] = [cmd_class(self) for cmd_class in cmd_classes]
|
||||
self._build_parser()
|
||||
|
||||
def _build_parser(self, argv: list[str] | None = None) -> None:
|
||||
|
||||
def add_cmd_to_parser(cmd: AbstractCmd, parsers: Any) -> ArgumentParser:
|
||||
parser = cast(
|
||||
'ArgumentParser',
|
||||
|
|
@ -157,7 +198,7 @@ class App: # export
|
|||
sc.cmd, sc.parser, sc.cmd.children, all = all
|
||||
)
|
||||
return
|
||||
args, _ = self.__parser.parse_known_args()
|
||||
args, _ = self.__parser.parse_known_args(argv)
|
||||
cmd_name = getattr(args, 'command', None)
|
||||
if cmd_name in scs:
|
||||
sc = scs[cmd_name]
|
||||
|
|
@ -165,61 +206,27 @@ class App: # export
|
|||
|
||||
from .Cmd import AbstractCmd
|
||||
|
||||
self.__args: Namespace | None = None
|
||||
self.__cmdline: str | None = None
|
||||
|
||||
self.__default_log_flags = self._default_log_flags(
|
||||
LogFlag.STDERR | LogFlag.POSITION | LogFlag.PRIO | LogFlag.COLOR
|
||||
cmdline = sys.argv if argv is None else argv
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
add_all_parsers = (
|
||||
'-h' in argv or '--help' in argv or '_ARGCOMPLETE' in os.environ
|
||||
)
|
||||
if (env := os.getenv(self._default_log_flags_env(), None)) is not None:
|
||||
self.__default_log_flags = parse_log_flags(env)
|
||||
|
||||
self.__default_log_level = self._default_log_level(NOTICE)
|
||||
if (env := os.getenv(self._default_log_level_env(), None)) is not None:
|
||||
self.__default_log_level = parse_log_level(env)
|
||||
|
||||
self.__default_log_file = self._default_log_file(None)
|
||||
if (env := os.getenv(self._default_log_file_env(), None)) is not None:
|
||||
self.__default_log_file = env
|
||||
|
||||
self.__back_trace = self._default_show_backtrace(False)
|
||||
if (env := os.getenv(self._default_show_backtrace_env())) is not None:
|
||||
self.__back_trace = env.lower() in ['1', 'true']
|
||||
|
||||
set_log_flags(self.__default_log_flags)
|
||||
set_log_level(self.__default_log_level)
|
||||
|
||||
self.__async_runner: AsyncRunner | None = None
|
||||
self.__eloop = eloop
|
||||
self.__own_eloop = False
|
||||
|
||||
self.__parser = ArgumentParser(
|
||||
formatter_class = ArgumentDefaultsHelpFormatter,
|
||||
description = description,
|
||||
description = self.__description,
|
||||
add_help = False,
|
||||
)
|
||||
self._add_arguments(self.__parser)
|
||||
|
||||
args, _ = self.__parser.parse_known_args()
|
||||
args, _ = self.__parser.parse_known_args(argv)
|
||||
set_log_flags(args.log_flags)
|
||||
set_log_level(args.log_level)
|
||||
|
||||
log(DEBUG, f'-------------- Running: >{pretty_cmd(sys.argv)}<')
|
||||
log(DEBUG, f'-------------- Running: >{pretty_cmd(cmdline)}<')
|
||||
|
||||
cmd_classes: LoadTypes[AbstractCmd] = LoadTypes(
|
||||
modules if modules else ['__main__'],
|
||||
type_name_filter = name_filter,
|
||||
type_filter = [AbstractCmd],
|
||||
)
|
||||
add_all_parsers = (
|
||||
'-h' in sys.argv or '--help' in sys.argv or '_ARGCOMPLETE' in os.environ
|
||||
)
|
||||
add_cmds_to_parser(
|
||||
self,
|
||||
self.__parser,
|
||||
[cmd_class(self) for cmd_class in cmd_classes],
|
||||
all = add_all_parsers,
|
||||
)
|
||||
add_cmds_to_parser(self, self.__parser, self.__cmds, all = add_all_parsers)
|
||||
|
||||
# -- Add help only now, wouldn't want to have parse_known_args() exit
|
||||
# on --help with subcommands missing
|
||||
|
|
@ -361,6 +368,8 @@ class App: # export
|
|||
self.__eloop = eloop
|
||||
self.__own_eloop = True
|
||||
try:
|
||||
if argv is not None:
|
||||
self._build_parser(argv)
|
||||
ret = self.eloop.run_until_complete(self.__run(argv))
|
||||
finally:
|
||||
self.close()
|
||||
|
|
|
|||
Loading…
Reference in a new issue