Skip to content

Commit ab62051

Browse files
authored
bpo-20028: Empty escapechar/quotechar is not allowed for csv.Dialect (GH-28833)
1 parent d74da9e commit ab62051

File tree

4 files changed

+22
-10
lines changed

4 files changed

+22
-10
lines changed

Doc/library/csv.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,8 @@ Dialects support the following attributes:
383383
:const:`False`. On reading, the *escapechar* removes any special meaning from
384384
the following character. It defaults to :const:`None`, which disables escaping.
385385

386+
.. versionchanged:: 3.11
387+
An empty *escapechar* is not allowed.
386388

387389
.. attribute:: Dialect.lineterminator
388390

@@ -402,6 +404,8 @@ Dialects support the following attributes:
402404
as the *delimiter* or *quotechar*, or which contain new-line characters. It
403405
defaults to ``'"'``.
404406

407+
.. versionchanged:: 3.11
408+
An empty *quotechar* is not allowed.
405409

406410
.. attribute:: Dialect.quoting
407411

Lib/test/test_csv.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ def _test_arg_valid(self, ctor, arg):
4444
quoting=csv.QUOTE_ALL, quotechar='')
4545
self.assertRaises(TypeError, ctor, arg,
4646
quoting=csv.QUOTE_ALL, quotechar=None)
47+
self.assertRaises(TypeError, ctor, arg,
48+
quoting=csv.QUOTE_NONE, quotechar='')
4749

4850
def test_reader_arg_valid(self):
4951
self._test_arg_valid(csv.reader, [])
@@ -342,7 +344,6 @@ def test_read_escape(self):
342344
self._read_test(['a,^b,c'], [['a', 'b', 'c']], escapechar='^')
343345
self._read_test(['a,\0b,c'], [['a', 'b', 'c']], escapechar='\0')
344346
self._read_test(['a,\\b,c'], [['a', '\\b', 'c']], escapechar=None)
345-
self._read_test(['a,\\b,c'], [['a', '\\b', 'c']], escapechar='')
346347
self._read_test(['a,\\b,c'], [['a', '\\b', 'c']])
347348

348349
def test_read_quoting(self):
@@ -913,6 +914,12 @@ class mydialect(csv.Dialect):
913914
self.assertEqual(d.quotechar, '"')
914915
self.assertTrue(d.doublequote)
915916

917+
mydialect.quotechar = ""
918+
with self.assertRaises(csv.Error) as cm:
919+
mydialect()
920+
self.assertEqual(str(cm.exception),
921+
'"quotechar" must be a 1-character string')
922+
916923
mydialect.quotechar = "''"
917924
with self.assertRaises(csv.Error) as cm:
918925
mydialect()
@@ -977,6 +984,10 @@ class mydialect(csv.Dialect):
977984
d = mydialect()
978985
self.assertEqual(d.escapechar, "\\")
979986

987+
mydialect.escapechar = ""
988+
with self.assertRaisesRegex(csv.Error, '"escapechar" must be a 1-character string'):
989+
mydialect()
990+
980991
mydialect.escapechar = "**"
981992
with self.assertRaisesRegex(csv.Error, '"escapechar" must be a 1-character string'):
982993
mydialect()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Empty escapechar/quotechar is not allowed when initializing
2+
:class:`csv.Dialect`. Patch by Vajrasky Kok and Dong-hee Na.

Modules/_csv.c

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -250,16 +250,14 @@ _set_char_or_none(const char *name, Py_UCS4 *target, PyObject *src, Py_UCS4 dflt
250250
if (len < 0) {
251251
return -1;
252252
}
253-
if (len > 1) {
253+
if (len != 1) {
254254
PyErr_Format(PyExc_TypeError,
255255
"\"%s\" must be a 1-character string",
256256
name);
257257
return -1;
258258
}
259259
/* PyUnicode_READY() is called in PyUnicode_GetLength() */
260-
else if (len > 0) {
261-
*target = PyUnicode_READ_CHAR(src, 0);
262-
}
260+
*target = PyUnicode_READ_CHAR(src, 0);
263261
}
264262
}
265263
return 0;
@@ -272,7 +270,6 @@ _set_char(const char *name, Py_UCS4 *target, PyObject *src, Py_UCS4 dflt)
272270
*target = dflt;
273271
}
274272
else {
275-
*target = NOT_SET;
276273
if (!PyUnicode_Check(src)) {
277274
PyErr_Format(PyExc_TypeError,
278275
"\"%s\" must be string, not %.200s", name,
@@ -283,16 +280,14 @@ _set_char(const char *name, Py_UCS4 *target, PyObject *src, Py_UCS4 dflt)
283280
if (len < 0) {
284281
return -1;
285282
}
286-
if (len > 1) {
283+
if (len != 1) {
287284
PyErr_Format(PyExc_TypeError,
288285
"\"%s\" must be a 1-character string",
289286
name);
290287
return -1;
291288
}
292289
/* PyUnicode_READY() is called in PyUnicode_GetLength() */
293-
else if (len > 0) {
294-
*target = PyUnicode_READ_CHAR(src, 0);
295-
}
290+
*target = PyUnicode_READ_CHAR(src, 0);
296291
}
297292
return 0;
298293
}

0 commit comments

Comments
 (0)