-
Notifications
You must be signed in to change notification settings - Fork 2
Add back struct support #102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
AdrienVannson
wants to merge
1
commit into
main
Choose a base branch
from
struct-support
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Is this related to danielgtaylor/python-betterproto#599? I'd love if Struct were smarter :D. Right now we do this: def struct_from_dict(d: dict[str, Any]):
"""
struct_from_dict converts the given dictionary into a proto Struct.
This is a sadly needed because of https://github.com/danielgtaylor/python-betterproto/issues/599.
"""
s = LegacyStruct()
s.update(d)
return Struct.FromString(s.SerializeToString())
def struct_to_dict(s: Struct) -> dict[str, Any]:
"""struct_to_dict converts a proto Struct into a python dictionary."""
output: dict[str, Any] = {}
for key, value in s.fields.items():
ret = value_to_py(value)
output[key] = ret
return output
def value_to_py(proto_value: Value, include_default_values: bool = False) -> Any | None:
"""value_to_py converts a proto Value to a native python value."""
field_name, actual_value = betterproto2.which_one_of(proto_value, "kind")
match field_name:
case "null_value" | "number_value" | "string_value" | "bool_value":
return actual_value
case "struct_value":
return struct_to_dict(typing.cast(Struct, actual_value))
case "list_value":
return [value_to_py(list_val) for list_val in typing.cast(ListValue, actual_value).values]
case _:
return None |
I am currently monkey patching the original betterproto to circumvent the bug I report I opened danielgtaylor/python-betterproto#599 """
Monkey patch betterproto to have Struct behave as expected.
In particular, the from_dict and to_dict methods of betterproto.lib.google.protobuf.Struct
do not work as expected, as they expect the values to be messages of type Value, while
protobuf Struct behaves as if the values are inlined in the struct.
After calling monkeypatch_betterproto_struct(), you can use from_dict and to_dict as expected.
Example:
>>> monkeypatch_betterproto_struct() # this is automatically done if you import amp.kit (or any module that imports it)
>>> from betterproto.lib.google.protobuf import Struct, Value
>>> import json
>>> s = Struct.from_dict({"a": 42, "b": "hello"})
>>> s.fields["b"]
Value(string_value='hello')
>>> json.loads(s.to_json()) == {"a": 42, "b": "hello"}
True
"""
from typing import Any, Mapping
import betterproto.lib.google.protobuf
from betterproto import Casing
from betterproto.lib.google.protobuf import Struct
from google.protobuf.json_format import MessageToDict as pb_MessageToDict
from google.protobuf.struct_pb2 import Struct as pb_Struct
def monkeypatch_betterproto_struct():
"""Monkey patch betterproto to have Struct behave"""
def struct_to_dict_method(
self: Struct,
casing: Casing = Casing.CAMEL, # type: ignore The Casing.CAMEL is not a real enum value...
include_default_values: bool = False,
) -> dict[str, Any]:
# Protobuf Struct special case things, in particular it behaves as if values are inlined
# in the struct, while betterproto sticks to the .proto definition of Struct,
# that is values in the dict have to be messages of type Value.
# So to_dict and to_json (which is just json_dumps(to_dict(()) do not work as expected.
#
# Fix: we create the corresponding proto struct, then use proto tool to get the dict representation with inlined values.
s = pb_Struct()
s.ParseFromString(self.SerializeToString())
return pb_MessageToDict(s, preserving_proto_field_name=True)
# Monkey patch betterproto Struct to_dict and to_pydict (they are the same for Struct, no custom types expected)
betterproto.lib.google.protobuf.Struct.to_dict = struct_to_dict_method
betterproto.lib.google.protobuf.Struct.to_pydict = struct_to_dict_method
def struct_from_dict_method(self: Struct, value: Mapping[str, Any]) -> Struct:
# Same reasoning as to_dict above.
# Issue: from_dict and from_json (which is just from_dict(json.loads()) do not work as expected (values not inlined).
# Fix: we create the struct using protobuf, serialize it and then parse it with betterproto.
s = pb_Struct()
s.update(value)
return self.FromString(s.SerializeToString())
# Betterproto uses hybridmethod for from_dict, i.e. it works as class method and instance method.
# so we have to create the hybridmethod correspondingly.
from betterproto.utils import hybridmethod
def struct_from_dict_classmethod(
cls: type[Struct], value: Mapping[str, Any]
) -> Struct:
return struct_from_dict_method(cls(), value)
from_dict = hybridmethod(struct_from_dict_classmethod)
from_dict.instance_func = struct_from_dict_method
betterproto.lib.google.protobuf.Struct.from_dict = from_dict |
My monkey patch is "working" but does not abide by a bunch of things like casing, etc. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
No description provided.