danielgtaylor / python-betterproto

Clean, modern, Python 3.6+ code generator & library for Protobuf 3 and async gRPC
MIT License
1.54k stars 216 forks source link

`to_dict` or `to_pydict` miss integer and boolean fields #490

Open alirezasafi opened 1 year ago

alirezasafi commented 1 year ago

when i use to_dict or to_pydict function to serialize message, False and 0 values are not work for boolean and integer fields. here is the sample code:

from dataclasses import dataclass

import betterproto

@dataclass
class Greeting(betterproto.Message):
    """Greeting represents a message you can tell a user."""

    int_field: int = betterproto.int32_field(1)
    bool_field: bool = betterproto.bool_field(2)

if __name__ == "__main__":
    greeting = Greeting(
        int_field=0,
        bool_field=False
    )
    print(greeting.to_pytdict())
    greeting.int_field = 1
    greeting.bool_field = True
    print(greeting.to_pydict())

and output is:

{}
{'intField': 1, 'boolField': True}

python version: 3.8 betterproto version: 2.0.0b5

alirezasafi commented 1 year ago

we could change to_pydict to this:

    def to_pydict(
        self, casing: Casing = Casing.CAMEL, include_default_values: bool = False
    ) -> Dict[str, Any]:
        """
        Returns a python dict representation of this object.

        Parameters
        -----------
        casing: :class:`Casing`
            The casing to use for key values. Default is :attr:`Casing.CAMEL` for
            compatibility purposes.
        include_default_values: :class:`bool`
            If ``True`` will include the default values of fields. Default is ``False``.
            E.g. an ``int32`` field will be included with a value of ``0`` if this is
            set to ``True``, otherwise this would be ignored.

        Returns
        --------
        Dict[:class:`str`, Any]
            The python dict representation of this object.
        """
        output: Dict[str, Any] = {}
        defaults = self._betterproto.default_gen
        for field_name, meta in self._betterproto.meta_by_field_name.items():
            field_is_repeated = defaults[field_name] is list
            if self.__raw_get(field_name) is PLACEHOLDER and \
                not include_default_values:
                continue
            value = getattr(self, field_name)
            cased_name = casing(field_name).rstrip("_")  # type: ignore
            if meta.proto_type == TYPE_MESSAGE:
                if isinstance(value, datetime):
                    if (
                        value != DATETIME_ZERO
                        or include_default_values
                        or self._include_default_value_for_oneof(
                            field_name=field_name, meta=meta
                        )
                    ):
                        output[cased_name] = value
                elif isinstance(value, timedelta):
                    if (
                        value != timedelta(0)
                        or include_default_values
                        or self._include_default_value_for_oneof(
                            field_name=field_name, meta=meta
                        )
                    ):
                        output[cased_name] = value
                elif meta.wraps:
                    if value is not None or include_default_values:
                        output[cased_name] = value
                elif field_is_repeated:
                    # Convert each item.
                    value = [i.to_pydict(casing, include_default_values) for i in value]
                    if value or include_default_values:
                        output[cased_name] = value
                elif (
                    value._serialized_on_wire
                    or include_default_values
                    or self._include_default_value_for_oneof(
                        field_name=field_name, meta=meta
                    )
                ):
                    output[cased_name] = value.to_pydict(casing, include_default_values)
            elif meta.proto_type == TYPE_MAP:
                for k in value:
                    if hasattr(value[k], "to_pydict"):
                        value[k] = value[k].to_pydict(casing, include_default_values)

                if value or include_default_values:
                    output[cased_name] = value
            else
                output[cased_name] = value
        return output
Gobot1234 commented 1 year ago

Coming back to this issue, why can't you use include_default_values=True?

alirezasafi commented 1 year ago

because i don't wanna set default value for all the unset fields.