Skip to content

Commit 01a95fb

Browse files
authored
feat: allow_alias for enums (#207)
Certain APIs, e.g. recommendationengine, have enums where variants are aliased, i.e. different names map to the same integer value. Allowing this behavior in proto plus for the cpp protobuf runtime requires constructing and passing the correct options.
1 parent cb5ed11 commit 01a95fb

File tree

3 files changed

+56
-1
lines changed

3 files changed

+56
-1
lines changed

packages/proto-plus/.github/sync-repo-settings.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ branchProtectionRules:
1212
- 'unit (3.7)'
1313
- 'unit (3.7, cpp)'
1414
- 'unit (3.8)'
15-
- 'unit (3.9, cpp)'
15+
# - 'unit (3.9, cpp)' # Don't have binary wheels for 3.9 cpp protobuf yet
1616
- 'unit (3.9)'
1717
- 'cla/google'
1818
requiredApprovingReviewCount: 1

packages/proto-plus/proto/enums.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,21 @@ def __new__(mcls, name, bases, attrs):
4747
filename = _file_info._FileInfo.proto_file_name(
4848
attrs.get("__module__", name.lower())
4949
)
50+
51+
# Retrieve any enum options.
52+
# We expect something that looks like an EnumOptions message,
53+
# either an actual instance or a dict-like representation.
54+
pb_options = "_pb_options"
55+
opts = attrs.pop(pb_options, {})
56+
# This is the only portable way to remove the _pb_options name
57+
# from the enum attrs.
58+
# In 3.7 onwards, we can define an _ignore_ attribute and do some
59+
# mucking around with that.
60+
if pb_options in attrs._member_names:
61+
idx = attrs._member_names.index(pb_options)
62+
attrs._member_names.pop(idx)
63+
64+
# Make the descriptor.
5065
enum_desc = descriptor_pb2.EnumDescriptorProto(
5166
name=name,
5267
# Note: the superclass ctor removes the variants, so get them now.
@@ -60,6 +75,7 @@ def __new__(mcls, name, bases, attrs):
6075
),
6176
key=lambda v: v.number,
6277
),
78+
options=opts,
6379
)
6480

6581
file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package)

packages/proto-plus/tests/test_fields_enum.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import os
1516
import proto
17+
import pytest
1618
import sys
1719

1820

@@ -353,3 +355,40 @@ class Task(proto.Message):
353355
t = Task(weekday="TUESDAY")
354356
t2 = Task.deserialize(Task.serialize(t))
355357
assert t == t2
358+
359+
360+
if os.environ.get("PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION", "python") == "cpp":
361+
# This test only works, and is only relevant, with the cpp runtime.
362+
# Python just doesn't give a care and lets it work anyway.
363+
def test_enum_alias_bad():
364+
# Certain enums may shadow the different enum monikers with the same value.
365+
# This is generally discouraged, and protobuf will object by default,
366+
# but will explicitly allow this behavior if the enum is defined with
367+
# the `allow_alias` option set.
368+
with pytest.raises(TypeError):
369+
370+
# The wrapper message is a hack to avoid manifest wrangling to
371+
# define the enum.
372+
class BadMessage(proto.Message):
373+
class BadEnum(proto.Enum):
374+
UNKNOWN = 0
375+
DEFAULT = 0
376+
377+
bad_dup_enum = proto.Field(proto.ENUM, number=1, enum=BadEnum)
378+
379+
380+
def test_enum_alias_good():
381+
# Have to split good and bad enum alias into two tests so that the generated
382+
# file descriptor is properly created.
383+
# For the python runtime, aliases are allowed by default, but we want to
384+
# make sure that the options don't cause problems.
385+
# For the cpp runtime, we need to verify that we can in fact define aliases.
386+
class GoodMessage(proto.Message):
387+
class GoodEnum(proto.Enum):
388+
_pb_options = {"allow_alias": True}
389+
UNKNOWN = 0
390+
DEFAULT = 0
391+
392+
good_dup_enum = proto.Field(proto.ENUM, number=1, enum=GoodEnum)
393+
394+
assert GoodMessage.GoodEnum.UNKNOWN == GoodMessage.GoodEnum.DEFAULT == 0

0 commit comments

Comments
 (0)