Skip to content

cli #

Utilities for defining CLIs

Modules:

  • femto

    A comprehensive toolkit for predicting free energies

Functions:

Attributes:

DEFAULT_MAIN_OPTIONS module-attribute #

DEFAULT_MAIN_OPTIONS = [
    option(
        "-v",
        "--verbose",
        "log_level",
        help="Log debug messages",
        flag_value=DEBUG,
    ),
    option(
        "-s",
        "--silent",
        "log_level",
        help="Log only warning messages",
        flag_value=WARNING,
    ),
]

The default set of click options to expose on the main command groups.

generate_slurm_cli_options #

generate_slurm_cli_options(
    job_name: str | None, required: bool = False
) -> list

A helper function to generate the default set of options to add to a CLI function that will submit SLURM jobs.

Parameters:

  • job_name (str | None) –

    The default job name to use.

  • required (bool, default: False ) –

    Whether options without defaults should be required or not.

Returns A list of click options.

Source code in femto/fe/utils/cli.py
def generate_slurm_cli_options(job_name: str | None, required: bool = False) -> list:
    """A helper function to generate the default set of options to add to a CLI
    function that will submit SLURM jobs.

    Args:
        job_name: The default job name to use.
        required: Whether options without defaults should be required or not.

    Returns
        A list of click options.
    """
    options = []

    for (
        field_name,
        field,
    ) in femto.fe.utils.queue.SLURMOptions.model_fields.items():
        description = field.description

        default_value = field.default
        has_default = (
            default_value is not Ellipsis
            and default_value != pydantic_core.PydanticUndefined
        )

        if job_name is not None and field_name == "job_name":
            has_default = True
            default_value = job_name

        flag = f"--slurm-{field_name}".replace("_", "-").replace("-n-", "-")
        field_type = field.annotation

        if isinstance(field_type, types.UnionType):
            type_args = typing.get_args(field_type)
            assert len(type_args) == 2 and types.NoneType in type_args

            required = False
            field_type = (
                type_args[0]  # noqa: E721
                if type_args[1] is types.NoneType  # noqa: E721
                else type_args[1]  # noqa: E721
            )

        options.append(
            click.option(
                flag,
                f"slurm_{field_name}",
                help=description,
                type=field_type,
                required=required and not has_default,
                default=None if not has_default else default_value,
                show_default=has_default,
            )
        )

    return options

add_options #

add_options(options)

Apply a list of options / arguments to a function

Source code in femto/fe/utils/cli.py
def add_options(options):
    """Apply a list of options / arguments to a function"""

    options = options if isinstance(options, typing.Iterable) else [options]

    def _apply_options(func):
        for option in reversed(options):
            func = option(func)
        return func

    return _apply_options

validate_mutually_exclusive_groups #

validate_mutually_exclusive_groups(
    context: Context,
    group_1_titles: list[str] | str,
    group_2_titles: list[str] | str,
    optional_fields: set[str] | None = None,
) -> tuple[bool, bool]

Checks whether the user tried to specify options from two mutually exclusive sets of option groups.

Parameters:

  • context (Context) –

    The click context.

  • group_1_titles (list[str] | str) –

    The titles of the first groups.

  • group_2_titles (list[str] | str) –

    The titles of the second groups.

  • optional_fields (set[str] | None, default: None ) –

    A set of fields that are optional and do not have default values.

Returns:

  • tuple[bool, bool]

    Whether the user specified options from group 1 and group 2, respectively.

Source code in femto/fe/utils/cli.py
def validate_mutually_exclusive_groups(
    context: cloup.Context,
    group_1_titles: list[str] | str,
    group_2_titles: list[str] | str,
    optional_fields: set[str] | None = None,
) -> tuple[bool, bool]:
    """Checks whether the user tried to specify options from two mutually exclusive
    sets of option groups.

    Args:
        context: The click context.
        group_1_titles: The titles of the first groups.
        group_2_titles: The titles of the second groups.
        optional_fields: A set of fields that are optional and do not have default
            values.

    Returns:
        Whether the user specified options from group 1 and group 2, respectively.
    """
    optional_fields = set() if optional_fields is None else optional_fields

    group_1_titles = (
        [group_1_titles] if isinstance(group_1_titles, str) else group_1_titles
    )
    group_2_titles = (
        [group_2_titles] if isinstance(group_2_titles, str) else group_2_titles
    )

    command = typing.cast(cloup.Command, context.command)

    group_1_matches = [
        group for group in command.option_groups if group.title in group_1_titles
    ]
    assert len(group_1_matches) == len(
        group_1_titles
    ), f"found {len(group_1_matches)} group 1 matches."

    group_2_matches = [
        group for group in command.option_groups if group.title in group_2_titles
    ]
    assert len(group_2_matches) == len(
        group_2_titles
    ), f"found {len(group_2_matches)} group 2 matches."

    group_1 = group_1_matches[0]
    group_1_options = [option.name for option in group_1.options]
    group_2 = group_2_matches[0]
    group_2_options = [option.name for option in group_2.options]

    found_group_1_options = any(
        context.get_parameter_source(option) != click.core.ParameterSource.DEFAULT
        for option in group_1_options
    )
    found_group_2_options = any(
        context.get_parameter_source(option) != click.core.ParameterSource.DEFAULT
        for option in group_2_options
    )

    if found_group_1_options and found_group_2_options:
        raise click.UsageError(
            f"Options from the {group_1_titles} and {group_2_titles} option groups are "
            f"mutually exclusive."
        )
    if not found_group_1_options and not found_group_2_options:
        raise click.UsageError(
            f"Options from either the {group_1_titles} or {group_2_titles} option "
            f"groups must be specified."
        )

    required_options = group_1.options if found_group_1_options else group_2.options

    for option in required_options:
        value = context.params[option.name]

        if option.name in optional_fields or value is not None:
            continue

        raise click.MissingParameter(ctx=context, param=option)

    return found_group_1_options, found_group_2_options

configure_logging #

configure_logging(log_level: int | None)

Set up basic logging for the CLI, silencing any overly verbose modules (e.g. parmed).

Parameters:

  • log_level (int | None) –

    The log level to use.

Source code in femto/fe/utils/cli.py
def configure_logging(log_level: int | None):
    """Set up basic logging for the CLI, silencing any overly verbose modules (e.g.
    parmed).

    Args:
        log_level: The log level to use.
    """

    logging.basicConfig(
        level=log_level if log_level is not None else logging.INFO,
        format="%(asctime)s [%(levelname)s] %(name)s - %(message)s",
        datefmt="%c",
    )
    logging.getLogger("parmed").setLevel(logging.WARNING)
    logging.getLogger("numexpr").setLevel(logging.WARNING)
    logging.getLogger("pymbar").setLevel(logging.WARNING)
    logging.getLogger("jax").setLevel(logging.ERROR)