Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-76728: Coerce DictReader and DictWriter fieldnames argument to a list #32225

Merged
merged 15 commits into from
Aug 25, 2022
6 changes: 6 additions & 0 deletions Doc/library/csv.rst
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ The :mod:`csv` module defines the following classes:
All other optional or keyword arguments are passed to the underlying
:class:`reader` instance.

If the argument passed to *fieldnames* is not a :term:`sequence`,
a :exc:`TypeError` is raised.

.. versionchanged:: 3.6
Returned rows are now of type :class:`OrderedDict`.

Expand Down Expand Up @@ -209,6 +212,9 @@ The :mod:`csv` module defines the following classes:
Note that unlike the :class:`DictReader` class, the *fieldnames* parameter
of the :class:`DictWriter` class is not optional.

If the argument passed to *fieldnames* is not a
:mod:`sequence <collections.abc>`, a :exc:`TypeError` is raised.

A short usage example::

import csv
Expand Down
5 changes: 5 additions & 0 deletions Lib/csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
QUOTE_MINIMAL, QUOTE_ALL, QUOTE_NONNUMERIC, QUOTE_NONE, \
__doc__
from _csv import Dialect as _Dialect
from _collections_abc import Sequence

from io import StringIO

Expand Down Expand Up @@ -80,6 +81,8 @@ class unix_dialect(Dialect):
class DictReader:
def __init__(self, f, fieldnames=None, restkey=None, restval=None,
dialect="excel", *args, **kwds):
if fieldnames is not None and not isinstance(fieldnames, Sequence):
raise TypeError(f"Expected a sequence, got {type(fieldnames)}")
dignissimus marked this conversation as resolved.
Show resolved Hide resolved
self._fieldnames = fieldnames # list of keys for the dict
self.restkey = restkey # key to catch long rows
self.restval = restval # default value for short rows
Expand Down Expand Up @@ -130,6 +133,8 @@ def __next__(self):
class DictWriter:
def __init__(self, f, fieldnames, restval="", extrasaction="raise",
dialect="excel", *args, **kwds):
if not isinstance(fieldnames, Sequence):
raise TypeError(f"Expected a sequence, got {type(fieldnames)}")
self.fieldnames = fieldnames # list of keys for the dict
self.restval = restval # for writing short dicts
if extrasaction.lower() not in ("raise", "ignore"):
Expand Down
30 changes: 30 additions & 0 deletions Lib/test/test_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,36 @@ def test_write_field_not_in_field_names_ignore(self):
csv.DictWriter.writerow(writer, dictrow)
self.assertEqual(fileobj.getvalue(), "1,2\r\n")

def test_dict_reader_accepts_only_sequences(self):
dignissimus marked this conversation as resolved.
Show resolved Hide resolved
fieldnames = ["a", "b", "c"]
f = StringIO()
self.assertRaises(TypeError, csv.DictReader, f, iter(fieldnames))

try:
dignissimus marked this conversation as resolved.
Show resolved Hide resolved
reader = csv.DictReader(f, fieldnames)
except:
self.fail("csv.DictReader(f, fieldnames) threw an error")

def test_dict_writer_accepts_only_sequences(self):
fieldnames = ["a", "b", "c"]
f = StringIO()
self.assertRaises(TypeError, csv.DictWriter, f, iter(fieldnames))

try:
writer = csv.DictWriter(f, fieldnames)
except:
self.fail("csv.DictWriter(f, fieldnames) threw an error")


def test_dict_reader_fieldnames_is_optional(self):
fieldnames = ["a", "b", "c"]
f = StringIO()

try:
reader = csv.DictReader(f, fieldnames=None)
except:
self.fail("csv.DictReader(f, fieldnames=None) threw an error")

def test_read_dict_fields(self):
with TemporaryFile("w+", encoding="utf-8") as fileobj:
fileobj.write("1,2,abc\r\n")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The constructors for :class:`~csv.DictWriter` and :class:`~csv.DictReader` raise :exc:`TypeError` when
the ``fieldnames`` argument is not a sequence.