click.testing.CliRunner.invoke prevents use of pdb

Created on 28 Aug 2017  路  4Comments  路  Source: pallets/click

Will someone please re-open #138. It's still a problem with Click 6.7. In particular, my stack trace ends with:

  File "/home/rsyring/projects/agari-adm-src/agariadm/cli.py", line 64, in batch
    import pdb; pdb.set_trace()
  File "/usr/lib/python3.5/bdb.py", line 52, in trace_dispatch
    return self.dispatch_return(frame, arg)
  File "/usr/lib/python3.5/bdb.py", line 96, in dispatch_return
    if self.quitting: raise BdbQuit
bdb.BdbQuit

And there is explanation about why this is happening in the related issue.

bug

Most helpful comment

Since CliRunner overrides sys.{stdin,stdout} and pdb depends on them, it's quite expected that this doesn't just work. pytest, which also does IO redirection, monkey patches the global pdb.set_trace so that it disables IO redirection when called allowing it work even if IO redirection is enabled. But I think click generally tries to avoid affecting global state...

You can try this workaround:

import sys
import pdb
import click
from click.testing import CliRunner


stdin, stdout = sys.stdin, sys.stdout


def set_trace():
    pdb.Pdb(stdin=stdin, stdout=stdout).set_trace()


@click.command()
def main():
    set_trace()


if __name__ == '__main__':
    runner = CliRunner()
    print(runner.invoke(main))

How click should be handling this issue is left open for discussion.

All 4 comments

@rsyring Perhaps you've figured this out in the interim, but the workaround that I've come up for this is to mock out click.testing.make_input_stream and replace the returned value with the original sys.stdin.

For example:

import sys

from mock import patch

patcher = patch('click.testing.make_input_stream')
m_make_input_stream = patcher.start()
m_make_input_stream.return_value = sys.stdin

# ...

patcher.stop()

Please let me know if you have any more questions.

Since CliRunner overrides sys.{stdin,stdout} and pdb depends on them, it's quite expected that this doesn't just work. pytest, which also does IO redirection, monkey patches the global pdb.set_trace so that it disables IO redirection when called allowing it work even if IO redirection is enabled. But I think click generally tries to avoid affecting global state...

You can try this workaround:

import sys
import pdb
import click
from click.testing import CliRunner


stdin, stdout = sys.stdin, sys.stdout


def set_trace():
    pdb.Pdb(stdin=stdin, stdout=stdout).set_trace()


@click.command()
def main():
    set_trace()


if __name__ == '__main__':
    runner = CliRunner()
    print(runner.invoke(main))

How click should be handling this issue is left open for discussion.

This might be useful for some:

Click CliRunner with PDB working better under pytest
https://gist.github.com/rcoup/2566c92a1c47d66cfb429a6e3cb0cca2

I use this snipped and it works basically always (argsparse, click, sys.argv):

import unittest
import pytest

from thing.__main__ import cli


class TestCli(unittest.TestCase):

    @pytest.fixture(autouse=True)
    def capsys(self, capsys):
        self.capsys = capsys

    def test_cli(self):
        with pytest.raises(SystemExit) as ex:
            cli(["create", "--name", "test"])
        self.assertEqual(ex.value.code, 0)
        out, err = self.capsys.readouterr()
        self.assertEqual(out, "Succesfully created test\n")
Was this page helpful?
0 / 5 - 0 ratings