From da9de8d4d4cb5d16cfee726e52d06e91846e3578 Mon Sep 17 00:00:00 2001 From: Jie Luo Date: Fri, 4 Nov 2022 18:31:37 -0700 Subject: [PATCH] Added is_closed to EnumDescriptor in protobuf python PiperOrigin-RevId: 486274963 --- protobuf_deps.bzl | 4 ++-- python/google/protobuf/descriptor.py | 13 +++++++++++++ python/google/protobuf/internal/python_message.py | 2 +- python/google/protobuf/internal/type_checkers.py | 10 +++------- python/google/protobuf/json_format.py | 15 ++++++++------- python/google/protobuf/pyext/descriptor.cc | 6 ++++++ python/google/protobuf/text_format.py | 8 ++------ 7 files changed, 35 insertions(+), 23 deletions(-) diff --git a/protobuf_deps.bzl b/protobuf_deps.bzl index fb2248a2dd..52e71aea3f 100644 --- a/protobuf_deps.bzl +++ b/protobuf_deps.bzl @@ -135,6 +135,6 @@ def protobuf_deps(): _github_archive( name = "upb", repo = "https://github.com/protocolbuffers/upb", - commit = "9e2d7f02da5440bfb0dfb069f61baa278aa2fbf6", - sha256 = "9eb13368a136af314855e1497838cf3124846b6a73a7e7c882455a52b8c04662", + commit = "73661563dbb82bf7fdd614dd8da1186c0acc6b17", + sha256 = "0b2789aa957c665165fa66892a6402489d6491cb097391fd8ea5b5a248dbde35", ) diff --git a/python/google/protobuf/descriptor.py b/python/google/protobuf/descriptor.py index b21622011b..e5d547a3bb 100644 --- a/python/google/protobuf/descriptor.py +++ b/python/google/protobuf/descriptor.py @@ -66,6 +66,7 @@ if _USE_C_DESCRIPTORS: # and make it return True when the descriptor is an instance of the extension # type written in C++. class DescriptorMetaclass(type): + def __instancecheck__(cls, obj): if super(DescriptorMetaclass, cls).__instancecheck__(obj): return True @@ -736,6 +737,18 @@ class EnumDescriptor(_NestedDescriptorBase): # Values are reversed to ensure that the first alias is retained. self.values_by_number = dict((v.number, v) for v in reversed(values)) + @property + def is_closed(self): + """If the enum is closed. + + closed enum means: + - Has a fixed set of named values. + - Encountering values not in this set causes them to be treated as + unknown fields. + - The first value (i.e., the default) may be nonzero. + """ + return self.file.syntax == 'proto2' + def CopyToProto(self, proto): """Copies this to a descriptor_pb2.EnumDescriptorProto. diff --git a/python/google/protobuf/internal/python_message.py b/python/google/protobuf/internal/python_message.py index 6f61980726..1e71aa2a62 100644 --- a/python/google/protobuf/internal/python_message.py +++ b/python/google/protobuf/internal/python_message.py @@ -308,7 +308,7 @@ def _AttachFieldHelpers(cls, field_descriptor): tag_bytes = encoder.TagBytes(field_descriptor.number, wiretype) decode_type = field_descriptor.type if (decode_type == _FieldDescriptor.TYPE_ENUM and - type_checkers.SupportsOpenEnums(field_descriptor)): + not field_descriptor.enum_type.is_closed): decode_type = _FieldDescriptor.TYPE_INT32 oneof_descriptor = None diff --git a/python/google/protobuf/internal/type_checkers.py b/python/google/protobuf/internal/type_checkers.py index a53e71fe8e..165dcd8c2e 100644 --- a/python/google/protobuf/internal/type_checkers.py +++ b/python/google/protobuf/internal/type_checkers.py @@ -75,10 +75,6 @@ def ToShortestFloat(original): return rounded -def SupportsOpenEnums(field_descriptor): - return field_descriptor.containing_type.syntax == 'proto3' - - def GetTypeChecker(field): """Returns a type checker for a message field of the specified types. @@ -93,11 +89,11 @@ def GetTypeChecker(field): field.type == _FieldDescriptor.TYPE_STRING): return UnicodeValueChecker() if field.cpp_type == _FieldDescriptor.CPPTYPE_ENUM: - if SupportsOpenEnums(field): + if field.enum_type.is_closed: + return EnumValueChecker(field.enum_type) + else: # When open enums are supported, any int32 can be assigned. return _VALUE_CHECKERS[_FieldDescriptor.CPPTYPE_INT32] - else: - return EnumValueChecker(field.enum_type) return _VALUE_CHECKERS[field.cpp_type] diff --git a/python/google/protobuf/json_format.py b/python/google/protobuf/json_format.py index f35a432126..1cf29d1393 100644 --- a/python/google/protobuf/json_format.py +++ b/python/google/protobuf/json_format.py @@ -287,10 +287,11 @@ class _Printer(object): if enum_value is not None: return enum_value.name else: - if field.file.syntax == 'proto3': + if field.enum_type.is_closed: + raise SerializeToJsonError('Enum field contains an integer value ' + 'which can not mapped to an enum value.') + else: return value - raise SerializeToJsonError('Enum field contains an integer value ' - 'which can not mapped to an enum value.') elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING: if field.type == descriptor.FieldDescriptor.TYPE_BYTES: # Use base64 Data encoding for bytes @@ -799,11 +800,11 @@ def _ConvertScalarFieldValue(value, field, path, require_str=False): raise ParseError('Invalid enum value {0} for enum type {1}'.format( value, field.enum_type.full_name)) if enum_value is None: - if field.file.syntax == 'proto3': - # Proto3 accepts unknown enums. + if field.enum_type.is_closed: + raise ParseError('Invalid enum value {0} for enum type {1}'.format( + value, field.enum_type.full_name)) + else: return number - raise ParseError('Invalid enum value {0} for enum type {1}'.format( - value, field.enum_type.full_name)) return enum_value.number except ParseError as e: raise ParseError('{0} at {1}'.format(e, path)) diff --git a/python/google/protobuf/pyext/descriptor.cc b/python/google/protobuf/pyext/descriptor.cc index fc97b0fa6c..f6db77a160 100644 --- a/python/google/protobuf/pyext/descriptor.cc +++ b/python/google/protobuf/pyext/descriptor.cc @@ -1182,6 +1182,11 @@ static PyObject* GetHasOptions(PyBaseDescriptor *self, void *closure) { Py_RETURN_FALSE; } } + +static PyObject* GetIsClosed(PyBaseDescriptor* self, void* closure) { + return PyBool_FromLong(_GetDescriptor(self)->is_closed()); +} + static int SetHasOptions(PyBaseDescriptor *self, PyObject *value, void *closure) { return CheckCalledFromGeneratedFile("has_options"); @@ -1225,6 +1230,7 @@ static PyGetSetDef Getters[] = { "Containing type"}, {"has_options", (getter)GetHasOptions, (setter)SetHasOptions, "Has Options"}, + {"is_closed", (getter)GetIsClosed, nullptr, "If the enum is closed"}, {"_options", (getter) nullptr, (setter)SetOptions, "Options"}, {"_serialized_options", (getter) nullptr, (setter)SetSerializedOptions, "Serialized Options"}, diff --git a/python/google/protobuf/text_format.py b/python/google/protobuf/text_format.py index cab7cb700d..9d7ab82c63 100644 --- a/python/google/protobuf/text_format.py +++ b/python/google/protobuf/text_format.py @@ -1852,12 +1852,8 @@ def ParseEnum(field, value): raise ValueError('Enum type "%s" has no value named %s.' % (enum_descriptor.full_name, value)) else: - # Numeric value. - if hasattr(field.file, 'syntax'): - # Attribute is checked for compatibility. - if field.file.syntax == 'proto3': - # Proto3 accept numeric unknown enums. - return number + if not field.enum_type.is_closed: + return number enum_value = enum_descriptor.values_by_number.get(number, None) if enum_value is None: raise ValueError('Enum type "%s" has no value with number %d.' %