Skip to content

Commit

Permalink
feat(grpc): adding service metadata (#3278)
Browse files Browse the repository at this point in the history
  • Loading branch information
aarnphm committed Dec 7, 2022
1 parent eb3df53 commit 970c5b2
Show file tree
Hide file tree
Showing 6 changed files with 503 additions and 25 deletions.
64 changes: 62 additions & 2 deletions src/bentoml/_internal/server/grpc/servicer/v1/__init__.py
@@ -1,12 +1,13 @@
from __future__ import annotations

import sys
import typing as t
import asyncio
import logging
from typing import TYPE_CHECKING

import anyio

from .....utils import LazyLoader
from ......exceptions import InvalidArgument
from ......exceptions import BentoMLException
from ......grpc.utils import import_grpc
Expand All @@ -20,14 +21,15 @@
from logging import _ExcInfoType as ExcInfoType # type: ignore (private warning)

import grpc

from google.protobuf import struct_pb2
from ......grpc.v1 import service_pb2 as pb
from ......grpc.v1 import service_pb2_grpc as services
from ......grpc.types import BentoServicerContext
from .....service.service import Service
else:
grpc, _ = import_grpc()
pb, services = import_generated_stubs(version="v1")
struct_pb2 = LazyLoader("struct_pb2", globals(), "google.protobuf.struct_pb2")


def log_exception(request: pb.Request, exc_info: ExcInfoType) -> None:
Expand Down Expand Up @@ -97,4 +99,62 @@ async def Call( # type: ignore (no async types) # pylint: disable=invalid-overr
)
return response

async def ServiceMetadata( # type: ignore (no async types) # pylint: disable=invalid-overridden-method
self,
request: pb.ServiceMetadataRequest, # pylint: disable=unused-argument
context: BentoServicerContext, # pylint: disable=unused-argument
) -> pb.ServiceMetadataResponse:
return pb.ServiceMetadataResponse(
name=service.name,
docs=service.doc,
apis=[
pb.ServiceMetadataResponse.InferenceAPI(
name=api.name,
docs=api.doc,
input=make_descriptor_spec(
api.input.to_spec(), pb.ServiceMetadataResponse
),
output=make_descriptor_spec(
api.output.to_spec(), pb.ServiceMetadataResponse
),
)
for api in service.apis.values()
],
)

return BentoServiceImpl()


if TYPE_CHECKING:
NestedDictStrAny = dict[str, dict[str, t.Any] | t.Any]
TupleAny = tuple[t.Any, ...]


def _tuple_converter(d: NestedDictStrAny | None) -> NestedDictStrAny | None:
# handles case for struct_pb2.Value where nested items are tuple.
# if that is the case, then convert to list.
# This dict is only one level deep, as we don't allow nested Multipart.
if d is not None:
for key, value in d.items():
if isinstance(value, tuple):
d[key] = list(t.cast("TupleAny", value))
elif isinstance(value, dict):
d[key] = _tuple_converter(t.cast("NestedDictStrAny", value))
return d


def make_descriptor_spec(
spec: dict[str, t.Any], pb: type[pb.ServiceMetadataResponse]
) -> pb.ServiceMetadataResponse.DescriptorMetadata:
from .....io_descriptors.json import parse_dict_to_proto

descriptor_id = spec.pop("id")
return pb.DescriptorMetadata(
descriptor_id=descriptor_id,
attributes=struct_pb2.Struct(
fields={
key: parse_dict_to_proto(_tuple_converter(value), struct_pb2.Value())
for key, value in spec.items()
}
),
)
37 changes: 37 additions & 0 deletions src/bentoml/grpc/v1/service.proto
Expand Up @@ -18,6 +18,43 @@ option py_generic_services = true;
service BentoService {
// Call handles methodcaller of given API entrypoint.
rpc Call(Request) returns (Response) {}
// ServiceMetadata returns metadata of bentoml.Service.
rpc ServiceMetadata(ServiceMetadataRequest) returns (ServiceMetadataResponse) {}
}

// ServiceMetadataRequest message doesn't take any arguments.
message ServiceMetadataRequest {}

// ServiceMetadataResponse returns metadata of bentoml.Service.
// Currently it includes name, version, apis, and docs.
message ServiceMetadataResponse {
// DescriptorMetadata is a metadata of any given IODescriptor.
message DescriptorMetadata {
// descriptor_id describes the given ID of the descriptor, which matches with our OpenAPI definition.
optional string descriptor_id = 1;

// attributes is the kwargs of the given descriptor.
google.protobuf.Struct attributes = 2;
}
// InferenceAPI is bentoml._internal.service.inferece_api.InferenceAPI
// that is exposed to gRPC client.
// There is no way for reflection to get information of given @svc.api.
message InferenceAPI {
// name is the name of the API.
string name = 1;
// input is the input descriptor of the API.
DescriptorMetadata input = 2;
// output is the output descriptor of the API.
DescriptorMetadata output = 3;
// docs is the optional documentation of the API.
optional string docs = 4;
}
// name is the service name.
string name = 1;
// apis holds a list of InferenceAPI of the service.
repeated InferenceAPI apis = 2;
// docs is the documentation of the service.
string docs = 3;
}

// Request message for incoming Call.
Expand Down

0 comments on commit 970c5b2

Please sign in to comment.