Click: Support click.option with default values / optional arguments

Created on 1 Apr 2016  路  24Comments  路  Source: pallets/click

Since the default value always applies, there should be a way to differentiate between the following options usage:

file.py
file.py --foo
file.py --foo 2

where '2' is an argument to --foo. This should produce 3 different values for the destination of 'foo'. This is implemented in argparse with nargs='?' but has no functional equivalent in click (that I can identify). I've created a pull request that implements a potential solution. I'm not asking for a specific way of implementing the solution, and what I provided is mostly for a functional example. It does need test cases.

548

Most helpful comment

I really need this feature.

I'm developing a CLI and I want to write results to stdout by default and use an option to write a file instead of write to stdout, but I want to specify a path or use a default one when no path is specified. For example:

cli -> stdout
cli -f -> default_file
cli -f path -> specific file

I have found a workaround:
https://stackoverflow.com/questions/40753999/python-click-make-option-value-optional
But is not as fast as just an annotation.

All 24 comments

How would you know if 2 is an argument to file.py or --foo?

I had a similar issue.
I want to check that the input values are different from the defaults, and I ended up with this (in case others want the same):

@cli.command()
@click.option('--usb')
@click.option('--adk')
@click.pass_context
def local(ctx, usb, adk):
....
    default_dict = dict(zip([command.name for command in ctx.command.params], [command.default for command in ctx.command.params]))

Then you can look up the defaults in default_dict - and compare to what you received.

@untitaker ordering. first comes first served.

could this be merged if unit tests are added to #548? this is a feature i'm often missing from click...

is there a way to monkeypatch our way around this limitation?

The thing here is that there is no way to tell if an option was entered by a user or if the default value has been used. A user-entered option that has the same value as the default is the same as a non user-entered option with a default. As a result, the simple nargs='?' pattern in argparse cannot be implemented and instead two options must be used: a flag and a value. See https://gist.github.com/pombredanne/aeab7f5b4783919dfe5265b2c553cdb8 for some examples.

Not having this capability makes a CLI bloated with more options for no good reason IMHO when wanting to use this approach

@pombredanne argparse provides a const option in addition to default.

add_parser = subparsers.add_parser('add')
add_parser.add_argument('-w', '--words', metavar='length', type=int, nargs='?', const=5, default=None)

So my_prog add gives args.words == None, my_prog add -w gives args.words == 5, and my_prog add -w 10 gives args.words == 10.

@untitaker I honestly don't think its an issue (do what @anarcat said), but you can get around this by not having a space after short options (e.g. -n3) and by using an equals sign for long arguments (e.g. --number=3). Here's an excerpt from the manpage for man:

-X[dpi], --gxditview[=dpi]
   This option displays the output of groff in a graphical window using the gxditview program.  The dpi (dots per inch) may be 75, 75-12, 100, or 100-12, defaulting  to  75;  the  -12
   variants use a 12-point base font.  This option implies -T with the X75, X75-12, X100, or X100-12 device respectively.

This is a feature I'm missing badly too. It's keeping me from switching my CLI program from argparse.

@untitaker would you like to see this in Click or not?

How does Click conform to say the POSIX command line argument syntax standard without supporting optional option-arguments?

@mbrancato which POSIX standard? could you link to or quote the relevant section? oh no, you can't, because IEEE standards are behind paywalls... :p

i mean i'm all for implementing this feature, but heralding POSIX here does not further the discussion.. I think the only thing that needs to happen is to complete unit tests in #548 and make sure they suffice for this issue to be fixed.

@mbrancato you wrote that code, i guess it was to resolve this issue?

how does your code resolve the following ambiguities?

--foo --bar
--foo 2 --bar
--foo -- 2 --bar
--foo --bar 2

Excuse me, how does it not conform to those conventions?

Click doesn't support the [-f[option_argument]] bit.

Conforming to a convention does not mean that it has to fully exercise all rights. I can conform to a protocol without using all of its features

The document specifies a well defined behavior for [-f[option_argument]]. (no space, single letter)

If the SYNOPSIS shows an optional option-argument (as with [ -f[ option_argument]] in the example), 
a conforming application shall place any option-argument for that option directly adjacent to the option 
in the same argument string, without intervening <blank> characters. If the utility receives an 
argument containing only the option, it shall behave as specified in its description for an omitted 
option-argument; it shall not treat the next argument (if any) as the option-argument for that option.

I don't understand the resistance.

Also, to clear up any confusion as what 'conform' means:

The utilities in the Shell and Utilities volume of POSIX.1-2008 that claim conformance to these 
guidelines shall conform completely to these guidelines as if these guidelines contained the term 
"shall" instead of "should". On some implementations, the utilities accept usage in violation of these 
guidelines for backwards-compatibility as well as accepting the required form.

But I guess such utilities could use a subset of the features and still comply/conform/whatever. I personally don't care whether it conforms or not. It's just a useful feature that I would like to have.

I am willing to chip in a PR. I had to create quirky option subclasses to handle this in a brittle way ;) And I options with a default that is activated only if the option is selected is a bit more than nice to have for me (FWIW, this is how one of my more fleshed out CLI help looks like: https://github.com/nexB/scancode-toolkit/blob/develop/tests/scancode/data/help/help.txt )

Click conforms because the synopsis will never show this, because click doesn't support the feature. Your second quote does not support your argument.

The suggestions in both the guideline and in this thread to resolve the parsing ambiguity are not good UX imo. I don't really know why anybody needs this feature. Why not have two options?

I don't really know why anybody needs this feature. Why not have two options?

Here are two examples of how I use this feature with the argparse module. I have a --syslog flag that when specified alone, sends logs with the INFO level to syslog, but an optional argument can also be specified to tweak the logging level (--syslog WARNING). i could make a --syslog-level level, but that makes the whole commandline a little verbose (e.g. --syslog --syslog-level WARNING). Also, what happens if --syslog-level WARNING is specified alone? i am guessing that it will not enable the --syslog flag, so this is rather error prone.

another example is a --force flag: by default, it forces "everything" to happen, but I'd like to be able to restrict to some subsystem (e.g. --force=metadata).

My use cases are different but resort to the same. For instance I have plugins that contribute various output format options. If one such option is selected (e.g. --json) and no output files is specified I would like this to default to stdout e.g. -
Today this is really hard todo even custom: how do you distinguish from a non-selected option with a default value vs. an option that was selected? (irrespective of the other discussions here, this is something rather difficult)

Here is an example.

I have a password generator that can be used for generating symbolic (default) or word-based passwords of varying length.

With argparse, it looks like this

usage: ph add [-h] [-w [length]] PATH

positional arguments:
  PATH                  path to entry (e.g. 'foo') or group (e.g. 'foo/')

optional arguments:
  ...
  -w [length], --words [length]
                        generate 'correct horse battery staple' style password
                        when creating entry
  ...

In Click, the synopsis would be a bit more cluttered and you have to code in that length requires -w (not sure if that is built in).

usage: ph add [-h] [-w] PATH

positional arguments:
  PATH                  path to entry (e.g. 'foo') or group (e.g. 'foo/')

optional arguments:
  ...
  -w, --words    generate 'correct horse battery staple' style password
                 when creating entry (use --length to specify password length)
  --length       specify the length of a password (requires argument -w)
  ...

@untitaker re

Why not have two options?

When you start to have two options for most options and have quite a few options, this is doubling the number of options and gets you an ugly UX & help very quickly, guaranteed to confuse your users together with less explicit, and more verbose code with a lot of boilerplate for no good reasons IMHO.

Now leaving aside POSIX, the doc example is to build a git clone. This would not actually be possible with Click as it is: for instance $ git branch --column , $ git clone --recurse-submodules , $ git init --shared and other options such as --log, --gpg-sign , --rebase have a default value or can accept a value. The default only applies if the option is selected (And FWIW, I am not saying that git is a model of UX ;) )

regarding @anarcat https://github.com/pallets/click/issues/549#issuecomment-369253406 - The POSIX thing was mostly off the top of my head, just thinking about other drivers / compatibility. But my code was a simple implementation, it needs significant work to resolve ambiguities. I think there are some ways to solve ambiguities by defining an order preference.

Ignoring the space issue causing ambiguities between options and optional arguments, does click support something like the following with three separate results to provide similar functionality as getopt_long?

--bar
--foo --bar
--foo=4 --bar

I really need this feature.

I'm developing a CLI and I want to write results to stdout by default and use an option to write a file instead of write to stdout, but I want to specify a path or use a default one when no path is specified. For example:

cli -> stdout
cli -f -> default_file
cli -f path -> specific file

I have found a workaround:
https://stackoverflow.com/questions/40753999/python-click-make-option-value-optional
But is not as fast as just an annotation.

Has click team introduced any way to differential between the default and user-specified values for optional arguments?

This feature request opened since 2016, any possibilities that this will be implemented? I see no answer from maintainers.

Having this feature mean less options for user, that will make our app simpler for user to operate.

Was this page helpful?
0 / 5 - 0 ratings