diff --git a/gapic/samplegen/samplegen.py b/gapic/samplegen/samplegen.py index 2a7e68a1f5..ab1b817300 100644 --- a/gapic/samplegen/samplegen.py +++ b/gapic/samplegen/samplegen.py @@ -110,7 +110,7 @@ class TransformedRequest: # Resource patterns look something like # kingdom/{kingdom}/phylum/{phylum}/class/{class} - RESOURCE_RE = re.compile(r"\{([^}/]+)\}") + RESOURCE_RE = wrappers.MessageType.PATH_ARG_RE @classmethod def build( @@ -198,6 +198,11 @@ def build( raise types.NoSuchResourcePattern( f"Resource {resource_typestr} has no pattern with params: {attr_name_str}" ) + # This re-writes + # patterns like: 'projects/{project}/metricDescriptors/{metric_descriptor=**}' + # to 'projects/{project}/metricDescriptors/{metric_descriptor} + # so it can be used in sample code as an f-string. + pattern = cls.RESOURCE_RE.sub(r"{\g<1>}", pattern) return cls(base=base, body=attrs, single=None, pattern=pattern,) diff --git a/gapic/schema/wrappers.py b/gapic/schema/wrappers.py index eef7b9f14c..9dabdfa4da 100644 --- a/gapic/schema/wrappers.py +++ b/gapic/schema/wrappers.py @@ -317,7 +317,8 @@ def __getattr__(self, name): class MessageType: """Description of a message (defined with the ``message`` keyword).""" # Class attributes - PATH_ARG_RE = re.compile(r'\{([a-zA-Z0-9_-]+)\}') + # https://google.aip.dev/122 + PATH_ARG_RE = re.compile(r'\{([a-zA-Z0-9_\-]+)(?:=\*\*)?\}') # Instance attributes message_pb: descriptor_pb2.DescriptorProto diff --git a/tests/snippetgen/goldens/mollusca_generated_mollusca_v1_snippets_list_resources_async.py b/tests/snippetgen/goldens/mollusca_generated_mollusca_v1_snippets_list_resources_async.py index 7688660298..649e56a88b 100644 --- a/tests/snippetgen/goldens/mollusca_generated_mollusca_v1_snippets_list_resources_async.py +++ b/tests/snippetgen/goldens/mollusca_generated_mollusca_v1_snippets_list_resources_async.py @@ -38,8 +38,13 @@ async def sample_list_resources(): part_id = "part_id_value" parent = f"items/{item_id}/parts/{part_id}" + item_id = "item_id_value" + part_id = "part_id_value" + resource_with_wildcard = f"items/{item_id}/parts/{part_id}" + request = mollusca_v1.ListResourcesRequest( parent=parent, + resource_with_wildcard=resource_with_wildcard, ) # Make the request diff --git a/tests/snippetgen/goldens/mollusca_generated_mollusca_v1_snippets_list_resources_sync.py b/tests/snippetgen/goldens/mollusca_generated_mollusca_v1_snippets_list_resources_sync.py index f1aea49390..8892452b84 100644 --- a/tests/snippetgen/goldens/mollusca_generated_mollusca_v1_snippets_list_resources_sync.py +++ b/tests/snippetgen/goldens/mollusca_generated_mollusca_v1_snippets_list_resources_sync.py @@ -38,8 +38,13 @@ def sample_list_resources(): part_id = "part_id_value" parent = f"items/{item_id}/parts/{part_id}" + item_id = "item_id_value" + part_id = "part_id_value" + resource_with_wildcard = f"items/{item_id}/parts/{part_id}" + request = mollusca_v1.ListResourcesRequest( parent=parent, + resource_with_wildcard=resource_with_wildcard, ) # Make the request diff --git a/tests/snippetgen/snippets.proto b/tests/snippetgen/snippets.proto index d5bbb9f78b..fbffcea258 100644 --- a/tests/snippetgen/snippets.proto +++ b/tests/snippetgen/snippets.proto @@ -67,9 +67,15 @@ message ListResourcesRequest { (google.api.resource_reference) = { type: "snippets.example.com/Resource" }]; + + string resource_with_wildcard = 2 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + type: "snippets.example.com/ResourceWithWildcardSegment" + }]; - int32 page_size = 2; - string page_token = 3; + int32 page_size = 3; + string page_token = 4; } message ListResourcesResponse { @@ -91,10 +97,17 @@ message Resource { pattern: "items/{item_id}/parts/{part_id}" }; string name = 1; +} - +message ResourceWithWildcardSegment { + option (google.api.resource) = { + type: "snippets.example.com/ResourceWithWildcardSegment" + pattern: "items/{item_id}/parts/{part_id=**}" + }; + string name = 1; } + message MessageWithNesting { message NestedMessage { string required_string = 1 [(google.api.field_behavior) = REQUIRED]; diff --git a/tests/unit/schema/wrappers/test_message.py b/tests/unit/schema/wrappers/test_message.py index da21f66ebd..1519fadc67 100644 --- a/tests/unit/schema/wrappers/test_message.py +++ b/tests/unit/schema/wrappers/test_message.py @@ -201,6 +201,27 @@ def test_resource_path(): assert message.resource_type == "Class" +def test_resource_path_with_wildcard(): + options = descriptor_pb2.MessageOptions() + resource = options.Extensions[resource_pb2.resource] + resource.pattern.append( + "kingdoms/{kingdom}/phyla/{phylum}/classes/{klass=**}") + resource.pattern.append( + "kingdoms/{kingdom}/divisions/{division}/classes/{klass}") + resource.type = "taxonomy.biology.com/Class" + message = make_message('Squid', options=options) + + assert message.resource_path == "kingdoms/{kingdom}/phyla/{phylum}/classes/{klass=**}" + assert message.resource_path_args == ["kingdom", "phylum", "klass"] + assert message.resource_type == "Class" + assert re.match(message.path_regex_str, + "kingdoms/my-kingdom/phyla/my-phylum/classes/my-klass") + assert re.match(message.path_regex_str, + "kingdoms/my-kingdom/phyla/my-phylum/classes/my-klass/additional-segment") + assert re.match(message.path_regex_str, + "kingdoms/my-kingdom/phyla/my-phylum/classes/") is None + + def test_parse_resource_path(): options = descriptor_pb2.MessageOptions() resource = options.Extensions[resource_pb2.resource]