python-fire
python-fire copied to clipboard
Get a list of all valid commands when writing --help
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? :)
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.
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 :)
It should be showing all the possible commands
This doesn't seem to be the case? (see my previous comment and example)
@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.
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
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 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
@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.
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.
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).
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.
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
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
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.
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!
This is desired basic functionality for any CLI argument parser. Is there an alternative straightforward tool that does this?
Would be happy to help and submit a PR along the lines of @dbieber's last comment if a maintainer is open to it.
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.