diff --git a/doc/source/whatsnew/v0.19.0.txt b/doc/source/whatsnew/v0.19.0.txt index f65f7d57d5d08..068207936818c 100644 --- a/doc/source/whatsnew/v0.19.0.txt +++ b/doc/source/whatsnew/v0.19.0.txt @@ -271,7 +271,7 @@ API changes - ``__setitem__`` will no longer apply a callable rhs as a function instead of storing it. Call ``where`` directly to get the previous behavior. (:issue:`13299`) - Passing ``Period`` with multiple frequencies to normal ``Index`` now returns ``Index`` with ``object`` dtype (:issue:`13664`) - ``PeriodIndex.fillna`` with ``Period`` has different freq now coerces to ``object`` dtype (:issue:`13664`) - +- More informative exceptions are passed through the parser. The exception type would now be the original exception type instead of ``CParserError``. (:issue `13652`) .. _whatsnew_0190.api.tolist: diff --git a/pandas/io/tests/parser/common.py b/pandas/io/tests/parser/common.py index 670f3df6f3984..11eed79e03267 100644 --- a/pandas/io/tests/parser/common.py +++ b/pandas/io/tests/parser/common.py @@ -3,6 +3,7 @@ import csv import os import platform +import codecs import re import sys @@ -45,6 +46,27 @@ def test_empty_decimal_marker(self): with tm.assertRaisesRegexp(ValueError, msg): self.read_csv(StringIO(data), decimal='') + def test_bad_stream_exception(self): + # Issue 13652: + # This test validates that both python engine + # and C engine will raise UnicodeDecodeError instead of + # c engine raising CParserError and swallowing exception + # that caused read to fail. + handle = open(self.csv_shiftjs, "rb") + codec = codecs.lookup("utf-8") + utf8 = codecs.lookup('utf-8') + # stream must be binary UTF8 + stream = codecs.StreamRecoder( + handle, utf8.encode, utf8.decode, codec.streamreader, + codec.streamwriter) + if compat.PY3: + msg = "'utf-8' codec can't decode byte" + else: + msg = "'utf8' codec can't decode byte" + with tm.assertRaisesRegexp(UnicodeDecodeError, msg): + self.read_csv(stream) + stream.close() + def test_read_csv(self): if not compat.PY3: if compat.is_platform_windows(): diff --git a/pandas/io/tests/parser/data/sauron.SHIFT_JIS.csv b/pandas/io/tests/parser/data/sauron.SHIFT_JIS.csv new file mode 100644 index 0000000000000..218ddf333ef52 --- /dev/null +++ b/pandas/io/tests/parser/data/sauron.SHIFT_JIS.csv @@ -0,0 +1,14 @@ +num, text +1,サウロン(Sauron、アイヌアの創造の時 - 第三紀3019年3月25日)は、J・R・R・トールキンの中つ国を舞台とした小説『ホビットの冒険』『指輪物語』『シルマリルの物語』の登場人物。 +2,『ホビットの冒険』に言及のある「死人うらない師」(映画『ホビットシリーズ』の字幕では「死人遣い(ネクロマンサー)」)とは彼のことである。 +3,その続編である『指輪物語』においては「一つの指輪(the One Ring)」の作り主、「冥王(Dark Lord)」、「かの者(the One)[1]」として登場する。前史にあたる『シルマリルの物語』では、初代の冥王モルゴスの最も力ある側近であった。 +4,サウロンは元来、アルダ(地球)の創造を担った天使的種族アイヌアの一員であったが、主メルコールの反逆に加担して堕落し、アルダに害をなす存在となった。 +5,「サウロン」とはクウェンヤで「身の毛のよだつもの」という意味であり、シンダリンで同様の意味である名前「ゴルサウア」と呼ばれることもある。 +6,これらは、サウロンを恐れ、忌み嫌ったエルフによる名であり、『指輪物語』作中においてアラゴルンは「かれ(サウロン)は自分の本当の名は使わないし、それを字に書いたり口に出したりすることも許さない」と発言している。 +7,そのほか、第二紀にエルフに対して自称したとされる名に、「アンナタール(物贈る君)」、「アルタノ(高貴な細工師)」、「アウレンディル(アウレの下僕)」がある。 +8,第一紀の頃のサウロンは、自在に変身する能力を持っていた。 +9,その能力を使えば見目麗しい立派な外見を装うことや、また巨大な狼や吸血こうもりといった怪物に変じることもでき、エルフから恐れられた。 +10,第二紀に一つの指輪を作り上げたサウロンは、他の力の指輪で成される事柄やその所有者を支配できるようになった。 +11,また、肉体が滅びても指輪がある限り何度でも蘇ることができた。 +12,ただしヌーメノール没落の際に美しい肉体を破壊された後は、二度と美しく変身することはできなくなり、その悪意の具現のような見るも恐ろしい姿しかとれなくなったという。 +13,またしばしば「まぶたのない火に縁取られた目」といった心象表現で捉えられた。 diff --git a/pandas/io/tests/parser/test_parsers.py b/pandas/io/tests/parser/test_parsers.py index 21f903342a611..6001c85ae76b1 100644 --- a/pandas/io/tests/parser/test_parsers.py +++ b/pandas/io/tests/parser/test_parsers.py @@ -44,6 +44,7 @@ def setUp(self): self.csv1 = os.path.join(self.dirpath, 'test1.csv') self.csv2 = os.path.join(self.dirpath, 'test2.csv') self.xls1 = os.path.join(self.dirpath, 'test.xls') + self.csv_shiftjs = os.path.join(self.dirpath, 'sauron.SHIFT_JIS.csv') class TestCParserHighMemory(BaseParser, CParserTests, tm.TestCase): diff --git a/pandas/parser.pyx b/pandas/parser.pyx index 3928bc8472113..61a1e038b89ce 100644 --- a/pandas/parser.pyx +++ b/pandas/parser.pyx @@ -10,7 +10,9 @@ import warnings from csv import QUOTE_MINIMAL, QUOTE_NONNUMERIC, QUOTE_NONE from cpython cimport (PyObject, PyBytes_FromString, PyBytes_AsString, PyBytes_Check, - PyUnicode_Check, PyUnicode_AsUTF8String) + PyUnicode_Check, PyUnicode_AsUTF8String, + PyErr_Occurred, PyErr_Fetch) +from cpython.ref cimport PyObject, Py_XDECREF from io.common import CParserError, DtypeWarning, EmptyDataError @@ -1878,6 +1880,17 @@ cdef kh_float64_t* kset_float64_from_list(values) except NULL: cdef raise_parser_error(object base, parser_t *parser): + cdef: + object old_exc + PyObject *type, *value, *traceback + if PyErr_Occurred(): + PyErr_Fetch(&type, &value, &traceback); + Py_XDECREF(type) + Py_XDECREF(traceback) + if value != NULL: + old_exc = value + Py_XDECREF(value) + raise old_exc message = '%s. C error: ' % base if parser.error_msg != NULL: if PY3: