Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit b2ea14a

Browse files
authored
Add a test and impl for map field mock value (#335)
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.
1 parent 65041f2 commit b2ea14a

3 files changed

Lines changed: 56 additions & 6 deletions

File tree

gapic/schema/wrappers.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,16 @@ def mock_value(self) -> str:
110110
sub = next(iter(self.type.fields.values()))
111111
answer = f'{self.type.ident}({sub.name}={sub.mock_value})'
112112

113-
# If this is a repeated field, then the mock answer should
114-
# be a list.
115-
if self.repeated:
113+
if self.map:
114+
# Maps are a special case beacuse they're represented internally as
115+
# a list of a generated type with two fields: 'key' and 'value'.
116+
answer = '{{{}: {}}}'.format(
117+
self.type.fields["key"].mock_value,
118+
self.type.fields["value"].mock_value,
119+
)
120+
elif self.repeated:
121+
# If this is a repeated field, then the mock answer should
122+
# be a list.
116123
answer = f'[{answer}]'
117124

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

574580
def filter_fields(sig):
@@ -585,6 +591,7 @@ def filter_fields(sig):
585591

586592
yield name, field
587593

594+
signatures = self.options.Extensions[client_pb2.method_signature]
588595
answer: Dict[str, Field] = collections.OrderedDict(
589596
name_and_field
590597
for sig in signatures

gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,11 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
204204
{% endif %} {# different request package #}
205205

206206
{#- Vanilla python protobuf wrapper types cannot _set_ repeated fields #}
207-
{%- for key, field in method.flattened_fields.items() if not(field.repeated and method.input.ident.package != method.ident.package) %}
207+
{% if method.flattened_fields -%}
208208
# If we have keyword arguments corresponding to fields on the
209209
# request, apply these.
210+
{% endif -%}
211+
{%- for key, field in method.flattened_fields.items() if not(field.repeated and method.input.ident.package != method.ident.package) %}
210212
if {{ field.name }} is not None:
211213
request.{{ key }} = {{ field.name }}
212214
{%- endfor %}

tests/unit/schema/wrappers/test_field.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,25 @@ def test_mock_value_repeated():
173173
assert field.mock_value == "['foo_bar_value']"
174174

175175

176+
def test_mock_value_map():
177+
entry_msg = make_message(
178+
name='SquidEntry',
179+
fields=(
180+
make_field(name='key', type='TYPE_STRING'),
181+
make_field(name='value', type='TYPE_STRING'),
182+
),
183+
options=descriptor_pb2.MessageOptions(map_entry=True),
184+
)
185+
field = make_field(
186+
name='squids',
187+
type_name='mollusc.SquidEntry',
188+
message=entry_msg,
189+
label=3,
190+
type='TYPE_MESSAGE',
191+
)
192+
assert field.mock_value == "{'key_value': 'value_value'}"
193+
194+
176195
def test_mock_value_enum():
177196
values = [
178197
descriptor_pb2.EnumValueDescriptorProto(name='UNSPECIFIED', number=0),
@@ -227,4 +246,26 @@ def make_field(*, message=None, enum=None, **kwargs) -> wrappers.Field:
227246
if isinstance(kwargs['type'], str):
228247
kwargs['type'] = T.Value(kwargs['type'])
229248
field_pb = descriptor_pb2.FieldDescriptorProto(**kwargs)
230-
return wrappers.Field(field_pb=field_pb, message=message, enum=enum)
249+
field = wrappers.Field(field_pb=field_pb, message=message, enum=enum)
250+
return field
251+
252+
253+
def make_message(
254+
name, package='foo.bar.v1', module='baz', fields=(), meta=None, options=None
255+
) -> wrappers.MessageType:
256+
message_pb = descriptor_pb2.DescriptorProto(
257+
name=name,
258+
field=[i.field_pb for i in fields],
259+
options=options,
260+
)
261+
return wrappers.MessageType(
262+
message_pb=message_pb,
263+
fields=collections.OrderedDict((i.name, i) for i in fields),
264+
nested_messages={},
265+
nested_enums={},
266+
meta=meta or metadata.Metadata(address=metadata.Address(
267+
name=name,
268+
package=tuple(package.split('.')),
269+
module=module,
270+
)),
271+
)

0 commit comments

Comments
 (0)