Search icon indicating copy to clipboard operation
Search copied to clipboard

Use of keyword-only arguments in the CLI's `run` commands

Open Stannislav opened this issue 4 years ago • 6 comments

Whenever we have positional and optional CLI arguments, how do we reflect this in the run command?

Option 1: make all arguments keyword only. E.g. my-command file_name --verbose would be implemented as

def run(*, file_name, verbose=True)

Option 2: turn positional CLI arguments to normal arguments, optional ones to keyword-only. E.g. my-command file_name --verbose would be implemented as

def run(file_name, *, verbose=True)

This issue emerged from the discussion posted by @jankrepl in https://github.com/BlueBrain/Search/pull/482#discussion_r736296577_

Stannislav avatar Oct 26 '21 15:10 Stannislav

I would also say that there is Option 3: Not using * at all.

Option 1 comment

For the record, I was the one who introduced this logic. And let me just stress that it is actually composed of 2 ideas: "keyword only" and "without defaults" signatures.

Why "keyword only" signatures could be a good idea? Making sure that the run function and the corresponding parser always stay in sync. Alternatively, we could write unit tests that check that the run commands and their respective parsers are in sync (as done in a recent PR using inspect).

Why "without defaults" signatures could be a good idea? The goal is to force the developer to specify all the defaults in the parser. This way we don't really have to worry about maintaining 2 sets of defaults. Maybe this could also be checked using inspect.

Option 2 comment

One thing to note here is that parser.parse_args() is going to return a Namespace object that does not really store any information about what parameters were arguments and what parameters were options in the CLI. (I hope I am not wrong). Therefore at "call time" we never pass any positional arguments anyway. Additionally, the developer would be forced not only to make sure that the run and the parser have the same arguments but also that

  • CLI options = Python keyword arguments
  • CLI arguments = Python positional arguments

which is extra work with IMO no benefits.

jankrepl avatar Oct 27 '21 14:10 jankrepl

Good point about maintaining/synching 2 sets of defaults. I agree it makes sens to have run without any defaults and let argparse manage this.

Stannislav avatar Oct 27 '21 14:10 Stannislav

This being said, are there any other practical reasons to favour one of the three options mentioned so far?

  • run(file_name, *, verbose)
  • run(*, file_name, verbose)
  • run(file_name, verbose)

Stannislav avatar Oct 27 '21 14:10 Stannislav

This being said, are there any other practical reasons to favour one of the three options mentioned so far?

  • run(file_name, *, verbose)
  • run(*, file_name, verbose)
  • run(file_name, verbose)

Maybe it doesn't really matter.

For me the most important things when it comes to the run function vs parser

  • Avoid duplication of defaults
  • Avoid duplication of docstrings=help (unless you want the Python function to contain different kind of information)
  • Enforce somehow that vars(parser.parse_args()).keys() is equal to the parameters of run (unit tests are fine)

jankrepl avatar Oct 27 '21 14:10 jankrepl

I was trying to find a way to get a list of arguments from in instance of ArgumentParser, and I couldn't find a straightforward way... The tests using inspect was the best I could come up with...

Stannislav avatar Oct 27 '21 14:10 Stannislav

I was trying to find a way to get a list of arguments from in instance of ArgumentParser, and I couldn't find a straightforward way... The tests using inspect was the best I could come up with...

You mean without actually running parse_args, right? Hmm...good point, I don't know how to do it. However, there must be a way...

jankrepl avatar Oct 27 '21 14:10 jankrepl