Skip to content

Commit

Permalink
Merge pull request #166 from jacebrowning/fix-optional-dataclass-seri…
Browse files Browse the repository at this point in the history
…alization

Fix serialization of optional nested dataclasses
  • Loading branch information
jacebrowning committed Apr 1, 2020
2 parents fbecdc7 + 03f4bf0 commit 342e4c5
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 42 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 0.9 (beta)

- Fixed serialization of optional nested dataclasses with a value of `None`.

# 0.8.1 (2020-03-30)

- Fixed loading of `Missing` nested dataclasses attributes.
Expand Down
2 changes: 1 addition & 1 deletion datafiles/converters/_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class Converter:
"""Base class for immutable attribute conversion."""

TYPE: type = object
DEFAULT: Any = None
DEFAULT: Any = NotImplemented

@classmethod
def as_optional(cls):
Expand Down
11 changes: 7 additions & 4 deletions datafiles/converters/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,19 +171,22 @@ def to_python_value(cls, deserialized_data, *, target_object):
def to_preserialization_data(cls, python_value, *, default_to_skip=None):
data = {}

if python_value is None and cls.DEFAULT is None:
return None

for name, converter in cls.CONVERTERS.items():

if isinstance(python_value, dict):
try:
value = python_value[name]
except KeyError as e:
log.debug(e)
except KeyError:
log.debug(f'Added missing nested attribute: {name}')
value = None
else:
try:
value = getattr(python_value, name)
except AttributeError as e:
log.debug(e)
except AttributeError:
log.debug(f'Added missing nested attribute: {name}')
value = None

with suppress(AttributeError):
Expand Down
8 changes: 0 additions & 8 deletions datafiles/mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,6 @@ def _get_data(self, include_default_values: Trilean = None) -> Dict:

if getattr(converter, 'DATACLASS', None):
log.debug(f"Converting '{name}' dataclass with {converter}")
if value is None:
value = {}

for field in dataclasses.fields(converter.DATACLASS):
if field.name not in value:
log.debug(f'Added missing nested attribute: {field.name}')
value[field.name] = None

data[name] = converter.to_preserialization_data(
value,
default_to_skip=Missing
Expand Down
5 changes: 0 additions & 5 deletions datafiles/tests/test_converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,6 @@ def when_immutable(expect, converter, data, value):
(MyDataclassConverter, None, MyDataclass(foobar=0)),
(MyDataclassConverterList, None, []),
(MyDataclassConverterList, 42, [MyDataclass(foobar=0)]),
(
MyNestedDataclassConverter,
None,
MyNestedDataclass(name='', dc=MyDataclass(foobar=0, flag=False)),
),
],
)
def when_mutable(expect, converter, data, value):
Expand Down
69 changes: 46 additions & 23 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[tool.poetry]

name = "datafiles"
version = "0.8.1"
version = "0.9b1"
description = "File-based ORM for dataclasses."

license = "MIT"
Expand Down
23 changes: 23 additions & 0 deletions tests/test_saving.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

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

from typing import Optional

import pytest

from datafiles import datafile
from datafiles.utils import dedent, logbreak, read, write

from .samples import (
Expand Down Expand Up @@ -224,6 +227,26 @@ def with_nones(expect):
"""
)

def when_nested_dataclass_is_none(expect):
@datafile
class Name:
value: str

@datafile("../tmp/samples/{self.key}.yml")
class Sample:

key: int
name: Optional[Name]
value: float = 0.0

sample = Sample(42, None)

expect(read('tmp/samples/42.yml')) == dedent(
"""
name:
"""
)


def describe_defaults():
def with_custom_values(expect):
Expand Down

0 comments on commit 342e4c5

Please sign in to comment.