Skip to content

Commit 5f709c1

Browse files
authored
Fix dataclass initialization (dfurtado#48)
* Fix dataclass initialization Fix the dataclass initialization so the order of the members of the dataclass doesn't need to match the order of the columns in the CSV file. That adds flexibility when non-required fields with default values needs to be defined last in the dataclass.
1 parent 872ce02 commit 5f709c1

File tree

4 files changed

+64
-24
lines changed

4 files changed

+64
-24
lines changed

dataclass_csv/dataclass_reader.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ def _parse_date_value(self, field, date_value):
178178
return datetime.strptime(date_value, dateformat)
179179

180180
def _process_row(self, row):
181-
values = []
181+
values = dict()
182182

183183
for field in dataclasses.fields(self._cls):
184184
if not field.init:
@@ -190,7 +190,7 @@ def _process_row(self, row):
190190
raise CsvValueError(ex, line_number=self._reader.line_num) from None
191191

192192
if not value and field.default is None:
193-
values.append(None)
193+
values[field.name] = None
194194
continue
195195

196196
field_type = self.type_hints[field.name]
@@ -206,7 +206,7 @@ def _process_row(self, row):
206206
except ValueError as ex:
207207
raise CsvValueError(ex, line_number=self._reader.line_num) from None
208208
else:
209-
values.append(transformed_value)
209+
values[field.name] = transformed_value
210210
continue
211211

212212
if field_type is bool:
@@ -219,7 +219,7 @@ def _process_row(self, row):
219219
except ValueError as ex:
220220
raise CsvValueError(ex, line_number=self._reader.line_num) from None
221221
else:
222-
values.append(transformed_value)
222+
values[field.name] = transformed_value
223223
continue
224224

225225
try:
@@ -233,8 +233,8 @@ def _process_row(self, row):
233233
line_number=self._reader.line_num,
234234
) from e
235235
else:
236-
values.append(transformed_value)
237-
return self._cls(*values)
236+
values[field.name] = transformed_value
237+
return self._cls(**values)
238238

239239
def __next__(self):
240240
row = next(self._reader)

tests/mocks.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,9 @@ class UserWithSSN:
122122
class UserWithEmail:
123123
name: str
124124
email: str
125+
126+
127+
@dataclasses.dataclass
128+
class UserWithOptionalEmail:
129+
name: str
130+
email: str = "not specified"

tests/test_dataclass_reader.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
UserWithSSN,
1616
SSN,
1717
UserWithEmail,
18+
UserWithOptionalEmail,
1819
)
1920

2021

@@ -248,3 +249,36 @@ def test_skip_header_validation(create_csv):
248249
with csv_file.open() as f:
249250
reader = DataclassReader(f, UserWithEmail, validate_header=False)
250251
list(reader)
252+
253+
254+
def test_dt_different_order_as_csv(create_csv):
255+
csv_file = create_csv(
256+
{"email": "[email protected]", "name": "User1"},
257+
fieldnames=[
258+
"email",
259+
"name",
260+
],
261+
)
262+
263+
with csv_file.open() as f:
264+
reader = DataclassReader(f, UserWithEmail)
265+
list(reader)
266+
267+
268+
def test_dt_different_order_as_csv_and_option_field(create_csv):
269+
data = [
270+
{"email": "[email protected]", "name": "User1"},
271+
{"name": "User1"},
272+
]
273+
274+
csv_file = create_csv(
275+
data,
276+
fieldnames=[
277+
"email",
278+
"name",
279+
],
280+
)
281+
282+
with csv_file.open() as f:
283+
reader = DataclassReader(f, UserWithOptionalEmail)
284+
list(reader)

tests/test_decorators.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,70 +14,70 @@
1414

1515

1616
def test_should_raise_error_without_dateformat(create_csv):
17-
csv_file = create_csv({'name': 'Test', 'create_date': '2018-12-09'})
17+
csv_file = create_csv({"name": "Test", "create_date": "2018-12-09"})
1818

19-
with csv_file.open('r') as f:
19+
with csv_file.open("r") as f:
2020
with pytest.raises(AttributeError):
2121
reader = DataclassReader(f, UserWithoutDateFormatDecorator)
2222
list(reader)
2323

2424

2525
def test_shold_not_raise_error_when_using_dateformat_decorator(create_csv):
26-
csv_file = create_csv({'name': 'Test', 'create_date': '2018-12-09'})
26+
csv_file = create_csv({"name": "Test", "create_date": "2018-12-09"})
2727

28-
with csv_file.open('r') as f:
28+
with csv_file.open("r") as f:
2929
reader = DataclassReader(f, UserWithDateFormatDecorator)
3030
list(reader)
3131

3232

3333
def test_shold_not_raise_error_when_dateformat_metadata(create_csv):
34-
csv_file = create_csv({'name': 'Test', 'create_date': '2018-12-09'})
34+
csv_file = create_csv({"name": "Test", "create_date": "2018-12-09"})
3535

36-
with csv_file.open('r') as f:
36+
with csv_file.open("r") as f:
3737
reader = DataclassReader(f, UserWithDateFormatMetadata)
3838
list(reader)
3939

4040

4141
def test_use_decorator_when_metadata_is_not_defined(create_csv):
4242
csv_file = create_csv(
4343
{
44-
'name': 'Test',
45-
'birthday': '1977-08-26',
46-
'create_date': '2018-12-09 11:11',
44+
"name": "Test",
45+
"birthday": "1977-08-26",
46+
"create_date": "2018-12-09 11:11",
4747
}
4848
)
4949

50-
with csv_file.open('r') as f:
50+
with csv_file.open("r") as f:
5151
reader = DataclassReader(f, UserWithDateFormatDecoratorAndMetadata)
5252
list(reader)
5353

5454

5555
def test_should_raise_error_when_value_is_whitespaces(create_csv):
56-
csv_file = create_csv({'name': ' '})
56+
csv_file = create_csv({"name": " "})
5757

58-
with csv_file.open('r') as f:
58+
with csv_file.open("r") as f:
5959
with pytest.raises(CsvValueError):
6060
reader = DataclassReader(f, UserWithoutAcceptWhiteSpacesDecorator)
6161
list(reader)
6262

6363

6464
def test_should_not_raise_error_when_value_is_whitespaces(create_csv):
65-
csv_file = create_csv({'name': ' '})
65+
csv_file = create_csv({"name": " "})
6666

67-
with csv_file.open('r') as f:
67+
with csv_file.open("r") as f:
6868
reader = DataclassReader(f, UserWithAcceptWhiteSpacesDecorator)
6969
data = list(reader)
7070

7171
user = data[0]
72-
assert user.name == ' '
72+
assert user.name == " "
7373

7474

7575
def test_should_not_raise_error_when_using_meta_accept_whitespaces(create_csv):
76-
csv_file = create_csv({'name': ' '})
76+
csv_file = create_csv({"name": " "})
7777

78-
with csv_file.open('r') as f:
78+
with csv_file.open("r") as f:
7979
reader = DataclassReader(f, UserWithAcceptWhiteSpacesMetadata)
8080
data = list(reader)
8181

8282
user = data[0]
83-
assert user.name == ' '
83+
assert user.name == " "

0 commit comments

Comments
 (0)