def test_get_response_enumerates_services():
    g = make_generator()
    with mock.patch.object(jinja2.FileSystemLoader, "list_templates") as lt:
        lt.return_value = [
            "foo/%service/baz.py.j2",
            "molluscs/squid/sample.py.j2",
        ]
        with mock.patch.object(jinja2.Environment, "get_template") as gt:
            gt.return_value = jinja2.Template("Service: {{ service.name }}")
            cgr = g.get_response(
                api_schema=make_api(
                    make_proto(
                        descriptor_pb2.FileDescriptorProto(service=[
                            descriptor_pb2.ServiceDescriptorProto(name="Spam"),
                            descriptor_pb2.ServiceDescriptorProto(
                                name="EggsService"),
                        ]), )),
                opts=Options.build(""),
            )
            assert len(cgr.file) == 2
            assert {i.name
                    for i in cgr.file} == {
                        "foo/spam/baz.py",
                        "foo/eggs_service/baz.py",
                    }
def test_get_response_divides_subpackages():
    # NOTE: autogen-snippets is intentionally disabled for this test
    # The API schema below is incomplete and will result in errors when the
    # snippetgen logic tries to parse it.
    g = make_generator("autogen-snippets=false")
    api_schema = api.API.build(
        [
            descriptor_pb2.FileDescriptorProto(
                name="top.proto",
                package="foo.v1",
                service=[descriptor_pb2.ServiceDescriptorProto(name="Top")],
            ),
            descriptor_pb2.FileDescriptorProto(
                name="a/spam/ham.proto",
                package="foo.v1.spam",
                service=[descriptor_pb2.ServiceDescriptorProto(name="Bacon")],
            ),
            descriptor_pb2.FileDescriptorProto(
                name="a/eggs/yolk.proto",
                package="foo.v1.eggs",
                service=[
                    descriptor_pb2.ServiceDescriptorProto(name="Scramble")
                ],
            ),
        ],
        package="foo.v1",
    )
    with mock.patch.object(jinja2.FileSystemLoader, "list_templates") as lt:
        lt.return_value = [
            "foo/%sub/types/%proto.py.j2",
            "foo/%sub/services/%service.py.j2",
            "molluscs/squid/sample.py.j2",
        ]
        with mock.patch.object(jinja2.Environment, "get_template") as gt:
            gt.return_value = jinja2.Template("""
                {{- '' }}Subpackage: {{ '.'.join(api.subpackage_view) }}
            """.strip())
            cgr = g.get_response(api_schema=api_schema,
                                 opts=Options.build("autogen-snippets=false"))
            assert len(cgr.file) == 6
            assert {i.name
                    for i in cgr.file} == {
                        "foo/types/top.py",
                        "foo/services/top.py",
                        "foo/spam/types/ham.py",
                        "foo/spam/services/bacon.py",
                        "foo/eggs/types/yolk.py",
                        "foo/eggs/services/scramble.py",
                    }
def test_out_of_order_enums():
    # Enums can be referenced as field types before they
    # are defined in the proto file.
    # This happens when they're a nested type within a message.
    messages = (make_message_pb2(
        name='Squid',
        fields=(make_field_pb2(
            name='base_color',
            type_name='google.mollusca.Chromatophore.Color',
            number=1,
        ), ),
    ),
                make_message_pb2(
                    name='Chromatophore',
                    enum_type=(descriptor_pb2.EnumDescriptorProto(name='Color',
                                                                  value=()), ),
                ))
    fd = (make_file_pb2(
        name='squid.proto',
        package='google.mollusca',
        messages=messages,
        services=(descriptor_pb2.ServiceDescriptorProto(
            name='SquidService', ), ),
    ), )
    api_schema = api.API.build(fd, package='google.mollusca')
    field_type = (
        api_schema.messages['google.mollusca.Squid'].fields['base_color'].type)
    enum_type = api_schema.enums['google.mollusca.Chromatophore.Color']
    assert field_type == enum_type
Example #4
0
def make_service_with_method_options(
    *,
    http_rule: http_pb2.HttpRule = None,
    method_signature: str = '',
    in_fields: typing.Tuple[desc.FieldDescriptorProto] = ()
) -> wrappers.Service:
    # Declare a method with options enabled for long-running operations and
    # field headers.
    method = get_method(
        'DoBigThing',
        'foo.bar.ThingRequest',
        'google.longrunning.operations_pb2.Operation',
        lro_response_type='foo.baz.ThingResponse',
        lro_metadata_type='foo.qux.ThingMetadata',
        in_fields=in_fields,
        http_rule=http_rule,
        method_signature=method_signature,
    )

    # Define a service descriptor.
    service_pb = desc.ServiceDescriptorProto(name='ThingDoer')

    # Return a service object to test.
    return wrappers.Service(
        service_pb=service_pb,
        methods={method.name: method},
    )
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'), )
Example #6
0
def test_get_response_divides_subpackages():
    g = make_generator()
    api_schema = api.API.build(
        [
            descriptor_pb2.FileDescriptorProto(
                name="top.proto",
                package="foo.v1",
                service=[descriptor_pb2.ServiceDescriptorProto(name="Top")],
            ),
            descriptor_pb2.FileDescriptorProto(
                name="a/spam/ham.proto",
                package="foo.v1.spam",
                service=[descriptor_pb2.ServiceDescriptorProto(name="Bacon")],
            ),
            descriptor_pb2.FileDescriptorProto(
                name="a/eggs/yolk.proto",
                package="foo.v1.eggs",
                service=[descriptor_pb2.ServiceDescriptorProto(
                    name="Scramble")],
            ),
        ],
        package="foo.v1",
    )
    with mock.patch.object(jinja2.FileSystemLoader, "list_templates") as lt:
        lt.return_value = [
            "foo/%sub/types/%proto.py.j2",
            "foo/%sub/services/%service.py.j2",
            "molluscs/squid/sample.py.j2",
        ]
        with mock.patch.object(jinja2.Environment, "get_template") as gt:
            gt.return_value = jinja2.Template(
                """
                {{- '' }}Subpackage: {{ '.'.join(api.subpackage_view) }}
            """.strip()
            )
            cgr = g.get_response(api_schema=api_schema,
                                 opts=options.Options.build(""))
            assert len(cgr.file) == 6
            assert {i.name for i in cgr.file} == {
                "foo/types/top.py",
                "foo/services/top.py",
                "foo/spam/types/ham.py",
                "foo/spam/services/bacon.py",
                "foo/eggs/types/yolk.py",
                "foo/eggs/services/scramble.py",
            }
Example #7
0
def test_get_response():
    # Create a generator with mock data.
    #
    # We want to ensure that templates are rendered for each service,
    # which we prove by sending two services.
    file_pb2 = descriptor_pb2.FileDescriptorProto(
        name='bacon.proto',
        package='foo.bar.v1',
        service=[
            descriptor_pb2.ServiceDescriptorProto(name='SpamService'),
            descriptor_pb2.ServiceDescriptorProto(name='EggsService')
        ],
    )
    api_schema = make_api(make_proto(file_pb2))
    g = generator.Generator(api_schema=api_schema)

    # Mock all the rendering methods.
    with mock.patch.object(g, '_render_templates') as _render_templates:
        _render_templates.return_value = [
            plugin_pb2.CodeGeneratorResponse.File(
                name='template_file',
                content='This was a template.',
            ),
        ]

        # Okay, now run the `get_response` method.
        response = g.get_response()

        # First and foremost, we care that we got a valid response
        # object back (albeit not so much what is in it).
        assert isinstance(response, plugin_pb2.CodeGeneratorResponse)

        # Next, determine that the general API templates and service
        # templates were both called; the method should be called
        # once per service plus one for the API as a whole.
        assert _render_templates.call_count == len(file_pb2.service) + 1

        # The service templates should have been called with the
        # filename transformation and the additional `service` variable.
        for call in _render_templates.mock_calls:
            _, args, kwargs = call
            if args[0] != g._env.loader.service_templates:
                continue
            service = kwargs['additional_context']['service']
            assert isinstance(service, wrappers.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.'
Example #9
0
def test_get_response_enumerates_services():
    g = make_generator()
    with mock.patch.object(jinja2.FileSystemLoader, 'list_templates') as lt:
        lt.return_value = ['foo/$service/baz.py.j2']
        with mock.patch.object(jinja2.Environment, 'get_template') as gt:
            gt.return_value = jinja2.Template('Service: {{ service.name }}')
            cgr = g.get_response(api_schema=make_api(
                make_proto(
                    descriptor_pb2.FileDescriptorProto(service=[
                        descriptor_pb2.ServiceDescriptorProto(name='Spam'),
                        descriptor_pb2.ServiceDescriptorProto(
                            name='EggsService'),
                    ]), )))
    assert len(cgr.file) == 2
    assert {i.name
            for i in cgr.file} == {
                'foo/spam/baz.py',
                'foo/eggs_service/baz.py',
            }
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
Example #11
0
def test_get_response_divides_subpackages():
    g = make_generator()
    api_schema = api.API.build([
        descriptor_pb2.FileDescriptorProto(
            name='top.proto',
            package='foo.v1',
            service=[descriptor_pb2.ServiceDescriptorProto(name='Top')],
        ),
        descriptor_pb2.FileDescriptorProto(
            name='a/spam/ham.proto',
            package='foo.v1.spam',
            service=[descriptor_pb2.ServiceDescriptorProto(name='Bacon')],
        ),
        descriptor_pb2.FileDescriptorProto(
            name='a/eggs/yolk.proto',
            package='foo.v1.eggs',
            service=[descriptor_pb2.ServiceDescriptorProto(name='Scramble')],
        ),
    ],
                               package='foo.v1')
    with mock.patch.object(jinja2.FileSystemLoader, 'list_templates') as lt:
        lt.return_value = [
            'foo/%sub/types/%proto.py.j2',
            'foo/%sub/services/%service.py.j2',
            'molluscs/squid/sample.py.j2',
        ]
        with mock.patch.object(jinja2.Environment, 'get_template') as gt:
            gt.return_value = jinja2.Template("""
                {{- '' }}Subpackage: {{ '.'.join(api.subpackage_view) }}
            """.strip())
            cgr = g.get_response(api_schema=api_schema,
                                 opts=options.Options.build(''))
    assert len(cgr.file) == 6
    assert {i.name
            for i in cgr.file} == {
                'foo/types/top.py',
                'foo/services/top.py',
                'foo/spam/types/ham.py',
                'foo/spam/services/bacon.py',
                'foo/eggs/types/yolk.py',
                'foo/eggs/services/scramble.py',
            }
Example #12
0
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.'
Example #13
0
def test_get_filenames_with_service():
    g = generator.Generator(api_schema=make_api(naming=make_naming(
        namespace=(), name='Spam', version='v2'), ))
    template_name = '$name/$service/foo.py.j2'
    assert g._get_filenames(
        template_name,
        context={
            'service':
            wrappers.Service(
                methods=[],
                service_pb=descriptor_pb2.ServiceDescriptorProto(name='Eggs'),
            ),
        }) == ('spam/eggs/foo.py', )
Example #14
0
def test_not_target_file():
    """Establish that services are not ignored for untargeted protos."""
    message_pb = make_message_pb2(name='Foo',
        fields=(make_field_pb2(name='bar', type=3, number=1),)
    )
    service_pb = descriptor_pb2.ServiceDescriptorProto()
    fdp = make_file_pb2(messages=(message_pb,), services=(service_pb,))

    # Actually make the proto object.
    proto = api.Proto.build(fdp, file_to_generate=False, naming=make_naming())

    # The proto object should have the message, but no service.
    assert len(proto.messages) == 1
    assert len(proto.services) == 0
def make_service(name: str = 'Placeholder', host: str = '',
                 methods: typing.Tuple[wrappers.Method] = (),
                 scopes: typing.Tuple[str] = ()) -> wrappers.Service:
    # Define a service descriptor, and set a host and oauth scopes if
    # appropriate.
    service_pb = descriptor_pb2.ServiceDescriptorProto(name=name)
    if host:
        service_pb.options.Extensions[client_pb2.default_host] = host
    service_pb.options.Extensions[client_pb2.oauth_scopes] = ','.join(scopes)

    # Return a service object to test.
    return wrappers.Service(
        service_pb=service_pb,
        methods={m.name: m for m in methods},
    )
Example #16
0
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 test_get_response_ignores_unwanted_transports_and_clients():
    g = make_generator()
    with mock.patch.object(jinja2.FileSystemLoader, "list_templates") as lt:
        lt.return_value = [
            "foo/%service/transports/river.py.j2",
            "foo/%service/transports/car.py.j2",
            "foo/%service/transports/grpc.py.j2",
            "foo/%service/transports/__init__.py.j2",
            "foo/%service/transports/base.py.j2",
            "foo/%service/async_client.py.j2",
            "foo/%service/client.py.j2",
            "mollusks/squid/sample.py.j2",
        ]

        with mock.patch.object(jinja2.Environment, "get_template") as gt:
            gt.return_value = jinja2.Template("Service: {{ service.name }}")
            api_schema = make_api(
                make_proto(
                    descriptor_pb2.FileDescriptorProto(service=[
                        descriptor_pb2.ServiceDescriptorProto(
                            name="SomeService"),
                    ]), ))

            cgr = g.get_response(api_schema=api_schema,
                                 opts=Options.build("transport=river+car"))
            assert len(cgr.file) == 5
            assert {i.name
                    for i in cgr.file} == {
                        "foo/some_service/transports/river.py",
                        "foo/some_service/transports/car.py",
                        "foo/some_service/transports/__init__.py",
                        "foo/some_service/transports/base.py",
                        # Only generate async client with grpc transport
                        "foo/some_service/client.py",
                    }

            cgr = g.get_response(api_schema=api_schema,
                                 opts=Options.build("transport=grpc"))
            assert len(cgr.file) == 5
            assert {i.name
                    for i in cgr.file} == {
                        "foo/some_service/transports/grpc.py",
                        "foo/some_service/transports/__init__.py",
                        "foo/some_service/transports/base.py",
                        "foo/some_service/client.py",
                        "foo/some_service/async_client.py",
                    }
def test_get_filename_with_service():
    g = make_generator()
    template_name = "%name/%service/foo.py.j2"
    assert (g._get_filename(
        template_name,
        api_schema=make_api(naming=make_naming(namespace=(),
                                               name="Spam",
                                               version="v2"), ),
        context={
            "service":
            wrappers.Service(
                methods=[],
                service_pb=descriptor_pb2.ServiceDescriptorProto(name="Eggs"),
                visible_resources={},
            ),
        },
    ) == "spam/eggs/foo.py")
Example #19
0
  def CopyToProto(self, proto):
    """Copies this to a descriptor_pb2.MethodDescriptorProto.

    Args:
      proto (descriptor_pb2.MethodDescriptorProto): An empty descriptor proto.

    Raises:
      Error: If self couldn't be serialized, due to too few constructor
        arguments.
    """
    if self.containing_service is not None:
      from google.protobuf import descriptor_pb2
      service_proto = descriptor_pb2.ServiceDescriptorProto()
      self.containing_service.CopyToProto(service_proto)
      proto.CopyFrom(service_proto.method[self.index])
    else:
      raise Error('Descriptor does not contain a service.')
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 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_proto_builder_constructor():
    sentinel_message = descriptor_pb2.DescriptorProto()
    sentinel_enum = descriptor_pb2.EnumDescriptorProto()
    sentinel_service = descriptor_pb2.ServiceDescriptorProto()

    # Create a file descriptor proto. It does not matter that none
    # of the sentinels have actual data because this test just ensures
    # they are sent off to the correct methods unmodified.
    fdp = make_file_pb2(
        messages=(sentinel_message, ),
        enums=(sentinel_enum, ),
        services=(sentinel_service, ),
    )

    # Test the load function.
    with mock.patch.object(api._ProtoBuilder, '_load_children') as lc:
        pb = api._ProtoBuilder(
            fdp,
            file_to_generate=True,
            naming=make_naming(),
        )

        # There should be three total calls to load the different types
        # of children.
        assert lc.call_count == 3

        # The enum type should come first.
        _, args, _ = lc.mock_calls[0]
        assert args[0][0] == sentinel_enum
        assert args[1] == pb._load_enum

        # The message type should come second.
        _, args, _ = lc.mock_calls[1]
        assert args[0][0] == sentinel_message
        assert args[1] == pb._load_message

        # The services should come third.
        _, args, _ = lc.mock_calls[2]
        assert args[0][0] == sentinel_service
        assert args[1] == pb._load_service
Example #23
0
def make_service(name: str = 'Placeholder',
                 host: str = '',
                 scopes: typing.Tuple[str] = ()) -> wrappers.Service:
    # Declare a few methods, with messages in distinct packages.
    methods = (
        get_method('DoThing', 'foo.bar.ThingRequest', 'foo.baz.ThingResponse'),
        get_method('Jump', 'foo.bacon.JumpRequest', 'foo.bacon.JumpResponse'),
        get_method('Yawn', 'a.b.v1.c.YawnRequest', 'x.y.v1.z.YawnResponse'),
    )

    # Define a service descriptor, and set a host and oauth scopes if
    # appropriate.
    service_pb = descriptor_pb2.ServiceDescriptorProto(name=name)
    if host:
        service_pb.options.Extensions[annotations_pb2.default_host] = host
    service_pb.options.Extensions[annotations_pb2.oauth].scopes.extend(scopes)

    # Return a service object to test.
    return wrappers.Service(
        service_pb=service_pb,
        methods={m.name: m
                 for m in methods},
    )
def make_service(
    name: str = "Placeholder",
    host: str = "",
    methods: typing.Tuple[wrappers.Method] = (),
    scopes: typing.Tuple[str] = (),
    visible_resources: typing.Optional[typing.Mapping[
        str, wrappers.CommonResource]] = None,
) -> wrappers.Service:
    visible_resources = visible_resources or {}
    # Define a service descriptor, and set a host and oauth scopes if
    # appropriate.
    service_pb = desc.ServiceDescriptorProto(name=name)
    if host:
        service_pb.options.Extensions[client_pb2.default_host] = host
    service_pb.options.Extensions[client_pb2.oauth_scopes] = ','.join(scopes)

    # Return a service object to test.
    return wrappers.Service(
        service_pb=service_pb,
        methods={m.name: m
                 for m in methods},
        visible_resources=visible_resources,
    )
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_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,
    }
def test_dont_generate_in_code_samples(mock_gmtime, mock_generate_sample, fs):
    # These time values are nothing special,
    # they just need to be deterministic.
    returner = mock.MagicMock()
    returner.tm_year = 2112
    returner.tm_mon = 6
    returner.tm_mday = 1
    returner.tm_hour = 13
    returner.tm_min = 13
    returner.tm_sec = 13
    mock_gmtime.return_value = returner

    config_fpath = "samples.yaml"
    fs.create_file(
        config_fpath,
        contents=dedent("""
            type: com.google.api.codegen.samplegen.v1p2.SampleConfigProto
            schema_version: 1.2.0
            samples:
            - id: squid_sample
              rpc: IdentifyMollusc
              service: Mollusc.v1.Mollusc
              sample_type:
              - standalone
              - incode/SQUID
            - id: clam_sample
              rpc: IdentifyMollusc
              service: Mollusc.v1.Mollusc
              sample_type:
              - incode/CLAM
            - id: whelk_sample
              rpc: IdentifyMollusc
              service: Mollusc.v1.Mollusc
              sample_type:
              - standalone
            - id: octopus_sample
              rpc: IdentifyMollusc
              service: Mollusc.v1.Mollusc
            """),
    )

    generator = make_generator(f"samples={config_fpath}")
    generator._env.loader = jinja2.DictLoader({"sample.py.j2": ""})
    api_schema = make_api(
        make_proto(
            descriptor_pb2.FileDescriptorProto(
                name="mollusc.proto",
                package="Mollusc.v1",
                service=[
                    descriptor_pb2.ServiceDescriptorProto(name="Mollusc")
                ],
            ), ),
        naming=naming.NewNaming(name="Mollusc", version="v6"),
    )

    # Note that we do NOT expect a clam sample.
    # There are four tests going on:
    # 1) Just an explicit standalone sample type.
    # 2) Multiple sample types, one of which is standalone.
    # 3) Explicit sample types but NO standalone sample type.
    # 4) Implicit standalone sample type.
    expected = CodeGeneratorResponse(file=[
        CodeGeneratorResponse.File(
            name="samples/generated_samples/squid_sample.py",
            content="\n",
        ),
        CodeGeneratorResponse.File(
            name="samples/generated_samples/whelk_sample.py",
            content="\n",
        ),
        CodeGeneratorResponse.File(
            name="samples/generated_samples/octopus_sample.py",
            content="\n",
        ),
        # TODO(busunkim): Re-enable manifest generation once metadata
        # format has been formalized.
        # https://docs.google.com/document/d/1ghBam8vMj3xdoe4xfXhzVcOAIwrkbTpkMLgKc9RPD9k/edit#heading=h.sakzausv6hue
        # CodeGeneratorResponse.File(
        #     name="samples/generated_samples/mollusc.v6.python.21120601.131313.manifest.yaml",
        #     content=dedent(
        #         """                ---
        #     type: manifest/samples
        #     schema_version: 3
        #     python: &python
        #       environment: python
        #       bin: python3
        #       base_path: samples
        #       invocation: \'{bin} {path} @args\'
        #     samples:
        #     - <<: *python
        #       sample: squid_sample
        #       path: \'{base_path}/squid_sample.py\'
        #     - <<: *python
        #       sample: whelk_sample
        #       path: \'{base_path}/whelk_sample.py\'
        #     - <<: *python
        #       sample: octopus_sample
        #       path: \'{base_path}/octopus_sample.py\'
        #     """
        #     ),
        # ),
    ])
    expected.supported_features |= CodeGeneratorResponse.Feature.FEATURE_PROTO3_OPTIONAL

    actual = generator.get_response(api_schema=api_schema,
                                    opts=Options.build(""))
    assert actual == expected
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_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
Example #30
0
# Generated by the pRPC protocol buffer compiler plugin.  DO NOT EDIT!
# source: test.proto

from google.protobuf import descriptor_pb2

TestServiceDescription = {
    'descriptor': descriptor_pb2.ServiceDescriptorProto(),
    'package': 'test',
}
TestServiceDescription['descriptor'].ParseFromString(
    '0a045465737412330a044769766512112e746573742e47697665526571756573741a162e676f6f676c652e70726f746f6275662e456d707479220012340a0454616b6512162e676f6f676c652e70726f746f6275662e456d7074791a122e746573742e54616b65526573706f6e73652200122f0a044563686f12112e746573742e4563686f526571756573741a122e746573742e4563686f526573706f6e73652200'
    .decode('hex'))