Search code examples
pythonpython-click

Is there a way to hand the command line parsing to python-click but then have click hand execution back over to my code?


When I run the code below, nothing below the domains() call is processed. The gibberish doesn't even throw an exception. It seems like to use click you have to do everything the click way, which as best I can tell is to move all of my code into the decorator functions?

Is there a way to hand the command line parsing to click but then have click hand execution back over to my code?

import click
my_cfg = { 'domains': [], 'def_domains': ['stackoverflow.com', 'google.com'] } 

@click.command()
@click.option('--domain', '-d', multiple=True, type=str, nargs=1, default=my_cfg['def_domains'])
def domains(domain):
    click.echo('\n'.join(domain))
    my_cfg['domains'].append(domain)
domains()

fgddtruhygtuit5r # No exception thrown!! Once domains() is called my code never runs

### Bunch of code here that I cannot easily move into domains() ###

Running python 3.10.x on Windows from within PyCharm.


Solution

  • Short version is pass in standalone_mode=False when calling your click commands.

    Found the answer in Program stops when running @click.command. And I learned about it in Using the Python 'click' library's parser as a simple function.

    Here's an example:

    import click
    my_cfg = {
        'domains':     [],
        'def_domains': ['stackoverflow.com', 'google.com'],
        }
    
    @click.command()
    @click.option('--domain', '-d', multiple=True, type=str, nargs=1, default=my_cfg['def_domains'])
    def domains(domain):
        my_cfg['domains'].append(domain)
    
    domains(standalone_mode=False)
    
    print(f"my_cfg: {my_cfg}")
    

    for_testing.py

    my_cfg: {'domains': [('stackoverflow.com', 'google.com')], 'def_domains': ['stackoverflow.com', 'google.com']}
    

    for_testing.py -d duckduckgo.com

    my_cfg: {'domains': [('duckduckgo.com',)], 'def_domains': ['stackoverflow.com', 'google.com']}
    

    for_testing.py -d duckduckgo.com -d google.com -d bing.com

    my_cfg: {'domains': [('duckduckgo.com', 'google.com', 'bing.com')], 'def_domains': ['stackoverflow.com', 'google.com']}
    

    Edit: You'll need to handle exceptions and even exiting for --version, -h, and -help yourself. Here's some code I kludged together:

    def process_cli_using_click(my_cli):
        #param my_cli: The function you defined via click to process the command line arguments
        click_invoke_rc = None
    
        try:
            click_invoke_rc = my_cli(standalone_mode=False)
        except click.exceptions.NoSuchOption as err:
            print(f"{err}")
            print(f"Try running the program with -h or --help.")
            exit(3)
        except click.exceptions.UsageError as err:
            print(f"{err}")
            print(f"Try running the program with -h or --help.")
            exit(5)
        except:
            err = sys.exc_info()[0]
            print(f"An unexpected command line processing error occurred:")
            print(f"{err}")
            print(f"Try running the program with -h or --help.")
            exit(10)
    
        if click_invoke_rc == 0:  # Catch if -h, --help, --version, or something unknown was specified
            exit(1)