betterproto: support Struct and Value (#551)

* betterproto: support `Struct` and `Value`

Signed-off-by: William Woodruff <william@trailofbits.com>

* betterproto: handle struct in to_dict as well

Signed-off-by: William Woodruff <william@trailofbits.com>

* tests: add Struct roundtrip tests

Signed-off-by: William Woodruff <william@trailofbits.com>

* specialize from_dict and to_dict on Struct

...rather than special-casing in the Message ABC.

Signed-off-by: William Woodruff <william@trailofbits.com>

* betterproto: `poe format`

Signed-off-by: William Woodruff <william@trailofbits.com>

* Update src/betterproto/__init__.py

Co-authored-by: James Hilton-Balfe <gobot1234yt@gmail.com>

* remove future annotations

Signed-off-by: William Woodruff <william@trailofbits.com>

* replace type[...] with typing.T

Signed-off-by: William Woodruff <william@trailofbits.com>

* quote instead

Signed-off-by: William Woodruff <william@trailofbits.com>

---------

Signed-off-by: William Woodruff <william@trailofbits.com>
Co-authored-by: James Hilton-Balfe <gobot1234yt@gmail.com>
This commit is contained in:
William Woodruff 2024-01-02 15:16:15 -05:00 committed by GitHub
parent ce5093eec0
commit 5666393f9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 55 additions and 0 deletions

View File

@ -2,14 +2,19 @@
# sources: google/protobuf/any.proto, google/protobuf/api.proto, google/protobuf/descriptor.proto, google/protobuf/duration.proto, google/protobuf/empty.proto, google/protobuf/field_mask.proto, google/protobuf/source_context.proto, google/protobuf/struct.proto, google/protobuf/timestamp.proto, google/protobuf/type.proto, google/protobuf/wrappers.proto
# plugin: python-betterproto
# This file has been @generated
import warnings
from dataclasses import dataclass
from typing import (
Dict,
List,
Mapping,
)
from typing_extensions import Self
import betterproto
from betterproto.utils import hybridmethod
class Syntax(betterproto.Enum):
@ -1458,6 +1463,32 @@ class Struct(betterproto.Message):
)
"""Unordered map of dynamically typed values."""
@hybridmethod
def from_dict(cls: "type[Self]", value: Mapping[str, Any]) -> Self: # type: ignore
self = cls()
return self.from_dict(value)
@from_dict.instancemethod
def from_dict(self, value: Mapping[str, Any]) -> Self:
fields = {**value}
for k in fields:
if hasattr(fields[k], "from_dict"):
fields[k] = fields[k].from_dict()
self.fields = fields
return self
def to_dict(
self,
casing: betterproto.Casing = betterproto.Casing.CAMEL,
include_default_values: bool = False,
) -> Dict[str, Any]:
output = {**self.fields}
for k in self.fields:
if hasattr(self.fields[k], "to_dict"):
output[k] = self.fields[k].to_dict(casing, include_default_values)
return output
@dataclass(eq=False, repr=False)
class Value(betterproto.Message):

24
tests/test_struct.py Normal file
View File

@ -0,0 +1,24 @@
import json
from betterproto.lib.google.protobuf import Struct
def test_struct_roundtrip():
data = {
"foo": "bar",
"baz": None,
"quux": 123,
"zap": [1, {"two": 3}, "four"],
}
data_json = json.dumps(data)
struct_from_dict = Struct().from_dict(data)
assert struct_from_dict.fields == data
assert struct_from_dict.to_dict() == data
assert struct_from_dict.to_json() == data_json
struct_from_json = Struct().from_json(data_json)
assert struct_from_json.fields == data
assert struct_from_json.to_dict() == data
assert struct_from_json == struct_from_dict
assert struct_from_json.to_json() == data_json