Skip to content

Commit

Permalink
Add a test and impl for map field mock value (#335)
Browse files Browse the repository at this point in the history
Protobuf map fields are special: under the hood they are implemnted as a
sequence of generated type with two fields: 'key', whose type is the
map key type, and 'value', whose type is the map value type.

The user almost never wants to know about this implementation detail,
and the python proto surface allows python dictionaries as rvalues
when assigning to a mapped field.

This change uses dict literals in generated unit tests where flattened
parameters may refer to mapped fields.
  • Loading branch information
software-dov authored Mar 5, 2020
1 parent 65041f2 commit b2ea14a
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 6 deletions.
15 changes: 11 additions & 4 deletions gapic/schema/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,16 @@ def mock_value(self) -> str:
sub = next(iter(self.type.fields.values()))
answer = f'{self.type.ident}({sub.name}={sub.mock_value})'

# If this is a repeated field, then the mock answer should
# be a list.
if self.repeated:
if self.map:
# Maps are a special case beacuse they're represented internally as
# a list of a generated type with two fields: 'key' and 'value'.
answer = '{{{}: {}}}'.format(
self.type.fields["key"].mock_value,
self.type.fields["value"].mock_value,
)
elif self.repeated:
# If this is a repeated field, then the mock answer should
# be a list.
answer = f'[{answer}]'

# Done; return the mock value.
Expand Down Expand Up @@ -568,7 +575,6 @@ def field_headers(self) -> Sequence[str]:
@utils.cached_property
def flattened_fields(self) -> Mapping[str, Field]:
"""Return the signature defined for this method."""
signatures = self.options.Extensions[client_pb2.method_signature]
cross_pkg_request = self.input.ident.package != self.ident.package

def filter_fields(sig):
Expand All @@ -585,6 +591,7 @@ def filter_fields(sig):

yield name, field

signatures = self.options.Extensions[client_pb2.method_signature]
answer: Dict[str, Field] = collections.OrderedDict(
name_and_field
for sig in signatures
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,11 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
{% endif %} {# different request package #}

{#- Vanilla python protobuf wrapper types cannot _set_ repeated fields #}
{%- for key, field in method.flattened_fields.items() if not(field.repeated and method.input.ident.package != method.ident.package) %}
{% if method.flattened_fields -%}
# If we have keyword arguments corresponding to fields on the
# request, apply these.
{% endif -%}
{%- for key, field in method.flattened_fields.items() if not(field.repeated and method.input.ident.package != method.ident.package) %}
if {{ field.name }} is not None:
request.{{ key }} = {{ field.name }}
{%- endfor %}
Expand Down
43 changes: 42 additions & 1 deletion tests/unit/schema/wrappers/test_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,25 @@ def test_mock_value_repeated():
assert field.mock_value == "['foo_bar_value']"


def test_mock_value_map():
entry_msg = make_message(
name='SquidEntry',
fields=(
make_field(name='key', type='TYPE_STRING'),
make_field(name='value', type='TYPE_STRING'),
),
options=descriptor_pb2.MessageOptions(map_entry=True),
)
field = make_field(
name='squids',
type_name='mollusc.SquidEntry',
message=entry_msg,
label=3,
type='TYPE_MESSAGE',
)
assert field.mock_value == "{'key_value': 'value_value'}"


def test_mock_value_enum():
values = [
descriptor_pb2.EnumValueDescriptorProto(name='UNSPECIFIED', number=0),
Expand Down Expand Up @@ -227,4 +246,26 @@ def make_field(*, message=None, enum=None, **kwargs) -> wrappers.Field:
if isinstance(kwargs['type'], str):
kwargs['type'] = T.Value(kwargs['type'])
field_pb = descriptor_pb2.FieldDescriptorProto(**kwargs)
return wrappers.Field(field_pb=field_pb, message=message, enum=enum)
field = wrappers.Field(field_pb=field_pb, message=message, enum=enum)
return field


def make_message(
name, package='foo.bar.v1', module='baz', fields=(), meta=None, options=None
) -> wrappers.MessageType:
message_pb = descriptor_pb2.DescriptorProto(
name=name,
field=[i.field_pb for i in fields],
options=options,
)
return wrappers.MessageType(
message_pb=message_pb,
fields=collections.OrderedDict((i.name, i) for i in fields),
nested_messages={},
nested_enums={},
meta=meta or metadata.Metadata(address=metadata.Address(
name=name,
package=tuple(package.split('.')),
module=module,
)),
)

0 comments on commit b2ea14a

Please sign in to comment.