Skip to content

Commit

Permalink
Out of order enums (#334)
Browse files Browse the repository at this point in the history
Message fields can reference enum types before they're defined in a proto file. Even if all enums are loaded before all messages, messages can define nested proto types, which means that the order message types are read in can determine whether field's type exists before it is referenced.

The workaround for this is to do a two pass type resolution: load top level enums and top level messages (which may recursively add additional types), then resolve any field in any message whose type is empty.

This cl extends existing logic for out of order message definitions to include out of order enums.
  • Loading branch information
software-dov authored Mar 4, 2020
1 parent 2884e72 commit 65041f2
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 7 deletions.
25 changes: 18 additions & 7 deletions gapic/schema/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,13 +425,24 @@ def __init__(
# In this situation, we would not have come across the message yet,
# and the field would have its original textual reference to the
# message (`type_name`) but not its resolved message wrapper.
for message in self.proto_messages.values():
for field in message.fields.values():
if field.type_name and not any((field.message, field.enum)):
object.__setattr__(
field, 'message',
self.proto_messages[field.type_name.lstrip('.')],
)
orphan_field_gen = (
(field.type_name.lstrip('.'), field)
for message in self.proto_messages.values()
for field in message.fields.values()
if field.type_name and not (field.message or field.enum)
)
for key, field in orphan_field_gen:
maybe_msg_type = self.proto_messages.get(key)
maybe_enum_type = self.proto_enums.get(key)
if maybe_msg_type:
object.__setattr__(field, 'message', maybe_msg_type)
elif maybe_enum_type:
object.__setattr__(field, 'enum', maybe_enum_type)
else:
raise TypeError(
f"Unknown type referenced in "
"{self.file_descriptor.name}: '{key}'"
)

# Only generate the service if this is a target file to be generated.
# This prevents us from generating common services (e.g. LRO) when
Expand Down
68 changes: 68 additions & 0 deletions tests/unit/schema/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,74 @@ def test_messages_nested():
assert bar not in proto.messages


def test_out_of_order_enums():
# Enums can be referenced as field types before they
# are defined in the proto file.
# This happens when they're a nested type within a message.
messages = (
make_message_pb2(
name='Squid',
fields=(
make_field_pb2(
name='base_color',
type_name='google.mollusca.Chromatophore.Color',
number=1,
),
),
),
make_message_pb2(
name='Chromatophore',
enum_type=(
descriptor_pb2.EnumDescriptorProto(name='Color', value=()),
),
)
)
fd = (
make_file_pb2(
name='squid.proto',
package='google.mollusca',
messages=messages,
services=(
descriptor_pb2.ServiceDescriptorProto(
name='SquidService',
),
),
),
)
api_schema = api.API.build(fd, package='google.mollusca')
field_type = (
api_schema
.messages['google.mollusca.Squid']
.fields['base_color']
.type
)
enum_type = api_schema.enums['google.mollusca.Chromatophore.Color']
assert field_type == enum_type


def test_undefined_type():
fd = (
make_file_pb2(
name='mollusc.proto',
package='google.mollusca',
messages=(
make_message_pb2(
name='Mollusc',
fields=(
make_field_pb2(
name='class',
type_name='google.mollusca.Class',
number=1,
),
)
),
),
),
)
with pytest.raises(TypeError):
api.API.build(fd, package='google.mollusca')


def test_python_modules_nested():
fd = (
make_file_pb2(
Expand Down

0 comments on commit 65041f2

Please sign in to comment.