42

I have built a cli application using the click library in python. There is no documentation on how to debug commands.

Without click, its easy to just debug python files in IDE, but when we use click, the commands need to be run through console_scripts setup in setup.py.

0

6 Answers 6

54

This is not well documented, but you can call your command functions directly, and thus can run the code in a debugger:

Sample Code:

import click

@click.command()
@click.option('--my_arg', default=1, help='a number')
def my_command(my_arg):
    click.echo("my_arg='%d'" % my_arg)

if __name__ == '__main__':
    my_command(['--my_arg', '3'])

Result:

my_arg='3'
Sign up to request clarification or add additional context in comments.

8 Comments

How to pass multiple arguments? Especially if one of them is a flag? What if there is another @click.option("--full", is_flag=True). What would be the correct syntax for it to be passed to my_command? If I add another array with ["--full", True] the execution fails. I would need this in order to debug.
It is just a list. Basically, think like cmdlinestr.split()
Stephen, thanks! Lint complains that the second parameter is missing and that was misleading, but it works!
Does this invocation remain trivial if the click application is using subcommands and other advanced patterns? The need to debug within an IDE correlates to developing non-trivial applications.
@blamblam, This is test code. Much better to be complete here... Additionally, in an actual program you can leave the sys.argv[1:] off completely and click will do that for you.
|
12

I'm a couple years late to the party, but in case anyone else comes here looking for an answer like I just did:

Calling your function with CliRunner.invoke() will return a "Result" object with an "exc_info" attribute. You can feed that to traceback.print_exception() like so:

runner = CliRunner()
result = runner.invoke(my_command)
traceback.print_exception(*result.exc_info)

Comments

4

The setup.py generates:

  • console_script.exe
  • console_script-script.py

from cmdline:

console_app --help

expands out to the IDE configuration cmd:

python <absolute path to>\console_app-script.py --help

Tested in PyCharm 2018.2 - you can set and hit breakpoints and preserve the expected cmdline/arg paradigm.

Comments

3

I have found that writing tests is a great way to debug Click CLI applications: http://click.pocoo.org/5/testing/

Start really simple with your function and a test, then add to it, making sure the test tells you what you need it to...

Also, setting defaults helps:

def run_files(input_file='/path/to/input_file', output_file='/path/to/output_file'): click.echo(input_file, output_file)

I also generally set up logging and log everything when I begin:

logging.basicConfig(format='%(levelname)s %(message)s', level=logging.DEBUG)

Then sprinkle these throughout (IU have utilities with timestamp stuff, but this is not required):

logging.info('[{0}] Blah blah selected...'.format(
             utils.get_timestamp()))

You can do this with print or click.echo too.

Comments

3

You can use pdb like this, put inside the code you want to debug:

import pdb
pdb.set_trace()

For example:

import click

@click.command()
def hello():
    msg = "hi"
    num = 3

    import pdb
    pdb.set_trace()

    click.echo('Hello1!')
    click.echo('Hello2!')
    click.echo(msg + num)

if __name__ == '__main__':
    hello()

Then you can use pdb:

$ python hello.py 
> /home/eduardo/w/ifpb/cozer/hello.py(11)hello()
-> click.echo('Hello1!')
(Pdb) msg
'hi'
(Pdb) num
3
(Pdb) msg = hello
(Pdb) b 13
Breakpoint 1 at /home/eduardo/w/ifpb/cozer/hello.py:13
(Pdb) c
Hello1!
Hello2!
> /home/eduardo/w/ifpb/cozer/hello.py(13)hello()
-> click.echo(msg + num)
(Pdb) c
Traceback (most recent call last):
  File "hello.py", line 16, in <module>
    hello()
  File "/home/eduardo/w/ifpb/cozer/venv/lib/python3.5/site-packages/click/core.py", line 764, in __call__
    return self.main(*args, **kwargs)
  File "/home/eduardo/w/ifpb/cozer/venv/lib/python3.5/site-packages/click/core.py", line 717, in main
    rv = self.invoke(ctx)
  File "/home/eduardo/w/ifpb/cozer/venv/lib/python3.5/site-packages/click/core.py", line 956, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/eduardo/w/ifpb/cozer/venv/lib/python3.5/site-packages/click/core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "hello.py", line 13, in hello
    click.echo(msg + num)
TypeError: unsupported operand type(s) for +: 'Command' and 'int'

After the error we can try again a solution:

$ python hello.py 
> /home/eduardo/w/ifpb/cozer/hello.py(11)hello()
-> click.echo('Hello1!')
(Pdb) msg
'hi'
(Pdb) num
3
(Pdb) b 13
Breakpoint 1 at /home/eduardo/w/ifpb/cozer/hello.py:13
(Pdb) c
Hello1!
Hello2!
> /home/eduardo/w/ifpb/cozer/hello.py(13)hello()
-> click.echo(msg + num)
(Pdb) num = str(num)
(Pdb) c
hi3

Comments

-3

If you are looking to debug the modules/classes that are executed after click command functions.

An affordable solution is to create a script that simulates the flow that is performed after executing the command.

Then by placing debug points in the required lines you can achieve the debug functionality.

This is what I figured to do after a brief web search.

1 Comment

Would you mind providing some code examples to show how trivial and affordable this really is?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.