Skip to content

Commit 342e4c5

Browse files
authored
Merge pull request #166 from jacebrowning/fix-optional-dataclass-serialization
Fix serialization of optional nested dataclasses
2 parents fbecdc7 + 03f4bf0 commit 342e4c5

File tree

8 files changed

+82
-42
lines changed

8 files changed

+82
-42
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 0.9 (beta)
2+
3+
- Fixed serialization of optional nested dataclasses with a value of `None`.
4+
15
# 0.8.1 (2020-03-30)
26

37
- Fixed loading of `Missing` nested dataclasses attributes.

datafiles/converters/_bases.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class Converter:
77
"""Base class for immutable attribute conversion."""
88

99
TYPE: type = object
10-
DEFAULT: Any = None
10+
DEFAULT: Any = NotImplemented
1111

1212
@classmethod
1313
def as_optional(cls):

datafiles/converters/containers.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,19 +171,22 @@ def to_python_value(cls, deserialized_data, *, target_object):
171171
def to_preserialization_data(cls, python_value, *, default_to_skip=None):
172172
data = {}
173173

174+
if python_value is None and cls.DEFAULT is None:
175+
return None
176+
174177
for name, converter in cls.CONVERTERS.items():
175178

176179
if isinstance(python_value, dict):
177180
try:
178181
value = python_value[name]
179-
except KeyError as e:
180-
log.debug(e)
182+
except KeyError:
183+
log.debug(f'Added missing nested attribute: {name}')
181184
value = None
182185
else:
183186
try:
184187
value = getattr(python_value, name)
185-
except AttributeError as e:
186-
log.debug(e)
188+
except AttributeError:
189+
log.debug(f'Added missing nested attribute: {name}')
187190
value = None
188191

189192
with suppress(AttributeError):

datafiles/mapper.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,6 @@ def _get_data(self, include_default_values: Trilean = None) -> Dict:
138138

139139
if getattr(converter, 'DATACLASS', None):
140140
log.debug(f"Converting '{name}' dataclass with {converter}")
141-
if value is None:
142-
value = {}
143-
144-
for field in dataclasses.fields(converter.DATACLASS):
145-
if field.name not in value:
146-
log.debug(f'Added missing nested attribute: {field.name}')
147-
value[field.name] = None
148-
149141
data[name] = converter.to_preserialization_data(
150142
value,
151143
default_to_skip=Missing

datafiles/tests/test_converters.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,6 @@ def when_immutable(expect, converter, data, value):
146146
(MyDataclassConverter, None, MyDataclass(foobar=0)),
147147
(MyDataclassConverterList, None, []),
148148
(MyDataclassConverterList, 42, [MyDataclass(foobar=0)]),
149-
(
150-
MyNestedDataclassConverter,
151-
None,
152-
MyNestedDataclass(name='', dc=MyDataclass(foobar=0, flag=False)),
153-
),
154149
],
155150
)
156151
def when_mutable(expect, converter, data, value):

poetry.lock

Lines changed: 46 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[tool.poetry]
22

33
name = "datafiles"
4-
version = "0.8.1"
4+
version = "0.9b1"
55
description = "File-based ORM for dataclasses."
66

77
license = "MIT"

tests/test_saving.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
# pylint: disable=unused-variable,assigning-non-slot
44

5+
from typing import Optional
6+
57
import pytest
68

9+
from datafiles import datafile
710
from datafiles.utils import dedent, logbreak, read, write
811

912
from .samples import (
@@ -224,6 +227,26 @@ def with_nones(expect):
224227
"""
225228
)
226229

230+
def when_nested_dataclass_is_none(expect):
231+
@datafile
232+
class Name:
233+
value: str
234+
235+
@datafile("../tmp/samples/{self.key}.yml")
236+
class Sample:
237+
238+
key: int
239+
name: Optional[Name]
240+
value: float = 0.0
241+
242+
sample = Sample(42, None)
243+
244+
expect(read('tmp/samples/42.yml')) == dedent(
245+
"""
246+
name:
247+
"""
248+
)
249+
227250

228251
def describe_defaults():
229252
def with_custom_values(expect):

0 commit comments

Comments
 (0)