From 0c69a0f5142003eb52d6570d4f6b5d08119d13de Mon Sep 17 00:00:00 2001 From: ccp_zeulix Date: Mon, 23 Sep 2024 13:08:05 +0000 Subject: [PATCH] Version 5.2.0 - Support for Structs ### Added - Support for the `google.protobuf.Struct` message --- CHANGELOG.md | 7 +++ protoplasm/__init__.py | 2 +- protoplasm/casting/dictators/__init__.py | 19 ++++++ requirements-unittesting.txt | 2 +- requirements.txt | 8 +-- .../res/proto/sandbox/test/googlestruct.proto | 10 ++++ tests/test_dictator.py | 60 ++++++++++++++++++- tests/test_objectifier.py | 53 ++++++++++++++++ 8 files changed, 154 insertions(+), 7 deletions(-) create mode 100644 tests/res/proto/sandbox/test/googlestruct.proto diff --git a/CHANGELOG.md b/CHANGELOG.md index bff8c02..2d96de4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/protoplasm/__init__.py b/protoplasm/__init__.py index 73e3d2f..34231bf 100644 --- a/protoplasm/__init__.py +++ b/protoplasm/__init__.py @@ -1,4 +1,4 @@ -__version__ = '5.0.1' +__version__ = '5.2.0' __author__ = 'Thordur Matthiasson ' __license__ = 'MIT License' diff --git a/protoplasm/casting/dictators/__init__.py b/protoplasm/casting/dictators/__init__.py index b3b5ece..4897595 100644 --- a/protoplasm/casting/dictators/__init__.py +++ b/protoplasm/casting/dictators/__init__.py @@ -6,6 +6,7 @@ 'LongDictator', 'DurationDictator', 'AnyDictator', + 'StructDictator', ] import datetime import base64 @@ -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 diff --git a/requirements-unittesting.txt b/requirements-unittesting.txt index 2e7377b..143b725 100644 --- a/requirements-unittesting.txt +++ b/requirements-unittesting.txt @@ -1 +1 @@ -neobuilder >=5.0, <6 +neobuilder >=5.2.0-rc.1, <6 diff --git a/requirements.txt b/requirements.txt index f874001..c10b505 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/tests/res/proto/sandbox/test/googlestruct.proto b/tests/res/proto/sandbox/test/googlestruct.proto new file mode 100644 index 0000000..4830578 --- /dev/null +++ b/tests/res/proto/sandbox/test/googlestruct.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package sandbox.test; + +import "google/protobuf/struct.proto"; + + +message StructMessage { + google.protobuf.Struct my_struct = 1; +} diff --git a/tests/test_dictator.py b/tests/test_dictator.py index c4c3318..0bf0ffe 100644 --- a/tests/test_dictator.py +++ b/tests/test_dictator.py @@ -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) @@ -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 @@ -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)) diff --git a/tests/test_objectifier.py b/tests/test_objectifier.py index 1bc94a5..96e35d5 100644 --- a/tests/test_objectifier.py +++ b/tests/test_objectifier.py @@ -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))