--- a/setup.py +++ b/setup.py @@ -42,10 +42,10 @@ long_description=README, platforms="Posix; MacOS X", include_package_data=True, - install_requires=("protobuf >= 3.19.0, <5.0.0dev",), + install_requires=("protobuf >= 3.19.0, <6.0.0dev",), extras_require={ "testing": [ - "google-api-core[grpc] >= 1.31.5", + "google-api-core >= 1.31.5", ], }, python_requires=">=3.6", From 4cedebc7a4d6f9f3ee4b617458cf2bd4c4376e78 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 13 Feb 2024 23:14:51 +0000 Subject: [PATCH 02/16] add compatibility with protobuf 5.x diff --git a/proto/message.py b/proto/message.py index 7232d42f..048ca665 100644 --- a/proto/message.py +++ b/proto/message.py @@ -18,6 +18,7 @@ import re from typing import List, Type +import google.protobuf from google.protobuf import descriptor_pb2 from google.protobuf import message from google.protobuf.json_format import MessageToDict, MessageToJson, Parse @@ -32,6 +33,8 @@ from proto.utils import has_upb +PROTOBUF_VERSION = google.protobuf.__version__ + _upb = has_upb() # Important to cache result here. @@ -379,6 +382,7 @@ def to_json( sort_keys=False, indent=2, float_precision=None, + always_print_fields_with_no_presence=True, ) -> str: """Given a message instance, serialize it to json @@ -398,18 +402,43 @@ def to_json( Pass None for the most compact representation without newlines. float_precision (Optional(int)): If set, use this to specify float field valid digits. Default is None. + always_print_fields_with_no_presence (Optional(bool)): If True, fields without + presence (implicit presence scalars, repeated fields, and map fields) will + always be serialized. Any field that supports presence is not affected by + this option (including singular message fields and oneof fields). If + `including_default_value_fields` is set to False, this option has no effect. Returns: str: The json string representation of the protocol buffer. """ - return MessageToJson( - cls.pb(instance), - use_integers_for_enums=use_integers_for_enums, - including_default_value_fields=including_default_value_fields, - preserving_proto_field_name=preserving_proto_field_name, - sort_keys=sort_keys, - indent=indent, - float_precision=float_precision, - ) + # For backwards compatibility of this breaking change in protobuf 5.x which is specific to proto2 + # https://github.com/protocolbuffers/protobuf/commit/26995798757fbfef5cf6648610848e389db1fecf + if PROTOBUF_VERSION[0] in ("3", "4"): + return MessageToJson( + cls.pb(instance), + use_integers_for_enums=use_integers_for_enums, + including_default_value_fields=including_default_value_fields, + preserving_proto_field_name=preserving_proto_field_name, + sort_keys=sort_keys, + indent=indent, + float_precision=float_precision, + ) + else: + # The `including_default_value_fields` argument was removed from protobuf 5.x + # and replaced with `always_print_fields_with_no_presence` which very similar but has + # consistent handling of optional fields by not affecting them. + # The old flag accidentally had inconsistent behavior between proto2 optional and proto3 optional fields. + return MessageToJson( + cls.pb(instance), + use_integers_for_enums=use_integers_for_enums, + always_print_fields_with_no_presence=( + including_default_value_fields + and always_print_fields_with_no_presence + ), + preserving_proto_field_name=preserving_proto_field_name, + sort_keys=sort_keys, + indent=indent, + float_precision=float_precision, + ) def from_json(cls, payload, *, ignore_unknown_fields=False) -> "Message": """Given a json string representing an instance, @@ -436,6 +465,7 @@ def to_dict( preserving_proto_field_name=True, including_default_value_fields=True, float_precision=None, + always_print_fields_with_no_presence=True, ) -> "Message": """Given a message instance, return its representation as a python dict. @@ -448,24 +478,48 @@ def to_dict( preserving_proto_field_name (Optional(bool)): An option that determines whether field name representations preserve proto case (snake_case) or use lowerCamelCase. Default is True. - including_default_value_fields (Optional(bool)): An option that + including_default_value_fields (Optional(bool)): Deprecated. Use argument + `always_print_fields_with_no_presence` instead. An option that determines whether the default field values should be included in the results. Default is True. float_precision (Optional(int)): If set, use this to specify float field valid digits. Default is None. + always_print_fields_with_no_presence (Optional(bool)): If True, fields without + presence (implicit presence scalars, repeated fields, and map fields) will + always be serialized. Any field that supports presence is not affected by + this option (including singular message fields and oneof fields). If + `including_default_value_fields` is set to False, this option has no effect. Returns: dict: A representation of the protocol buffer using pythonic data structures. Messages and map fields are represented as dicts, repeated fields are represented as lists. """ - return MessageToDict( - cls.pb(instance), - including_default_value_fields=including_default_value_fields, - preserving_proto_field_name=preserving_proto_field_name, - use_integers_for_enums=use_integers_for_enums, - float_precision=float_precision, - ) + # For backwards compatibility of this breaking change in protobuf 5.x which is specific to proto2 + # https://github.com/protocolbuffers/protobuf/commit/26995798757fbfef5cf6648610848e389db1fecf + if PROTOBUF_VERSION[0] in ("3", "4"): + return MessageToDict( + cls.pb(instance), + including_default_value_fields=including_default_value_fields, + preserving_proto_field_name=preserving_proto_field_name, + use_integers_for_enums=use_integers_for_enums, + float_precision=float_precision, + ) + else: + # The `including_default_value_fields` argument was removed from protobuf 5.x + # and replaced with `always_print_fields_with_no_presence` which very similar but has + # consistent handling of optional fields by not affecting them. + # The old flag accidentally had inconsistent behavior between proto2 optional and proto3 optional fields + return MessageToDict( + cls.pb(instance), + always_print_fields_with_no_presence=( + including_default_value_fields + and always_print_fields_with_no_presence + ), + preserving_proto_field_name=preserving_proto_field_name, + use_integers_for_enums=use_integers_for_enums, + float_precision=float_precision, + ) def copy_from(cls, instance, other): """Equivalent for protobuf.Message.CopyFrom From d40dcbb36baebb9f8dbfb9d1220da23e2654e67b Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 14 Feb 2024 15:07:01 +0000 Subject: [PATCH 03/16] fix RecursionError: maximum recursion depth exceeded while calling a Python object in tests --- tests/conftest.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 765326c6..252ac30c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -67,6 +67,14 @@ def pytest_runtest_setup(item): for name in dir(item.module): if name.endswith("_pb2") and not name.startswith("test_"): module = getattr(item.module, name) + + # Exclude `google.protobuf.descriptor_pb2` which causes error + # `RecursionError: maximum recursion depth exceeded while calling a Python object` + # when running the test suite and is not required for tests. + # See https://github.com/googleapis/proto-plus-python/issues/425 + if module.__package__ == "google.protobuf" and name == "descriptor_pb2": + continue + pool.AddSerializedFile(module.DESCRIPTOR.serialized_pb) fd = pool.FindFileByName(module.DESCRIPTOR.name) From fe352b23eabec34e06fa6a83e39d075d33119ddc Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 14 Feb 2024 15:23:57 +0000 Subject: [PATCH 05/16] update comments --- proto/message.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/proto/message.py b/proto/message.py index 048ca665..768f7020 100644 --- a/proto/message.py +++ b/proto/message.py @@ -392,6 +392,11 @@ def to_json( use_integers_for_enums (Optional(bool)): An option that determines whether enum values should be represented by strings (False) or integers (True). Default is True. + including_default_value_fields (Optional(bool)): Deprecated. Use argument + `always_print_fields_with_no_presence` instead. An option that + determines whether the default field values should be included in the results. + Default is True. If `always_print_fields_with_no_presence` is set to False, this + option has no effect. preserving_proto_field_name (Optional(bool)): An option that determines whether field name representations preserve proto case (snake_case) or use lowerCamelCase. Default is False. @@ -481,7 +486,8 @@ def to_dict( including_default_value_fields (Optional(bool)): Deprecated. Use argument `always_print_fields_with_no_presence` instead. An option that determines whether the default field values should be included in the results. - Default is True. + Default is True. If `always_print_fields_with_no_presence` is set to False, this + option has no effect. float_precision (Optional(int)): If set, use this to specify float field valid digits. Default is None. always_print_fields_with_no_presence (Optional(bool)): If True, fields without From fb5f2483fef3bc946630d8e111f3ee64be8cda5d Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Thu, 25 Apr 2024 14:42:39 +0000 Subject: [PATCH 07/16] See https://github.com/protocolbuffers/protobuf/issues/16596;avoid breaking change --- proto/marshal/compat.py | 9 +++++++-- proto/marshal/marshal.py | 6 +++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/proto/marshal/compat.py b/proto/marshal/compat.py index e10999bb..0c11d705 100644 --- a/proto/marshal/compat.py +++ b/proto/marshal/compat.py @@ -19,6 +19,9 @@ # not be included. from google.protobuf.internal import containers +import google.protobuf + +PROTOBUF_VERSION = google.protobuf.__version__ # Import protobuf 4.xx first and fallback to earlier version # if not present. @@ -36,15 +39,17 @@ repeated_composite_types = (containers.RepeatedCompositeFieldContainer,) repeated_scalar_types = (containers.RepeatedScalarFieldContainer,) map_composite_types = (containers.MessageMap,) +map_composite_types_str = ('MessageMapContainer') if _message: repeated_composite_types += (_message.RepeatedCompositeContainer,) repeated_scalar_types += (_message.RepeatedScalarContainer,) - map_composite_types += (_message.MessageMapContainer,) - + if PROTOBUF_VERSION[0:2] in ["3.", "4."]: + map_composite_types += (_message.MessageMapContainer,) __all__ = ( "repeated_composite_types", "repeated_scalar_types", "map_composite_types", + "map_composite_types_str", ) diff --git a/proto/marshal/marshal.py b/proto/marshal/marshal.py index e9fbbb9f..783c8417 100644 --- a/proto/marshal/marshal.py +++ b/proto/marshal/marshal.py @@ -188,7 +188,11 @@ def to_python(self, proto_type, value, *, absent: bool = None): return Repeated(value, marshal=self) # Same thing for maps of messages. - if value_type in compat.map_composite_types: + # See https://github.com/protocolbuffers/protobuf/issues/16596 + # We need to look up the name of the type in compat.map_composite_types_str + # as class `MessageMapContainer` is no longer exposed + # This is done to avoid taking a breaking change in proto-plus + if value_type in compat.map_composite_types or value_type.__name__ in compat.map_composite_types_str: return MapComposite(value, marshal=self) return self.get_rule(proto_type=proto_type).to_python(value, absent=absent) From 0fff4e5bbc873978650fb432e8d974a3e245fa07 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Thu, 25 Apr 2024 14:43:25 +0000 Subject: [PATCH 08/16] lint --- proto/marshal/compat.py | 2 +- proto/marshal/marshal.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/proto/marshal/compat.py b/proto/marshal/compat.py index 0c11d705..1753629c 100644 --- a/proto/marshal/compat.py +++ b/proto/marshal/compat.py @@ -39,7 +39,7 @@ repeated_composite_types = (containers.RepeatedCompositeFieldContainer,) repeated_scalar_types = (containers.RepeatedScalarFieldContainer,) map_composite_types = (containers.MessageMap,) -map_composite_types_str = ('MessageMapContainer') +map_composite_types_str = ("MessageMapContainer",) if _message: repeated_composite_types += (_message.RepeatedCompositeContainer,) diff --git a/proto/marshal/marshal.py b/proto/marshal/marshal.py index 783c8417..8302a795 100644 --- a/proto/marshal/marshal.py +++ b/proto/marshal/marshal.py @@ -192,7 +192,10 @@ def to_python(self, proto_type, value, *, absent: bool = None): # We need to look up the name of the type in compat.map_composite_types_str # as class `MessageMapContainer` is no longer exposed # This is done to avoid taking a breaking change in proto-plus - if value_type in compat.map_composite_types or value_type.__name__ in compat.map_composite_types_str: + if ( + value_type in compat.map_composite_types + or value_type.__name__ in compat.map_composite_types_str + ): return MapComposite(value, marshal=self) return self.get_rule(proto_type=proto_type).to_python(value, absent=absent) From ac98abe7e8b5b09973ed71efcc598d1791b0bcf0 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Thu, 25 Apr 2024 15:28:18 +0000 Subject: [PATCH 09/16] Address review feedback --- proto/message.py | 63 ++++++++++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/proto/message.py b/proto/message.py index 768f7020..a148b7fb 100644 --- a/proto/message.py +++ b/proto/message.py @@ -377,12 +377,12 @@ def to_json( instance, *, use_integers_for_enums=True, - including_default_value_fields=True, + including_default_value_fields=None, preserving_proto_field_name=False, sort_keys=False, indent=2, float_precision=None, - always_print_fields_with_no_presence=True, + always_print_fields_with_no_presence=None, ) -> str: """Given a message instance, serialize it to json @@ -395,8 +395,8 @@ def to_json( including_default_value_fields (Optional(bool)): Deprecated. Use argument `always_print_fields_with_no_presence` instead. An option that determines whether the default field values should be included in the results. - Default is True. If `always_print_fields_with_no_presence` is set to False, this - option has no effect. + This value must match `always_print_fields_with_no_presence`, + if both arguments are explictly set. preserving_proto_field_name (Optional(bool)): An option that determines whether field name representations preserve proto case (snake_case) or use lowerCamelCase. Default is False. @@ -410,18 +410,30 @@ def to_json( always_print_fields_with_no_presence (Optional(bool)): If True, fields without presence (implicit presence scalars, repeated fields, and map fields) will always be serialized. Any field that supports presence is not affected by - this option (including singular message fields and oneof fields). If - `including_default_value_fields` is set to False, this option has no effect. + this option (including singular message fields and oneof fields). + This value must match `including_default_value_fields`, + if both arguments are explictly set. Returns: str: The json string representation of the protocol buffer. """ + # For backwards compatibility of this breaking change in protobuf 5.x which is specific to proto2 # https://github.com/protocolbuffers/protobuf/commit/26995798757fbfef5cf6648610848e389db1fecf + if always_print_fields_with_no_presence is not None and including_default_value_fields is not None and always_print_fields_with_no_presence != including_default_value_fields: + raise ValueError("Arguments `always_print_fields_with_no_presence` and `including_default_value_fields` must match") + + # By default, fields with no presense will be included in the results + # when both `always_print_fields_with_no_presence` and `including_default_value_fields` are not set + if always_print_fields_with_no_presence is None and including_default_value_fields is None: + print_fields = True + else: + print_fields = always_print_fields_with_no_presence or including_default_value_fields + if PROTOBUF_VERSION[0] in ("3", "4"): return MessageToJson( cls.pb(instance), use_integers_for_enums=use_integers_for_enums, - including_default_value_fields=including_default_value_fields, + including_default_value_fields=print_fields, preserving_proto_field_name=preserving_proto_field_name, sort_keys=sort_keys, indent=indent, @@ -435,10 +447,7 @@ def to_json( return MessageToJson( cls.pb(instance), use_integers_for_enums=use_integers_for_enums, - always_print_fields_with_no_presence=( - including_default_value_fields - and always_print_fields_with_no_presence - ), + always_print_fields_with_no_presence=print_fields, preserving_proto_field_name=preserving_proto_field_name, sort_keys=sort_keys, indent=indent, @@ -468,15 +477,15 @@ def to_dict( *, use_integers_for_enums=True, preserving_proto_field_name=True, - including_default_value_fields=True, + including_default_value_fields=None, float_precision=None, - always_print_fields_with_no_presence=True, + always_print_fields_with_no_presence=None, ) -> "Message": """Given a message instance, return its representation as a python dict. Args: instance: An instance of this message type, or something - compatible (accepted by the type's constructor). + compatible (accepted by the type's constructor). use_integers_for_enums (Optional(bool)): An option that determines whether enum values should be represented by strings (False) or integers (True). Default is True. @@ -486,27 +495,38 @@ def to_dict( including_default_value_fields (Optional(bool)): Deprecated. Use argument `always_print_fields_with_no_presence` instead. An option that determines whether the default field values should be included in the results. - Default is True. If `always_print_fields_with_no_presence` is set to False, this - option has no effect. + This value must match `always_print_fields_with_no_presence`, + if both arguments are explictly set. float_precision (Optional(int)): If set, use this to specify float field valid digits. Default is None. always_print_fields_with_no_presence (Optional(bool)): If True, fields without presence (implicit presence scalars, repeated fields, and map fields) will always be serialized. Any field that supports presence is not affected by - this option (including singular message fields and oneof fields). If - `including_default_value_fields` is set to False, this option has no effect. + this option (including singular message fields and oneof fields). This value + must match `including_default_value_fields`, if both arguments are explictly set. Returns: dict: A representation of the protocol buffer using pythonic data structures. Messages and map fields are represented as dicts, repeated fields are represented as lists. """ + # For backwards compatibility of this breaking change in protobuf 5.x which is specific to proto2 # https://github.com/protocolbuffers/protobuf/commit/26995798757fbfef5cf6648610848e389db1fecf + if always_print_fields_with_no_presence is not None and including_default_value_fields is not None and always_print_fields_with_no_presence != including_default_value_fields: + raise ValueError("Arguments `always_print_fields_with_no_presence` and `including_default_value_fields` must match") + + # By default, fields with no presense will be included in the results + # when both `always_print_fields_with_no_presence` and `including_default_value_fields` are not set + if always_print_fields_with_no_presence is None and including_default_value_fields is None: + print_fields = True + else: + print_fields = always_print_fields_with_no_presence or including_default_value_fields + if PROTOBUF_VERSION[0] in ("3", "4"): return MessageToDict( cls.pb(instance), - including_default_value_fields=including_default_value_fields, + including_default_value_fields=print_fields, preserving_proto_field_name=preserving_proto_field_name, use_integers_for_enums=use_integers_for_enums, float_precision=float_precision, @@ -518,10 +538,7 @@ def to_dict( # The old flag accidentally had inconsistent behavior between proto2 optional and proto3 optional fields return MessageToDict( cls.pb(instance), - always_print_fields_with_no_presence=( - including_default_value_fields - and always_print_fields_with_no_presence - ), + always_print_fields_with_no_presence=print_fields, preserving_proto_field_name=preserving_proto_field_name, use_integers_for_enums=use_integers_for_enums, float_precision=float_precision, From 117907a5060cdf8dd69fcbe2cb717ff8d7a0021d Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Thu, 25 Apr 2024 15:29:49 +0000 Subject: [PATCH 10/16] lint --- proto/message.py | 38 +++++++++++++++++++------ tests/conftest.py | 8 ++++-- tests/test_fields_mitigate_collision.py | 1 + 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/proto/message.py b/proto/message.py index a148b7fb..95b3b614 100644 --- a/proto/message.py +++ b/proto/message.py @@ -419,15 +419,26 @@ def to_json( # For backwards compatibility of this breaking change in protobuf 5.x which is specific to proto2 # https://github.com/protocolbuffers/protobuf/commit/26995798757fbfef5cf6648610848e389db1fecf - if always_print_fields_with_no_presence is not None and including_default_value_fields is not None and always_print_fields_with_no_presence != including_default_value_fields: - raise ValueError("Arguments `always_print_fields_with_no_presence` and `including_default_value_fields` must match") + if ( + always_print_fields_with_no_presence is not None + and including_default_value_fields is not None + and always_print_fields_with_no_presence != including_default_value_fields + ): + raise ValueError( + "Arguments `always_print_fields_with_no_presence` and `including_default_value_fields` must match" + ) # By default, fields with no presense will be included in the results # when both `always_print_fields_with_no_presence` and `including_default_value_fields` are not set - if always_print_fields_with_no_presence is None and including_default_value_fields is None: + if ( + always_print_fields_with_no_presence is None + and including_default_value_fields is None + ): print_fields = True else: - print_fields = always_print_fields_with_no_presence or including_default_value_fields + print_fields = ( + always_print_fields_with_no_presence or including_default_value_fields + ) if PROTOBUF_VERSION[0] in ("3", "4"): return MessageToJson( @@ -513,15 +524,26 @@ def to_dict( # For backwards compatibility of this breaking change in protobuf 5.x which is specific to proto2 # https://github.com/protocolbuffers/protobuf/commit/26995798757fbfef5cf6648610848e389db1fecf - if always_print_fields_with_no_presence is not None and including_default_value_fields is not None and always_print_fields_with_no_presence != including_default_value_fields: - raise ValueError("Arguments `always_print_fields_with_no_presence` and `including_default_value_fields` must match") + if ( + always_print_fields_with_no_presence is not None + and including_default_value_fields is not None + and always_print_fields_with_no_presence != including_default_value_fields + ): + raise ValueError( + "Arguments `always_print_fields_with_no_presence` and `including_default_value_fields` must match" + ) # By default, fields with no presense will be included in the results # when both `always_print_fields_with_no_presence` and `including_default_value_fields` are not set - if always_print_fields_with_no_presence is None and including_default_value_fields is None: + if ( + always_print_fields_with_no_presence is None + and including_default_value_fields is None + ): print_fields = True else: - print_fields = always_print_fields_with_no_presence or including_default_value_fields + print_fields = ( + always_print_fields_with_no_presence or including_default_value_fields + ) if PROTOBUF_VERSION[0] in ("3", "4"): return MessageToDict( diff --git a/tests/conftest.py b/tests/conftest.py index 252ac30c..9f2f5c95 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -41,9 +41,11 @@ def pytest_runtest_setup(item): item._mocks.append( mock.patch( - "google._upb._message.default_pool" - if has_upb() - else "google.protobuf.pyext._message.default_pool", + ( + "google._upb._message.default_pool" + if has_upb() + else "google.protobuf.pyext._message.default_pool" + ), pool, ) ) diff --git a/tests/test_fields_mitigate_collision.py b/tests/test_fields_mitigate_collision.py index 117af48a..07eac5ff 100644 --- a/tests/test_fields_mitigate_collision.py +++ b/tests/test_fields_mitigate_collision.py @@ -15,6 +15,7 @@ import proto import pytest + # Underscores may be appended to field names # that collide with python or proto-plus keywords. # In case a key only exists with a `_` suffix, coerce the key From 09b9bc41f6b2651a3cd4df24c8281ed38fa9dc28 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Thu, 25 Apr 2024 15:45:09 +0000 Subject: [PATCH 11/16] add tests --- tests/test_json.py | 17 +++++++++++++++++ tests/test_message.py | 14 ++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/tests/test_json.py b/tests/test_json.py index e94e935a..e5111751 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -121,6 +121,23 @@ class Squid(proto.Message): ) assert json1 == '{"name":"Steve"}' + json1 = ( + Squid.to_json(s, always_print_fields_with_no_presence=False) + .replace(" ", "") + .replace("\n", "") + ) + assert json1 == '{"name":"Steve"}' + + with pytest.raises( + ValueError, + match="Arguments.*always_print_fields_with_no_presence.*including_default_value_fields.*must match", + ): + Squid.to_json( + s, + including_default_value_fields=True, + always_print_fields_with_no_presence=False, + ).replace(" ", "").replace("\n", "") + json2 = Squid.to_json(s).replace(" ", "").replace("\n", "") assert ( json2 == '{"name":"Steve","massKg":0}' or json2 == '{"massKg":0,"name":"Steve"}' diff --git a/tests/test_message.py b/tests/test_message.py index 983cde82..7ba05ab4 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -267,6 +267,20 @@ class Color(proto.Enum): expected_dict = {"mass_kg": 20} assert s_dict_2 == expected_dict + s_dict_2 = Squid.to_dict(s_new_2, always_print_fields_with_no_presence=False) + expected_dict = {"mass_kg": 20} + assert s_dict_2 == expected_dict + + with pytest.raises( + ValueError, + match="Arguments.*always_print_fields_with_no_presence.*including_default_value_fields.*must match", + ): + s_dict_2 = Squid.to_dict( + s_new_2, + including_default_value_fields=True, + always_print_fields_with_no_presence=False, + ) + new_s = Squid(s_dict) assert new_s == s From 6e17595596c9a944248c52f1d964fb66b360dc86 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Mon, 13 May 2024 12:30:01 -0400 Subject: [PATCH 12/16] Update tests/test_fields_mitigate_collision.py --- tests/test_fields_mitigate_collision.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_fields_mitigate_collision.py b/tests/test_fields_mitigate_collision.py index 07eac5ff..117af48a 100644 --- a/tests/test_fields_mitigate_collision.py +++ b/tests/test_fields_mitigate_collision.py @@ -15,7 +15,6 @@ import proto import pytest - # Underscores may be appended to field names # that collide with python or proto-plus keywords. # In case a key only exists with a `_` suffix, coerce the key From 4d50f04c39753b8e11577e706009deda58e9ae69 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 22 May 2024 13:56:48 +0000 Subject: [PATCH 13/16] add deprecation warning --- proto/message.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/proto/message.py b/proto/message.py index 95b3b614..1da45205 100644 --- a/proto/message.py +++ b/proto/message.py @@ -17,6 +17,7 @@ import copy import re from typing import List, Type +import warnings import google.protobuf from google.protobuf import descriptor_pb2 @@ -417,6 +418,15 @@ def to_json( str: The json string representation of the protocol buffer. """ + # Warn Protobuf 5.x users that `including_default_value_fields` is deprecated. + if PROTOBUF_VERSION[0] == "5" and including_default_value_fields is not None: + warnings.warn( + """The argument `including_default_value_fields` is deprecated. Please use + `always_print_fields_with_no_presence` instead. + """, + DeprecationWarning, + ) + # For backwards compatibility of this breaking change in protobuf 5.x which is specific to proto2 # https://github.com/protocolbuffers/protobuf/commit/26995798757fbfef5cf6648610848e389db1fecf if ( @@ -522,6 +532,15 @@ def to_dict( repeated fields are represented as lists. """ + # Warn Protobuf 5.x users that `including_default_value_fields` is deprecated. + if PROTOBUF_VERSION[0] == "5" and including_default_value_fields is not None: + warnings.warn( + """The argument `including_default_value_fields` is deprecated. Please use + `always_print_fields_with_no_presence` instead. + """, + DeprecationWarning, + ) + # For backwards compatibility of this breaking change in protobuf 5.x which is specific to proto2 # https://github.com/protocolbuffers/protobuf/commit/26995798757fbfef5cf6648610848e389db1fecf if ( From 70e28ad65a30c520c8579b505333ba5af1818ee2 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 22 May 2024 13:58:33 +0000 Subject: [PATCH 14/16] Cater for protobuf 5.x+ --- proto/message.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proto/message.py b/proto/message.py index 1da45205..b6ad3f68 100644 --- a/proto/message.py +++ b/proto/message.py @@ -418,8 +418,8 @@ def to_json( str: The json string representation of the protocol buffer. """ - # Warn Protobuf 5.x users that `including_default_value_fields` is deprecated. - if PROTOBUF_VERSION[0] == "5" and including_default_value_fields is not None: + # Warn Protobuf 5.x+ users that `including_default_value_fields` is deprecated. + if PROTOBUF_VERSION[0] not in ("3", "4") and including_default_value_fields is not None: warnings.warn( """The argument `including_default_value_fields` is deprecated. Please use `always_print_fields_with_no_presence` instead. @@ -532,8 +532,8 @@ def to_dict( repeated fields are represented as lists. """ - # Warn Protobuf 5.x users that `including_default_value_fields` is deprecated. - if PROTOBUF_VERSION[0] == "5" and including_default_value_fields is not None: + # Warn Protobuf 5.x+ users that `including_default_value_fields` is deprecated. + if PROTOBUF_VERSION[0] not in ("3", "4") and including_default_value_fields is not None: warnings.warn( """The argument `including_default_value_fields` is deprecated. Please use `always_print_fields_with_no_presence` instead. From 5d247e07329515b9e7a60dce173c8d5703671745 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 22 May 2024 14:00:36 +0000 Subject: [PATCH 15/16] style --- proto/message.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/proto/message.py b/proto/message.py index b6ad3f68..f3d15eb6 100644 --- a/proto/message.py +++ b/proto/message.py @@ -419,7 +419,10 @@ def to_json( """ # Warn Protobuf 5.x+ users that `including_default_value_fields` is deprecated. - if PROTOBUF_VERSION[0] not in ("3", "4") and including_default_value_fields is not None: + if ( + PROTOBUF_VERSION[0] not in ("3", "4") + and including_default_value_fields is not None + ): warnings.warn( """The argument `including_default_value_fields` is deprecated. Please use `always_print_fields_with_no_presence` instead. @@ -533,7 +536,10 @@ def to_dict( """ # Warn Protobuf 5.x+ users that `including_default_value_fields` is deprecated. - if PROTOBUF_VERSION[0] not in ("3", "4") and including_default_value_fields is not None: + if ( + PROTOBUF_VERSION[0] not in ("3", "4") + and including_default_value_fields is not None + ): warnings.warn( """The argument `including_default_value_fields` is deprecated. Please use `always_print_fields_with_no_presence` instead.