def get_method( name: str, in_type: str, out_type: str, lro_payload_type: str = '', lro_metadata_type: str = '', http_rule: http_pb2.HttpRule = None, ) -> wrappers.Method: input_ = get_message(in_type) output = get_message(out_type) lro_payload = get_message(lro_payload_type) if lro_payload_type else None lro_meta = get_message(lro_metadata_type) if lro_metadata_type else None # Define a method descriptor. Set the field headers if appropriate. method_pb = descriptor_pb2.MethodDescriptorProto( name=name, input_type=input_.proto_path, output_type=output.proto_path, ) if http_rule: ext_key = annotations_pb2.http method_pb.options.Extensions[ext_key].MergeFrom(http_rule) return wrappers.Method( method_pb=method_pb, input=input_, lro_metadata=lro_meta, lro_payload=lro_payload, output=output, )
def get_method() -> wrappers.Method: # Create the address where this method lives, and the types live, # and make them distinct. method_addr = metadata.Address(package=['foo', 'bar'], module='baz') types_addr = metadata.Address(package=['foo', 'bar'], module='bacon') # Create the method pb2 and set an overload in it. method_pb = descriptor_pb2.MethodDescriptorProto( name='DoTheThings', input_type='foo.bar.Input', output_type='foo.bar.Output', ) # Instantiate the wrapper class. return wrappers.Method( method_pb=method_pb, input=wrappers.MessageType( fields=[], message_pb=descriptor_pb2.DescriptorProto(name='Input'), meta=metadata.Metadata(address=types_addr), ), output=wrappers.MessageType( fields=[], message_pb=descriptor_pb2.DescriptorProto(name='Output'), meta=metadata.Metadata(address=types_addr), ), meta=metadata.Metadata(address=method_addr), )
def get_method(name: str, in_type: str, out_type: str, lro_response_type: str = '', lro_metadata_type: str = '', *, in_fields: typing.Tuple[descriptor_pb2.FieldDescriptorProto] = (), http_rule: http_pb2.HttpRule = None, method_signature: signature_pb2.MethodSignature = None, ) -> wrappers.Method: input_ = get_message(in_type, fields=in_fields) output = get_message(out_type) # Define a method descriptor. Set the field headers if appropriate. method_pb = descriptor_pb2.MethodDescriptorProto( name=name, input_type=input_.proto_path, output_type=output.proto_path, ) if lro_response_type: output = wrappers.OperationType( lro_response=get_message(lro_response_type), lro_metadata=get_message(lro_metadata_type), ) if http_rule: ext_key = annotations_pb2.http method_pb.options.Extensions[ext_key].MergeFrom(http_rule) if method_signature: ext_key = annotations_pb2.method_signature method_pb.options.Extensions[ext_key].MergeFrom(method_signature) return wrappers.Method( method_pb=method_pb, input=input_, output=output, )
def make_method( name: str, input_message: wrappers.MessageType = None, output_message: wrappers.MessageType = None, package: str = 'foo.bar.v1', module: str = 'baz', **kwargs) -> wrappers.Method: # Use default input and output messages if they are not provided. input_message = input_message or make_message('MethodInput') output_message = output_message or make_message('MethodOutput') # Create the method pb2. method_pb = descriptor_pb2.MethodDescriptorProto( name=name, input_type=str(input_message.meta.address), output_type=str(output_message.meta.address), ) # Instantiate the wrapper class. return wrappers.Method( method_pb=method_pb, input=input_message, output=output_message, meta=metadata.Metadata(address=metadata.Address( package=package, module=module, )), )
def test_python_modules_nested(): fd = ( make_file_pb2( name='dep.proto', package='google.dep', messages=(make_message_pb2(name='ImportedMessage', fields=()), ), ), make_file_pb2( name='common.proto', package='google.example.v1.common', messages=(make_message_pb2(name='Bar'), ), ), make_file_pb2( name='foo.proto', package='google.example.v1', messages=( make_message_pb2( name='GetFooRequest', fields=( make_field_pb2(name='primitive', number=2, type=1), make_field_pb2( name='foo', number=3, type=1, type_name='.google.example.v1.GetFooRequest.Foo', ), ), nested_type=(make_message_pb2( name='Foo', fields=(make_field_pb2( name='imported_message', number=1, type_name='.google.dep.ImportedMessage'), ), ), ), ), make_message_pb2( name='GetFooResponse', fields=(make_field_pb2( name='foo', number=1, type_name='.google.example.v1.GetFooRequest.Foo', ), ), ), ), services=(descriptor_pb2.ServiceDescriptorProto( name='FooService', method=(descriptor_pb2.MethodDescriptorProto( name='GetFoo', input_type='google.example.v1.GetFooRequest', output_type='google.example.v1.GetFooResponse', ), ), ), ), ), ) api_schema = api.API.build(fd, package='google.example.v1') assert api_schema.protos['foo.proto'].python_modules == (imp.Import( package=('google', 'dep'), module='dep_pb2'), )
def make_method( name: str, input_message: wrappers.MessageType = None, output_message: wrappers.MessageType = None, package: typing.Union[typing.Tuple[str], str] = 'foo.bar.v1', module: str = 'baz', http_rule: http_pb2.HttpRule = None, signatures: typing.Sequence[str] = (), is_deprecated: bool = False, routing_rule: routing_pb2.RoutingRule = None, **kwargs ) -> wrappers.Method: # Use default input and output messages if they are not provided. input_message = input_message or make_message('MethodInput') output_message = output_message or make_message('MethodOutput') # Create the method pb2. method_pb = desc.MethodDescriptorProto( name=name, input_type=str(input_message.meta.address), output_type=str(output_message.meta.address), **kwargs ) if routing_rule: ext_key = routing_pb2.routing method_pb.options.Extensions[ext_key].MergeFrom(routing_rule) # If there is an HTTP rule, process it. if http_rule: ext_key = annotations_pb2.http method_pb.options.Extensions[ext_key].MergeFrom(http_rule) # If there are signatures, include them. for sig in signatures: ext_key = client_pb2.method_signature method_pb.options.Extensions[ext_key].append(sig) if isinstance(package, str): package = tuple(package.split('.')) if is_deprecated: method_pb.options.deprecated = True # Instantiate the wrapper class. return wrappers.Method( method_pb=method_pb, input=input_message, output=output_message, meta=metadata.Metadata(address=metadata.Address( name=name, package=package, module=module, parent=(f'{name}Service',), )), )
def test_prior_protos(): L = descriptor_pb2.SourceCodeInfo.Location # Set up a prior proto that mimics google/protobuf/empty.proto empty_proto = api.Proto.build(make_file_pb2( name='empty.proto', package='google.protobuf', messages=(make_message_pb2(name='Empty'), ), ), file_to_generate=False, naming=make_naming()) # Set up the service with an RPC. service_pb = descriptor_pb2.ServiceDescriptorProto( name='PingService', method=(descriptor_pb2.MethodDescriptorProto( name='Ping', input_type='google.protobuf.Empty', output_type='google.protobuf.Empty', ), ), ) # Fake-document our fake stuff. locations = ( L(path=(6, 0), leading_comments='This is the PingService service.'), L(path=(6, 0, 2, 0), leading_comments='This is the Ping method.'), ) # Finally, set up the file that encompasses these. fdp = make_file_pb2( package='google.example.v1', services=(service_pb, ), locations=locations, ) # Make the proto object. proto = api.Proto.build(fdp, file_to_generate=True, prior_protos={ 'google/protobuf/empty.proto': empty_proto, }, naming=make_naming()) # Establish that our data looks correct. assert len(proto.services) == 1 assert len(empty_proto.messages) == 1 assert len(proto.messages) == 0 service = proto.services['google.example.v1.PingService'] assert service.meta.doc == 'This is the PingService service.' assert len(service.methods) == 1 method = service.methods['Ping'] assert isinstance(method.input, wrappers.MessageType) assert isinstance(method.output, wrappers.MessageType) assert method.input.name == 'Empty' assert method.output.name == 'Empty' assert method.meta.doc == 'This is the Ping method.'
def test_lro(): # Set up a prior proto that mimics google/protobuf/empty.proto lro_proto = api.Proto.build(make_file_pb2( name='operations.proto', package='google.longrunning', messages=(make_message_pb2(name='Operation'), ), ), file_to_generate=False, naming=make_naming()) # Set up a method with LRO annotations. method_pb2 = descriptor_pb2.MethodDescriptorProto( name='AsyncDoThing', input_type='google.example.v3.AsyncDoThingRequest', output_type='google.longrunning.Operation', ) method_pb2.options.Extensions[operations_pb2.operation_info].MergeFrom( operations_pb2.OperationInfo( response_type='google.example.v3.AsyncDoThingResponse', metadata_type='google.example.v3.AsyncDoThingMetadata', ), ) # Set up the service with an RPC. service_pb = descriptor_pb2.ServiceDescriptorProto( name='LongRunningService', method=(method_pb2, ), ) # Set up the messages, including the annotated ones. messages = ( make_message_pb2(name='AsyncDoThingRequest', fields=()), make_message_pb2(name='AsyncDoThingResponse', fields=()), make_message_pb2(name='AsyncDoThingMetadata', fields=()), ) # Finally, set up the file that encompasses these. fdp = make_file_pb2( package='google.example.v3', messages=messages, services=(service_pb, ), ) # Make the proto object. proto = api.Proto.build(fdp, file_to_generate=True, prior_protos={ 'google/longrunning/operations.proto': lro_proto, }, naming=make_naming()) # Establish that our data looks correct. assert len(proto.services) == 1 assert len(proto.messages) == 3 assert len(lro_proto.messages) == 1
def test_services(): L = descriptor_pb2.SourceCodeInfo.Location # Set up messages for our RPC. request_message_pb = make_message_pb2(name='GetFooRequest', fields=(make_field_pb2(name='name', type=9, number=1), )) response_message_pb = make_message_pb2(name='GetFooResponse', fields=()) # Set up the service with an RPC. service_pb = descriptor_pb2.ServiceDescriptorProto( name='FooService', method=(descriptor_pb2.MethodDescriptorProto( name='GetFoo', input_type='google.example.v2.GetFooRequest', output_type='google.example.v2.GetFooResponse', ), ), ) # Fake-document our fake stuff. locations = ( L(path=(6, 0), leading_comments='This is the FooService service.'), L(path=(6, 0, 2, 0), leading_comments='This is the GetFoo method.'), L(path=(4, 0), leading_comments='This is the GetFooRequest message.'), L(path=(4, 1), leading_comments='This is the GetFooResponse message.'), ) # Finally, set up the file that encompasses these. fdp = make_file_pb2( package='google.example.v2', messages=(request_message_pb, response_message_pb), services=(service_pb, ), locations=locations, ) # Make the proto object. proto = api.Proto.build(fdp, file_to_generate=True) # Establish that our data looks correct. assert len(proto.services) == 1 assert len(proto.messages) == 2 service = proto.services['google.example.v2.FooService'] assert service.meta.doc == 'This is the FooService service.' assert len(service.methods) == 1 method = service.methods['GetFoo'] assert method.meta.doc == 'This is the GetFoo method.' assert isinstance(method.input, wrappers.MessageType) assert isinstance(method.output, wrappers.MessageType) assert method.input.name == 'GetFooRequest' assert method.input.meta.doc == 'This is the GetFooRequest message.' assert method.output.name == 'GetFooResponse' assert method.output.meta.doc == 'This is the GetFooResponse message.'
def test_api_build(): # Put together a couple of minimal protos. fd = ( make_file_pb2( name='dep.proto', package='google.dep', messages=(make_message_pb2(name='ImportedMessage', fields=()), ), ), make_file_pb2( name='foo.proto', package='google.example.v1', messages=( make_message_pb2(name='Foo', fields=()), make_message_pb2( name='GetFooRequest', fields=(make_field_pb2( name='imported_message', number=1, type_name='google.dep.ImportedMessasge'), )), make_message_pb2(name='GetFooResponse', fields=(make_field_pb2( name='foo', number=1, type_name='google.example.v1.Foo'), )), ), services=(descriptor_pb2.ServiceDescriptorProto( name='FooService', method=(descriptor_pb2.MethodDescriptorProto( name='GetFoo', input_type='google.example.v1.GetFooRequest', output_type='google.example.v1.GetFooResponse', ), ), ), ), ), ) # Create an API with those protos. api_schema = api.API.build(fd, package='google.example.v1') # Establish that the API has the data expected. assert isinstance(api_schema, api.API) assert len(api_schema.protos) == 2 assert 'google.dep.ImportedMessage' in api_schema.messages assert 'google.example.v1.Foo' in api_schema.messages assert 'google.example.v1.GetFooRequest' in api_schema.messages assert 'google.example.v1.GetFooResponse' in api_schema.messages assert 'google.example.v1.FooService' in api_schema.services assert len(api_schema.enums) == 0
def make_method_pb2( name: str, input_type: str, output_type: str, client_streaming: bool = False, server_streaming: bool = False, **kwargs, ) -> desc.MethodDescriptorProto: # Create the method pb2. return desc.MethodDescriptorProto( name=name, input_type=input_type, output_type=output_type, client_streaming=client_streaming, server_streaming=server_streaming, **kwargs, )
def _get_methods_from_service(self, service_pb) -> Dict[str, MethodDescriptorProto]: services = service_pb.DESCRIPTOR.services_by_name methods = {} methods_to_generate = {} for service_name in services: service: ServiceDescriptor = services[service_name] for method in service.methods: fqn = "{}.{}.{}".format( service_pb.DESCRIPTOR.package, service.name, method.name) methods[fqn] = method for rule in self.service_yaml_config.http.rules: if rule.selector in methods: m = methods[rule.selector] x = descriptor_pb2.MethodDescriptorProto() m.CopyToProto(x) x.options.Extensions[annotations_pb2.http].CopyFrom(rule) methods_to_generate[x.name] = x return methods_to_generate
def test_resources_referenced_but_not_typed(reference_attr="type"): fdp = make_file_pb2( name="nomenclature.proto", package="nomenclature.linneaen.v1", messages=( make_message_pb2(name="Species", ), make_message_pb2( name="CreateSpeciesRequest", fields=(make_field_pb2(name='species', number=1, type=9), ), ), make_message_pb2(name="CreateSpeciesResponse", ), ), services=(descriptor_pb2.ServiceDescriptorProto( name="SpeciesService", method=(descriptor_pb2.MethodDescriptorProto( name="CreateSpecies", input_type="nomenclature.linneaen.v1.CreateSpeciesRequest", output_type="nomenclature.linneaen.v1.CreateSpeciesResponse", ), ), ), ), ) # Set up the resource species_resource_opts = fdp.message_type[0].options.Extensions[ resource_pb2.resource] species_resource_opts.type = "nomenclature.linnaen.com/Species" species_resource_opts.pattern.append( "families/{family}/genera/{genus}/species/{species}") # Set up the reference name_resource_opts = fdp.message_type[1].field[0].options.Extensions[ resource_pb2.resource_reference] if reference_attr == "type": name_resource_opts.type = species_resource_opts.type else: name_resource_opts.child_type = species_resource_opts.type api_schema = api.API.build([fdp], package="nomenclature.linneaen.v1") expected = {api_schema.messages["nomenclature.linneaen.v1.Species"]} actual = api_schema.services[ "nomenclature.linneaen.v1.SpeciesService"].resource_messages assert actual == expected
def make_method(name: str, input_message: wrappers.MessageType = None, output_message: wrappers.MessageType = None, package: str = 'foo.bar.v1', module: str = 'baz', http_rule: http_pb2.HttpRule = None, signatures: Sequence[str] = (), **kwargs) -> wrappers.Method: # Use default input and output messages if they are not provided. input_message = input_message or make_message('MethodInput') output_message = output_message or make_message('MethodOutput') # Create the method pb2. method_pb = descriptor_pb2.MethodDescriptorProto( name=name, input_type=str(input_message.meta.address), output_type=str(output_message.meta.address), **kwargs) # If there is an HTTP rule, process it. if http_rule: ext_key = annotations_pb2.http method_pb.options.Extensions[ext_key].MergeFrom(http_rule) # If there are signatures, include them. for sig in signatures: ext_key = client_pb2.method_signature method_pb.options.Extensions[ext_key].append(sig) # Instantiate the wrapper class. return wrappers.Method( method_pb=method_pb, input=input_message, output=output_message, meta=metadata.Metadata(address=metadata.Address( name=name, package=package, module=module, parent=(f'{name}Service', ), )), )
def test_lro_missing_annotation(): # Set up a prior proto that mimics google/protobuf/empty.proto lro_proto = api.Proto.build(make_file_pb2( name='operations.proto', package='google.longrunning', messages=(make_message_pb2(name='Operation'), ), ), file_to_generate=False, naming=make_naming()) # Set up a method with an LRO but no annotation. method_pb2 = descriptor_pb2.MethodDescriptorProto( name='AsyncDoThing', input_type='google.example.v3.AsyncDoThingRequest', output_type='google.longrunning.Operation', ) # Set up the service with an RPC. service_pb = descriptor_pb2.ServiceDescriptorProto( name='LongRunningService', method=(method_pb2, ), ) # Set up the messages, including the annotated ones. messages = (make_message_pb2(name='AsyncDoThingRequest', fields=()), ) # Finally, set up the file that encompasses these. fdp = make_file_pb2( package='google.example.v3', messages=messages, services=(service_pb, ), ) # Make the proto object. with pytest.raises(TypeError): api.Proto.build(fdp, file_to_generate=True, prior_protos={ 'google/longrunning/operations.proto': lro_proto, }, naming=make_naming())
def test_api_build(): # Put together a couple of minimal protos. fd = ( make_file_pb2( name='dep.proto', package='google.dep', messages=(make_message_pb2(name='ImportedMessage', fields=()), ), ), make_file_pb2( name='common.proto', package='google.example.v1.common', messages=(make_message_pb2(name='Bar'), ), ), make_file_pb2( name='foo.proto', package='google.example.v1', messages=( make_message_pb2(name='Foo', fields=()), make_message_pb2( name='GetFooRequest', fields=( make_field_pb2( name='imported_message', number=1, type_name='.google.dep.ImportedMessage'), make_field_pb2(name='primitive', number=2, type=1), )), make_message_pb2(name='GetFooResponse', fields=(make_field_pb2( name='foo', number=1, type_name='.google.example.v1.Foo'), )), ), services=(descriptor_pb2.ServiceDescriptorProto( name='FooService', method=(descriptor_pb2.MethodDescriptorProto( name='GetFoo', input_type='google.example.v1.GetFooRequest', output_type='google.example.v1.GetFooResponse', ), ), ), ), ), ) # Create an API with those protos. api_schema = api.API.build(fd, package='google.example.v1') # Establish that the API has the data expected. assert isinstance(api_schema, api.API) assert len(api_schema.all_protos) == 3 assert len(api_schema.protos) == 2 assert 'google.dep.ImportedMessage' not in api_schema.messages assert 'google.example.v1.common.Bar' in api_schema.messages assert 'google.example.v1.Foo' in api_schema.messages assert 'google.example.v1.GetFooRequest' in api_schema.messages assert 'google.example.v1.GetFooResponse' in api_schema.messages assert 'google.example.v1.FooService' in api_schema.services assert len(api_schema.enums) == 0 assert api_schema.protos['foo.proto'].python_modules == (imp.Import( package=('google', 'dep'), module='dep_pb2'), ) assert api_schema.requires_package(('google', 'example', 'v1')) assert not api_schema.requires_package(('elgoog', 'example', 'v1')) # Establish that the subpackages work. assert 'common' in api_schema.subpackages sub = api_schema.subpackages['common'] assert len(sub.protos) == 1 assert 'google.example.v1.common.Bar' in sub.messages assert 'google.example.v1.Foo' not in sub.messages
def test_file_level_resources(): fdp = make_file_pb2( name="nomenclature.proto", package="nomenclature.linneaen.v1", messages=( make_message_pb2( name="CreateSpeciesRequest", fields=(make_field_pb2(name='species', number=1, type=9), ), ), make_message_pb2(name="CreateSpeciesResponse", ), ), services=(descriptor_pb2.ServiceDescriptorProto( name="SpeciesService", method=(descriptor_pb2.MethodDescriptorProto( name="CreateSpecies", input_type="nomenclature.linneaen.v1.CreateSpeciesRequest", output_type="nomenclature.linneaen.v1.CreateSpeciesResponse", ), ), ), ), ) res_pb2 = fdp.options.Extensions[resource_pb2.resource_definition] definitions = [ ("nomenclature.linnaen.com/Species", "families/{family}/genera/{genus}/species/{species}"), ("nomenclature.linnaen.com/Phylum", "kingdoms/{kingdom}/phyla/{phylum}"), ] for type_, pattern in definitions: resource_definition = res_pb2.add() resource_definition.type = type_ resource_definition.pattern.append(pattern) species_field = fdp.message_type[0].field[0] resource_reference = species_field.options.Extensions[ resource_pb2.resource_reference] resource_reference.type = "nomenclature.linnaen.com/Species" api_schema = api.API.build([fdp], package='nomenclature.linneaen.v1') actual = api_schema.protos['nomenclature.proto'].resource_messages expected = collections.OrderedDict(( ("nomenclature.linnaen.com/Species", wrappers.CommonResource( type_name="nomenclature.linnaen.com/Species", pattern="families/{family}/genera/{genus}/species/{species}"). message_type), ("nomenclature.linnaen.com/Phylum", wrappers.CommonResource( type_name="nomenclature.linnaen.com/Phylum", pattern="kingdoms/{kingdom}/phyla/{phylum}").message_type), )) assert actual == expected # The proto file _owns_ the file level resources, but the service needs to # see them too because the client class owns all the helper methods. service = api_schema.services["nomenclature.linneaen.v1.SpeciesService"] actual = service.visible_resources assert actual == expected # The service doesn't own any method that owns a message that references # Phylum, so the service doesn't count it among its resource messages. expected.pop("nomenclature.linnaen.com/Phylum") expected = frozenset(expected.values()) actual = service.resource_messages assert actual == expected
def test_proto_names_import_collision_flattening(): lro_proto = api.Proto.build(make_file_pb2( name='operations.proto', package='google.longrunning', messages=(make_message_pb2(name='Operation'), ), ), file_to_generate=False, naming=make_naming()) fd = ( make_file_pb2( name='mollusc.proto', package='google.animalia.mollusca', messages=( make_message_pb2(name='Mollusc', ), make_message_pb2(name='MolluscResponse', ), make_message_pb2(name='MolluscMetadata', ), ), ), make_file_pb2( name='squid.proto', package='google.animalia.mollusca', messages=( make_message_pb2( name='IdentifySquidRequest', fields=(make_field_pb2( name='mollusc', number=1, type_name='.google.animalia.mollusca.Mollusc'), ), ), make_message_pb2( name='IdentifySquidResponse', fields=(), ), ), services=(descriptor_pb2.ServiceDescriptorProto( name='SquidIdentificationService', method=(descriptor_pb2.MethodDescriptorProto( name='IdentifyMollusc', input_type='google.animalia.mollusca.IdentifySquidRequest', output_type='google.longrunning.Operation', ), ), ), ), ), ) method_options = fd[1].service[0].method[0].options # Notice that a signature field collides with the name of an imported module method_options.Extensions[client_pb2.method_signature].append('mollusc') method_options.Extensions[operations_pb2.operation_info].MergeFrom( operations_pb2.OperationInfo( response_type='google.animalia.mollusca.MolluscResponse', metadata_type='google.animalia.mollusca.MolluscMetadata', )) api_schema = api.API.build(fd, package='google.animalia.mollusca', prior_protos={ 'google/longrunning/operations.proto': lro_proto, }) actual_imports = { ref_type.ident.python_import for service in api_schema.services.values() for method in service.methods.values() for ref_type in method.ref_types } expected_imports = { imp.Import( package=('google', 'animalia', 'mollusca', 'types'), module='mollusc', alias='gam_mollusc', ), imp.Import( package=('google', 'animalia', 'mollusca', 'types'), module='squid', ), imp.Import( package=('google', 'api_core'), module='operation', ), imp.Import( package=('google', 'api_core'), module='operation_async', ), } assert expected_imports == actual_imports method = (api_schema. services['google.animalia.mollusca.SquidIdentificationService']. methods['IdentifyMollusc']) actual_response_import = method.lro.response_type.ident.python_import expected_response_import = imp.Import( package=('google', 'animalia', 'mollusca', 'types'), module='mollusc', alias='gam_mollusc', ) assert actual_response_import == expected_response_import
def test_cross_file_lro(): # Protobuf annotations for longrunning operations use strings to name types. # As far as the protobuf compiler is concerned they don't reference the # _types_ at all, so the corresponding proto file that owns the types # does not need to be imported. # This creates a potential issue when building rich structures around # LRO returning methods. This test is intended to verify that the issue # is handled correctly. # Set up a prior proto that mimics google/protobuf/empty.proto lro_proto = api.Proto.build(make_file_pb2( name='operations.proto', package='google.longrunning', messages=(make_message_pb2(name='Operation'), ), ), file_to_generate=False, naming=make_naming()) # Set up a method with LRO annotations. method_pb2 = descriptor_pb2.MethodDescriptorProto( name='AsyncDoThing', input_type='google.example.v3.AsyncDoThingRequest', output_type='google.longrunning.Operation', ) method_pb2.options.Extensions[operations_pb2.operation_info].MergeFrom( operations_pb2.OperationInfo( response_type='google.example.v3.AsyncDoThingResponse', metadata_type='google.example.v3.AsyncDoThingMetadata', ), ) # Set up the service with an RPC. service_file = make_file_pb2( name='service_file.proto', package='google.example.v3', messages=(make_message_pb2(name='AsyncDoThingRequest', fields=()), ), services=(descriptor_pb2.ServiceDescriptorProto( name='LongRunningService', method=(method_pb2, ), ), )) # Set up the messages, including the annotated ones. # This file is distinct and is not explicitly imported # into the file that defines the service. messages_file = make_file_pb2( name='messages_file.proto', package='google.example.v3', messages=( make_message_pb2(name='AsyncDoThingResponse', fields=()), make_message_pb2(name='AsyncDoThingMetadata', fields=()), ), ) api_schema = api.API.build( file_descriptors=( service_file, messages_file, ), package='google.example.v3', prior_protos={ 'google/longrunning/operations.proto': lro_proto, }, ) method = (api_schema.all_protos['service_file.proto'].services[ 'google.example.v3.LongRunningService'].methods['AsyncDoThing']) assert method.lro assert method.lro.response_type.name == 'AsyncDoThingResponse' assert method.lro.metadata_type.name == 'AsyncDoThingMetadata'
def test_services(): L = descriptor_pb2.SourceCodeInfo.Location # Make a silly helper method to not repeat some of the structure. def _n(method_name: str): return { 'service': 'google.example.v2.FooService', 'method': method_name, } # Set up retry information. opts = Options( retry={ 'methodConfig': [ { 'name': [_n('TimeoutableGetFoo')], 'timeout': '30s' }, { 'name': [_n('RetryableGetFoo')], 'retryPolicy': { 'maxAttempts': 3, 'initialBackoff': '%dn' % 1e6, 'maxBackoff': '60s', 'backoffMultiplier': 1.5, 'retryableStatusCodes': ['UNAVAILABLE', 'ABORTED'], } }, ] }) # Set up messages for our RPC. request_message_pb = make_message_pb2(name='GetFooRequest', fields=(make_field_pb2(name='name', type=9, number=1), )) response_message_pb = make_message_pb2(name='GetFooResponse', fields=()) # Set up the service with an RPC. service_pb = descriptor_pb2.ServiceDescriptorProto( name='FooService', method=( descriptor_pb2.MethodDescriptorProto( name='GetFoo', input_type='google.example.v2.GetFooRequest', output_type='google.example.v2.GetFooResponse', ), descriptor_pb2.MethodDescriptorProto( name='TimeoutableGetFoo', input_type='google.example.v2.GetFooRequest', output_type='google.example.v2.GetFooResponse', ), descriptor_pb2.MethodDescriptorProto( name='RetryableGetFoo', input_type='google.example.v2.GetFooRequest', output_type='google.example.v2.GetFooResponse', ), ), ) # Fake-document our fake stuff. locations = ( L(path=(6, 0), leading_comments='This is the FooService service.'), L(path=(6, 0, 2, 0), leading_comments='This is the GetFoo method.'), L(path=(4, 0), leading_comments='This is the GetFooRequest message.'), L(path=(4, 1), leading_comments='This is the GetFooResponse message.'), ) # Finally, set up the file that encompasses these. fdp = make_file_pb2( name='test.proto', package='google.example.v2', messages=(request_message_pb, response_message_pb), services=(service_pb, ), locations=locations, ) # Make the proto object. proto = api.API.build( [fdp], 'google.example.v2', opts=opts, ).protos['test.proto'] # Establish that our data looks correct. assert len(proto.services) == 1 assert len(proto.messages) == 2 service = proto.services['google.example.v2.FooService'] assert service.meta.doc == 'This is the FooService service.' assert len(service.methods) == 3 method = service.methods['GetFoo'] assert method.meta.doc == 'This is the GetFoo method.' assert isinstance(method.input, wrappers.MessageType) assert isinstance(method.output, wrappers.MessageType) assert method.input.name == 'GetFooRequest' assert method.input.meta.doc == 'This is the GetFooRequest message.' assert method.output.name == 'GetFooResponse' assert method.output.meta.doc == 'This is the GetFooResponse message.' assert not method.timeout assert not method.retry # Establish that the retry information on a timeout-able method also # looks correct. timeout_method = service.methods['TimeoutableGetFoo'] assert timeout_method.timeout == pytest.approx(30.0) assert not timeout_method.retry # Establish that the retry information on the retryable method also # looks correct. retry_method = service.methods['RetryableGetFoo'] assert retry_method.timeout is None assert retry_method.retry.max_attempts == 3 assert retry_method.retry.initial_backoff == pytest.approx(0.001) assert retry_method.retry.backoff_multiplier == pytest.approx(1.5) assert retry_method.retry.max_backoff == pytest.approx(60.0) assert retry_method.retry.retryable_exceptions == { exceptions.ServiceUnavailable, exceptions.Aborted, }