Skip to content

Commit 7b2e911

Browse files
authored
Test and impl for resource-name requests (logic side) … (#208)
Contains logic and tests for validating and transforming resource-name based requests. Also contains test and impl for the template rendering. End-to-end testing is forthcoming. Addresses #194
1 parent 61edbd6 commit 7b2e911

7 files changed

Lines changed: 476 additions & 92 deletions

File tree

packages/gapic-generator/gapic/samplegen/samplegen.py

Lines changed: 241 additions & 86 deletions
Large diffs are not rendered by default.

packages/gapic-generator/gapic/samplegen_utils/types.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,18 @@ class DuplicateSample(SampleError):
8888
pass
8989

9090

91+
class ResourceRequestMismatch(SampleError):
92+
pass
93+
94+
95+
class NoSuchResource(SampleError):
96+
pass
97+
98+
99+
class NoSuchResourcePattern(SampleError):
100+
pass
101+
102+
91103
class CallingForm(Enum):
92104
Request = auto()
93105
RequestPaged = auto()

packages/gapic-generator/gapic/templates/examples/feature_fragments.j2

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,22 @@ with open({{ attr.input_parameter }}, "rb") as f:
155155
{% endmacro %}
156156

157157
{% macro render_request_setup(request) %}
158-
{% for parameter_block in request if parameter_block.body %}
158+
{% for parameter_block in request if parameter_block.body %}
159+
{% if parameter_block.pattern -%}
160+
{# This is a resource-name patterned lookup parameter #}
161+
{% with formals = [] -%}
162+
{% for attr in parameter_block.body -%}
163+
{% do formals.append("%s=%s"|format(attr.field, attr.input_parameter or attr.value)) -%}
164+
{% endfor -%}
165+
{{ parameter_block.base }} = "{{parameter_block.pattern }}".format({{ formals|join(", ") }})
166+
{% endwith -%}
167+
{% else -%}
159168
{{ parameter_block.base }} = {}
160-
{% for attr in parameter_block.body %}
169+
{% for attr in parameter_block.body %}
161170
{{ render_request_attr(parameter_block.base, attr) }}
162-
{% endfor %}
163-
{% endfor %}
171+
{% endfor %}
172+
{% endif -%}
173+
{% endfor %}
164174
{% endmacro %}
165175

166176
{% macro render_request_params(request) %}

packages/gapic-generator/setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
'click >= 6.7',
4545
'googleapis-common-protos >= 1.6.0',
4646
'jinja2 >= 2.10',
47+
'google_api >= 0.1.12',
4748
'protobuf >= 3.7.1',
4849
'pypandoc >= 1.4',
4950
'PyYAML >= 5.1.1',

packages/gapic-generator/tests/unit/samplegen/common_types.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@
5252

5353
DummyService = namedtuple("DummyService", ["methods"])
5454

55-
DummyApiSchema = namedtuple("DummyApiSchema", ["services", "naming"])
55+
DummyApiSchema = namedtuple("DummyApiSchema",
56+
["services", "naming", "messages"])
5657
DummyApiSchema.__new__.__defaults__ = (False,) * len(DummyApiSchema._fields)
5758

5859
DummyNaming = namedtuple(

packages/gapic-generator/tests/unit/samplegen/test_samplegen.py

Lines changed: 176 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from typing import (TypeVar, Sequence)
1919
from collections import (OrderedDict, namedtuple)
20+
from google.api import resource_pb2
2021
from google.protobuf import descriptor_pb2
2122

2223
import gapic.samplegen.samplegen as samplegen
@@ -25,7 +26,7 @@
2526
from gapic.schema import (api, metadata, naming)
2627
import gapic.schema.wrappers as wrappers
2728

28-
from common_types import (DummyField, DummyMessage,
29+
from common_types import (DummyApiSchema, DummyField, DummyMessage,
2930
DummyMethod, message_factory, enum_factory)
3031
from gapic.samplegen_utils import utils
3132

@@ -1585,6 +1586,69 @@ def test_validate_request_enum_not_last_attr():
15851586
)
15861587

15871588

1589+
def test_validate_request_resource_name():
1590+
request = [
1591+
{"field": "taxon%kingdom", "value": "animalia"},
1592+
{"field": "taxon%phylum", "value": "mollusca", "input_parameter": "phylum"}
1593+
]
1594+
1595+
resource_type = "taxonomy.google.com/Linnaean"
1596+
taxon_field = make_field(name="taxon")
1597+
rr = taxon_field.options.Extensions[resource_pb2.resource_reference]
1598+
rr.type = resource_type
1599+
request_descriptor = make_message(name="Request", fields=[taxon_field])
1600+
1601+
# Strictly speaking, 'phylum' is the resource, but it's not what we're
1602+
# manipulating to let samplegen know it's the resource.
1603+
phylum_options = descriptor_pb2.MessageOptions()
1604+
resource = phylum_options.Extensions[resource_pb2.resource]
1605+
resource.type = resource_type
1606+
resource.pattern.append("kingdom/{kingdom}/phylum/{phylum}")
1607+
phylum_descriptor = make_message(name="Phylum", options=phylum_options)
1608+
1609+
method = DummyMethod(input=request_descriptor)
1610+
# We don't actually care about the key,
1611+
# but the 'messages' property is a mapping type,
1612+
# and the implementation code expects this.
1613+
api_schema = DummyApiSchema(
1614+
messages={
1615+
k: v
1616+
for k, v in enumerate([
1617+
request_descriptor,
1618+
phylum_descriptor,
1619+
])
1620+
}
1621+
)
1622+
1623+
v = samplegen.Validator(method=method, api_schema=api_schema)
1624+
1625+
actual = v.validate_and_transform_request(
1626+
types.CallingForm.Request,
1627+
request
1628+
)
1629+
1630+
expected = [
1631+
samplegen.TransformedRequest(
1632+
base="taxon",
1633+
pattern="kingdom/{kingdom}/phylum/{phylum}",
1634+
single=None,
1635+
body=[
1636+
samplegen.AttributeRequestSetup(
1637+
field="kingdom",
1638+
value="animalia",
1639+
),
1640+
samplegen.AttributeRequestSetup(
1641+
field="phylum",
1642+
value="mollusca",
1643+
input_parameter="phylum",
1644+
),
1645+
]
1646+
)
1647+
]
1648+
1649+
assert actual == expected
1650+
1651+
15881652
def test_validate_request_primitive_field():
15891653
field = make_field(name="species", type="TYPE_STRING")
15901654
request_type = make_message(name="request", fields=[field])
@@ -1612,6 +1676,117 @@ def test_validate_request_primitive_field():
16121676
assert actual == expected
16131677

16141678

1679+
def test_validate_request_resource_name_mixed(request=None):
1680+
# Note the mixing of resource name and non-resource name request field
1681+
request = request or [
1682+
{"field": "taxon%kingdom", "value": "animalia"},
1683+
{"field": "taxon.domain", "value": "eukarya"},
1684+
]
1685+
v = samplegen.Validator(
1686+
method=DummyMethod(
1687+
input=make_message(
1688+
name="taxonomy",
1689+
fields=[
1690+
make_field(
1691+
name="taxon",
1692+
message=make_message(
1693+
name="Taxon",
1694+
fields=[
1695+
make_field(
1696+
name="domain",
1697+
message=make_message(name="Domain")
1698+
)
1699+
]
1700+
)
1701+
)
1702+
]
1703+
),
1704+
),
1705+
api_schema=None
1706+
)
1707+
1708+
with pytest.raises(types.ResourceRequestMismatch):
1709+
v.validate_and_transform_request(
1710+
types.CallingForm.Request,
1711+
request
1712+
)
1713+
1714+
1715+
def test_validate_request_resource_name_mixed_reversed():
1716+
# Again, note the mixed use of . and %
1717+
request = [
1718+
{"field": "taxon.domain", "value": "eukarya"},
1719+
{"field": "taxon%kingdom", "value": "animalia"},
1720+
]
1721+
test_validate_request_resource_name_mixed(request)
1722+
1723+
1724+
def test_validate_request_no_such_attr():
1725+
request = [
1726+
{"field": "taxon%kingdom", "value": "animalia"}
1727+
]
1728+
method = DummyMethod(input=make_message(name="Request"))
1729+
v = samplegen.Validator(method=method)
1730+
1731+
with pytest.raises(types.BadAttributeLookup):
1732+
v.validate_and_transform_request(types.CallingForm.Request, request)
1733+
1734+
1735+
def test_validate_request_no_such_resource():
1736+
request = [
1737+
{"field": "taxon%kingdom", "value": "animalia"}
1738+
]
1739+
resource_type = "taxonomy.google.com/Linnaean"
1740+
taxon_field = make_field(name="taxon")
1741+
rr = taxon_field.options.Extensions[resource_pb2.resource_reference]
1742+
rr.type = resource_type
1743+
request_descriptor = make_message(name="Request", fields=[taxon_field])
1744+
1745+
method = DummyMethod(input=request_descriptor)
1746+
api_schema = DummyApiSchema(
1747+
messages={k: v for k, v in enumerate([request_descriptor])}
1748+
)
1749+
1750+
v = samplegen.Validator(method=method, api_schema=api_schema)
1751+
with pytest.raises(types.NoSuchResource):
1752+
v.validate_and_transform_request(types.CallingForm.Request, request)
1753+
1754+
1755+
def test_validate_request_no_such_pattern():
1756+
request = [
1757+
# Note that there's only the one attribute, 'phylum', and that the only
1758+
# pattern expects both 'kingdom' and 'phylum'.
1759+
{"field": "taxon%phylum", "value": "mollusca", "input_parameter": "phylum"}
1760+
]
1761+
1762+
resource_type = "taxonomy.google.com/Linnaean"
1763+
taxon_field = make_field(name="taxon")
1764+
rr = taxon_field.options.Extensions[resource_pb2.resource_reference]
1765+
rr.type = resource_type
1766+
request_descriptor = make_message(name="Request", fields=[taxon_field])
1767+
1768+
phylum_options = descriptor_pb2.MessageOptions()
1769+
resource = phylum_options.Extensions[resource_pb2.resource]
1770+
resource.type = resource_type
1771+
resource.pattern.append("kingdom/{kingdom}/phylum/{phylum}")
1772+
phylum_descriptor = make_message(name="Phylum", options=phylum_options)
1773+
1774+
method = DummyMethod(input=request_descriptor)
1775+
api_schema = DummyApiSchema(
1776+
messages={
1777+
k: v
1778+
for k, v in enumerate([
1779+
request_descriptor,
1780+
phylum_descriptor,
1781+
])
1782+
}
1783+
)
1784+
1785+
v = samplegen.Validator(method=method, api_schema=api_schema)
1786+
with pytest.raises(types.NoSuchResourcePattern):
1787+
v.validate_and_transform_request(types.CallingForm.Request, request)
1788+
1789+
16151790
def test_validate_request_non_terminal_primitive_field():
16161791
field = make_field(name="species", type="TYPE_STRING")
16171792
request_type = make_message(name="request", fields=[field])

packages/gapic-generator/tests/unit/samplegen/test_template.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,36 @@ def test_render_request_basic():
173173
)
174174

175175

176+
def test_render_request_resource_name():
177+
check_template(
178+
'''
179+
{% import "feature_fragments.j2" as frags %}
180+
{{ frags.render_request_setup(request) }}
181+
''',
182+
'''
183+
taxon = "kingdom/{kingdom}/phylum/{phylum}".format(kingdom="animalia", phylum=mollusca)
184+
''',
185+
request=[
186+
samplegen.TransformedRequest(
187+
base="taxon",
188+
single=None,
189+
body=[
190+
samplegen.AttributeRequestSetup(
191+
field="kingdom",
192+
value='"animalia"',
193+
),
194+
samplegen.AttributeRequestSetup(
195+
field="phylum",
196+
value="mollusca",
197+
input_parameter="mollusca",
198+
)
199+
],
200+
pattern="kingdom/{kingdom}/phylum/{phylum}"
201+
),
202+
]
203+
)
204+
205+
176206
def test_render_print():
177207
check_template(
178208
'''

0 commit comments

Comments
 (0)