Search code examples
pythoncommand-line-interfacepython-3.6python-click

How to iterate through the list of files supplied by the shell in a Python 3 Click app?


I am writing a console app with the Click library (almost no experience with it yet) and Python 3.6.

I need a program that can be called like this from the OS shell:

myapp --myoptiona=123 --myoptionb=456 *

to iterate through the list of all the files in the current directory (or whatever the mask specifies) and do something with them.

How can this be achieved with Click?


Solution

  • If you need to accept a variable number of file parameters, you can use nargs=-1 with a click.argument to generate the command like:

    Code:

    @click.command()
    @click.option('--myoptiona')
    @click.option('--myoptionb')
    @click.argument('files', nargs=-1)
    def cli(myoptiona, myoptionb, files):
        """The Great CLI APP"""
        filenames = []
        for filename in files:
            # if our shell does not do filename globbing
            expanded = list(glob(filename))
            if len(expanded) == 0 and '*' not in filename:
                raise(click.BadParameter('{}: file not found'.format(filename)))
            filenames.extend(expanded)
    
        click.echo('myoptiona: %s' % myoptiona)
        click.echo('myoptionb: %s' % myoptionb)
        for name in filenames:
            click.echo('a file: %s' % name)
    

    Test Code:

    import click
    from glob import glob
    
    @click.command()
    @click.option('--myoptiona')
    @click.option('--myoptionb')
    @click.argument('files', nargs=-1)
    def cli(myoptiona, myoptionb, files):
        """The Great CLI APP"""
        filenames = []
        for filename in files:
            expanded = list(glob(filename))
            if len(expanded) == 0 and '*' not in filename:
                raise(click.BadParameter(
                    "file '{}' not found".format(filename)))
            filenames.extend(expanded)
    
        click.echo('myoptiona: %s' % myoptiona)
        click.echo('myoptionb: %s' % myoptionb)
        for name in filenames:
            click.echo('a file: %s' % name)
    
    
    if __name__ == "__main__":
        commands = (
            '--myoptiona 3 --myoptionb 4',
            '--myoptiona 3 file1 file2',
            '--myoptiona 3 --myoptionb 4 file1 file4',
            'file1 file2 file3',
            'file*',
            '--help',
        )
    
        import sys, time
    
        time.sleep(1)
        print('Click Version: {}'.format(click.__version__))
        print('Python Version: {}'.format(sys.version))
        for cmd in commands:
            try:
                time.sleep(0.1)
                print('-----------')
                print('> ' + cmd)
                time.sleep(0.1)
                cli(cmd.split())
    
            except BaseException as exc:
                if str(exc) != '0' and \
                        not isinstance(exc, (click.ClickException, SystemExit)):
                    raise
    

    Results:

    Click Version: 6.7
    Python Version: 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
    -----------
    > --myoptiona 3 --myoptionb 4
    myoptiona: 3
    myoptionb: 4
    -----------
    > --myoptiona 3 file1 file2
    myoptiona: 3
    myoptionb: None
    a file: file1
    a file: file2
    -----------
    > --myoptiona 3 --myoptionb 4 file1 file4
    Usage: test.py [OPTIONS] [FILES]...
    
    Error: Invalid value: 'file4': file not found
    -----------
    > file1 file2 file3
    myoptiona: None
    myoptionb: None
    a file: file1
    a file: file2
    a file: file3
    -----------
    > file*
    myoptiona: None
    myoptionb: None
    a file: file.conf
    a file: file1
    a file: file2
    a file: file3
    -----------
    > --help
    Usage: test.py [OPTIONS] [FILES]...
    
      The Great CLI APP
    
    Options:
      --myoptiona TEXT
      --myoptionb TEXT
      --help            Show this message and exit.