Skip to content

Commit

Permalink
Fix embedded VO's to_dict output
Browse files Browse the repository at this point in the history
`to_dict` of embedded VO was not extracting dict-appropriate
values from enclosed fields. This commit fixes the issue and
adds relevenat test cases.
  • Loading branch information
subhashb committed Oct 2, 2021
1 parent 291eef2 commit 90ab253
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 8 deletions.
4 changes: 3 additions & 1 deletion src/protean/core/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,9 @@ def to_dict(self):
getattr(self, field_name, None)
)
elif isinstance(field_obj, ValueObject):
field_values.update(field_obj.as_dict(getattr(self, field_name, None)))
value = field_obj.as_dict(getattr(self, field_name, None))
if value:
field_values.update(value)

return field_values

Expand Down
20 changes: 13 additions & 7 deletions src/protean/fields/embedded.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
class _ShadowField(Field):
"""Shadow Attribute Field to back Value Object Fields"""

def __init__(self, owner, field_name, field_type, **kwargs):
def __init__(self, owner, field_name, field_obj, **kwargs):
"""Preserve link to owner, and original field type for later reference"""
super().__init__(**kwargs)

self.owner = owner
self.field_name = field_name
self.field_type = field_type
self.field_obj = field_obj

def __set__(self, instance, value):
"""Override `__set__` to update owner field and silently fail to update values.
Expand Down Expand Up @@ -52,7 +52,7 @@ def __init__(self, value_object_cls, *args, **kwargs):
self.embedded_fields[field_name] = _ShadowField(
self,
field_name,
field_obj.__class__,
field_obj,
# FIXME Pass all other kwargs here
# Because we want the shadow field to mimic the behavior of the actual field
# Which means that ShadowField somehow has to become an Integer, Float, String, etc.
Expand Down Expand Up @@ -102,10 +102,16 @@ def _cast_to_type(self, value):

def as_dict(self, value):
"""Return JSON-compatible value of self"""
return {
field_obj.attribute_name: getattr(value, field_name)
for field_name, field_obj in self.embedded_fields.items()
}
return (
{
shadow_field_obj.attribute_name: shadow_field_obj.field_obj.as_dict(
getattr(value, field_name, None)
)
for field_name, shadow_field_obj in self.embedded_fields.items()
}
if value
else None
)

def __set__(self, instance, value):
"""Override `__set__` to coordinate between value object and its embedded fields"""
Expand Down
50 changes: 50 additions & 0 deletions tests/value_object/test_to_dict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from datetime import datetime
from protean.core.value_object import BaseValueObject

from protean.fields.basic import DateTime, String
from protean.fields.embedded import ValueObject
from protean import BaseAggregate


class SimpleVO(BaseValueObject):
foo = String()
bar = String()


class VOWithDateTime(BaseValueObject):
foo = String()
now = DateTime()


class SimpleVOEntity(BaseAggregate):
vo = ValueObject(SimpleVO)


class EntityWithDateTimeVO(BaseAggregate):
vo = ValueObject(VOWithDateTime)


class TestAsDict:
def test_empty_simple_vo(self):
simple = SimpleVOEntity(id=12)
assert simple.to_dict() == {"id": 12}

def test_simple_vo_dict(self):
vo = SimpleVO(foo="foo", bar="bar")
assert vo.to_dict() == {"foo": "foo", "bar": "bar"}

def test_embedded_simple_vo(self):
vo = SimpleVO(foo="foo", bar="bar")
simple = SimpleVOEntity(id=12, vo=vo)
assert simple.to_dict() == {"id": 12, "vo_foo": "foo", "vo_bar": "bar"}

def test_datetime_vo_dict(self):
now = datetime.utcnow()
vo = VOWithDateTime(foo="foo", now=now)
assert vo.to_dict() == {"foo": "foo", "now": str(now)}

def test_embedded_datetime_vo(self):
now = datetime.utcnow()
vo = VOWithDateTime(foo="foo", now=now)
simple = EntityWithDateTimeVO(id=12, vo=vo)
assert simple.to_dict() == {"id": 12, "vo_foo": "foo", "vo_now": str(now)}

0 comments on commit 90ab253

Please sign in to comment.