feat: do not exit() with exit_on_error to False

This commit is contained in:
egarette@silique.fr 2025-10-07 20:58:45 +02:00
parent 1ca99df30e
commit 44378162b2
2 changed files with 36 additions and 9 deletions

View file

@ -306,7 +306,7 @@ def test_leadership_modif_follower_choice(json):
def test_leadership_modif_follower_choice_unknown(json): def test_leadership_modif_follower_choice_unknown(json):
output = """usage: prog.py [-h] [--leader.leader [LEADER ...]] [--leader.pop-leader INDEX] [--leader.follower INDEX [FOLLOWER]] [--leader.follower_submulti INDEX [FOLLOWER_SUBMULTI ...]] [--leader.follower_integer INDEX [FOLLOWER_INTEGER]] [--leader.follower_boolean INDEX] [--leader.no-follower_boolean INDEX] [--leader.follower_choice INDEX [{opt1,opt2}]] output = """usage: prog.py [-h] [--leader.leader [LEADER ...]] [--leader.pop-leader INDEX] [--leader.follower INDEX [FOLLOWER]] [--leader.follower_submulti INDEX [FOLLOWER_SUBMULTI ...]] [--leader.follower_integer INDEX [FOLLOWER_INTEGER]] [--leader.follower_boolean INDEX] [--leader.no-follower_boolean INDEX] [--leader.follower_choice INDEX [{opt1,opt2}]]
prog.py: error: argument --leader.follower_choice: invalid choice: 'opt_unknown' (choose from 'opt1', 'opt2') prog.py: error: argument --leader.follower_choice: invalid choice: 'opt_unknown' (choose from opt1, opt2)
""" """
config = get_config(json) config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py', formatter_class=TestHelpFormatter) parser = TiramisuCmdlineParser(config, 'prog.py', formatter_class=TestHelpFormatter)

View file

@ -15,6 +15,7 @@
from typing import Union, List, Dict, Tuple, Optional, Any from typing import Union, List, Dict, Tuple, Optional, Any
from argparse import ( from argparse import (
ArgumentParser, ArgumentParser,
ArgumentError,
Namespace, Namespace,
SUPPRESS, SUPPRESS,
_HelpAction, _HelpAction,
@ -123,11 +124,13 @@ class TiramisuNamespace(Namespace):
option = self._config.option(true_key) option = self._config.option(true_key)
if option.isfollower(): if option.isfollower():
_setattr = self._setattr_follower _setattr = self._setattr_follower
if not value[0].isdecimal(): index = value[0]
raise ValueError("index must be a number, not {}".format(value[0])) if isinstance(index, str):
index = int(value[0]) if not value[0].isdecimal():
raise ValueError("index must be a number, not {}".format(value[0]))
index = int(index)
option = self._config.option(true_key, index) option = self._config.option(true_key, index)
true_value = ",".join(value[1:]) true_value = ",".join([str(v) for v in value[1:]])
else: else:
_setattr = self._setattr _setattr = self._setattr
true_value = value true_value = value
@ -168,7 +171,7 @@ class TiramisuNamespace(Namespace):
"argument {}: invalid choice: '{}' (choose from {})".format( "argument {}: invalid choice: '{}' (choose from {})".format(
self.arguments[key], self.arguments[key],
display_value, display_value,
", ".join([f"'{val}'" for val in choices]), ", ".join([f"{val}" for val in choices]),
) )
) )
else: else:
@ -177,6 +180,12 @@ class TiramisuNamespace(Namespace):
def _setattr(self, option: "Option", true_key: str, key: str, value: Any) -> None: def _setattr(self, option: "Option", true_key: str, key: str, value: Any) -> None:
if option.ismulti() and value is not None and not isinstance(value, list): if option.ismulti() and value is not None and not isinstance(value, list):
value = [value] value = [value]
if option.isleader():
# set value for a leader, it began to remove all values!
len_leader = option.value.len()
if len_leader:
for idx in range(len_leader - 1, -1, -1):
option.value.pop(idx)
try: try:
option.value.set(value) option.value.set(value)
except PropertiesOptionError as err: except PropertiesOptionError as err:
@ -400,6 +409,7 @@ class TiramisuCmdlineParser(ArgumentParser):
self.config, self.config,
self.prog, self.prog,
root=self.root, root=self.root,
exit_on_error=self.exit_on_error,
remove_empty_od=self.remove_empty_od, remove_empty_od=self.remove_empty_od,
display_modified_value=self.display_modified_value, display_modified_value=self.display_modified_value,
formatter_class=self.formatter_class, formatter_class=self.formatter_class,
@ -654,7 +664,7 @@ class TiramisuCmdlineParser(ArgumentParser):
kwargs["nargs"] = 2 kwargs["nargs"] = 2
if _forhelp and "mandatory" not in properties: if _forhelp and "mandatory" not in properties:
metavar = "[{}]".format(metavar) metavar = "[{}]".format(metavar)
if option.type() == "choice": if _forhelp and option.type() == "choice":
# do not manage choice with argparse there is problem with integer problem # do not manage choice with argparse there is problem with integer problem
kwargs["metavar"] = ( kwargs["metavar"] = (
"INDEX", "INDEX",
@ -673,13 +683,15 @@ class TiramisuCmdlineParser(ArgumentParser):
if _forhelp and option.type() == "boolean": if _forhelp and option.type() == "boolean":
kwargs["metavar"] = "INDEX" kwargs["metavar"] = "INDEX"
kwargs["nargs"] = 1 kwargs["nargs"] = 1
elif option.type() == "choice" and not option.isfollower(): elif _forhelp and option.type() == "choice" and not option.isfollower():
# do not manage choice with argparse there is problem with integer problem # do not manage choice with argparse there is problem with integer problem
kwargs["choices"] = get_choice_list(option, properties, False) kwargs["choices"] = get_choice_list(option, properties, False)
elif option.type() == "float": elif option.type() == "float":
kwargs["type"] = float kwargs["type"] = float
else: else:
pass pass
if not _forhelp and option.type() != "boolean" and "nargs" not in kwargs.kwargs:
kwargs["nargs"] = "?"
actions.setdefault(kwargs.ga_name, []).append(kwargs) actions.setdefault(kwargs.ga_name, []).append(kwargs)
for option_is_not_default in options_is_not_default.values(): for option_is_not_default in options_is_not_default.values():
@ -696,7 +708,9 @@ class TiramisuCmdlineParser(ArgumentParser):
def parse_args(self, *args, valid_mandatory=True, **kwargs): def parse_args(self, *args, valid_mandatory=True, **kwargs):
kwargs["namespace"] = self.namespace kwargs["namespace"] = self.namespace
try: try:
namespaces = super().parse_args(*args, **kwargs) namespaces, unknown = super().parse_known_args(*args, **kwargs)
if unknown:
msg_unknown = 'unrecognized arguments: %s' % ' '.join(unknown)
except PropertiesOptionError as err: except PropertiesOptionError as err:
name = err._subconfig.path name = err._subconfig.path
properties = self.config.option(name).property.get() properties = self.config.option(name).property.get()
@ -739,6 +753,13 @@ class TiramisuCmdlineParser(ArgumentParser):
self.error( self.error(
"the following arguments are required: {}".format(", ".join(errors)) "the following arguments are required: {}".format(", ".join(errors))
) )
if unknown:
if self.exit_on_error:
self.error(msg_unknown)
else:
err = ArgumentError(None, msg_unknown)
err.unknown = unknown
raise err
return namespaces return namespaces
def format_usage(self, *args, **kwargs): def format_usage(self, *args, **kwargs):
@ -779,3 +800,9 @@ class TiramisuCmdlineParser(ArgumentParser):
def get_config(self): def get_config(self):
return self.config return self.config
def error(self, msg):
if self.exit_on_error:
super().error(msg)
else:
raise ArgumentError(None, msg)