-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add mutually exclusive group #422
Add mutually exclusive group #422
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We currently already have plenty of mutually exclusive arguments, but all of them are optional. So this feature would benefit us a lot more if both required and optional mutually exclusive groups were supported.
Please also track the changes in CHANGELOG.md. This is useful for easily and publicly backtracking what and when was modified, but it is also later used to generate release notes.
def _get_argument_group(self, argument) -> ArgumentGroup: | ||
if argument.argument_group_name is None: | ||
if argument.argument_group_name is None and argument.mutually_exclusive_required_group is None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be great if something somewhere validated that argument is not assigned both to a named group and a mutually exclusive group.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition what is pointed out in the code comments I like to bring out that so far we've fully mirrored CLI help messages in Markdown docs. Now that arguments in mutually exclusive groups are all explicitly separated from other arguments, this will not be reflected in Markdown documents. As such, I think we need changes to doc.py
too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, while the functionality is there in the most part, it really bothers me that the args in help messages are in rather unexpected order. I created a test app to illustrate it
Test app code
from typing import Optional
from codemagic import cli
from codemagic.cli import Colors
yes_no_group = cli.MutuallyExclusiveGroup(
name="agree or disagree",
required=False,
)
project_number_group = cli.MutuallyExclusiveGroup(
name="specify project number",
required=True,
)
class Argument(cli.Argument):
PROJECT_NUMBER = cli.ArgumentProperties(
key="project_number",
flags=("--project-number", "-p"),
description="Project number in Firebase",
mutually_exclusive_group=project_number_group,
)
PROJECT_ID = cli.ArgumentProperties(
key="project_id",
flags=("--project-id",),
description=(
f"{Colors.BOLD('Deprecated in version x.y.z')} and will be removed in a future release, "
f"use `{Colors.BRIGHT_BLUE(cli.ArgumentProperties.get_flag(PROJECT_NUMBER))}` instead. "
"Project number in Firebase"
),
mutually_exclusive_group=project_number_group,
)
YES = cli.ArgumentProperties(
flags=("--yes", "-y"),
key="yes",
type=bool,
description="Agree",
argparse_kwargs={"action": "store_true", "default": None},
mutually_exclusive_group=yes_no_group,
)
NO = cli.ArgumentProperties(
flags=("--no", "-n"),
key="no",
type=bool,
description="Disagree",
argparse_kwargs={"action": "store_false", "default": None},
mutually_exclusive_group=yes_no_group,
)
TOOL_OPTIONAL = cli.ArgumentProperties(
flags=("--tool-optional",),
key="tool_optional",
description="This value is optional for the tool",
argparse_kwargs={"required": False},
)
TOOL_REQUIRED = cli.ArgumentProperties(
flags=("--tool-required",),
key="tool_required",
description="This value is required for the tool",
argparse_kwargs={"required": True},
)
TOOL_POSITIONAL = cli.ArgumentProperties(
key="tool_positional",
description="Positional value for tool",
)
ACTION_OPTIONAL = cli.ArgumentProperties(
flags=("--action-optional",),
key="action_optional",
description="This value is optional for the action",
argparse_kwargs={"required": False},
)
ACTION_REQUIRED = cli.ArgumentProperties(
flags=("--action-required",),
key="action_required",
description="This value is required for the action",
argparse_kwargs={"required": True},
)
ACTION_POSITIONAL = cli.ArgumentProperties(
key="action_positional",
description="Positional value for action",
)
@cli.common_arguments(
Argument.TOOL_POSITIONAL,
Argument.TOOL_REQUIRED,
Argument.TOOL_OPTIONAL,
Argument.PROJECT_ID,
Argument.PROJECT_NUMBER,
)
class MutuallyExclusiveArguments(cli.CliApp):
"""
Help message for mutually-exclusive-arguments tool
"""
def __init__(
self,
*,
tool_positional: str,
tool_required: str,
tool_optional: Optional[str],
project_id: Optional[str] = None,
project_number: Optional[str] = None,
**kwargs,
):
super().__init__(**kwargs)
if not project_number and not project_id:
raise ValueError("Either project ID or number must be supplied")
self.tool_positional = tool_positional
self.tool_required = tool_required
self.tool_optional = tool_optional
self.project_id = project_id
self.project_number = project_number
@cli.action(
"show-values",
Argument.ACTION_POSITIONAL,
Argument.ACTION_REQUIRED,
Argument.ACTION_OPTIONAL,
Argument.YES,
Argument.NO,
)
def show_values(
self,
action_positional: str,
action_required: str,
action_optional: Optional[str],
yes: Optional[bool] = None,
no: Optional[bool] = None,
):
"""
Help message for action show-values
"""
self.echo("-" * 20)
self.echo("Tool arguments:")
self.echo(f"{self.tool_positional=}")
self.echo(f"{self.tool_required=}")
self.echo(f"{self.tool_optional=}")
self.echo(f"{self.project_id=}")
self.echo(f"{self.project_number=}")
self.echo("-" * 20)
self.echo("Action arguments:")
self.echo(f"{action_positional=}")
self.echo(f"{action_required=}")
self.echo(f"{action_optional=}")
self.echo(f"{yes=}")
self.echo(f"{no=}")
self.echo("-" * 20)
if __name__ == "__main__":
MutuallyExclusiveArguments.invoke_cli()
Help message for this in the terminal is as follows:
Note the order in which options are shown:
- required actions args,
- optional actions args,
- common optional args for all tools,
- optional mutually exclusive action args,
- required args for tool,
- optional args for tool,
- required mutually exclusive action tool.
The mix of optional and required things is rather difficult to follow and can be only source for confusion. I find that either action or tool level args should come first (in logical order), then tool or action (whatever was not used in previous part) args in the same order as the previous. Finally the options that apply to all tools can follow.
So, one way to clean things up would be to show args in the following order:
- required args for tool (common for all actions in tool),
- required mutually exlusive args for tool (common for all actions in tool),
- optional args for tool (common for all actions in tool),
- optional mutually exlcusive args for tool (common for all actions in tool),
- required args for action,
- required mutually exclusive args for action,
- optional args for action,
- optional mutually exclusive args for action,
- common optional args for all tools.
Couple of more things:
- tool's common mutually exclusive arguments are described as action arguments, which is contradicting with how other common args are described in help messages,
- optional mutually exclusive groups are not documented in generated markdown docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks great! 🙌
Currently still requesting changes because of merge conflicts in changelog.
Apart from that, there are some really minor comments on markdown docs generation script.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed on #421 we should support mutually exclusive groups ,
this PR aims to add this functionality to our cli arguments.
QA
New keyword
mutually_exclusive_required_group
can be added to anycli.Argument
and expected XOR validation is executed byargparse
library.