neithere / argh

An argparse wrapper that doesn't make you say "argh" each time you deal with it.
http://argh.rtfd.org
GNU Lesser General Public License v3.0
369 stars 55 forks source link

Keyword-Only Arguments as Options #191

Closed neithere closed 11 months ago

neithere commented 11 months ago

Current state

Argh currently uses the following approach to map a Python function signature onto CLI options and arguments:

Problem

While this mapping is simple and easy to understand, it's overly simplified:

Solution

The well-established PEP-3102 (2006) designates the syntax for separating positional and keyword-only arguments in function signature.

If we translate all "args" as positional CLI args and "kwonly args" as named ones ("options"), the mapping becomes far more straightforward and the developer has the ability to (not) define the default value and the positional/named way of calling independently.

Example 1

def compare(foo, bar, *, baz=123):
    ...

Translates to:

prog [-h] [--baz BAZ] foo bar

Example 2

def compare(foo, bar="bar", *, baz=123, quux):
    ...

Translates to:

prog [-h] [--baz BAZ] --quux QUUX foo [bar]

Concerns

Transition

Changing the classic implementation to the kwonly-driven one can break a lot of existing code that depends on Argh. Moreover, this approach may turn out to be not as good in practice as it is on paper. To ensure a smooth transition, the following steps should be considered:

  1. add the new approach as optional, make it accessible via a new argument — e.g. dispatch(..., defaults_policy=DefaultsPolicy.KWONLY) — to be set;
  2. if the approach proves to be superior to the classic simplified one, begin a slow deprecation of the latter:
    • if neither argument is passed, set this one to DefaultsPolicy.SIMPLIFIED and issue a warning;
    • switch to the new approach by default;
    • possibly deprecate the old one (if there's associated tech debt and no obvious value).

PyLint

The keyword-arg-before-vararg / W1113 warning is issued in the func(x=None, *args) case; it's recommended to replace it with func(*args, x=None). This supports the approach taken in this RFC.