Skip to content

Commit 84555c8

Browse files
authored
Merge pull request #4855 from blueyed/pdbcls-attr
--pdbcls: improve validation, and allow for "mod:attr.class"
2 parents 42561db + f7a3e00 commit 84555c8

File tree

3 files changed

+52
-5
lines changed

3 files changed

+52
-5
lines changed

changelog/4855.feature.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
The ``--pdbcls`` option handles classes via module attributes now (e.g.
2+
``pdb:pdb.Pdb`` with `pdb++`_), and its validation was improved.
3+
4+
.. _pdb++: https://pypi.org/project/pdbpp/

src/_pytest/debugging.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import division
44
from __future__ import print_function
55

6+
import argparse
67
import pdb
78
import sys
89
from doctest import UnexpectedException
@@ -11,6 +12,31 @@
1112
from _pytest.config import hookimpl
1213

1314

15+
def _validate_usepdb_cls(value):
16+
try:
17+
modname, classname = value.split(":")
18+
except ValueError:
19+
raise argparse.ArgumentTypeError(
20+
"{!r} is not in the format 'modname:classname'".format(value)
21+
)
22+
23+
try:
24+
__import__(modname)
25+
mod = sys.modules[modname]
26+
27+
# Handle --pdbcls=pdb:pdb.Pdb (useful e.g. with pdbpp).
28+
parts = classname.split(".")
29+
pdb_cls = getattr(mod, parts[0])
30+
for part in parts[1:]:
31+
pdb_cls = getattr(pdb_cls, part)
32+
33+
return pdb_cls
34+
except Exception as exc:
35+
raise argparse.ArgumentTypeError(
36+
"could not get pdb class for {!r}: {}".format(value, exc)
37+
)
38+
39+
1440
def pytest_addoption(parser):
1541
group = parser.getgroup("general")
1642
group._addoption(
@@ -23,6 +49,7 @@ def pytest_addoption(parser):
2349
"--pdbcls",
2450
dest="usepdb_cls",
2551
metavar="modulename:classname",
52+
type=_validate_usepdb_cls,
2653
help="start a custom interactive Python debugger on errors. "
2754
"For example: --pdbcls=IPython.terminal.debugger:TerminalPdb",
2855
)
@@ -35,11 +62,8 @@ def pytest_addoption(parser):
3562

3663

3764
def pytest_configure(config):
38-
if config.getvalue("usepdb_cls"):
39-
modname, classname = config.getvalue("usepdb_cls").split(":")
40-
__import__(modname)
41-
pdb_cls = getattr(sys.modules[modname], classname)
42-
else:
65+
pdb_cls = config.getvalue("usepdb_cls")
66+
if not pdb_cls:
4367
pdb_cls = pdb.Pdb
4468

4569
if config.getvalue("trace"):

testing/test_pdb.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
from __future__ import division
33
from __future__ import print_function
44

5+
import argparse
56
import os
67
import platform
78
import sys
89

910
import _pytest._code
1011
import pytest
12+
from _pytest.debugging import _validate_usepdb_cls
1113

1214
try:
1315
breakpoint
@@ -688,6 +690,23 @@ def test_pdb_custom_cls(self, testdir, custom_pdb_calls):
688690
result.stdout.fnmatch_lines(["*NameError*xxx*", "*1 error*"])
689691
assert custom_pdb_calls == ["init", "reset", "interaction"]
690692

693+
def test_pdb_custom_cls_invalid(self, testdir):
694+
result = testdir.runpytest_inprocess("--pdbcls=invalid")
695+
result.stderr.fnmatch_lines(
696+
[
697+
"*: error: argument --pdbcls: 'invalid' is not in the format 'modname:classname'"
698+
]
699+
)
700+
701+
def test_pdb_validate_usepdb_cls(self, testdir):
702+
assert _validate_usepdb_cls("os.path:dirname.__name__") == "dirname"
703+
704+
with pytest.raises(
705+
argparse.ArgumentTypeError,
706+
match=r"^could not get pdb class for 'pdb:DoesNotExist': .*'DoesNotExist'",
707+
):
708+
_validate_usepdb_cls("pdb:DoesNotExist")
709+
691710
def test_pdb_custom_cls_without_pdb(self, testdir, custom_pdb_calls):
692711
p1 = testdir.makepyfile("""xxx """)
693712
result = testdir.runpytest_inprocess("--pdbcls=_pytest:_CustomPdb", p1)

0 commit comments

Comments
 (0)