Skip to content

Commit 6f3bc5e

Browse files
bpo-20028: Improve error message of csv.Dialect when initializing (GH-28705)
(cherry picked from commit 34bbc87) Co-authored-by: Dong-hee Na <[email protected]>
1 parent 87f0156 commit 6f3bc5e

File tree

3 files changed

+70
-9
lines changed

3 files changed

+70
-9
lines changed

Lib/test/test_csv.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,7 @@ class mydialect(csv.Dialect):
881881
with self.assertRaises(csv.Error) as cm:
882882
mydialect()
883883
self.assertEqual(str(cm.exception),
884-
'"quotechar" must be string, not int')
884+
'"quotechar" must be string or None, not int')
885885

886886
def test_delimiter(self):
887887
class mydialect(csv.Dialect):
@@ -918,6 +918,35 @@ class mydialect(csv.Dialect):
918918
self.assertEqual(str(cm.exception),
919919
'"delimiter" must be string, not int')
920920

921+
mydialect.delimiter = None
922+
with self.assertRaises(csv.Error) as cm:
923+
mydialect()
924+
self.assertEqual(str(cm.exception),
925+
'"delimiter" must be string, not NoneType')
926+
927+
def test_escapechar(self):
928+
class mydialect(csv.Dialect):
929+
delimiter = ";"
930+
escapechar = '\\'
931+
doublequote = False
932+
skipinitialspace = True
933+
lineterminator = '\r\n'
934+
quoting = csv.QUOTE_NONE
935+
d = mydialect()
936+
self.assertEqual(d.escapechar, "\\")
937+
938+
mydialect.escapechar = "**"
939+
with self.assertRaisesRegex(csv.Error, '"escapechar" must be a 1-character string'):
940+
mydialect()
941+
942+
mydialect.escapechar = b"*"
943+
with self.assertRaisesRegex(csv.Error, '"escapechar" must be string or None, not bytes'):
944+
mydialect()
945+
946+
mydialect.escapechar = 4
947+
with self.assertRaisesRegex(csv.Error, '"escapechar" must be string or None, not int'):
948+
mydialect()
949+
921950
def test_lineterminator(self):
922951
class mydialect(csv.Dialect):
923952
delimiter = ";"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve error message of :class:`csv.Dialect` when initializing.
2+
Patch by Vajrasky Kok and Dong-hee Na.

Modules/_csv.c

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -231,30 +231,60 @@ _set_int(const char *name, int *target, PyObject *src, int dflt)
231231
}
232232

233233
static int
234-
_set_char(const char *name, Py_UCS4 *target, PyObject *src, Py_UCS4 dflt)
234+
_set_char_or_none(const char *name, Py_UCS4 *target, PyObject *src, Py_UCS4 dflt)
235235
{
236-
if (src == NULL)
236+
if (src == NULL) {
237237
*target = dflt;
238+
}
238239
else {
239240
*target = '\0';
240241
if (src != Py_None) {
241-
Py_ssize_t len;
242242
if (!PyUnicode_Check(src)) {
243243
PyErr_Format(PyExc_TypeError,
244-
"\"%s\" must be string, not %.200s", name,
244+
"\"%s\" must be string or None, not %.200s", name,
245245
Py_TYPE(src)->tp_name);
246246
return -1;
247247
}
248-
len = PyUnicode_GetLength(src);
248+
Py_ssize_t len = PyUnicode_GetLength(src);
249249
if (len > 1) {
250250
PyErr_Format(PyExc_TypeError,
251251
"\"%s\" must be a 1-character string",
252252
name);
253253
return -1;
254254
}
255255
/* PyUnicode_READY() is called in PyUnicode_GetLength() */
256-
if (len > 0)
256+
else {
257257
*target = PyUnicode_READ_CHAR(src, 0);
258+
}
259+
}
260+
}
261+
return 0;
262+
}
263+
264+
static int
265+
_set_char(const char *name, Py_UCS4 *target, PyObject *src, Py_UCS4 dflt)
266+
{
267+
if (src == NULL) {
268+
*target = dflt;
269+
}
270+
else {
271+
*target = '\0';
272+
if (!PyUnicode_Check(src)) {
273+
PyErr_Format(PyExc_TypeError,
274+
"\"%s\" must be string, not %.200s", name,
275+
Py_TYPE(src)->tp_name);
276+
return -1;
277+
}
278+
Py_ssize_t len = PyUnicode_GetLength(src);
279+
if (len > 1) {
280+
PyErr_Format(PyExc_TypeError,
281+
"\"%s\" must be a 1-character string",
282+
name);
283+
return -1;
284+
}
285+
/* PyUnicode_READY() is called in PyUnicode_GetLength() */
286+
else {
287+
*target = PyUnicode_READ_CHAR(src, 0);
258288
}
259289
}
260290
return 0;
@@ -423,9 +453,9 @@ dialect_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
423453
goto err
424454
DIASET(_set_char, "delimiter", &self->delimiter, delimiter, ',');
425455
DIASET(_set_bool, "doublequote", &self->doublequote, doublequote, true);
426-
DIASET(_set_char, "escapechar", &self->escapechar, escapechar, 0);
456+
DIASET(_set_char_or_none, "escapechar", &self->escapechar, escapechar, 0);
427457
DIASET(_set_str, "lineterminator", &self->lineterminator, lineterminator, "\r\n");
428-
DIASET(_set_char, "quotechar", &self->quotechar, quotechar, '"');
458+
DIASET(_set_char_or_none, "quotechar", &self->quotechar, quotechar, '"');
429459
DIASET(_set_int, "quoting", &self->quoting, quoting, QUOTE_MINIMAL);
430460
DIASET(_set_bool, "skipinitialspace", &self->skipinitialspace, skipinitialspace, false);
431461
DIASET(_set_bool, "strict", &self->strict, strict, false);

0 commit comments

Comments
 (0)