Skip to content

Commit

Permalink
Fix dataclass initialization (#48)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
dfurtado authored Dec 8, 2021
1 parent 872ce02 commit 5f709c1
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 24 deletions.
12 changes: 6 additions & 6 deletions dataclass_csv/dataclass_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ def _parse_date_value(self, field, date_value):
return datetime.strptime(date_value, dateformat)

def _process_row(self, row):
values = []
values = dict()

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

if not value and field.default is None:
values.append(None)
values[field.name] = None
continue

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

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

try:
Expand All @@ -233,8 +233,8 @@ def _process_row(self, row):
line_number=self._reader.line_num,
) from e
else:
values.append(transformed_value)
return self._cls(*values)
values[field.name] = transformed_value
return self._cls(**values)

def __next__(self):
row = next(self._reader)
Expand Down
6 changes: 6 additions & 0 deletions tests/mocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,9 @@ class UserWithSSN:
class UserWithEmail:
name: str
email: str


@dataclasses.dataclass
class UserWithOptionalEmail:
name: str
email: str = "not specified"
34 changes: 34 additions & 0 deletions tests/test_dataclass_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
UserWithSSN,
SSN,
UserWithEmail,
UserWithOptionalEmail,
)


Expand Down Expand Up @@ -248,3 +249,36 @@ def test_skip_header_validation(create_csv):
with csv_file.open() as f:
reader = DataclassReader(f, UserWithEmail, validate_header=False)
list(reader)


def test_dt_different_order_as_csv(create_csv):
csv_file = create_csv(
{"email": "test@test.com", "name": "User1"},
fieldnames=[
"email",
"name",
],
)

with csv_file.open() as f:
reader = DataclassReader(f, UserWithEmail)
list(reader)


def test_dt_different_order_as_csv_and_option_field(create_csv):
data = [
{"email": "test@test.com", "name": "User1"},
{"name": "User1"},
]

csv_file = create_csv(
data,
fieldnames=[
"email",
"name",
],
)

with csv_file.open() as f:
reader = DataclassReader(f, UserWithOptionalEmail)
list(reader)
36 changes: 18 additions & 18 deletions tests/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,70 +14,70 @@


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

with csv_file.open('r') as f:
with csv_file.open("r") as f:
with pytest.raises(AttributeError):
reader = DataclassReader(f, UserWithoutDateFormatDecorator)
list(reader)


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

with csv_file.open('r') as f:
with csv_file.open("r") as f:
reader = DataclassReader(f, UserWithDateFormatDecorator)
list(reader)


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

with csv_file.open('r') as f:
with csv_file.open("r") as f:
reader = DataclassReader(f, UserWithDateFormatMetadata)
list(reader)


def test_use_decorator_when_metadata_is_not_defined(create_csv):
csv_file = create_csv(
{
'name': 'Test',
'birthday': '1977-08-26',
'create_date': '2018-12-09 11:11',
"name": "Test",
"birthday": "1977-08-26",
"create_date": "2018-12-09 11:11",
}
)

with csv_file.open('r') as f:
with csv_file.open("r") as f:
reader = DataclassReader(f, UserWithDateFormatDecoratorAndMetadata)
list(reader)


def test_should_raise_error_when_value_is_whitespaces(create_csv):
csv_file = create_csv({'name': ' '})
csv_file = create_csv({"name": " "})

with csv_file.open('r') as f:
with csv_file.open("r") as f:
with pytest.raises(CsvValueError):
reader = DataclassReader(f, UserWithoutAcceptWhiteSpacesDecorator)
list(reader)


def test_should_not_raise_error_when_value_is_whitespaces(create_csv):
csv_file = create_csv({'name': ' '})
csv_file = create_csv({"name": " "})

with csv_file.open('r') as f:
with csv_file.open("r") as f:
reader = DataclassReader(f, UserWithAcceptWhiteSpacesDecorator)
data = list(reader)

user = data[0]
assert user.name == ' '
assert user.name == " "


def test_should_not_raise_error_when_using_meta_accept_whitespaces(create_csv):
csv_file = create_csv({'name': ' '})
csv_file = create_csv({"name": " "})

with csv_file.open('r') as f:
with csv_file.open("r") as f:
reader = DataclassReader(f, UserWithAcceptWhiteSpacesMetadata)
data = list(reader)

user = data[0]
assert user.name == ' '
assert user.name == " "

0 comments on commit 5f709c1

Please sign in to comment.