Using Python Click, how do I add more than 5 options i.e. more than 5?

Viewed 601

Current code:

@click.command()
@click.option('--logs', type=click.Choice(['all', 'errors', 'no-errors', 'archive',
                                           'archive-with-errors']))
@click.option('--process', type=click.Choice(['all', 'fix', 'spool', 'status', 'import']))
@click.option('--quiet', is_flag=True)
@click.option('--check', is_flag=True)
@click.option('--scan', nargs=1)
def main(logs, process, quiet, check, scan):

If I add a 6th option to the main function, pylint complains about "Too many arguments (6/5) (52:0) [too-many-arguments]"

I do not want to add an exception to pylint config file.

I want to know how to pass more options to python @click. Or do I need to restructure my file, so each option has its own @click.command and its own python function?

Sorry if this is duplicated, I could not find it since click is a very general term.

2 Answers

Not quite sure what are you trying to do with click, including usage of 'click' in main would be nice.

After testing module click, it just sends keyword arguments to decorated function 'main'.

Python gets keyword arguments as dictionary, so you normally use **kwargs to pass unlimited number of keyword arguments.


Example:

@click.command()
@click.option('--logs', type=click.Choice(['all', 'errors', 'no-errors', 'archive', 'archive-with-errors']))
@click.option('--process', type=click.Choice(['all', 'fix', 'spool', 'status', 'import']))
@click.option('--quiet', is_flag=True)
@click.option('--check', is_flag=True)
@click.option('--scan', nargs=1)
def main(*args, **kwargs):
    click.echo(kwargs)

Result:

{'logs': None, 'process': None, 'quiet': False, 'check': False, 'scan': None}

Process finished with exit code 0


Your Code:

@click.command()
@click.option('--logs', type=click.Choice(['all', 'errors', 'no-errors', 'archive', 'archive-with-errors']))
@click.option('--process', type=click.Choice(['all', 'fix', 'spool', 'status', 'import']))
@click.option('--quiet', is_flag=True)
@click.option('--check', is_flag=True)
@click.option('--scan', nargs=1)
def main(logs, process, quiet, check, scan):
    click.echo(locals())

Result:

{'logs': None, 'process': None, 'quiet': False, 'check': False, 'scan': None}

Process finished with exit code 0


As you see, using **kwargs yields same results. I don't know if this will affect module's behavior, but at least arguments are passed correctly. Try this out.

I see 3 options to handle this pylint complaint.

Option 1: Inline Suppression

This option does not require that you add an exception to your pylint config file.
However, it does require that you add this extra inline comment: # pylint: disable=too-many-arguments to your code as follows:

@click.command()
@click.option('--logs', type=click.Choice(['all', 'errors', 'no-errors', 'archive',
                                           'archive-with-errors']))
@click.option('--process', type=click.Choice(['all', 'fix', 'spool', 'status', 'import']))
@click.option('--quiet', is_flag=True)
@click.option('--check', is_flag=True)
@click.option('--scan', nargs=1)
def main(logs, process, quiet, check, scan):
    # pylint: disable=too-many-arguments

This will disable too-many-arguments just for this function, not for your entire code base.

Option 2: Refactor Your Code

This option is a good choice whenever you have a complex command, which is doing many things, that also can be performed by multiple simple commands.

I'm not sure what your main @click.command is doing, but it seems as if it is trying to:

  1. show logs
  2. process some things
  3. check some status
  4. scan something

If that is the case, then could refactor this:

@click.command()
@click.option('--logs', type=click.Choice(['all', 'errors', 'no-errors', 'archive',
                                           'archive-with-errors']))
@click.option('--process', type=click.Choice(['all', 'fix', 'spool', 'status', 'import']))
@click.option('--check', is_flag=True)
@click.option('--scan', nargs=1)
def main(logs, process, quiet, check, scan):

to:

# @click.command has become Wclick.group.
@click.group()
def main(): pass

# We split off the functionality from `main` into sub-commands.
@main.command()
@click.argument("logtype", 
                type=click.Choice(['all', 'errors', 'no-errors', 'archive',  
                                   'archive-with-errors']))
def logs(logtype): pass


@main.command()
@click.argument("process_category", 
                type=click.Choice(['all', 'fix', 'spool', 'status', 'import']))
def process(process_category): pass


@main.command()
def check(): pass


@main.command()
@click.argument("arg")
def scan(arg): pass

This would solve the too-many-arguments complain, without suppressing pylint.
Now, with the refactored code, instead of invoking main --logs=archive you would invoke main logs archive.

Option 3:

Use the *args **kwargs trick:

@click.command()
@click.option('--logs', 
              type=click.Choice(['all', 'errors', 'no-errors', 'archive',
                                 'archive-with-errors']))
@click.option('--process', 
              type=click.Choice(['all', 'fix', 'spool', 'status', 'import']))
@click.option('--quiet', is_flag=True)
@click.option('--check', is_flag=True)
@click.option('--scan', nargs=1)
def main(*args, **kwargs):

as explained by @jupiterbjy in this answer

This option would be suitable if you intend to pass the *args and **kwargs unchanged anyway.
If you would first pack *args and **kwargs in the function signature, and then unpack them manually, just to satisfy pylint, then you would be better off with either option 1 or 2.

Related