python-fire icon indicating copy to clipboard operation
python-fire copied to clipboard

Get a list of all valid commands when writing --help

Open bfelbo opened this issue 5 years ago • 18 comments
trafficstars

Thanks for creating this amazing library. I'm somewhat confused as to how the --help command is implemented. When running python example.py --help Fire only prints the flags, not the actual commands that can be run (see example below):

NAME
    fury.py

SYNOPSIS
    fury.py <flags>

FLAGS
    --verbosity=VERBOSITY

How do I make --help show all the possible commands and why is that not the default? :)

bfelbo avatar Feb 19 '20 02:02 bfelbo

Hi @bfelbo,

Are you suggesting that the help show:

fury.py --verbosity=VERBOSITY

or is there a command that's actually missing from the help, that you'd like it to show?

It should be showing all the possible commands, but not listed out as all possible complete commands explicitly simply due to formatting considerations. In particular, all commands share a prefix, and sometimes that prefix is quite long, so we choose not to write out that prefix repeatedly. Always open to suggestions for improvement though.

dbieber avatar Feb 21 '20 16:02 dbieber

Thanks for the response. I'll make it more clear :) See the below example from the docs.

import fire

class Calculator(object):

  def add(self, x, y):
    return x + y

  def multiply(self, x, y):
    return x * y

if __name__ == '__main__':
  fire.Fire(Calculator)

Right now running python example.py --help returns the following:

NAME
    example.py

SYNOPSIS
    example.py

I think it'd be great if running help would actually tell you what CLI commands are available, i.e. add and multiply in this case. Otherwise, how can you figure out what you can do with the CLI? Maybe I'm missing something obvious here :)

bfelbo avatar Feb 21 '20 17:02 bfelbo

It should be showing all the possible commands

This doesn't seem to be the case? (see my previous comment and example)

bfelbo avatar Feb 21 '20 18:02 bfelbo

@bfelbo As far as I know, it doesn't generate the options for you. You're saying it would be nice if help also showed something like this?

add - examply.py add x y
multiply - examply.py multiply x y

I don't think that would be too hard to add-in.

saltyfireball avatar Feb 24 '20 17:02 saltyfireball

Exactly. That would be amazing!

For the class-based interface, I would probably ignore the self argument. It would then become something like the following:

NAME
    example.py

COMMANDS
    add x y
    multiply x y

SYNOPSIS
    example.py

bfelbo avatar Feb 24 '20 17:02 bfelbo

If the arguments have a default, it'd be great to include them too, e.g.:

NAME
    example.py

COMMANDS
    add x y
    multiply x y
    root x z=2

SYNOPSIS
    example.py

I'm not really sure what the SYNOPSIS part is useful for, even in the case of NAME != SYNOPSIS. If you're worried the output gets too long, it would perhaps make sense to remove it?

bfelbo avatar Feb 24 '20 17:02 bfelbo

@bfelbo so I was just playing around. If you return self from your functions you can get a little bit closer to what you're looking for:

import fire
class Calculator(object):
    """A simple calculator class."""
    def __init__(self, meow=True):
        self.meow=meow
        self.db = None

    def double(self, number):
        self.db = 2 * number
        return self

    def add(self, number):
        self.db = 2 * number
        return self

if __name__ == '__main__':
    fire.Fire(Calculator)

Now when I run this I get a help screen with COMMANDS:

python runner.py double 10
NAME
    runner.py double 10 - A simple calculator class.

SYNOPSIS
    runner.py double 10 COMMAND | VALUE

DESCRIPTION
    A simple calculator class.

COMMANDS
    COMMAND is one of the following:

     add

     double

saltyfireball avatar Feb 24 '20 17:02 saltyfireball

@bfelbo Ignoring that my example isn't really functional, it does show that COMMANDS are parsed at some point. and we could make use of that to extend the general response from --help

This would be a nice upgrade - and I think fits the theme of bootstrapping classes into a more friendly cli.

saltyfireball avatar Feb 24 '20 17:02 saltyfireball

Agreed this is an issue. Let me explain why the current behavior is as it is, and how we can probably fix it.

Current State

"--help" vs "-- --help"

--help, --interactive, --verbose, etc are special Fire arguments. Normally they are separated from the standard arguments to a Fire CLI by a separating --. This is to avoid confusion between arguments intended for the CLI developers functions and arguments intended for Fire itself.

However, in order to let users get help the way they expect, we make an exception for --help. (Eventually we may want to broaden this exception to all of Fire's args, but for now it's just for --help.) This means that if a user specifies "--help" or "-h" and the CLI isn't expecting an argument named "help" or "h", we'll show the user help for the current component.

Help for Objects vs Help for Classes

The help screen is meant to show all the commands for the current component. If the current component is a class, often the only thing you can do is initialize it. Only after it's initialized does it make sense to access and call its methods. If the current component is an object (which happens for example after a class has been initialized) then the methods on that object are shown in the help as commands.

You can use a separator (-) to indicate that you're done passing arguments to a component. Using a separator will cause a class to be initialized without waiting for additional arguments.

Getting Help Today

class --help # Shows help for the class. This is often unhelpful, and is the main issue in this issue. class -- --help # Same as previous class - --help # Instantiates an object of class class, then shows help for that object. This is more useful help, but unintuitive to users. class - -- --help # Same as previous.

Next Steps [Edit: never mind, see next comment instead]

The solution will be to make class --help equivalent to class - -- --help, rather than to class -- --help as it is today.

References in the Code

Here is where Fire checks to see if the user is requesting help without a separating --.

https://github.com/google/python-fire/blob/1ac5105a0437518785033f47dd78746678e5133a/fire/core.py#L438-439

The logic for separators is at https://github.com/google/python-fire/blob/1ac5105a0437518785033f47dd78746678e5133a/fire/core.py#L442-L450

and

https://github.com/google/python-fire/blob/1ac5105a0437518785033f47dd78746678e5133a/fire/core.py#L570

Basically what we'll have to change is:

Instead of immediately breaking at 439 when --help is observed, we'll first do one more pass through the big while loop as if there was a separator, and then we'll break to show help.

dbieber avatar Feb 24 '20 17:02 dbieber

Actually, perhaps it's even better to just show the methods of a class in that class's help screen, even if the class hasn't been instantiated yet. We'll just need to be careful in the presentation to make clear which flags are required (because the methods can't be called until after the class is initialized).

dbieber avatar Feb 24 '20 17:02 dbieber

Actually, perhaps it's even better to just show the methods of a class in that class's help screen, even if the class hasn't been instantiated yet. We'll just need to be careful in the presentation to make clear which flags are required (because the methods can't be called until after the class is initialized).

@dbieber I think this would work perfectly. Like you previously outlined

class --help 

Should just go into details on that class, like an improved version of what class - --help does.

saltyfireball avatar Feb 24 '20 19:02 saltyfireball

The help text is generated at: https://github.com/google/python-fire/blob/1ac5105a0437518785033f47dd78746678e5133a/fire/helptext.py#L47

The list of possible actions (including commands) is generated by https://github.com/google/python-fire/blob/1ac5105a0437518785033f47dd78746678e5133a/fire/helptext.py#L61

The commands are aggregated at: https://github.com/google/python-fire/blob/1ac5105a0437518785033f47dd78746678e5133a/fire/helptext.py#L330-L331

And isCommand is defined at: https://github.com/google/python-fire/blob/1ac5105a0437518785033f47dd78746678e5133a/fire/value_types.py#L35-L36


The actual string formatting to build the help text starts at https://github.com/google/python-fire/blob/1ac5105a0437518785033f47dd78746678e5133a/fire/helptext.py#L65

dbieber avatar Feb 24 '20 19:02 dbieber

Here's a minimal example that would need to be fixed to resolve this:

In [1]: class A: 
   ...:     def x(self): 
   ...:         return 0 
   ...:                                                                                                                                                           

In [5]: import fire                                                                                                                                               

In [6]: fire.completion.VisibleMembers(A)                                                                                                                         
Out[6]: []  # This will need to include x.

In [7]: fire.completion.VisibleMembers(A())                                                                                                                       
Out[7]: [('x', <bound method A.x of <__main__.A object at 0x10af2c790>>)]

VisibleMembers is defined here: https://github.com/google/python-fire/blob/1ac5105a0437518785033f47dd78746678e5133a/fire/completion.py#L347

dbieber avatar Feb 24 '20 19:02 dbieber

The code ignoring methods in classes is here: https://github.com/google/python-fire/blob/1ac5105a0437518785033f47dd78746678e5133a/fire/completion.py#L331-L334

Simply changing that from False to True will result in methods being included in the help. Additional changes will likely also be needed to make sure flags are marked as required when appropriate so we don't falsely give the impression that these methods are callable before the class has been instantiated.

dbieber avatar Feb 24 '20 19:02 dbieber

Thanks for this!

falsely give the impression that these methods are callable before the class has been instantiated.

Perhaps wise to commit the current solution and refine per this comment later? Status quo help message is a bit tricky unless user is familiar with fire, and the solution you sketched out is a big improvement!

tbenst avatar Apr 11 '20 00:04 tbenst

This is desired basic functionality for any CLI argument parser. Is there an alternative straightforward tool that does this?

impredicative avatar Aug 20 '20 21:08 impredicative

Would be happy to help and submit a PR along the lines of @dbieber's last comment if a maintainer is open to it.

jwickens avatar Nov 25 '20 22:11 jwickens

Just reviving this old thread to say: showing the methods of a class was my expected default behavior for "--help" and I was very surprised when I discovered it did not work as expected.

kylemcdonald avatar Sep 25 '23 23:09 kylemcdonald