diff --git a/README.md b/README.md index 969f3cc..7b81104 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,10 @@ If it is a subcommand that is executed: The object may register any arguments or subparsers that it needs. * The `run` function is called when the subcommand is used by the user. The arguments to this function are passed in by name, and the names can - be any of the options that the subcommand registered. There are two other - special argument names: + be any of the options that the subcommand registered. A parameter without + a corresponding registered option must have a default value; when the + option is absent, Python uses that default. There are also these special + argument names: * `options` - if specified, this is the Namespace returned by parse_args * `robot_class` - if specified, the user's robot.py will be loaded and it will be inspected for their robot class, which will be passed in diff --git a/robotpy/main.py b/robotpy/main.py index 6cf4a1e..9912d66 100644 --- a/robotpy/main.py +++ b/robotpy/main.py @@ -296,7 +296,8 @@ def _run( for k, v in params.items(): if v.kind in ok_args: # An error here is an error in the command -- should never happen - kwargs[k] = getattr(options, k) + if v.default is inspect.Parameter.empty or hasattr(options, k): + kwargs[k] = getattr(options, k) elif v.kind in bad_args: raise ValueError( "internal error: subcommands may only have keyword or normal arguments" diff --git a/tests/test_argparse.py b/tests/test_argparse.py index c52d606..5968b2f 100644 --- a/tests/test_argparse.py +++ b/tests/test_argparse.py @@ -35,6 +35,22 @@ def run(self, count): assert received == {"count": 3} +def test_run_parameter_without_parser_argument_uses_default(): + received = [] + + class Command: + def __init__(self, parser): + pass + + def run(self, mode="default-mode"): + received.append(mode) + + exit_code = main._run(["sample"], [("sample", Command)]) + + assert exit_code == 0 + assert received == ["default-mode"] + + def test_nested_subcommand_is_dispatched(): received = []