Skip to content

Commit

Permalink
Added is_closed to EnumDescriptor in protobuf python
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 486274963
  • Loading branch information
anandolee authored and copybara-github committed Nov 5, 2022
1 parent e71376e commit da9de8d
Show file tree
Hide file tree
Showing 7 changed files with 35 additions and 23 deletions.
4 changes: 2 additions & 2 deletions protobuf_deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
13 changes: 13 additions & 0 deletions python/google/protobuf/descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class TypeTransformationError(Error):
# 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
Expand Down Expand Up @@ -736,6 +737,18 @@ def __init__(self, name, full_name, filename, values,
# 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.
Expand Down
2 changes: 1 addition & 1 deletion python/google/protobuf/internal/python_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ def AddDecoder(wiretype, is_packed):
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
Expand Down
10 changes: 3 additions & 7 deletions python/google/protobuf/internal/type_checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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]


Expand Down
15 changes: 8 additions & 7 deletions python/google/protobuf/json_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,10 +287,11 @@ def _FieldToJsonObject(self, field, value):
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
Expand Down Expand Up @@ -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))
Expand Down
6 changes: 6 additions & 0 deletions python/google/protobuf/pyext/descriptor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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"},
Expand Down
8 changes: 2 additions & 6 deletions python/google/protobuf/text_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.' %
Expand Down

0 comments on commit da9de8d

Please sign in to comment.