8000 Added `include_default_values` parameter to `to_dict` function. · enixdark/python-betterproto@c0170f4 · GitHub
[go: up one dir, main page]

Skip to content

Commit c0170f4

Browse files
committed
Added include_default_values parameter to to_dict function.
1 parent f9c351a commit c0170f4

File tree

2 files changed

+108
-10
lines changed

2 files changed

+108
-10
lines changed

betterproto/__init__.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -700,11 +700,16 @@ def parse(self: T, data: bytes) -> T:
700700
def FromString(cls: Type[T], data: bytes) -> T:
701701
return cls().parse(data)
702702

703-
def to_dict(self, casing: Casing = Casing.CAMEL) -> dict:
703+
def to_dict(self, casing: Casing = Casing.CAMEL, include_default_values: bool = False) -> dict:
704704
"""
705705
Returns a dict representation of this message instance which can be
706706
used to serialize to e.g. JSON. Defaults to camel casing for
707707
compatibility but can be set to other modes.
708+
709+
`include_default_values` can be set to `True` to include default
710+
values of fields. E.g. an `int32` type field with `0` value will
711+
not be in returned dict if `include_default_values` is set to
712+
`False`.
708713
"""
709714
output: Dict[str, Any] = {}
710715
for field in dataclasses.fields(self):
@@ -713,28 +718,29 @@ def to_dict(self, casing: Casing = Casing.CAMEL) -> dict:
713718
cased_name = casing(field.name).rstrip("_") # type: ignore
714719
if meta.proto_type == "message":
715720
if isinstance(v, datetime):
716-
if v != DATETIME_ZERO:
721+
if v != DATETIME_ZERO or include_default_values:
717722
output[cased_name] = _Timestamp.timestamp_to_json(v)
718723
elif isinstance(v, timedelta):
719-
if v != timedelta(0):
724+
if v != timedelta(0) or include_default_values:
720725
output[cased_name] = _Duration.delta_to_json(v)
721726
elif meta.wraps:
722-
if v is not None:
727+
if v is not None or include_default_values:
723728
output[cased_name] = v
724729
elif isinstance(v, list):
725730
# Convert each item.
726-
v = [i.to_dict(casing) for i in v]
731+
v = [i.to_dict(casing, include_default_values) for i in v]
727732
output[cased_name] = v
728-
elif v._serialized_on_wire:
729-
output[cased_name] = v.to_dict(casing)
733+
else:
734+
if v._serialized_on_wire or include_default_values:
735+
output[cased_name] = v.to_dict(casing, include_default_values)
730736
elif meta.proto_type == "map":
731737
for k in v:
732738
if hasattr(v[k], "to_dict"):
733-
v[k] = v[k].to_dict(casing)
739+
v[k] = v[k].to_dict(casing, include_default_values)
734740

735-
if v:
741+
if v or include_default_values:
736742
output[cased_name] = v
737-
elif v != self._get_field_default(field, meta):
743+
elif v != self._get_field_default(field, meta) or include_default_values:
738744
if meta.proto_type in INT_64_TYPES:
739745
if isinstance(v, list):
740746
output[cased_name] = [str(n) for n in v]

betterproto/tests/test_features.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,95 @@ class Request(betterproto.Message):
162162
# Differentiate between not passed and the zero-value.
163163
assert Request().parse(b"").flag == None
164164
assert Request().parse(b"\n\x00").flag == False
165+
166+
167+
def test_to_dict_default_values():
168+
@dataclass
169+
class TestMessage(betterproto.Message):
170+
some_int: int = betterproto.int32_field(1)
171+
some_double: float = betterproto.double_field(2)
172+
some_str: str = betterproto.string_field(3)
173+
some_bool: bool = betterproto.bool_field(4)
174+
175+
# Empty dict
176+
test = TestMessage().from_dict({})
177+
178+
assert test.to_dict(include_default_values=True) == {
179+
'someInt': 0,
180+
'someDouble': 0.0,
181+
'someStr': '',
182+
'someBool': False
183+
}
184+
185+
# All default values
186+
test = TestMessage().from_dict({
187+
'someInt': 0,
188+
'someDouble': 0.0,
189+
'someStr': '',
190+
'someBool': False
191+
})
192+
193+
assert test.to_dict(include_default_values=True) == {
194+
'someInt': 0,
195+
'someDouble': 0.0,
196+
'someStr': '',
197+
'someBool': False
198+
}
199+
200+
# Some default and some other values
201+
@dataclass
202+
class TestMessage2(betterproto.Message):
203+
some_int: int = betterproto.int32_field(1)
204+
some_double: float = betterproto.double_field(2)
205+
some_str: str = betterproto.string_field(3)
206+
some_bool: bool = betterproto.bool_field(4)
207+
some_default_int: int = betterproto.int32_field(5)
208+
some_default_double: float = betterproto.double_field(6)
209+
some_default_str: str = betterproto.string_field(7)
210+
some_default_bool: bool = betterproto.bool_field(8)
211+
212+
test = TestMessage2().from_dict({
213+
'someInt': 2,
214+
'someDouble': 1.2,
215+
'someStr': 'hello',
216+
'someBool': True,
217+
'someDefaultInt': 0,
218+
'someDefaultDouble': 0.0,
219+
'someDefaultStr': '',
220+
'someDefaultBool': False
221+
})
222+
223+
assert test.to_dict(include_default_values=True) == {
224+
'someInt': 2,
225+
'someDouble': 1.2,
226+
'someStr': 'hello',
227+
'someBool': True,
228+
'someDefaultInt': 0,
229+
'someDefaultDouble': 0.0,
230+
'someDefaultStr': '',
231+
'someDefaultBool': False
232+
}
233+
234+
# Nested messages
235+
@dataclass
236+
class TestChildMessage(betterproto.Message):
237+
some_other_int: int = betterproto.int32_field(1)
238+
239+
@dataclass
240+
class TestParentMessage(betterproto.Message):
241+
some_int: int = betterproto.int32_field(1)
242+
some_double: float = betterproto.double_field(2)
243+
some_message: TestChildMessage = betterproto.message_field(3)
244+
245+
test = TestParentMessage().from_dict({
246+
'someInt': 0,
247+
'someDouble': 1.2,
248+
})
249+
250+
assert test.to_dict(include_default_values=True) == {
251+
'someInt': 0,
252+
'someDouble': 1.2,
253+
'someMessage': {
254+
'someOtherInt': 0
255+
}
256+
}

0 commit comments

Comments
 (0)
0