Skip to content

Commit 3c2e71d

Browse files
committed
ENH:Add raise_with_traceback & use in assertRaises
* Adds `raise_with_traceback` to `pandas.compat`, which handles the Python 2/3 syntax differences for raising with traceback. * Uses `raise_with_traceback` in `assertRaises` and `assertRaisesRegexp` so they provide better feedback. ENH: raise_with_traceback method.
1 parent 3a2fe0b commit 3c2e71d

File tree

5 files changed

+67
-27
lines changed

5 files changed

+67
-27
lines changed

pandas/compat/__init__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,25 @@ def __and__(self, other):
665665
else:
666666
from collections import OrderedDict, Counter
667667

668+
if PY3:
669+
def raise_with_traceback(exc, traceback=Ellipsis):
670+
if traceback == Ellipsis:
671+
_, _, traceback = sys.exc_info()
672+
raise exc.with_traceback(traceback)
673+
else:
674+
# this version of raise is a syntax error in Python 3
675+
exec("""
676+
def raise_with_traceback(exc, traceback=Ellipsis):
677+
if traceback == Ellipsis:
678+
_, _, traceback = sys.exc_info()
679+
raise exc, None, traceback
680+
""")
681+
682+
raise_with_traceback.__doc__ = (
683+
"""Raise exception with existing traceback.
684+
If traceback is not passed, uses sys.exc_info() to get traceback."""
685+
)
686+
668687
# http://stackoverflow.com/questions/4126348
669688
# Thanks to @martineau at SO
670689

pandas/core/frame.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
# pylint: disable=E1101,E1103
1313
# pylint: disable=W0212,W0231,W0703,W0622
1414

15-
from pandas.compat import range, zip, lrange, lmap, lzip, StringIO, u, OrderedDict
16-
from pandas import compat
1715
import operator
1816
import sys
1917
import collections
@@ -38,6 +36,8 @@
3836
import pandas.core.expressions as expressions
3937
from pandas.sparse.array import SparseArray
4038
from pandas.compat.scipy import scoreatpercentile as _quantile
39+
from pandas.compat import(range, zip, lrange, lmap, lzip, StringIO, u,
40+
OrderedDict, raise_with_traceback)
4141
from pandas import compat
4242
from pandas.util.terminal import get_terminal_size
4343
from pandas.util.decorators import deprecate, Appender, Substitution
@@ -351,7 +351,7 @@ class DataFrame(NDFrame):
351351
Index to use for resulting frame. Will default to np.arange(n) if
352352
no indexing information part of input data and no index provided
353353
columns : Index or array-like
354-
Column labels to use for resulting frame. Will default to
354+
Column labels to use for resulting frame. Will default to
355355
np.arange(n) if no column labels are provided
356356
dtype : dtype, default None
357357
Data type to force, otherwise infer
@@ -438,9 +438,10 @@ def __init__(self, data=None, index=None, columns=None, dtype=None,
438438
else:
439439
try:
440440
arr = np.array(data, dtype=dtype, copy=copy)
441-
except (ValueError, TypeError):
442-
raise PandasError('DataFrame constructor called with '
443-
'incompatible data and dtype')
441+
except (ValueError, TypeError) as e:
442+
exc = TypeError('DataFrame constructor called with '
443+
'incompatible data and dtype: %s' % e)
444+
raise_with_traceback(exc)
444445

445446
if arr.ndim == 0 and index is not None and columns is not None:
446447
if isinstance(data, compat.string_types) and dtype is None:
@@ -528,7 +529,8 @@ def _init_ndarray(self, values, index, columns, dtype=None,
528529
try:
529530
values = values.astype(dtype)
530531
except Exception:
531-
raise ValueError('failed to cast to %s' % dtype)
532+
e = ValueError('failed to cast to %s' % dtype)
533+
raise_with_traceback(e)
532534

533535
N, K = values.shape
534536

@@ -4282,13 +4284,16 @@ def _reduce(self, op, axis=0, skipna=True, numeric_only=None,
42824284
try:
42834285
values = self.values
42844286
result = f(values)
4285-
except Exception:
4287+
except Exception as e:
42864288
if filter_type is None or filter_type == 'numeric':
42874289
data = self._get_numeric_data()
42884290
elif filter_type == 'bool':
42894291
data = self._get_bool_data()
4290-
else:
4291-
raise NotImplementedError
4292+
else: # pragma: no cover
4293+
e = NotImplementedError("Handling exception with filter_"
4294+
"type %s not implemented."
4295+
% filter_type)
4296+
raise_with_traceback(e)
42924297
result = f(data.values)
42934298
labels = data._get_agg_axis(axis)
42944299
else:
@@ -4297,8 +4302,10 @@ def _reduce(self, op, axis=0, skipna=True, numeric_only=None,
42974302
data = self._get_numeric_data()
42984303
elif filter_type == 'bool':
42994304
data = self._get_bool_data()
4300-
else:
4301-
raise NotImplementedError
4305+
else: # pragma: no cover
4306+
msg = ("Generating numeric_only data with filter_type %s"
4307+
"not supported." % filter_type)
4308+
raise NotImplementedError(msg)
43024309
values = data.values
43034310
labels = data._get_agg_axis(axis)
43044311
else:

pandas/tests/test_frame.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4329,6 +4329,8 @@ def test_arith_flex_frame(self):
43294329

43304330
result = self.frame[:0].add(self.frame)
43314331
assert_frame_equal(result, self.frame * np.nan)
4332+
with assertRaisesRegexp(NotImplementedError, 'fill_value'):
4333+
self.frame.add(self.frame.irow(0), fill_value=3)
43324334

43334335
def test_arith_mixed(self):
43344336

@@ -8157,7 +8159,8 @@ def test_shift(self):
81578159
assert_frame_equal(shifted2, shifted3)
81588160
assert_frame_equal(ps, shifted2.shift(-1, 'B'))
81598161

8160-
self.assertRaises(ValueError, ps.shift, freq='D')
8162+
assertRaisesRegexp(ValueError, 'does not match PeriodIndex freq',
8163+
ps.shift, freq='D')
81618164

81628165
def test_shift_bool(self):
81638166
df = DataFrame({'high': [True, False],
@@ -10588,7 +10591,7 @@ def test_dot(self):
1058810591
df = DataFrame(randn(3, 4), index=[1, 2, 3], columns=lrange(4))
1058910592
df2 = DataFrame(randn(5, 3), index=lrange(5), columns=[1, 2, 3])
1059010593

10591-
self.assertRaises(ValueError, df.dot, df2)
10594+
assertRaisesRegexp(ValueError, 'aligned', df.dot, df2)
1059210595

1059310596
def test_idxmin(self):
1059410597
frame = self.frame

pandas/tests/test_tests.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,32 @@
44
import unittest
55
import warnings
66
import nose
7+
import sys
78

8-
from pandas.util.testing import assert_almost_equal
9+
from pandas.util.testing import (
10+
assert_almost_equal, assertRaisesRegexp, raise_with_traceback
11+
)
912

1013
# let's get meta.
1114

1215
class TestUtilTesting(unittest.TestCase):
1316
_multiprocess_can_split_ = True
1417

15-
def __init__(self, *args):
16-
super(TestUtilTesting, self).__init__(*args)
17-
18-
def setUp(self):
19-
pass
20-
21-
def tearDown(self):
22-
pass
23-
2418
def test_assert_almost_equal(self):
2519
# don't die because values are not ndarrays
2620
assert_almost_equal(1.1,1.1,check_less_precise=True)
21+
22+
def test_raise_with_traceback(self):
23+
with assertRaisesRegexp(LookupError, "error_text"):
24+
try:
25+
raise ValueError("THIS IS AN ERROR")
26+
except ValueError as e:
27+
e = LookupError("error_text")
28+
raise_with_traceback(e)
29+
with assertRaisesRegexp(LookupError, "error_text"):
30+
try:
31+
raise ValueError("This is another error")
32+
except ValueError:
33+
e = LookupError("error_text")
34+
_, _, traceback = sys.exc_info()
35+
raise_with_traceback(e, traceback)

pandas/util/testing.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
import pandas.core.panel4d as panel4d
2727
import pandas.compat as compat
2828
from pandas.compat import(
29-
map, zip, range, unichr, lrange, lmap, lzip, u, callable, Counter
29+
map, zip, range, unichr, lrange, lmap, lzip, u, callable, Counter,
30+
raise_with_traceback
3031
)
3132

3233
from pandas import bdate_range
@@ -1111,8 +1112,9 @@ def handle_success(self, exc_type, exc_value, traceback):
11111112
if self.regexp is not None:
11121113
val = str(exc_value)
11131114
if not self.regexp.search(val):
1114-
raise AssertionError('"%s" does not match "%s"' %
1115-
(self.regexp.pattern, str(val)))
1115+
e = AssertionError('"%s" does not match "%s"' %
1116+
(self.regexp.pattern, str(val)))
1117+
raise_with_traceback(e, traceback)
11161118
return True
11171119

11181120

0 commit comments

Comments
 (0)