Open eric-j-ason opened 3 months ago
@eric-j-ason A easy workaround is to specify an empty string as the default value:
@dataclass
class Person:
firstname: str
lastname: str = ""
motto: str
However, I do believe that dataclass-csv should interpret an empty string field as an empty string, which would be consistent with the behavior of Python's csv module. The following code example below is both a comparison between Python's csv module and dataclass-csv, and to demonstrate what works for dataclass-csv:
import io
import csv
from dataclasses import dataclass
from typing import Optional
from dataclass_csv import DataclassReader, DataclassWriter
output = io.StringIO()
# csv module's default behavior is to output empty string and `None` both as empty string
# Using `csv.QUOTE_NOTNULL` in Python 3.12 can differentiate between `None` and not `None` though
writer = csv.writer(output, quoting=csv.QUOTE_MINIMAL)
writer.writerow(["empty", "", "none", None])
print(output.getvalue())
# When reading an empty string, it would still be interpreted as an empty string
output.seek(0)
reader = csv.reader(output)
print(list(reader))
@dataclass
class Test:
a: str
# (*) This works, but not providing a default value would cause issues below
b: str = ""
# The line below also works, except that the default value is `None`
# b: Optional[str] = None
# This does not work
# b: str
# It's a bit counterintuitive, but this also does not work
# b: Optional[str]
output = io.StringIO()
writer = DataclassWriter(output, [Test("empty", "")], Test)
writer.write()
print(output.getvalue())
output.seek(0)
reader = DataclassReader(output, Test)
# Line below would raise exception if `Test.b` does not have a default value according to (*)
print(list(reader))
output = io.StringIO()
# There is a type mismatch here using `None` though, but this is just for demonstration
writer = DataclassWriter(output, [Test("none", None)], Test)
writer.write()
print(output.getvalue())
output.seek(0)
reader = DataclassReader(output, Test)
# Line below would raise exception if `Test.b` does not have a default value according to (*)
print(list(reader))
The output is:
empty,,none,
[['empty', '', 'none', '']]
a,b
empty,
[Test(a='empty', b='')]
a,b
none,
[Test(a='none', b='')]
Additionally, you can see that only using Optional[str]
as a type hint also does not work suppose that you want an empty string in csv to be interpreted as None
. This is due to that the criteria for an optional field is whether it has a default value, and that Optional
type hint is not taken into consideration:
https://github.com/dfurtado/dataclass-csv/blob/2dc71be81cb253eb10aba5ba70c6cebe42ab0301/dataclass_csv/dataclass_reader.py#L82-L88
A easy workaround is to specify an empty string as the default value:
@dataclass class Person: firstname: str lastname: str = "" motto: str
Thank you, but that catches me out with another error:
TypeError: non-default argument 'motto' follows default argument
@eric-j-ason Oops, forgot to test, just deal with the error accordingly, putting lastname
as the last attribute should work:
@dataclass
class Person:
firstname: str
motto: str
lastname: str = ""
Or you can add a default value to motto
as well :
@dataclass
class Person:
firstname: str
lastname: str = ""
motto: str = ""
Empty strings in a CSV file, such as the middle field in
Greg,,The world's Greggest!
, causes an exception to be thrown when read.Example:
Result:
Using dataclass-csv 1.4.0.