omni-us / jsonargparse

Implement minimal boilerplate CLIs derived from type hints and parse from command line, config files and environment variables

Home Page:https://jsonargparse.readthedocs.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Dynamic selection of function given an argument fails with an `AttributeError`

carmocca opened this issue · comments

🐛 Bug report

I am trying to setup a CLI system where I have a multitude of subcommands (only 1 in the repro code, finetune) and some of them take a --method argument that allows selecting a different function.

See Lightning-AI/litgpt#996 for the real purpose of this.

To reproduce

from typing import Literal


def methodA(foo: int = 111):
    print(locals())


def methodB(bar: int = 123):
    print(locals())


def finetune(method: Literal["A", "B"], *args, **kwargs):
    if method == "A":
        return methodA(*args, **kwargs)
    elif method == "B":
        return methodB(*args, **kwargs)
    raise NotImplementedError(method)


if __name__ == "__main__":
    from jsonargparse import CLI

    CLI(finetune)
Traceback (most recent call last):
  File "/home/carmocca/git/repro.py", line 19, in <module>
    CLI(finetune)
  File "/home/carmocca/git/nightly-venv/lib/python3.10/site-packages/jsonargparse/_cli.py", line 88, in CLI
    _add_component_to_parser(components, parser, as_positional, fail_untyped, config_help)
  File "/home/carmocca/git/nightly-venv/lib/python3.10/site-packages/jsonargparse/_cli.py", line 184, in _add_component_to_parser
    added_args = parser.add_function_arguments(component, as_group=False, **kwargs)
  File "/home/carmocca/git/nightly-venv/lib/python3.10/site-packages/jsonargparse/_signatures.py", line 190, in add_function_arguments
    return self._add_signature_arguments(
  File "/home/carmocca/git/nightly-venv/lib/python3.10/site-packages/jsonargparse/_signatures.py", line 270, in _add_signature_arguments
    self._add_signature_parameter(
  File "/home/carmocca/git/nightly-venv/lib/python3.10/site-packages/jsonargparse/_signatures.py", line 343, in _add_signature_parameter
    if group_name in group.parser.groups:
AttributeError: 'ArgumentParser' object has no attribute 'parser'

Expected behavior

The ideal behaviour would be that python repro.py -h only lists the method argument:

positional arguments:
  {A,B}                 (required, type: Literal['A', 'B'])

And then when one does python repro.py --method B -h then it also shows:

options:
  -h, --help            Show this help message and exit.
  --config CONFIG       Path to a configuration file.
  --print_config[=flags]
                        Print the configuration after applying all other arguments and exit. The optional flags customizes the output and are one or more keywords separated by comma. The supported flags are: comments, skip_default,
                        skip_null.
  --bar BAR             (type: int, default: 123)

I don't know if the AST is smart enough to parse the *args, **kwargs specific to the method selected.

I'm happy to try out a different way to support CLI behaviour like this, if there are better ways to approach this problem.

Environment

  • jsonargparse version (e.g., 4.8.0): 4.27.5

The bug is something simple and would be fixed with #468.

The ideal behaviour would be that python repro.py -h only lists the method argument:

Unfortunately this is not how it currently works. All arguments are shown in the help, under "Conditional arguments" groups.

And then when one does python repro.py --method B -h then it also shows:

This would be rather complex to implement. One issue is that argparse prints the help using a single print_help method which does not depend on previous arguments given, thus not possible to be conditional on --method B. And would also be complex because the help would need to redo code introspection with provided parameter values to figure out what code branches to apply.

I don't know if the AST is smart enough to parse the *args, **kwargs specific to the method selected.

It is capable of identifying conditional parameters. Though not specific enough to group it by dependence on other parameter values.

I'm happy to try out a different way to support CLI behaviour like this, if there are better ways to approach this problem.

I would say that jsonargparse.CLI is just a convenience function for some cases. But, when targeting more specific behaviors, implementing the logic to build the parser and call respective functions might be more advisable. And relying on code introspection to get a kind of subcommand behavior is destined to have limitations. I think better to use actual subcommands. I you really want repro.py --method B ... instead of just repro.py B ... maybe it is possible to easily implement the subcommands requiring a prior --key, though not sure.

Thank you for the quick fix Mauricio!

The jsonargparse.CLI is awesome so I was trying to avoid building my own for as long as possible.