Skip to content
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

Version 5.2.0 - Support for Structs #5

Merged
merged 1 commit into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [5.2.0] - 2024-09-23

### Added

- Support for the `google.protobuf.Struct` message



## [5.0.0] - 2024-04-15

Expand Down
2 changes: 1 addition & 1 deletion protoplasm/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = '5.0.1'
__version__ = '5.2.0'

__author__ = 'Thordur Matthiasson <[email protected]>'
__license__ = 'MIT License'
Expand Down
19 changes: 19 additions & 0 deletions protoplasm/casting/dictators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
'LongDictator',
'DurationDictator',
'AnyDictator',
'StructDictator',
]
import datetime
import base64
Expand Down Expand Up @@ -309,3 +310,21 @@ def from_dict_value(cls, proto_value: collections.OrderedDict,
dc_cls = castutils.import_dataclass_by_proto(proto_class)

return dict_to_dataclass(dc_cls, proto_value)


class StructDictator:
@classmethod
def to_dict_value(cls, dc_value: Dict[str, Any],
field: dataclasses.Field, parent: DataclassBase) -> Dict[str, Any]:
if dc_value is None:
return {}

return dc_value

@classmethod
def from_dict_value(cls, proto_value: Dict[str, Any],
field: dataclasses.Field, parent_type: Type[DataclassBase]) -> Optional[Dict[str, Any]]:
if proto_value is None:
return {}

return proto_value
2 changes: 1 addition & 1 deletion requirements-unittesting.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
neobuilder >=5.0, <6
neobuilder >=5.2.0-rc.1, <6
8 changes: 4 additions & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
ccptools >= 1.1, <2

protobuf >=4.25.3, <5
grpcio >=1.62.1, <2
grpcio-tools >=1.62.1, <2
googleapis-common-protos >=1.63.0, <2
protobuf >=5.28.2, <6
grpcio >=1.66.1, <2
grpcio-tools >=1.66.1, <2
googleapis-common-protos >=1.65, <2
10 changes: 10 additions & 0 deletions tests/res/proto/sandbox/test/googlestruct.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
syntax = "proto3";

package sandbox.test;

import "google/protobuf/struct.proto";


message StructMessage {
google.protobuf.Struct my_struct = 1;
}
60 changes: 59 additions & 1 deletion tests/test_dictator.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
BUILD_ROOT = os.path.join(HERE, 'res', 'build')



class ProtoToDictTest(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
# Remove old stuff...

build_package = os.path.join(BUILD_ROOT, 'sandbox')
if os.path.exists(build_package):
shutil.rmtree(build_package)
Expand Down Expand Up @@ -215,6 +215,36 @@ def test_proto_enums_to_dict(self):

self.assertEqual(expected2, dictator.proto_to_dict(p2))

def test_proto_struct_to_dict(self):
from sandbox.test import googlestruct_pb2

expected_a = {
'my_bool': True,
'my_float': 4.2,
'my_null': None,
'my_dict': {'foo': 'bar', 'you': 'tube'},
'my_list': [1.0, 3.0, 5.0, 8.0],
'my_string': 'I am String, hear me spell!',
'my_int': 42.0
}

expected_b = {
'my_struct': expected_a
}

proto_struct = googlestruct_pb2.StructMessage()
proto_struct.my_struct['my_string'] = 'I am String, hear me spell!'
proto_struct.my_struct['my_int'] = 42
proto_struct.my_struct['my_float'] = 4.2
proto_struct.my_struct['my_null'] = None
proto_struct.my_struct['my_bool'] = True
proto_struct.my_struct['my_list'] = [1, 3, 5, 8]
proto_struct.my_struct['my_dict'] = {'foo': 'bar', 'you': 'tube'}

self.assertEqual(expected_a, dictator.proto_to_dict(proto_struct.my_struct))

self.assertEqual(expected_b, dictator.proto_to_dict(proto_struct))


class DataclassToDictTest(unittest.TestCase):
@classmethod
Expand Down Expand Up @@ -423,3 +453,31 @@ def test_dataclass_enums_to_dict(self):
'default': 0}}

self.assertEqual(expected2, dictator.dataclass_to_dict(dc2))

def test_proto_struct_to_dict(self):
from sandbox.test.googlestruct_dc import StructMessage
expected_a = {
'my_bool': True,
'my_float': 4.2,
'my_null': None,
'my_dict': {'foo': 'bar', 'you': 'tube'},
'my_list': [1.0, 3.0, 5.0, 8.0],
'my_string': 'I am String, hear me spell!',
'my_int': 42.0
}

expected_b = {
'my_struct': expected_a
}

struct_dc = StructMessage(my_struct={
'my_bool': True,
'my_float': 4.2,
'my_null': None,
'my_dict': {'foo': 'bar', 'you': 'tube'},
'my_list': [1.0, 3.0, 5.0, 8.0],
'my_string': 'I am String, hear me spell!',
'my_int': 42.0
})

self.assertEqual(expected_b, dictator.dataclass_to_dict(struct_dc))
53 changes: 53 additions & 0 deletions tests/test_objectifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -663,3 +663,56 @@ def test_enum_dict_to_dataclass(self):
'default': 0}}

self.assertEqual(p_expect2, objectifier.dict_to_dataclass(enums_dc.WithInternalEnum, dict_data2))

def test_struct_dict_to_proto(self):
from sandbox.test import googlestruct_pb2

the_dict = {
'my_struct': {
'my_bool': True,
'my_float': 4.2,
'my_null': None,
'my_dict': {'foo': 'bar', 'you': 'tube'},
'my_list': [1.0, 3.0, 5.0, 8.0],
'my_string': 'I am String, hear me spell!',
'my_int': 42.0
}
}

expected_pb = googlestruct_pb2.StructMessage()
expected_pb.my_struct['my_string'] = 'I am String, hear me spell!'
expected_pb.my_struct['my_int'] = 42
expected_pb.my_struct['my_float'] = 4.2
expected_pb.my_struct['my_null'] = None
expected_pb.my_struct['my_bool'] = True
expected_pb.my_struct['my_list'] = [1, 3, 5, 8]
expected_pb.my_struct['my_dict'] = {'foo': 'bar', 'you': 'tube'}

self.assertEqual(expected_pb, objectifier.dict_to_proto(googlestruct_pb2.StructMessage, the_dict))

def test_struct_dict_to_dataclass(self):
from sandbox.test.googlestruct_dc import StructMessage

the_dict = {
'my_struct': {
'my_bool': True,
'my_float': 4.2,
'my_null': None,
'my_dict': {'foo': 'bar', 'you': 'tube'},
'my_list': [1.0, 3.0, 5.0, 8.0],
'my_string': 'I am String, hear me spell!',
'my_int': 42.0
}
}

struct_dc = StructMessage(my_struct={
'my_bool': True,
'my_float': 4.2,
'my_null': None,
'my_dict': {'foo': 'bar', 'you': 'tube'},
'my_list': [1.0, 3.0, 5.0, 8.0],
'my_string': 'I am String, hear me spell!',
'my_int': 42.0
})

self.assertEqual(struct_dc, objectifier.dict_to_dataclass(StructMessage, the_dict))
Loading