Repository: pandas-dev/pandas

Issue:
ERR: csv parsing error consistency of exceptions
xref https://github.com/pydata/pandas/pull/12551

a) change `CParserError` to an actual `ValueError`. this is used in a very small number of cases in the `engine='c'` parser.  (in #12551 it will inherit from `ValueError`)

b) So we either change ALL `ValueError` to `ParserError(ValueError)` (e.g. a custom exception) and make the changes in both the `engine='c'` and `engine='python'`.



Discussion:
cc @gfyoung @jorisvandenbossche 

thoughts?

xref #12506 as well

Hmmm...from a semantic point of view, I would prefer `ParserError` over the more generic `ValueError`.  However, it does seem like converting everything to `ParserError` is going to break a lot more things compared to `ValueError` I'd imagine.

@jreback : Does the merged #12506 handle this issue satisfactorily?

yes, ty.

closed by #12506 

actually. What I eventually wanted was to make ALL parser exceptions the same, which is an API change.

By which you mean across both the `CParser` and the `PythonParser` or that they just always raise one exception whenever there is an error in the reading?

yes I think we need to have a single `ParserException` (which is just a `ValueError`). (and remove the `CParserException` moniker, that's why its an API change.

Okay, that makes sense.  Just wanted to clarify!


Resolving patch:
diff --git a/pandas/io/common.py b/pandas/io/common.py
--- a/pandas/io/common.py
+++ b/pandas/io/common.py
@@ -65,13 +65,15 @@ def urlopen(*args, **kwargs):
 _VALID_URLS.discard('')
 
 
-class CParserError(ValueError):
+class ParserError(ValueError):
     """
-    Exception that is thrown by the C engine when it encounters
-    a parsing error in `pd.read_csv`
+    Exception that is thrown by an error is encountered in `pd.read_csv`
     """
     pass
 
+# gh-12665: Alias for now and remove later.
+CParserError = ParserError
+
 
 class DtypeWarning(Warning):
     """
diff --git a/pandas/io/parsers.py b/pandas/io/parsers.py
--- a/pandas/io/parsers.py
+++ b/pandas/io/parsers.py
@@ -26,7 +26,7 @@
 from pandas.io.date_converters import generic_parser
 from pandas.io.common import (get_filepath_or_buffer, _validate_header_arg,
                               _get_handle, UnicodeReader, UTF8Recoder,
-                              BaseIterator, CParserError, EmptyDataError,
+                              BaseIterator, ParserError, EmptyDataError,
                               ParserWarning, _NA_VALUES)
 from pandas.tseries import tools
 
@@ -1141,7 +1141,7 @@ def tostr(x):
         # long
         for n in range(len(columns[0])):
             if all(['Unnamed' in tostr(c[n]) for c in columns]):
-                raise CParserError(
+                raise ParserError(
                     "Passed header=[%s] are too many rows for this "
                     "multi_index of columns"
                     % ','.join([str(x) for x in self.header])


Test patch:
diff --git a/pandas/io/tests/parser/common.py b/pandas/io/tests/parser/common.py
--- a/pandas/io/tests/parser/common.py
+++ b/pandas/io/tests/parser/common.py
@@ -50,7 +50,7 @@ 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
+        # c engine raising ParserError and swallowing exception
         # that caused read to fail.
         handle = open(self.csv_shiftjs, "rb")
         codec = codecs.lookup("utf-8")
diff --git a/pandas/io/tests/parser/test_textreader.py b/pandas/io/tests/parser/test_textreader.py
--- a/pandas/io/tests/parser/test_textreader.py
+++ b/pandas/io/tests/parser/test_textreader.py
@@ -154,7 +154,7 @@ def test_skip_bad_lines(self):
 
         reader = TextReader(StringIO(data), delimiter=':',
                             header=None)
-        self.assertRaises(parser.CParserError, reader.read)
+        self.assertRaises(parser.ParserError, reader.read)
 
         reader = TextReader(StringIO(data), delimiter=':',
                             header=None,
@@ -197,7 +197,7 @@ def test_header_not_enough_lines(self):
         assert_array_dicts_equal(expected, recs)
 
         # not enough rows
-        self.assertRaises(parser.CParserError, TextReader, StringIO(data),
+        self.assertRaises(parser.ParserError, TextReader, StringIO(data),
                           delimiter=',', header=5, as_recarray=True)
 
     def test_header_not_enough_lines_as_recarray(self):
@@ -218,7 +218,7 @@ def test_header_not_enough_lines_as_recarray(self):
         assert_array_dicts_equal(expected, recs)
 
         # not enough rows
-        self.assertRaises(parser.CParserError, TextReader, StringIO(data),
+        self.assertRaises(parser.ParserError, TextReader, StringIO(data),
                           delimiter=',', header=5, as_recarray=True)
 
     def test_escapechar(self):
diff --git a/pandas/io/tests/parser/test_unsupported.py b/pandas/io/tests/parser/test_unsupported.py
--- a/pandas/io/tests/parser/test_unsupported.py
+++ b/pandas/io/tests/parser/test_unsupported.py
@@ -15,7 +15,7 @@
 import pandas.util.testing as tm
 
 from pandas.compat import StringIO
-from pandas.io.common import CParserError
+from pandas.io.common import ParserError
 from pandas.io.parsers import read_csv, read_table
 
 
@@ -78,10 +78,10 @@ def test_c_engine(self):
 x   q   30      3    -0.6662 -0.5243 -0.3580  0.89145  2.5838"""
         msg = 'Error tokenizing data'
 
-        with tm.assertRaisesRegexp(CParserError, msg):
-            read_table(StringIO(text), sep=r'\s+')
-        with tm.assertRaisesRegexp(CParserError, msg):
-            read_table(StringIO(text), engine='c', sep=r'\s+')
+        with tm.assertRaisesRegexp(ParserError, msg):
+            read_table(StringIO(text), sep='\s+')
+        with tm.assertRaisesRegexp(ParserError, msg):
+            read_table(StringIO(text), engine='c', sep='\s+')
 
         msg = "Only length-1 thousands markers supported"
         data = """A|B|C
diff --git a/pandas/io/tests/test_common.py b/pandas/io/tests/test_common.py
--- a/pandas/io/tests/test_common.py
+++ b/pandas/io/tests/test_common.py
@@ -11,6 +11,7 @@
 from pandas.compat import is_platform_windows, StringIO
 
 from pandas import read_csv, concat
+import pandas as pd
 
 try:
     from pathlib import Path
@@ -88,6 +89,23 @@ def test_iterator(self):
         tm.assert_frame_equal(first, expected.iloc[[0]])
         tm.assert_frame_equal(concat(it), expected.iloc[1:])
 
+    def test_error_rename(self):
+        # see gh-12665
+        try:
+            raise common.CParserError()
+        except common.ParserError:
+            pass
+
+        try:
+            raise common.ParserError()
+        except common.CParserError:
+            pass
+
+        try:
+            raise common.ParserError()
+        except pd.parser.CParserError:
+            pass
+
 
 class TestMMapWrapper(tm.TestCase):
 
diff --git a/pandas/io/tests/test_html.py b/pandas/io/tests/test_html.py
--- a/pandas/io/tests/test_html.py
+++ b/pandas/io/tests/test_html.py
@@ -23,7 +23,7 @@
                            is_platform_windows)
 from pandas.io.common import URLError, urlopen, file_path_to_url
 from pandas.io.html import read_html
-from pandas.parser import CParserError
+from pandas.parser import ParserError
 
 import pandas.util.testing as tm
 from pandas.util.testing import makeCustomDataframe as mkdf, network
@@ -652,7 +652,7 @@ def test_parse_dates_combine(self):
 
     def test_computer_sales_page(self):
         data = os.path.join(DATA_PATH, 'computer_sales_page.html')
-        with tm.assertRaisesRegexp(CParserError, r"Passed header=\[0,1\] are "
+        with tm.assertRaisesRegexp(ParserError, r"Passed header=\[0,1\] are "
                                    "too many rows for this multi_index "
                                    "of columns"):
             self.read_html(data, header=[0, 1])
diff --git a/pandas/tests/frame/test_to_csv.py b/pandas/tests/frame/test_to_csv.py
--- a/pandas/tests/frame/test_to_csv.py
+++ b/pandas/tests/frame/test_to_csv.py
@@ -8,7 +8,7 @@
 import numpy as np
 
 from pandas.compat import (lmap, range, lrange, StringIO, u)
-from pandas.parser import CParserError
+from pandas.parser import ParserError
 from pandas import (DataFrame, Index, Series, MultiIndex, Timestamp,
                     date_range, read_csv, compat, to_datetime)
 import pandas as pd
@@ -589,7 +589,7 @@ def _make_frame(names=None):
 
             for i in [5, 6, 7]:
                 msg = 'len of {i}, but only 5 lines in file'.format(i=i)
-                with assertRaisesRegexp(CParserError, msg):
+                with assertRaisesRegexp(ParserError, msg):
                     read_csv(path, tupleize_cols=False,
                              header=lrange(i), index_col=0)