diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 00000000000..b49feff1945 --- /dev/null +++ b/.bazelignore @@ -0,0 +1,7 @@ +grpc-client/java/src/main/java/com/bentoml +grpc-client/kotlin/src/main/kotlin/com/bentoml +grpc-client/js/bentoml +grpc-client/go/bentoml +grpc-client/swift/Sources/bentoml +**/*/node_modules +grpc-client/thirdparty diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 00000000000..625f1e435a4 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,3 @@ +# load bazelrc from the legacy location as recommended +# in https://github.com/bazelbuild/bazel/issues/6319 +import %workspace%/tools/bazel.rc diff --git a/.gitignore b/.gitignore index a614ea885f3..a9342262643 100644 --- a/.gitignore +++ b/.gitignore @@ -117,3 +117,35 @@ catboost_info # ignore pyvenv pyvenv.cfg + +# bazel generated files +bazel-* +cpp/bentoml +go/bentoml +node/bentoml +node_modules +thirdparty + +# Swift-related +swift/**/*/bentoml +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Package.resolved + +# Java-Kotlin +**/*/java/**/*/bentoml +**/*/kotlin/**/*/bentoml +.gradle + +# PHP generated stubs +**/*/php/Bentoml +**/*/php/GPBMetadata +composer.lock +vendor diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 00000000000..733aa2ea532 --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,20 @@ +package(default_visibility = ["//visibility:public"]) + +load("@com_github_bazelbuild_buildtools//buildifier:def.bzl", "buildifier") + +buildifier( + name = "buildifier", +) + +# Create a generated proto library +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "service_v1alpha1_proto", + srcs = ["bentoml/grpc/v1alpha1/service.proto"], + visibility = ["//grpc-client:__subpackages__"], + deps = [ + "@com_google_protobuf//:struct_proto", + "@com_google_protobuf//:wrappers_proto", + ], +) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 15a034c82d1..a16ea34851a 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -450,5 +450,5 @@ your pull request. ## Documentations -Refers to [BentoML Documentation Guide](./docs/README.md) for how to build and write +Refer to [BentoML Documentation Guide](./docs/README.md) for how to build and write docs. diff --git a/MANIFEST.in b/MANIFEST.in index 5d698f3b671..b230cd032ee 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,6 +5,9 @@ exclude *.yml *.yaml exclude .git* exclude bentoml/grpc/buf.yaml exclude bentoml/_internal/frameworks/FRAMEWORK_TEMPLATE_PY +exclude .bazelignore +exclude .bazelrc +exclude BUILD WORKSPACE # Directories to exclude in PyPI package prune .devcontainer @@ -20,6 +23,8 @@ prune */__pycache__ prune */.DS_Store prune */.ipynb_checkpoints prune **/*/README* +prune bazel-* +prune grpc-client # Patterns to exclude from any directory global-exclude *.py[cod] diff --git a/Makefile b/Makefile index 274e0139cad..8d781d9802f 100644 --- a/Makefile +++ b/Makefile @@ -69,7 +69,7 @@ install-docs-deps: ## Install documentation dependencies # Docs watch-docs: install-docs-deps ## Build and watch documentation - sphinx-autobuild docs/source docs/build/html --watch $(GIT_ROOT)/bentoml + sphinx-autobuild docs/source docs/build/html --watch $(GIT_ROOT)/bentoml --ignore "bazel-*" spellcheck-docs: ## Spell check documentation sphinx-build -b spelling ./docs/source ./docs/build || (echo "Error running spellchecker.. You may need to run 'make install-spellchecker-deps'"; exit 1) diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 00000000000..2f66ccc4028 --- /dev/null +++ b/WORKSPACE @@ -0,0 +1,224 @@ +workspace(name = "bentoml") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +# install buildifier +http_archive( + name = "com_github_bazelbuild_buildtools", + sha256 = "ae34c344514e08c23e90da0e2d6cb700fcd28e80c02e23e4d5715dddcb42f7b3", + strip_prefix = "buildtools-4.2.2", + urls = [ + "https://github.com/bazelbuild/buildtools/archive/refs/tags/4.2.2.tar.gz", + ], +) + +# setup rules_proto and rules_proto_grpc +http_archive( + name = "rules_proto", + sha256 = "e017528fd1c91c5a33f15493e3a398181a9e821a804eb7ff5acdd1d2d6c2b18d", + strip_prefix = "rules_proto-4.0.0-3.20.0", + urls = [ + "https://github.com/bazelbuild/rules_proto/archive/refs/tags/4.0.0-3.20.0.tar.gz", + ], +) + +http_archive( + name = "rules_proto_grpc", + sha256 = "507e38c8d95c7efa4f3b1c0595a8e8f139c885cb41a76cab7e20e4e67ae87731", + strip_prefix = "rules_proto_grpc-4.1.1", + urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/4.1.1.tar.gz"], +) + +load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains") +load("@rules_proto_grpc//:repositories.bzl", "rules_proto_grpc_repos", "rules_proto_grpc_toolchains") + +rules_proto_grpc_toolchains() + +rules_proto_grpc_repos() + +rules_proto_dependencies() + +rules_proto_toolchains() + +# We need to load go_grpc rules first +load("@rules_proto_grpc//:repositories.bzl", "bazel_gazelle", "io_bazel_rules_go") # buildifier: disable=same-origin-load + +io_bazel_rules_go() + +bazel_gazelle() + +load("@rules_proto_grpc//go:repositories.bzl", rules_proto_grpc_go_repos = "go_repos") + +rules_proto_grpc_go_repos() + +load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") + +go_rules_dependencies() + +go_register_toolchains(version = "1.19") + +# grpc/grpc dependencies +# Currently c3714eced8c51db9092e0adc2a1dfb715655c795 address +# some build issues with upb for C++. +# TODO: Update this to v1.50.0 when 1.50.0 is out. +http_archive( + name = "com_github_grpc_grpc", + strip_prefix = "grpc-c3714eced8c51db9092e0adc2a1dfb715655c795", + urls = [ + "https://github.com/grpc/grpc/archive/c3714eced8c51db9092e0adc2a1dfb715655c795.tar.gz", + ], +) + +# Override the abseil-cpp version defined in grpc_deps(), which doesn't work on latest macOS +# Fixes https://github.com/bazelbuild/bazel/issues/15168 +# This section is excerpted from https://github.com/bazelbuild/bazel/blob/master/distdir_deps.bzl +http_archive( + name = "com_google_absl", + sha256 = "dcf71b9cba8dc0ca9940c4b316a0c796be8fab42b070bb6b7cab62b48f0e66c4", + strip_prefix = "abseil-cpp-20211102.0", + urls = [ + "https://mirror.bazel.build/github.com/abseil/abseil-cpp/archive/refs/tags/20211102.0.tar.gz", + "https://github.com/abseil/abseil-cpp/archive/refs/tags/20211102.0.tar.gz", + ], +) + +# Projects using gRPC as an external dependency must call both grpc_deps() and +# grpc_extra_deps(). +load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps") + +grpc_deps() + +load("@com_github_grpc_grpc//bazel:grpc_extra_deps.bzl", "grpc_extra_deps") + +grpc_extra_deps() + +load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") + +protobuf_deps() + +http_archive( + name = "com_google_googleapis", + sha256 = "5bb6b0253ccf64b53d6c7249625a7e3f6c3bc6402abd52d3778bfa48258703a0", + strip_prefix = "googleapis-2f9af297c84c55c8b871ba4495e01ade42476c92", + urls = [ + "https://mirror.bazel.build/github.com/googleapis/googleapis/archive/2f9af297c84c55c8b871ba4495e01ade42476c92.tar.gz", + "https://github.com/googleapis/googleapis/archive/2f9af297c84c55c8b871ba4495e01ade42476c92.tar.gz", + ], +) + +http_archive( + name = "upb", + sha256 = "03b642a535656560cd95cab3b26e8c53ce37e472307dce5bb7e47c9953bbca0f", + strip_prefix = "upb-e5f26018368b11aab672e8e8bb76513f3620c579", + urls = [ + "https://mirror.bazel.build/github.com/protocolbuffers/upb/archive/e5f26018368b11aab672e8e8bb76513f3620c579.tar.gz", + "https://github.com/protocolbuffers/upb/archive/e5f26018368b11aab672e8e8bb76513f3620c579.tar.gz", + ], +) + +http_archive( + name = "bazel_gazelle", + sha256 = "de69a09dc70417580aabf20a28619bb3ef60d038470c7cf8442fafcf627c21cb", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz", + "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz", + ], +) + +load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") + +gazelle_dependencies() + +# load python rules here +# Using commit f0efec5cf8c0ae16483ee677a09ec70737a01bf5 +http_archive( + name = "rules_python", + strip_prefix = "rules_python-f0efec5cf8c0ae16483ee677a09ec70737a01bf5", + url = "https://github.com/bazelbuild/rules_python/archive/f0efec5cf8c0ae16483ee677a09ec70737a01bf5.zip", +) + +load("@rules_python//python:pip.bzl", "pip_parse") + +pip_parse( + name = "bentoml_requirements", + requirements_lock = "//grpc-client/python:requirements.lock.txt", +) + +# Load the starlark macro which will define your dependencies. +load("@bentoml_requirements//:requirements.bzl", "install_deps") + +# Call it to define repos for your requirements. +install_deps() + +# io_grpc_grpc_java is for java_grpc_library and related dependencies. +# Using commit 0cda133c52ed937f9b0a19bcbfc36bf2892c7aa8 +http_archive( + name = "io_grpc_grpc_java", + sha256 = "35189faf484096c9eb2928c43b39f2457d1ca39046704ba8c65a69482f8ceed5", + strip_prefix = "grpc-java-0cda133c52ed937f9b0a19bcbfc36bf2892c7aa8", + urls = ["https://github.com/grpc/grpc-java/archive/0cda133c52ed937f9b0a19bcbfc36bf2892c7aa8.tar.gz"], +) + +http_archive( + name = "rules_jvm_external", + sha256 = "c21ce8b8c4ccac87c809c317def87644cdc3a9dd650c74f41698d761c95175f3", + strip_prefix = "rules_jvm_external-1498ac6ccd3ea9cdb84afed65aa257c57abf3e0a", + url = "https://github.com/bazelbuild/rules_jvm_external/archive/1498ac6ccd3ea9cdb84afed65aa257c57abf3e0a.zip", +) + +load("@rules_jvm_external//:defs.bzl", "maven_install") +load("@io_grpc_grpc_java//:repositories.bzl", "IO_GRPC_GRPC_JAVA_ARTIFACTS", "IO_GRPC_GRPC_JAVA_OVERRIDE_TARGETS", "grpc_java_repositories") + +IO_GRPC_GRPC_KOTLIN_ARTIFACTS = [ + "com.squareup:kotlinpoet:1.11.0", + "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2", + "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.2", + "org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.6.2", +] + +maven_install( + artifacts = [ + "com.google.jimfs:jimfs:1.1", + "com.google.truth.extensions:truth-proto-extension:1.0.1", + "com.google.protobuf:protobuf-kotlin:3.18.0", + ] + IO_GRPC_GRPC_KOTLIN_ARTIFACTS + IO_GRPC_GRPC_JAVA_ARTIFACTS, + generate_compat_repositories = True, + override_targets = IO_GRPC_GRPC_JAVA_OVERRIDE_TARGETS, + repositories = [ + "https://repo.maven.apache.org/maven2/", + ], +) + +load("@maven//:compat.bzl", "compat_repositories") + +compat_repositories() + +grpc_java_repositories() + +# loading kotlin rules +# first to load grpc/grpc-kotlin +http_archive( + name = "com_github_grpc_grpc_kotlin", + sha256 = "b1ec1caa5d81f4fa4dca0662f8112711c82d7db6ba89c928ca7baa4de50afbb2", + strip_prefix = "grpc-kotlin-a1659c1b3fb665e01a6854224c7fdcafc8e54d56", + urls = ["https://github.com/grpc/grpc-kotlin/archive/a1659c1b3fb665e01a6854224c7fdcafc8e54d56.tar.gz"], +) + +http_archive( + name = "io_bazel_rules_kotlin", + sha256 = "a57591404423a52bd6b18ebba7979e8cd2243534736c5c94d35c89718ea38f94", + urls = ["https://github.com/bazelbuild/rules_kotlin/releases/download/v1.6.0/rules_kotlin_release.tgz"], +) + +load("@io_bazel_rules_kotlin//kotlin:repositories.bzl", "kotlin_repositories") + +kotlin_repositories() + +load("@io_bazel_rules_kotlin//kotlin:core.bzl", "kt_register_toolchains") + +kt_register_toolchains() + +# swift rules +# TODO: Currently fails at detecting compiled gRPC swift library +# Since CgRPC is deprecated, seems like no rules are being maintained +# for the newer swift implementation. diff --git a/bentoml/_internal/io_descriptors/numpy.py b/bentoml/_internal/io_descriptors/numpy.py index aa730a7dead..c136faf57fe 100644 --- a/bentoml/_internal/io_descriptors/numpy.py +++ b/bentoml/_internal/io_descriptors/numpy.py @@ -192,6 +192,12 @@ async def predict(input_array: np.ndarray) -> np.ndarray: result = await runner.run(input_array) When ``enforce_shape=True`` is provided, BentoML will raise an exception if the input array received does not match the `shape` provided. + + .. dropdown:: About the behaviour of ``shape`` + :icon: triangle-down + + If specified, then both :meth:`bentoml.io.NumpyNdarray.from_http_request` and :meth:`bentoml.io.NumpyNdarray.from_proto` + will reshape the input array before sending it to the API function. enforce_shape: Whether to enforce a certain shape. If ``enforce_shape=True`` then ``shape`` must be specified. Returns: @@ -432,10 +438,32 @@ async def from_proto(self, field: pb.NDArray | bytes) -> ext.NpNDArray: context: grpc.ServicerContext Returns: - a ``numpy.ndarray`` object. This can then be used - inside users defined logics. + ``numpy.ndarray``: A ``np.array`` constructed from given protobuf message. + + .. seealso:: + + :ref:`Protobuf representation of np.ndarray ` + + .. note:: + + Currently, we support ``pb.NDArray`` and ``serialized_bytes`` as valid inputs. + ``serialized_bytes`` will be prioritised over ``pb.NDArray`` if both are provided. + Serialized bytes has a specialized bytes representation and should not be used by users directly. """ if isinstance(field, bytes): + + # We will be using ``np.frombuffer`` to deserialize the bytes. + # This means that we need to ensure that ``dtype`` are provided to the IO descriptor + # + # ```python + # from __future__ import annotations + # + # import numpy as np + # + # @svc.api(input=NumpyNdarray(dtype=np.float16), output=NumpyNdarray()) + # def predict(input: np.ndarray): + # ... # input will be serialized with np.frombuffer, and hence dtype is required + # ``` if not self._dtype: raise BadInput( "'serialized_bytes' requires specifying 'dtype'." @@ -445,6 +473,14 @@ async def from_proto(self, field: pb.NDArray | bytes) -> ext.NpNDArray: array = np.frombuffer(field, dtype=self._dtype) else: assert isinstance(field, pb.NDArray) + + # The behaviour of dtype are as follows: + # - if not provided: + # * All of the fields are empty, then we return a ``np.empty``. + # * We will loop through all of the provided fields, and only allows one field per message. + # If here are more than two fields (i.e. ``string_values`` and ``float_values``), then we will raise an error, as we don't know how to deserialize the data. + # - if provided: + # * We will use the provided dtype-to-field maps to get the data from the given message. if field.dtype == pb.NDArray.DTYPE_UNSPECIFIED: dtype = None else: @@ -474,9 +510,13 @@ async def from_proto(self, field: pb.NDArray | bytes) -> ext.NpNDArray: except ValueError: array = np.array(values_array) + # We will try to reshape the array if ``shape`` is provided. + # Note that all of the logics here are handled in-place, meaning that we will ensure + # not to create new copies of given initialized array. if field.shape: array = np.reshape(array, field.shape) + # We will try to run validation process before sending this of to the user. return self.validate_array(array) async def to_proto(self, obj: ext.NpNDArray) -> pb.NDArray: @@ -484,11 +524,10 @@ async def to_proto(self, obj: ext.NpNDArray) -> pb.NDArray: Process given objects and convert it to grpc protobuf response. Args: - obj: `np.ndarray` that will be serialized to protobuf - context: grpc.aio.ServicerContext from grpc.aio.Server + obj: ``np.array`` that will be serialized to protobuf. Returns: - `io_descriptor_pb2.Array`: - Protobuf representation of given `np.ndarray` + ``pb.NDArray``: + Protobuf representation of given ``np.ndarray`` """ try: obj = self.validate_array(obj) diff --git a/bentoml/_internal/server/grpc/config.py b/bentoml/_internal/server/grpc/config.py index 80735fedfb7..a025f1df501 100644 --- a/bentoml/_internal/server/grpc/config.py +++ b/bentoml/_internal/server/grpc/config.py @@ -68,6 +68,7 @@ def options(self) -> grpc.aio.ChannelArgumentType: if self.max_message_length: options.extend( ( + # grpc.max_message_length this is a deprecated options, for backward compatibility ("grpc.max_message_length", self.max_message_length), ("grpc.max_receive_message_length", self.max_message_length), ("grpc.max_send_message_length", self.max_message_length), diff --git a/bentoml/grpc/v1alpha1/service.proto b/bentoml/grpc/v1alpha1/service.proto index 4dd6b5810da..3fe8feeccf2 100644 --- a/bentoml/grpc/v1alpha1/service.proto +++ b/bentoml/grpc/v1alpha1/service.proto @@ -7,7 +7,7 @@ import "google/protobuf/wrappers.proto"; // cc_enable_arenas pre-allocate memory for given message to improve speed. (C++ only) option cc_enable_arenas = true; -option go_package = "github.com/bentoml/grpc/v1alpha1"; +option go_package = "github.com/bentoml/bentoml/grpc/v1alpha1;service"; option java_multiple_files = true; option java_outer_classname = "ServiceProto"; option java_package = "com.bentoml.grpc.v1alpha1"; diff --git a/bentoml/grpc/v1alpha1/service_pb2.py b/bentoml/grpc/v1alpha1/service_pb2.py index 7a8e2b89f05..4e5fbee4b21 100644 --- a/bentoml/grpc/v1alpha1/service_pb2.py +++ b/bentoml/grpc/v1alpha1/service_pb2.py @@ -21,9 +21,9 @@ name='bentoml/grpc/v1alpha1/service.proto', package='bentoml.grpc.v1alpha1', syntax='proto3', - serialized_options=b'\n\031com.bentoml.grpc.v1alpha1B\014ServiceProtoP\001Z github.com/bentoml/grpc/v1alpha1\220\001\001\370\001\001\242\002\003SVC', + serialized_options=b'\n\031com.bentoml.grpc.v1alpha1B\014ServiceProtoP\001Z0github.com/bentoml/bentoml/grpc/v1alpha1;service\220\001\001\370\001\001\242\002\003SVC', create_key=_descriptor._internal_create_key, - serialized_pb=b'\n#bentoml/grpc/v1alpha1/service.proto\x12\x15\x62\x65ntoml.grpc.v1alpha1\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1egoogle/protobuf/wrappers.proto\"\xa3\x03\n\x07Request\x12\x10\n\x08\x61pi_name\x18\x01 \x01(\t\x12\x31\n\x07ndarray\x18\x03 \x01(\x0b\x32\x1e.bentoml.grpc.v1alpha1.NDArrayH\x00\x12\x35\n\tdataframe\x18\x05 \x01(\x0b\x32 .bentoml.grpc.v1alpha1.DataFrameH\x00\x12/\n\x06series\x18\x06 \x01(\x0b\x32\x1d.bentoml.grpc.v1alpha1.SeriesH\x00\x12+\n\x04\x66ile\x18\x07 \x01(\x0b\x32\x1b.bentoml.grpc.v1alpha1.FileH\x00\x12,\n\x04text\x18\x08 \x01(\x0b\x32\x1c.google.protobuf.StringValueH\x00\x12&\n\x04json\x18\t \x01(\x0b\x32\x16.google.protobuf.ValueH\x00\x12\x35\n\tmultipart\x18\n \x01(\x0b\x32 .bentoml.grpc.v1alpha1.MultipartH\x00\x12\x1a\n\x10serialized_bytes\x18\x02 \x01(\x0cH\x00\x42\t\n\x07\x63ontentJ\x04\x08\x04\x10\x05J\x04\x08\x0b\x10\x0e\"\x92\x03\n\x08Response\x12\x31\n\x07ndarray\x18\x01 \x01(\x0b\x32\x1e.bentoml.grpc.v1alpha1.NDArrayH\x00\x12\x35\n\tdataframe\x18\x03 \x01(\x0b\x32 .bentoml.grpc.v1alpha1.DataFrameH\x00\x12/\n\x06series\x18\x05 \x01(\x0b\x32\x1d.bentoml.grpc.v1alpha1.SeriesH\x00\x12+\n\x04\x66ile\x18\x06 \x01(\x0b\x32\x1b.bentoml.grpc.v1alpha1.FileH\x00\x12,\n\x04text\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValueH\x00\x12&\n\x04json\x18\x08 \x01(\x0b\x32\x16.google.protobuf.ValueH\x00\x12\x35\n\tmultipart\x18\t \x01(\x0b\x32 .bentoml.grpc.v1alpha1.MultipartH\x00\x12\x1a\n\x10serialized_bytes\x18\x02 \x01(\x0cH\x00\x42\t\n\x07\x63ontentJ\x04\x08\x04\x10\x05J\x04\x08\n\x10\x0e\"\xde\x02\n\x04Part\x12\x31\n\x07ndarray\x18\x01 \x01(\x0b\x32\x1e.bentoml.grpc.v1alpha1.NDArrayH\x00\x12\x35\n\tdataframe\x18\x03 \x01(\x0b\x32 .bentoml.grpc.v1alpha1.DataFrameH\x00\x12/\n\x06series\x18\x05 \x01(\x0b\x32\x1d.bentoml.grpc.v1alpha1.SeriesH\x00\x12+\n\x04\x66ile\x18\x06 \x01(\x0b\x32\x1b.bentoml.grpc.v1alpha1.FileH\x00\x12,\n\x04text\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValueH\x00\x12&\n\x04json\x18\x08 \x01(\x0b\x32\x16.google.protobuf.ValueH\x00\x12\x1a\n\x10serialized_bytes\x18\x04 \x01(\x0cH\x00\x42\x10\n\x0erepresentationJ\x04\x08\x02\x10\x03J\x04\x08\t\x10\x0e\"\x95\x01\n\tMultipart\x12<\n\x06\x66ields\x18\x01 \x03(\x0b\x32,.bentoml.grpc.v1alpha1.Multipart.FieldsEntry\x1aJ\n\x0b\x46ieldsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12*\n\x05value\x18\x02 \x01(\x0b\x32\x1b.bentoml.grpc.v1alpha1.Part:\x02\x38\x01\"\xf1\x02\n\x04\x46ile\x12\x37\n\x04kind\x18\x01 \x01(\x0e\x32$.bentoml.grpc.v1alpha1.File.FileTypeH\x00\x88\x01\x01\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\x0c\"\x95\x02\n\x08\x46ileType\x12\x19\n\x15\x46ILE_TYPE_UNSPECIFIED\x10\x00\x12\x11\n\rFILE_TYPE_CSV\x10\x01\x12\x17\n\x13\x46ILE_TYPE_PLAINTEXT\x10\x02\x12\x12\n\x0e\x46ILE_TYPE_JSON\x10\x03\x12\x13\n\x0f\x46ILE_TYPE_BYTES\x10\x04\x12\x11\n\rFILE_TYPE_PDF\x10\x05\x12\x11\n\rFILE_TYPE_PNG\x10\x06\x12\x12\n\x0e\x46ILE_TYPE_JPEG\x10\x07\x12\x11\n\rFILE_TYPE_GIF\x10\x08\x12\x11\n\rFILE_TYPE_BMP\x10\t\x12\x12\n\x0e\x46ILE_TYPE_TIFF\x10\n\x12\x12\n\x0e\x46ILE_TYPE_WEBP\x10\x0b\x12\x11\n\rFILE_TYPE_SVG\x10\x0c\x42\x07\n\x05_kind\"Q\n\tDataFrame\x12\x14\n\x0c\x63olumn_names\x18\x01 \x03(\t\x12.\n\x07\x63olumns\x18\x02 \x03(\x0b\x32\x1d.bentoml.grpc.v1alpha1.Series\"\xa1\x01\n\x06Series\x12\x17\n\x0b\x62ool_values\x18\x01 \x03(\x08\x42\x02\x10\x01\x12\x18\n\x0c\x66loat_values\x18\x02 \x03(\x02\x42\x02\x10\x01\x12\x18\n\x0cint32_values\x18\x03 \x03(\x05\x42\x02\x10\x01\x12\x18\n\x0cint64_values\x18\x06 \x03(\x03\x42\x02\x10\x01\x12\x15\n\rstring_values\x18\x05 \x03(\t\x12\x19\n\rdouble_values\x18\x04 \x03(\x01\x42\x02\x10\x01\"\xc8\x03\n\x07NDArray\x12\x33\n\x05\x64type\x18\x01 \x01(\x0e\x32$.bentoml.grpc.v1alpha1.NDArray.DType\x12\r\n\x05shape\x18\x02 \x03(\x05\x12\x15\n\rstring_values\x18\x05 \x03(\t\x12\x18\n\x0c\x66loat_values\x18\x03 \x03(\x02\x42\x02\x10\x01\x12\x19\n\rdouble_values\x18\x04 \x03(\x01\x42\x02\x10\x01\x12\x17\n\x0b\x62ool_values\x18\x06 \x03(\x08\x42\x02\x10\x01\x12\x18\n\x0cint32_values\x18\x07 \x03(\x05\x42\x02\x10\x01\x12\x18\n\x0cint64_values\x18\x08 \x03(\x03\x42\x02\x10\x01\x12\x19\n\ruint32_values\x18\t \x03(\rB\x02\x10\x01\x12\x19\n\ruint64_values\x18\n \x03(\x04\x42\x02\x10\x01\"\xa9\x01\n\x05\x44Type\x12\x15\n\x11\x44TYPE_UNSPECIFIED\x10\x00\x12\x0f\n\x0b\x44TYPE_FLOAT\x10\x01\x12\x10\n\x0c\x44TYPE_DOUBLE\x10\x02\x12\x0e\n\nDTYPE_BOOL\x10\x03\x12\x0f\n\x0b\x44TYPE_INT32\x10\x04\x12\x0f\n\x0b\x44TYPE_INT64\x10\x05\x12\x10\n\x0c\x44TYPE_UINT32\x10\x06\x12\x10\n\x0c\x44TYPE_UINT64\x10\x07\x12\x10\n\x0c\x44TYPE_STRING\x10\x08\x32Y\n\x0c\x42\x65ntoService\x12I\n\x04\x43\x61ll\x12\x1e.bentoml.grpc.v1alpha1.Request\x1a\x1f.bentoml.grpc.v1alpha1.Response\"\x00\x42Y\n\x19\x63om.bentoml.grpc.v1alpha1B\x0cServiceProtoP\x01Z github.com/bentoml/grpc/v1alpha1\x90\x01\x01\xf8\x01\x01\xa2\x02\x03SVCb\x06proto3' + serialized_pb=b'\n#bentoml/grpc/v1alpha1/service.proto\x12\x15\x62\x65ntoml.grpc.v1alpha1\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1egoogle/protobuf/wrappers.proto\"\xa3\x03\n\x07Request\x12\x10\n\x08\x61pi_name\x18\x01 \x01(\t\x12\x31\n\x07ndarray\x18\x03 \x01(\x0b\x32\x1e.bentoml.grpc.v1alpha1.NDArrayH\x00\x12\x35\n\tdataframe\x18\x05 \x01(\x0b\x32 .bentoml.grpc.v1alpha1.DataFrameH\x00\x12/\n\x06series\x18\x06 \x01(\x0b\x32\x1d.bentoml.grpc.v1alpha1.SeriesH\x00\x12+\n\x04\x66ile\x18\x07 \x01(\x0b\x32\x1b.bentoml.grpc.v1alpha1.FileH\x00\x12,\n\x04text\x18\x08 \x01(\x0b\x32\x1c.google.protobuf.StringValueH\x00\x12&\n\x04json\x18\t \x01(\x0b\x32\x16.google.protobuf.ValueH\x00\x12\x35\n\tmultipart\x18\n \x01(\x0b\x32 .bentoml.grpc.v1alpha1.MultipartH\x00\x12\x1a\n\x10serialized_bytes\x18\x02 \x01(\x0cH\x00\x42\t\n\x07\x63ontentJ\x04\x08\x04\x10\x05J\x04\x08\x0b\x10\x0e\"\x92\x03\n\x08Response\x12\x31\n\x07ndarray\x18\x01 \x01(\x0b\x32\x1e.bentoml.grpc.v1alpha1.NDArrayH\x00\x12\x35\n\tdataframe\x18\x03 \x01(\x0b\x32 .bentoml.grpc.v1alpha1.DataFrameH\x00\x12/\n\x06series\x18\x05 \x01(\x0b\x32\x1d.bentoml.grpc.v1alpha1.SeriesH\x00\x12+\n\x04\x66ile\x18\x06 \x01(\x0b\x32\x1b.bentoml.grpc.v1alpha1.FileH\x00\x12,\n\x04text\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValueH\x00\x12&\n\x04json\x18\x08 \x01(\x0b\x32\x16.google.protobuf.ValueH\x00\x12\x35\n\tmultipart\x18\t \x01(\x0b\x32 .bentoml.grpc.v1alpha1.MultipartH\x00\x12\x1a\n\x10serialized_bytes\x18\x02 \x01(\x0cH\x00\x42\t\n\x07\x63ontentJ\x04\x08\x04\x10\x05J\x04\x08\n\x10\x0e\"\xde\x02\n\x04Part\x12\x31\n\x07ndarray\x18\x01 \x01(\x0b\x32\x1e.bentoml.grpc.v1alpha1.NDArrayH\x00\x12\x35\n\tdataframe\x18\x03 \x01(\x0b\x32 .bentoml.grpc.v1alpha1.DataFrameH\x00\x12/\n\x06series\x18\x05 \x01(\x0b\x32\x1d.bentoml.grpc.v1alpha1.SeriesH\x00\x12+\n\x04\x66ile\x18\x06 \x01(\x0b\x32\x1b.bentoml.grpc.v1alpha1.FileH\x00\x12,\n\x04text\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValueH\x00\x12&\n\x04json\x18\x08 \x01(\x0b\x32\x16.google.protobuf.ValueH\x00\x12\x1a\n\x10serialized_bytes\x18\x04 \x01(\x0cH\x00\x42\x10\n\x0erepresentationJ\x04\x08\x02\x10\x03J\x04\x08\t\x10\x0e\"\x95\x01\n\tMultipart\x12<\n\x06\x66ields\x18\x01 \x03(\x0b\x32,.bentoml.grpc.v1alpha1.Multipart.FieldsEntry\x1aJ\n\x0b\x46ieldsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12*\n\x05value\x18\x02 \x01(\x0b\x32\x1b.bentoml.grpc.v1alpha1.Part:\x02\x38\x01\"\xf1\x02\n\x04\x46ile\x12\x37\n\x04kind\x18\x01 \x01(\x0e\x32$.bentoml.grpc.v1alpha1.File.FileTypeH\x00\x88\x01\x01\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\x0c\"\x95\x02\n\x08\x46ileType\x12\x19\n\x15\x46ILE_TYPE_UNSPECIFIED\x10\x00\x12\x11\n\rFILE_TYPE_CSV\x10\x01\x12\x17\n\x13\x46ILE_TYPE_PLAINTEXT\x10\x02\x12\x12\n\x0e\x46ILE_TYPE_JSON\x10\x03\x12\x13\n\x0f\x46ILE_TYPE_BYTES\x10\x04\x12\x11\n\rFILE_TYPE_PDF\x10\x05\x12\x11\n\rFILE_TYPE_PNG\x10\x06\x12\x12\n\x0e\x46ILE_TYPE_JPEG\x10\x07\x12\x11\n\rFILE_TYPE_GIF\x10\x08\x12\x11\n\rFILE_TYPE_BMP\x10\t\x12\x12\n\x0e\x46ILE_TYPE_TIFF\x10\n\x12\x12\n\x0e\x46ILE_TYPE_WEBP\x10\x0b\x12\x11\n\rFILE_TYPE_SVG\x10\x0c\x42\x07\n\x05_kind\"Q\n\tDataFrame\x12\x14\n\x0c\x63olumn_names\x18\x01 \x03(\t\x12.\n\x07\x63olumns\x18\x02 \x03(\x0b\x32\x1d.bentoml.grpc.v1alpha1.Series\"\xa1\x01\n\x06Series\x12\x17\n\x0b\x62ool_values\x18\x01 \x03(\x08\x42\x02\x10\x01\x12\x18\n\x0c\x66loat_values\x18\x02 \x03(\x02\x42\x02\x10\x01\x12\x18\n\x0cint32_values\x18\x03 \x03(\x05\x42\x02\x10\x01\x12\x18\n\x0cint64_values\x18\x06 \x03(\x03\x42\x02\x10\x01\x12\x15\n\rstring_values\x18\x05 \x03(\t\x12\x19\n\rdouble_values\x18\x04 \x03(\x01\x42\x02\x10\x01\"\xc8\x03\n\x07NDArray\x12\x33\n\x05\x64type\x18\x01 \x01(\x0e\x32$.bentoml.grpc.v1alpha1.NDArray.DType\x12\r\n\x05shape\x18\x02 \x03(\x05\x12\x15\n\rstring_values\x18\x05 \x03(\t\x12\x18\n\x0c\x66loat_values\x18\x03 \x03(\x02\x42\x02\x10\x01\x12\x19\n\rdouble_values\x18\x04 \x03(\x01\x42\x02\x10\x01\x12\x17\n\x0b\x62ool_values\x18\x06 \x03(\x08\x42\x02\x10\x01\x12\x18\n\x0cint32_values\x18\x07 \x03(\x05\x42\x02\x10\x01\x12\x18\n\x0cint64_values\x18\x08 \x03(\x03\x42\x02\x10\x01\x12\x19\n\ruint32_values\x18\t \x03(\rB\x02\x10\x01\x12\x19\n\ruint64_values\x18\n \x03(\x04\x42\x02\x10\x01\"\xa9\x01\n\x05\x44Type\x12\x15\n\x11\x44TYPE_UNSPECIFIED\x10\x00\x12\x0f\n\x0b\x44TYPE_FLOAT\x10\x01\x12\x10\n\x0c\x44TYPE_DOUBLE\x10\x02\x12\x0e\n\nDTYPE_BOOL\x10\x03\x12\x0f\n\x0b\x44TYPE_INT32\x10\x04\x12\x0f\n\x0b\x44TYPE_INT64\x10\x05\x12\x10\n\x0c\x44TYPE_UINT32\x10\x06\x12\x10\n\x0c\x44TYPE_UINT64\x10\x07\x12\x10\n\x0c\x44TYPE_STRING\x10\x08\x32Y\n\x0c\x42\x65ntoService\x12I\n\x04\x43\x61ll\x12\x1e.bentoml.grpc.v1alpha1.Request\x1a\x1f.bentoml.grpc.v1alpha1.Response\"\x00\x42i\n\x19\x63om.bentoml.grpc.v1alpha1B\x0cServiceProtoP\x01Z0github.com/bentoml/bentoml/grpc/v1alpha1;service\x90\x01\x01\xf8\x01\x01\xa2\x02\x03SVCb\x06proto3' , dependencies=[google_dot_protobuf_dot_struct__pb2.DESCRIPTOR,google_dot_protobuf_dot_wrappers__pb2.DESCRIPTOR,]) diff --git a/docs/source/_static/css/custom.css b/docs/source/_static/css/custom.css index 279f1d10a2c..c3e32b9c673 100644 --- a/docs/source/_static/css/custom.css +++ b/docs/source/_static/css/custom.css @@ -1,39 +1,48 @@ /* adjust logo image position in sidebar */ .sidebar-logo-container { - margin: 23px 0 14px; + margin: 23px 0 14px; } .sidebar-logo-container img.sidebar-logo { - max-width: 60px; + max-width: 60px; } /* minimize logo image on mobile view */ @media screen and (max-width: 1070px) { - .sidebar-logo-container img.sidebar-logo { - max-width: 30px; - } + .sidebar-logo-container img.sidebar-logo { + max-width: 30px; + } } /* change the color of inactive labels in a :tabbed: view */ :root { - --tabs-color-label-inactive: rgb(149 149 149); + --tabs-color-label-inactive: rgb(149 149 149); } .sd-card-body { - background-color: var(--color-background-secondary); + background-color: var(--color-background-secondary); } /* lighter footer text color */ .bottom-of-page .left-details { - color: #c1c1c1; + color: #c1c1c1; } .bottom-of-page .left-details a { - color: #c1c1c1; - text-decoration: None; + color: #c1c1c1; + text-decoration: None; } body[data-theme="dark"] .bottom-of-page .left-details { - color: #81868d; + color: #81868d; } body[data-theme="dark"] .bottom-of-page .left-details a { - color: #81868d; - text-decoration: None; -} \ No newline at end of file + color: #81868d; + text-decoration: None; +} + +.content { + width: 50em; +} + +.sidebar-drawer { + width: calc(45% - 26em); +} + diff --git a/docs/source/_static/img/interceptor-flow.png b/docs/source/_static/img/interceptor-flow.png new file mode 100644 index 00000000000..3a01346fbac Binary files /dev/null and b/docs/source/_static/img/interceptor-flow.png differ diff --git a/docs/source/_static/img/interceptor-flow.svg b/docs/source/_static/img/interceptor-flow.svg new file mode 100644 index 00000000000..d8a75057016 --- /dev/null +++ b/docs/source/_static/img/interceptor-flow.svg @@ -0,0 +1,16 @@ + + + eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1dWVfbyrJ+z6/I4rzGOt1d3dXd5+UuIISZgM1871ks2Vx1MDAxNkZ4xJZcdTAwMTm81/7vt1pcdTAwMTAsXHUwMDBmkm2GXHUwMDFkkcR7J1x1MDAwMUmWSq36qr5cdTAwMWG69denz5+XoodOsPSfz0vBfcVvhNWuf7f0xW2/XHK6vbDdol1cIv691+53K/GRV1HU6f3n3/9cdTAwMWV+w6u0m4/fXG5cdTAwMWFBM2hFPTruf+n3z5//iv9OXFwnXG7uo/jYeOvwKpyx8a177VZ8Ra5ccuPKSDE8XCLsfaVLRUGVdl/6jV4w3OM2LYnB4UFttdjeulxcvYDT8+JxrTAoXGYve1x1MDAxOTZcdTAwMWGl6KFcdTAwMTFL1GvTXVxm9/WibrtcdTAwMWWchNXoyl17bHvat7rtfu2qXHUwMDE19Ho/XHUwMDA2LN7a7viVMHpcdTAwMTi7Pb9Vi88x3HLvjkAlPLpNg1x1MDAwMkCi0PJ5tzuB0eghjYExXHUwMDAyhJVgxlx1MDAwNFttN9pdJ9i/uEHpl4eilf1KvUbytarPx0Rdv9Xr+F16VMPj7n7cssTnbVdBWLuKaKNUw+tcdTAwMDXxsCstlGWGXHLFdFx1MDAxN+lsVodcdTAwMGY/3rrbf0C/sta4XHUwMDEzpqlPzVlh+ZadPV/VXHLHRbdYKJzs7VxcN3Y39prVXHUwMDA2trCx9LT/v+Pjf+V3O0/jvNRzvyRu1N3jWkJcdTAwMDOHX+53qv6jtnBESX+01Vpccu+zXHUwMDExtuq0s9VvNIbb2pX6XHUwMDE0XHUwMDA1u2y3olI4cFx1MDAwZlx1MDAxMHBk6ze/XHUwMDE5Nlx1MDAxZUaUJtZ2krRcdTAwMTjc9INelHwovYAuXHUwMDFhn0WMXHUwMDFjvtxcYmtcdTAwMGVcdTAwMTBLXHUwMDE1uo2gO4KVKCTIPVx1MDAxZlx1MDAxMLVcdTAwMTM3XyFcdTAwMDF8OmF3szp+I+1uWFx1MDAwYlt+43BMmHj/31+m47RcdTAwMWJUokdFnVx1MDAwNlalxrf+XHUwMDAwK3KUjGk51M9ZWFx1MDAxZFxcROvnXHUwMDE3p2vgXHUwMDE3dtehfqN5Yaead6xK5lx1MDAxOSuNlFZcdKn5OFalxzSTXHUwMDE2UaEh5L5cdTAwMWZW0ZBcdTAwMWMopVx1MDAwMLJcdTAwMWYwXHUwMDE1udZcdTAwMTNWWGCSxGJGmFx0JFx1MDAxYomWwJy4619cdTAwMWHJmWqf5p6E1mlcdTAwMWFPeqBcdTAwMTBcdTAwMTDn1/i78r69Wf6K3b1y86Qx0P0jeb6Vc40nXHUwMDFk8cg5cSk0M5YzNaLxVlx1MDAxOI9OwlFcdTAwMGJCPyidqvFcdTAwMTUrfOG/XFzjrZlUccvGVZrElEpaa3GGTp/jzsVh4fZkT+yffFxySp3SWqlw+fF0+nXeabVcdTAwMTGSUP/XmuqeXHUwMDEyiv/PuKdnaV7un4DZNLRcbiZIezXaIVx1MDAwZWbBVbZaXHUwMDA31ar2V8564aB7XT84XHSP7nJcdTAwMGZX8EBJ8lNScsvscDxcdTAwMWXhyjwmgSvJtDVEKd9ccq6cS1x1MDAwZlx1MDAwNV3FolHAyUtNwldcdTAwMTlPcbQglUZyiFxmxtEstVLaolx1MDAxOEI/d2B+vsZfzz8961x1MDAwZtS/nW6p7dZD5IviqmxcdTAwMWaebVx1MDAwZnRCrIRW+91u+27pec/fXHUwMDEzov0s94ephFx1MDAwZoRcdTAwMTFcXFx0NT+e/PVccrldre3fXHUwMDE26mtr953+1UH3ZjPfeFx1MDAxMlx1MDAxYZUnXHUwMDE0p6CHWbpdXHUwMDFjgVx1MDAxM9dcdTAwMDI9glx1MDAxMiNcdTAwMWEllTVKviOeYFx1MDAxMkCTwZkkSTWfTemq/vnVTaFcdTAwMWat9ztrq0GvssrDaOt3c3+loHub9GQ/MzZ7kuU1ro+Pb33Oo5Dnc4Y0QYpmQlx1MDAxNU+DvTNcYm63fLbOXHUwMDBlO1x1MDAxYq1a51veoaqkZ1x1MDAwMYWicIegysex6jxcdTAwMTJX4EyW4trCmGRviFVcIsVcdTAwMDBcdTAwMTa0spyT3eDDXHUwMDFieoYuomfAgCTqLFx1MDAxODeQXHUwMDE4p0ckW3KaXGYpxMsvkrN831llLbTVbatOq9vs+2VvsHJ9tPyxfJ8ymIoobo1cdTAwMDD6M3/sx1x1MDAwZqy+tuvRxt7Z3cpxdHhv+t9cdTAwMDb5Rlx1MDAxNLekpZZRQMU1XHUwMDE4zccyk1aAR/qprZCIXHUwMDAw0qZcdTAwMDIqQK1cdTAwMTl7OaBEXCLtlOH8XGJG2rpE6iy+eLRSXHUwMDE0p8VewVx1MDAxNNehYFprNih1r5KY6YqLK/+I7VxmeMm/b9arXHUwMDA3p6t1/bu5x++doHVcdTAwMTgn9qPuQz685KhImahcdTAwMGVcdTAwMWGNsNOb6ipcdTAwMTXPiFx1MDAxMilcdTAwMTaRKpndnlx1MDAwNWzVXHUwMDFiXHUwMDFjXHUwMDE1VypcdTAwMTDtRf7g6nLFv9m7h7xcdTAwMDObXFylRKvAcEHsXHUwMDAwx6JEXHUwMDA2XHUwMDFlSktuiTFltUyPXHUwMDEyX1xybIOeYIjknFx1MDAwMZiGoVx1MDAxY8NcdTAwMWOPXCJZgJ5cdTAwMGIqLoFcYvdcdTAwMDTqXHUwMDA1cW+BUs7ylHlGfZYrXV6xe3b5bvc2XFwr1K+r+mD7/PRgPlf6Jeu8v0J4ylOzs1x1MDAxY5QkdiVgftJ7VFvpf1x1MDAwZuXNbX2/dqNvNi7haE/mXHUwMDFiyUJw43GrkVtLTFLBXGKQuVDooVx1MDAxNYQs+lx00qPTV+OYTaG401x1MDAxYzSiXHUwMDExXHUwMDFjXHUwMDEzhHg6VO82T1bUyVqvXHUwMDEwti7KXHUwMDA3x7Lcur+p5Fx1MDAwNKr5cdD73TY5wqug38uHd07I81LXLHRqXHUwMDE0XHUwMDBizFx1MDAxYVx1MDAwM0LO75lXg+K537jZYZuV9mlwanb8Yuso73hm1pNWMeRMXHUwMDAxT5bkXHUwMDFmXHUwMDAxLZ3j1tKVXHUwMDE446LL+0GakyDk/7lA4EYnKjvPXHUwMDAw18qznFx1MDAwMVx1MDAxOVx1MDAxZlCKyYn0LSdcIkVBXHUwMDE0zmxcdTAwMTXIM9yzPGhls1x1MDAxM9xcZqLzy2AwqFx1MDAwZvY1XHUwMDBmXHUwMDAzuH1cdTAwMDPP7KtysVKsrjVrl2tcdTAwMGa971x1MDAxYlx1MDAxYvdcdTAwMWKy9bE8s4SM4JmUXHUwMDA1xVwiXHUwMDFju/7t3q+1l5vbW61qd3/A19ryXCLvSGaMfK+WRoGgXHUwMDEw2ZjRwilxWeZxgjkndNGx6YXT11x1MDAwMplY/Dy+WYF1XHUwMDE3glmFU75xUrMq2F82vFx1MDAxMOxcdTAwMWPZbmmlcZ9cdTAwMTOs5sc1L1cqJPNOu5ZcdTAwMGbPPFx1MDAxNOeljlkmzPt4548wWuJcdTAwMDJo3ru+7a9wiFx1MDAwNrzSq1x1MDAxN4+vK0d9uZFvNFx1MDAxM71cdTAwMTaelppcdTAwMTlcdTAwMDIzuV0+jmai4Vxi6Lpp0ILN6IN4vV+WJFxi40oz63Ldk9g23KNcdTAwMWTCZSct0HNJcIRHqGtcblx1MDAxNej/mW45z1DPcp+tu+uru2jlUNdX77bWfKhW927x57rlzPNuXpf3ukc0QitHXHUwMDA1s1fsdcpcdTAwMGb9w3y6+2roN9ut6jRcdTAwMGKh0julSFx1MDAxZJGcPs7fx+tcdTAwMWZvtppcdTAwMDfLxaPwXGZWWXtrLVx1MDAxYci9fJtcYoqBucclXHUwMDExeGmMNHwsXHUwMDE0Z1x1MDAxNjztelx1MDAwMplhQimZ3lx1MDAxYqgqVrLqK1x1MDAxY76aUirmfFwicSalcFm+mXmz3KF8KvdMKE9dh637Mi9FZvNha73hhyDC83nB+F7ZtveKXHUwMDE1XmHsclx1MDAxMyuo9CxcdTAwMWV3wTAws0CsMPXp5910YIbp4MJ64p8xXHUwMDFkcq4uXHUwMDEzblxmXHUwMDExXHUwMDBiXHUwMDA0M6vN5E8sQJK6YlVY6f3P24ZcdTAwMDLNsFpNdoKMRlx1MDAwM7NcdTAwMWPoeIDwLGMmgFx1MDAxZlxyyFx1MDAxNFx1MDAwNFuZmrazhlx1MDAxOc2Isc5ccuBsXHUwMDBinFNcdTAwMDBb6zFEa5VllsFooZy+LzzG0UilXGZFSjy9oPZaXHUwMDAwg+dql+DK8aC5XHUwMDExU+rmRnlcdTAwMTRcdTAwMDBYqVxcU5C0fFxuuoVAuo+ZbZfviO74lv9pdPdcIr9cdTAwMWKthK1q2KrRzqGz/TG9bXOOgm9sXHUwMDBmKn0nJfNcdTAwMTiTgmtEeu6C0YhcdTAwMGZNqVx1MDAxYlS/41TH04L+U4IzZSypyNNcdTAwMDHPXn8paFVnXHUwMDBilY33XHUwMDExoUhcdTAwMWRcdTAwMDGEtVxcM8E4mfFcdJk0MVUrJFx1MDAwM61JiUhZJmRq+L1otd1shlx1MDAxMY39fjtsReNjXHUwMDFjXHUwMDBm5rIzXHUwMDE4V4FcdTAwMWaLWPZcdTAwMTP2jG5qZCeZn7HWto4766jGXHJ/+jxcdTAwMDRg/Mvzz//9MvXoVFxcuM9cdTAwMDRcIoZn+5T8d2GzyHlq96xgsCixyWaQ+bSL3MQgYECRhk705TzyXHUwMDFhpTxGXHUwMDAzXHUwMDBlqEFcdTAwMGKWUZ98Na9xrUouv0nMxYKBac202nrG9UhqxZlmk/lRVFx1MDAwMPRnVunyNzWKc9ufXHUwMDAy80gg4FZcdTAwMWKBljEtXHUwMDEyXHUwMDA3PZpcdTAwMWbHhaUzP1x1MDAwZa+KNOdlJjG7/DdiXHUwMDEyhTKKk3ZIsolMJ9J3zzKRclx1MDAxONJcdTAwMWXiMsS/lTVcdTAwMWbeXCJqelx1MDAwZYZZtMZNOubDlij3IbhIZUAxumltKO5cdTAwMTCzTpeOsPhio9h6K/tKXCKmXHUwMDA2jlx1MDAxNC1JOzK5cZZ9zY6k82lfXHUwMDE5N1x1MDAxZVFcdTAwMDdtkaNFMdrHXHUwMDE1XHUwMDFiWOtcdTAwMWNcdTAwMWXxUkxy9De3r5Z7XHUwMDE2XFzJy9lcdTAwMWLN+ZTJekKRXHUwMDEyMM1cdTAwMTVpguvBTqTInywsaaHhfGbH81x1MDAxZlx1MDAwYjuL4VmjgLwuwZtYXHUwMDFliElrJt0kZsZcdKfO6KGZNGdzWdjsQs6ISPS8NZDdJH8vXHUwMDA0IJ9cdTAwMTBJeFZcdTAwMTgmRNxYTZZHw4c3sVx1MDAwNaL0niRTXGJA5FOphLlyXHUwMDFmUnXPxtaVcauAXHUwMDAyrlknNOiRQUPtXHUwMDA20LqpXGYjNntcdTAwMWNfb2VlZVwidlx1MDAxZrOyZM6VS1nNT2Kzayj5NLJARlZq8pKMXHUwMDExZ+fjTXZcdTAwMDQwzymudvFUZu3vtWaWkOOBlIa4KFl1kyDMyehcdTAwMWWZm25JXHUwMDA0S5HkkzPEnI9GXG48/ljZpz0vI42Ox1wiXHUwMDEyPaVcdTAwMDDZuNB9uNpDgshcIlx1MDAwNZWEU7dcdTAwMDaDQYEvXGbu57azXHUwMDA1Tlx1MDAwMZVBo7Sb48RIMydcdTAwMTNcdTAwMGVIh2iNTlUlk+RyP76d1crjhFx1MDAwYmLwwqDzXHUwMDFkya+TWVx1MDAxNMROmSOlzOXoZp4uXHUwMDE1Zu4zXHUwMDAxsFx1MDAwNc1saitzxjpI5MiZXHUwMDE0ev5OZrDfyvW7i85xceV76+Fko7pcdTAwMWNcdTAwMWOrnFtZJrWHzv9bTUGCMEMzXHUwMDFhn4DCQFwiXHUwMDExblY7s5rG41x1MDAxZFx1MDAxYqbEXHUwMDE09jqlXGJcdTAwMDJcdTAwMTRcdTAwMWOBMjPTpGx5//BsOSxfnJduS+HZxvfDg1x1MDAwNs9JXHUwMDFiRX6qJKv9XvS0mtjTU/t57VJPsmTiOKNcdTAwMTNC6NSwVCPFapItMG9ws1x1MDAxOZ2vnKLfXHSOXHUwMDA2fVxyNXOwu7+bdyhcdTAwMGLjMa6Jq1x1MDAxMjrc3J5RKCuyoYpcZigggzgj8H5QRuGR2VdAZsVcdTAwMTBxVlOmKZCcXHUwMDFleVxyx+wkPVx1MDAxYm0m+pjdpDBp6KnOyv3lXHUwMDE56c/XeNtcdTAwMDakL1nnrbTv9/dLXHUwMDE3XFxsXHUwMDFmf4tKvc2Hm1YweIPzfqDJxalRleDp8/WJWlAoYVx1MDAxNqhccmQ/wXxaXHTOjCcorqLAmNib0WNcdTAwMGWf8Oi5xc2MJWpt7XtcdTAwMTZcdTAwMDfitdSUXHUwMDE1XHUwMDE0qFuJbErRlKP1KE5H2muFmyA6biNcZkqmXFwn95+w6mnPizNFXHUwMDE0nDJcbqksXHRmXHUwMDAwJjNF3COXXCKU0lxmXHUwMDFjXHUwMDE3XHUwMDE0LyxcdTAwMGZkO9bPY/k0Q1x1MDAwM0XBXHUwMDAwuVx00HJCJEEhXHUwMDA2XHUwMDE3Vlxu1EiBXHUwMDFl/lx1MDAwMsmrdFS4XHUwMDBmSo/R09GK4lotXHUwMDAxzKzTXHUwMDE54WnL4lVcdTAwMDSAXHUwMDAwhJg821x1MDAwNLqGZ/uU/HfxXHUwMDAyrM1YXHUwMDE0hTGKleVcdTAwMDJRVbY7y6eRdZ1lgFx1MDAxNDxyt1x1MDAxZYlcdTAwMWVfXHUwMDE0XHUwMDA1NaEpXslSuaW48D2trPaAXGY5d0l+JpJcdTAwMGKFJqhcdTAwMTh4XG6AwngwLjutJlrXuUYlhMCZ01R+Uzs7t1FzmVwi5sJtXHUwMDFhc+K2hrFpVo2ehJZu4SvXJoJcdTAwMWOzLe3ofXwwe1eQ0qNnpI3i5MltYvGXeK+xnuRk4JlCZJrc0jxcdTAwMDZcdTAwMGbc7Ekm3Yp7KEZON6npXHUwMDBimry0NFJcIic8UVx1MDAxMbXEsWCRZH3x6n6709vqP1xctc5r5y22e7LeXGZzbvBcXFx1MDAxOVx1MDAwNFxc6Uso4tFWj03U4Vx1MDAxMlxcwZTcXHUwMDE2MHJIMj32ZGUtjHq5wVNzZZGI5lx1MDAwM1x1MDAxM5CcyfunlTY1SfRcdTAwMTDkZKq7XHUwMDEz5EX45OnFNHLBXHUwMDAwqLVZYFx0bW5Xwpo6Kdw1TyolWL8tXHUwMDA2Z/v5XHUwMDA2qCuXXHUwMDEwI3nkJKAovFx1MDAxYlx1MDAwM6hcdTAwMDFcdTAwMGZcdTAwMWQ63SqGPFx1MDAxOVx1MDAxNb41QKetNjMlzavdksJoZq5Z8Vx1MDAwN6Cuh6adXHUwMDBmfJJcdTAwMWMvgmdiraNxdDKtpXSB6dzovN5kXHUwMDAzefBwVy/2vrHC4NLyaHcn3+hcdTAwMTRcdTAwMTRcbniu504gXHUwMDFh11x1MDAxYTKeulx1MDAwNdfmJVxcWcz16KUv+fbPeE9uWTxnXHUwMDFkf2La5eOA8+N7z4xFJcC62Vx1MDAxNcl1pmfBc+Vqq10unVx1MDAxZl/j6u5Bu3FRXHKPjc43PCmIR88wtJpcdTAwMTFNYGxcdTAwMDKeys1SJ1x1MDAwZqtpIGwy2lx1MDAxZsenQVx1MDAxMdjKeztPwqbrXHUwMDBmMn/I7Y+tv4DvnPGGmHRcdTAwMGbqilx1MDAxYZLLXHUwMDA1ip9+dFg4/Nr8Vti3t8ereu04XHUwMDFhbL9txq3q966CNya44FxuXHUwMDE3SlqjmEYhhteO31x1MDAxMaPQXHUwMDEz5EZccqlcdTAwMTiFqOmrXHUwMDEwXHUwMDEzQ76s6JcjXHUwMDE0NfNcdTAwMTRz7TBkXHUwMDFiTfI1JcO1XHUwMDE1kXuET7JcdTAwMTXgXHUwMDEyXHUwMDFlk+5VuJytSEYkvzR+X672kEhnjr8mxq1BuNCKosFl8HB6fYDfLnt3Wlx1MDAxZLH1wp5dzrdjXHUwMDEypCTOMVFcdTAwMDBrmZViLO1iQHhcdTAwMTaVm9TBNVx1MDAwMktPM/vIXHUwMDAzmZ1m/telX2ZMTfFJYDzBhFx1MDAwNsNAusTmpMYr7bn8MUhuXHUwMDE0N0l6+6M9kmmBI1x1MDAxZK3TNb5ePi75u8vbW5uNvWZUrFx1MDAxZlxc8Z2vOSn1//RcdP1cdTAwMWFTeVx1MDAxYTqORmowP1x1MDAxYapcdTAwMGab3e1S806a+o1YuVu77Vxyem+bhHx7XHUwMDFmICj28Uw819DNbVx1MDAwMzuGXHUwMDA2N1nYZWO5YtZmvCaMxZ9XsDQx73x+13KQfL3F7631ryNym46YVYJO1O5+7kX0yPLB6ybFylx1MDAwNHdqUVx1MDAxNTC9xsCMcPWTXHUwMDA1KF72KsS5dHZcXLnijnRdXGKE8YlXokn0mDXCOTs3XyA9gSnKxofghb5cdTAwMGXBI0eH4NpjtOI4pWmFxat4S+7mXHUwMDE5o9JSJdI1j8BcdTAwMDeuXHUwMDA158m1gabj/tcslma/3+vzSK8/J1x1MDAxNmdIs4HTIyXebFx1MDAxMof9mFNFbs9N5WfEXHUwMDFm3Fs/nlx1MDAwZViwLWWR1Vx1MDAwNeJ3hFhi70KCpeGakMlNXHUwMDBlXHUwMDE0xkqtmSFN4XpCpo9Wpk3X+/jrXHUwMDEzXHUwMDFhPzzfp+S/XHUwMDBil21s6kx+Lt1qe25ccvq5bV5cdTAwMWQ6Jmi2/PJa92urpG53LlxuXHUwMDBmeZ/Jr8isuTeOonXNenY0qHXTpj3i9lx1MDAxNPJcbresWKrJ+4feUczdXFxcdTAwMWLJZqeFl9dqXHUwMDA3hfv11v7Xk9717lFhcLZrfje68ou9o1io9FxuK5NgXHUwMDAw+Vx1MDAwMotcdTAwMTGVo83O9WXnXHUwMDAxlyvbvYOL+ztxXcz7fEVcdTAwMDXKcz5Bca0oXHUwMDFhXHUwMDFl64BA65Z3MS6VjG5VmvfD6lu8o5jkJ4PD9czq66+B5JcxcsHSXHUwMDE353VcdTAwMTN0XHUwMDFkR5hb47Ob6/Op8cTOPKnAdVNJ6abqjnkn5d5hTM/CWiFcdTAwMWNzeFx1MDAxN0qOgJ5CXHUwMDA27rWYXG7tNEYuPLeivktcZiC6yaNcdTAwMTNrIFx1MDAxOJcqXHUwMDE0yZdq/k6EfJGWbFx1MDAxMlx1MDAwNlx1MDAxOPFcdTAwMGVcdTAwMWTPIZpcXETmhfQ7+8WHozGB0Fqa+P1cYsLyXHUwMDA0XHUwMDA2n2VQXHUwMDFl6Vx1MDAxYndcdTAwMDExMSOmxcen36k67j6FXHT1TmPfn54usOR3OqWItOz5iSzdhsHdyjTUxVx1MDAxZqfi8eg5O1x1MDAxNLi7+uvvT3//PyDWnz0ifQ== + + + + RequestClientServerOpenTelemetryPrometheusAccessLogmetrics?CustomyesnoyesnoInterceptor stackRequest \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 112e7f418d5..fd1644aaaa3 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -17,6 +17,14 @@ # See https://github.com/readthedocs/readthedocs.org/issues/2149 master_doc = "index" +# exclude patterns +exclude_patterns = [ + "**/*/bazel-*", # generated by bazel + "**/*/node_modules/*", # node_modules + "**/*/.build/*", # generated by swift + "**/*/thirdparty/*", # generated by swift +] + # Sphinx extensions extensions = [ "sphinxext.opengraph", @@ -26,6 +34,8 @@ "sphinx.ext.todo", "sphinx.ext.viewcode", "sphinx.ext.ifconfig", + "sphinx.ext.intersphinx", + "sphinx.ext.extlinks", "sphinx_click.ext", "sphinx_copybutton", "sphinx_design", @@ -33,7 +43,33 @@ "sphinxcontrib.spelling", "myst_parser", "sphinx_inline_tabs", + "hoverxref.extension", ] +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "pip": ("https://pip.pypa.io/en/latest", None), +} +extlinks = { + "pypi": ("https://pypi.org/project/%s", "%s"), # noqa: WPS323 + "wiki": ("https://wikipedia.org/wiki/%s", "%s"), # noqa: WPS323 + "github": ("https://github.com/%s", "%s"), # noqa: WPS323 +} +# custom roles +rst_prolog = """ +.. role:: raw-html(raw) + :format: html +""" +# hoverxref settings +hoverxref_auto_ref = True +hoverxref_sphinxtabs = True +hoverxref_role_types = { + "hoverxref": "modal", + "ref": "tooltip", + "mod": "tooltip", + "class": "tooltip", + "doc": "tooltip", +} +hoverxref_intersphinx = ["python", "pip"] # Plugin Configurations: napoleon_google_docstring = True diff --git a/docs/source/guides/configuration.rst b/docs/source/guides/configuration.rst index 3dd346d7638..77fc2e29421 100644 --- a/docs/source/guides/configuration.rst +++ b/docs/source/guides/configuration.rst @@ -7,34 +7,31 @@ features can be customized through configuration. Both BentoML CLI and Python AP by the configuration. Configuration is best used for scenarios where the customizations can be specified once and applied to the entire team. -.. note:: - BentoML configuration underwent a major redesign in release 0.13.0. Previous configuration set through the - `bentoml config` CLI command is no longer compatible with the configuration releases in 0.13.0 and above. - Please see legacy configuration property mapping table below to upgrade configuration to the new format. - -BentoML configuration is defined by a YAML file placed in a directory specified by the :code:`BENTOML_CONFIG` -environment variable. The example below starts the bento server with configuration defined in :code:`~/bentoml_configuration.yaml` +BentoML configuration is defined by a YAML file placed in a directory specified by the ``BENTOML_CONFIG`` +environment variable. The example below starts the bento server with configuration defined in ``~/bentoml_configuration.yaml``: .. code-block:: shell - $ BENTOML_CONFIG=~/bentoml_configuration.yml bentoml serve IrisClassifier:latest + $ BENTOML_CONFIG=~/bentoml_configuration.yaml bentoml serve iris_classifier:latest Users only need to specify a partial configuration with only the properties they wish to customize instead -of a full configuration schema. In the example below, the microbatching workers count is overridden to 4. +of a full configuration schema. In the example below, the microbatching workers count is overridden to 4. Remaining properties will take their defaults values. .. code-block:: yaml - :caption: BentoML Configuration + :caption: `~/bentoml_configuration.yaml` api_server: + workers: 4 + timeout: 60 + http: port: 6000 - timeout: 120 - backlog: 1024 Throughout the BentoML documentation, features that are customizable through configuration are demonstrated -like the example above. For a full configuration schema including all customizable properties, refer to -the BentoML configuration template defined in -`default_configuration.yml `_. +like the example above. For a full configuration schema including all customizable properties, refer to +the BentoML configuration template defined in :github:`default_configuration.yml `. + + Docker Deployment ----------------- diff --git a/docs/source/guides/containerization.rst b/docs/source/guides/containerization.rst index 01a26c26091..abaedec96b3 100644 --- a/docs/source/guides/containerization.rst +++ b/docs/source/guides/containerization.rst @@ -2,7 +2,7 @@ Advanced Containerization ========================= -This guides describes advanced containerization options +This guide describes advanced containerization options provided by BentoML: - :ref:`Using base image ` diff --git a/docs/source/guides/grpc.rst b/docs/source/guides/grpc.rst new file mode 100644 index 00000000000..374cc44b7e0 --- /dev/null +++ b/docs/source/guides/grpc.rst @@ -0,0 +1,1401 @@ +================= +Serving with gRPC +================= + +This guide will demonstrate advanced features that BentoML offers for you to get started +with `gRPC `_: + +- First-class support for :ref:`custom gRPC Servicer `, :ref:`custom interceptors `, handlers. +- Seemlessly adding gRPC support to existing Bento. + +This guide will also walk you through tradeoffs of serving with gRPC, as well as +recommendation on scenarios where gRPC might be a good fit. + +:bdg-info:`Requirements:` This guide assumes that you have basic knowledge of gRPC and protobuf. If you aren't +familar with gRPC, you can start with gRPC `quick start guide `_. + +.. seealso:: + + For quick introduction to serving with gRPC, see :ref:`Intro to BentoML ` + +Get started with gRPC in BentoML +-------------------------------- + +We will be using the example from :ref:`the quickstart` to +demonstrate BentoML capabilities with gRPC. + +Requirements +~~~~~~~~~~~~ + +BentoML supports for gRPC are introduced in version 1.0.6 and above. + +Install BentoML with gRPC support with :pypi:`pip`: + +.. code-block:: bash + + » pip install -U "bentoml[grpc]" + +Thats it! You can now serve your Bento with gRPC via :ref:`bentoml serve-grpc ` without having to modify your current service definition 😃. + +.. code-block:: bash + + » bentoml serve-grpc iris_classifier:latest --production + +Using your gRPC BentoService +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are two ways to interact with your gRPC BentoService: + +1. Use tools such as :github:`fullstorydev/grpcurl`, :github:`fullstorydev/grpcui`: + The server requires :github:`reflection ` to be enabled for those tools to work. + Pass in ``--enable-reflection`` to enable reflection: + + .. code-block:: bash + + » bentoml serve-grpc iris_classifier:latest --production --enable-reflection + + .. include:: ./snippets/grpc/grpc_tools.rst + + Open a different terminal and use one of the following: + +2. Use one of the below :ref:`client implementations ` to send test requests to your BentoService. + +.. _workspace: https://bazel.build/concepts/build-ref + +.. |workspace| replace:: ``WORKSPACE`` + +.. _build: https://bazel.build/concepts/build-files + +.. |build| replace:: ``BUILD`` + +Client Implementation +~~~~~~~~~~~~~~~~~~~~~ + +.. note:: + + All of the following client implementations are :github:`available on GitHub `. + +:raw-html:`
` + +From another terminal, use one of the following client implementation to send request to the +gRPC server: + +.. note:: + + gRPC comes with supports for multiple languages. In the upcoming sections + we will demonstrate two workflows of generating stubs and implementing clients: + + - Using `bazel `_ to manage and isolate dependencies (recommended) + - A manual approach using ``protoc`` its language-specific plugins + +.. tab-set:: + + .. tab-item:: Python + :sync: python + + We will create our Python client in the directory ``~/workspace/iris_python_client/``: + + .. code-block:: bash + + » mkdir -p ~/workspace/iris_python_client + » cd ~/workspace/iris_python_client + + Create a ``client.py`` file with the following content: + + .. literalinclude:: ../../../grpc-client/python/client.py + :language: python + :caption: `client.py` + + .. tab-item:: Go + :sync: golang + + :bdg-info:`Requirements:` Make sure to install the `prerequisites `_ before using Go. + + We will create our Golang client in the directory ``~/workspace/iris_go_client/``: + + .. code-block:: bash + + » mkdir -p ~/workspace/iris_go_client + » cd ~/workspace/iris_go_client + + .. tab-set:: + + .. tab-item:: Using bazel (recommended) + :sync: bazel-workflow + + Define a |workspace|_ file: + + .. dropdown:: ``WORKSPACE`` + :icon: code + + .. literalinclude:: ./snippets/grpc/go/WORKSPACE.snippet.bzl + :language: python + + Followed by defining a |build|_ file: + + .. dropdown:: ``BUILD`` + :icon: code + + .. literalinclude:: ./snippets/grpc/go/BUILD.snippet.bzl + :language: python + + .. tab-item:: Using protoc and language-specific plugins + :sync: protoc-and-plugins + + Create a Go module: + + .. code-block:: bash + + » go mod init iris_go_client && go mod tidy + + Add the following lines to ``~/workspace/iris_go_client/go.mod``: + + .. code-block:: go + + require github.com/bentoml/bentoml/grpc/v1alpha1 v0.0.0-unpublished + + replace github.com/bentoml/bentoml/grpc/v1alpha1 v0.0.0-unpublished => ./github.com/bentoml/bentoml/grpc/v1alpha1 + + By using `replace directive `_, we + ensure that Go will know where our generated stubs to be imported from. (since we don't host the generate gRPC stubs on `pkg.go.dev` 😄) + + .. include:: ./snippets/grpc/additional_setup.rst + + Here is the ``protoc`` command to generate the gRPC Go stubs: + + .. code-block:: bash + + » protoc -I. -I thirdparty/protobuf/src \ + --go_out=. --go_opt=paths=import \ + --go-grpc_out=. --go-grpc_opt=paths=import \ + bentoml/grpc/v1alpha1/service.proto + + Then run the following to make sure the generated stubs are importable: + + .. code-block:: bash + + » pushd github.com/bentoml/bentoml/grpc/v1alpha1 + » go mod init v1alpha1 && go mod tidy + » popd + + Create a ``client.go`` file with the following content: + + .. literalinclude:: ../../../grpc-client/go/client.go + :language: go + :caption: `client.go` + + .. tab-item:: C++ + :sync: cpp + + :bdg-info:`Requirements:` Make sure follow the `instructions `_ to install gRPC and Protobuf locally. + + We will create our C++ client in the directory ``~/workspace/iris_cc_client/``: + + .. code-block:: bash + + » mkdir -p ~/workspace/iris_cc_client + » cd ~/workspace/iris_cc_client + + .. tab-set:: + + .. tab-item:: Using bazel (recommended) + :sync: bazel-workflow + + Define a |workspace|_ file: + + .. dropdown:: ``WORKSPACE`` + :icon: code + + .. literalinclude:: ./snippets/grpc/cpp/WORKSPACE.snippet.bzl + :language: python + + Followed by defining a |build|_ file: + + .. dropdown:: ``BUILD`` + :icon: code + + .. literalinclude:: ./snippets/grpc/cpp/BUILD.snippet.bzl + :language: python + + .. tab-item:: Using protoc and language-specific plugins + :sync: protoc-and-plugins + + .. include:: ./snippets/grpc/additional_setup.rst + + Here is the ``protoc`` command to generate the gRPC C++ stubs: + + .. code-block:: bash + + » protoc -I . -I ./thirdparty/protobuf/src \ + --cpp_out=. --grpc_out=. \ + --plugin=protoc-gen-grpc=$(which grpc_cpp_plugin) \ + bentoml/grpc/v1alpha1/service.proto + + Create a ``client.cpp`` file with the following content: + + .. literalinclude:: ../../../grpc-client/cpp/client.cc + :language: cpp + :caption: `client.cpp` + + .. tab-item:: Java + :sync: java + + :bdg-info:`Requirements:` Make sure to have `JDK>=7 `_. + + :bdg-info:`Optional:` follow the :github:`instructions ` to install ``protoc`` plugin for gRPC Java if you plan to use ``protoc`` standalone. + + .. note:: + + Feel free to use any Java build tools of choice (Maven, Gradle, Bazel, etc.) to build and run the client you find fit. + + In this tutorial we will be using `bazel `_. + + We will create our Java client in the directory ``~/workspace/iris_java_client/``: + + .. code-block:: bash + + » mkdir -p ~/workspace/iris_java_client + » cd ~/workspace/iris_java_client + + Create the client Java package (``com.client.BentoServiceClient``): + + .. code-block:: bash + + » mkdir -p src/main/java/com/client + + .. tab-set:: + + .. tab-item:: Using bazel (recommended) + :sync: bazel-workflow + + Define a |workspace|_ file: + + .. dropdown:: ``WORKSPACE`` + :icon: code + + .. literalinclude:: ./snippets/grpc/java/WORKSPACE.snippet.bzl + :language: python + + Followed by defining a |build|_ file: + + .. dropdown:: ``BUILD`` + :icon: code + + .. literalinclude:: ./snippets/grpc/java/BUILD.snippet.bzl + :language: python + + .. tab-item:: Using others build system + :sync: protoc-and-plugins + + One simply can't manually running ``javac`` to compile the Java class, since + there are way too many dependencies to be resolved. + + Provided below is an example of how one can use `gradle `_ to build the Java client. + + .. code-block:: bash + + » gradle init --project-dir . + + The following ``build.gradle`` should be able to help you get started: + + .. literalinclude:: ../../../grpc-client/java/build.gradle + :language: groovy + :caption: build.gradle + + To build the client, run: + + .. code-block:: bash + + » ./gradlew build + + Proceed to create a ``src/main/java/com/client/BentoServiceClient.java`` file with the following content: + + .. literalinclude:: ../../../grpc-client/java/src/main/java/com/client/BentoServiceClient.java + :language: java + :caption: `BentoServiceClient.java` + + .. dropdown:: On running ``protoc`` standalone (optional) + :icon: book + + .. include:: ./snippets/grpc/additional_setup.rst + + Here is the ``protoc`` command to generate the gRPC Java stubs if you need to use ``protoc`` standalone: + + .. code-block:: bash + + » protoc -I . \ + -I ./thirdparty/protobuf/src \ + --java_out=./src/main/java \ + --grpc-java_out=./src/main/java \ + bentoml/grpc/v1alpha1/service.proto + + .. tab-item:: Kotlin + :sync: kotlin + + :bdg-info:`Requirements:` Make sure to have the `prequisites `_ to get started with :github:`grpc/grpc-kotlin`. + + :bdg-info:`Optional:` feel free to install :github:`Kotlin gRPC codegen ` in order to generate gRPC stubs if you plan to use ``protoc`` standalone. + + To bootstrap the Kotlin client, feel free to use either `gradle `_ or + `maven `_ to build and run the following client code. + + In this example, we will use `bazel `_ to build and run the client. + + We will create our Kotlin client in the directory ``~/workspace/iris_kotlin_client/``, followed by creating the client directory structure: + + .. code-block:: bash + + » mkdir -p ~/workspace/iris_kotlin_client + » cd ~/workspace/iris_kotlin_client + » mkdir -p src/main/kotlin/com/client + + .. tab-set:: + + .. tab-item:: Using bazel (recommended) + :sync: bazel-workflow + + Define a |workspace|_ file: + + .. dropdown:: ``WORKSPACE`` + + .. literalinclude:: ./snippets/grpc/kotlin/WORKSPACE.snippet.bzl + :language: python + + Followed by defining a |build|_ file: + + .. dropdown:: ``BUILD`` + + .. literalinclude:: ./snippets/grpc/kotlin/docs/BUILD.snippet.bzl + :language: python + + .. tab-item:: Using others build system + :sync: protoc-and-plugins + + One simply can't manually compile all the Kotlin files, since there are way too many dependencies to be resolved. + + Provided below is an example of how one can use `gradle `_ to build the Kotlin client. + + .. code-block:: bash + + » gradle init --project-dir . + + The following ``build.gradle.kts`` should be able to help you get started: + + .. literalinclude:: ../../../grpc-client/kotlin/build.gradle.kts + :language: groovy + :caption: build.gradle.kts + + To build the client, run: + + .. code-block:: bash + + » ./gradlew build + + Proceed to create a ``src/main/kotlin/com/client/BentoServiceClient.kt`` file with the following content: + + .. literalinclude:: ../../../grpc-client/kotlin/src/main/kotlin/com/client/BentoServiceClient.kt + :language: java + :caption: `BentoServiceClient.kt` + + .. dropdown:: On running ``protoc`` standalone (optional) + :icon: book + + .. include:: ./snippets/grpc/additional_setup.rst + + Here is the ``protoc`` command to generate the gRPC Kotlin stubs if you need to use ``protoc`` standalone: + + .. code-block:: bash + + » protoc -I. -I ./thirdparty/protobuf/src \ + --kotlin_out ./kotlin/src/main/kotlin/ \ + --grpc-kotlin_out ./kotlin/src/main/kotlin \ + --plugin=protoc-gen-grpc-kotlin=$(which protoc-gen-grpc-kotlin) \ + bentoml/grpc/v1alpha1/service.proto + + .. tab-item:: Node.js + :sync: nodejs + + :bdg-info:`Requirements:` Make sure to have `Node.js `_ + installed in your system. + + We will create our Node.js client in the directory ``~/workspace/iris_node_client/``: + + .. code-block:: bash + + » mkdir -p ~/workspace/iris_node_client + » cd ~/workspace/iris_node_client + + .. dropdown:: Initialize the project and use the following ``package.json``: + + .. literalinclude:: ../../../grpc-client/node/package.json + :language: json + :caption: `package.json` + + Install the dependencies with either ``npm`` or ``yarn``: + + .. code-block:: bash + + » yarn install --add-devs + + .. note:: + + If you are using M1, you might also have to prepend ``npm_config_target_arch=x64`` to ``yarn`` command: + + .. code-block:: bash + + » npm_config_target_arch=x64 yarn install --add-devs + + .. include:: ./snippets/grpc/additional_setup.rst + + Here is the ``protoc`` command to generate the gRPC Javascript stubs: + + .. code-block:: bash + + » $(npm bin)/grpc_tools_node_protoc \ + -I . -I ./thirdparty/protobuf/src \ + --js_out=import_style=commonjs,binary:. \ + --grpc_out=grpc_js:js \ + bentoml/grpc/v1alpha1/service.proto + + Proceed to create a ``client.js`` file with the following content: + + .. literalinclude:: ../../../grpc-client/node/client.js + :language: javascript + :caption: `client.js` + + .. tab-item:: Swift + :sync: swift + + .. tab-item:: PHP + :sync: php + + :bdg-info:`Requirements:` Make sure to follow the :github:`instructions ` to install ``grpc`` via either `pecl `_ or from source. + + We will then use `bazel `_, `composer `_ to build and run the client. + + We will create our PHP client in the directory ``~/workspace/iris_php_client/``: + + .. code-block:: bash + + » mkdir -p ~/workspace/iris_php_client + » cd ~/workspace/iris_php_client + + Create a new PHP package: + + .. code-block:: bash + + » composer init + + .. dropdown:: An example ``composer.json`` for the client: + :icon: code + + .. literalinclude:: ../../../grpc-client/php/composer.json + :language: json + + .. include:: ./snippets/grpc/additional_setup.rst + + Here is the ``protoc`` command to generate the gRPC swift stubs: + + .. code-block:: bash + + » protoc -I . -I ./thirdparty/protobuf/src \ + --php_out=. \ + --grpc_out=. \ + --plugin=protoc-gen-grpc=$(which grpc_php_plugin) \ + bentoml/grpc/v1alpha1/service.proto + + Proceed to create a ``BentoServiceClient.php`` file with the following content: + + .. literalinclude:: ../../../grpc-client/php/BentoServiceClient.php + :language: php + :caption: `BentoServiceClient.php` + +.. TODO:: + + Bazel instruction for ``swift``, ``nodejs``, ``python`` + +:raw-html:`
` + +Then you can proceed to run the client scripts: + +.. tab-set:: + + .. tab-item:: Python + :sync: python + + .. code-block:: bash + + » python -m client + + .. tab-item:: Go + :sync: golang + + .. tab-set:: + + .. tab-item:: Using bazel (recommended) + :sync: bazel-workflow + + .. code-block:: bash + + » bazel run //:client_go + + .. tab-item:: Using protoc and language-specific plugins + :sync: protoc-and-plugins + + .. code-block:: bash + + » go run ./client.go + + .. tab-item:: C++ + :sync: cpp + + .. tab-set:: + + .. tab-item:: Using bazel (recommended) + :sync: bazel-workflow + + .. code-block:: bash + + » bazel run :client_cc + + .. tab-item:: Using protoc and language-specific plugins + :sync: protoc-and-plugins + + Refer to :github:`grpc/grpc` for instructions on using CMake and other similar build tools. + + .. note:: + + See the :github:`instructions on GitHub ` for working C++ client. + + .. tab-item:: Java + :sync: java + + .. tab-set:: + + .. tab-item:: Using bazel (recommended) + :sync: bazel-workflow + + .. code-block:: bash + + » bazel run :client_java + + .. tab-item:: Using others build system + :sync: protoc-and-plugins + + We will use ``gradlew`` to build the client and run it: + + .. code-block:: bash + + » ./gradlew build && \ + ./build/tmp/scripts/bentoServiceClient/bento-service-client + + .. note:: + + See the :github:`instructions on GitHub ` for working Java client. + + .. tab-item:: Kotlin + :sync: kotlin + + .. tab-set:: + + .. tab-item:: Using bazel (recommended) + :sync: bazel-workflow + + .. code-block:: bash + + » bazel run :client_kt + + .. tab-item:: Using others build system + :sync: protoc-and-plugins + + We will use ``gradlew`` to build the client and run it: + + .. code-block:: bash + + » ./gradlew build && \ + ./build/tmp/scripts/bentoServiceClient/bento-service-client + + .. note:: + + See the :github:`instructions on GitHub ` for working Kotlin client. + + .. tab-item:: Node.js + :sync: nodejs + + .. code-block:: bash + + » node client.js + + .. tab-item:: Swift + :sync: swift + + .. code-block:: bash + + » swift run BentoServiceClient + + .. tab-item:: PHP + :sync: php + + .. code-block:: bash + + » php -d extension=/path/to/grpc.so -d max_execution_time=300 BentoServiceClient.php + + +.. dropdown:: Additional language support for client implementation + :icon: triangle-down + + .. tab-set:: + + .. tab-item:: Ruby + :sync: ruby + + :bdg-primary:`Note:` Please check out the :github:`gRPC Ruby ` for how to install from source. + Check out the :github:`examples folder ` for Ruby client implementation. + + .. tab-item:: .NET + :sync: dotnet + + :bdg-primary:`Note:` Please check out the :github:`gRPC .NET ` examples folder for :github:`grpc/grpc-dotnet` client implementation. + + .. tab-item:: Dart + :sync: dart + + :bdg-primary:`Note:` Please check out the :github:`gRPC Dart ` examples folder for :github:`grpc/grpc-dart` client implementation. + + .. tab-item:: Rust + :sync: rust + + :bdg-primary:`Note:` Currently there are no official gRPC Rust client implementation. Please check out the :github:`tikv/grpc-rs` as one of the unofficial implementation. + + +After successfully running the client, proceed to build the bento as usual: + +.. code-block:: bash + + » bentoml build + +:raw-html:`
` + +Containerize your Bento 🍱 with gRPC support +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To containerize the Bento with gRPC features, pass in ``--enable-features=grpc`` to +:ref:`bentoml containerize ` to add additional gRPC +dependencies to your Bento + +.. code-block:: bash + + » bentoml containerize iris_classifier:latest --enable-features=grpc + +``--enable-features`` allows users to containerize any of the existing Bentos with :ref:`additional features ` without having to rebuild the Bento. + +.. note:: + + ``--enable-features`` accepts a comma-separated list of features or multiple arguments. + +After containerization, your Bento container can now be used with gRPC: + +.. code-block:: bash + + » docker run -it --rm \ + -p 3000:3000 -p 3001:3001 \ + iris_classifier:6otbsmxzq6lwbgxi serve-grpc --production + +Congratulations! You have successfully served, containerized and tested your BentoService with gRPC. + +------------- + +Using gRPC in BentoML +--------------------- + +We will dive into some of the details of how gRPC is implemented in BentoML. + +Protobuf definition +~~~~~~~~~~~~~~~~~~~ + +Let's take a quick look at `protobuf `_ definition of the BentoService: + +.. code-block:: protobuf + + service BentoService { + rpc Call(Request) returns (Response) {} + } + +.. dropdown:: `Expands for current protobuf definition.` + :icon: code + + .. tab-set:: + + .. tab-item:: v1alpha1 + + .. literalinclude:: ../../../bentoml/grpc/v1alpha1/service.proto + :language: protobuf + +As you can see, BentoService defines a `simple rpc` ``Call`` that sends a ``Request`` message and returns a ``Response`` message. + +A ``Request`` message takes in: + +* `api_name`: the name of the API function defined inside your BentoService. +* `oneof `_ `content`: the field can be one of the following types: + ++------------------------------------------------------------------+-------------------------------------------------------------------------------------------+ +| Protobuf definition | IO Descriptor | ++------------------------------------------------------------------+-------------------------------------------------------------------------------------------+ +| :ref:`guides/grpc:Array representation via ``NDArray``` | :ref:`bentoml.io.NumpyNdarray ` | ++------------------------------------------------------------------+-------------------------------------------------------------------------------------------+ +| :ref:`guides/grpc:Tabular data representation via ``DataFrame``` | :ref:`bentoml.io.PandasDataFrame ` | ++------------------------------------------------------------------+-------------------------------------------------------------------------------------------+ +| :ref:`guides/grpc:File-like object via ``File``` | :ref:`bentoml.io.File ` | ++------------------------------------------------------------------+-------------------------------------------------------------------------------------------+ +| |google_protobuf_string_value|_ | :ref:`bentoml.io.Text ` | ++------------------------------------------------------------------+-------------------------------------------------------------------------------------------+ +| |google_protobuf_value|_ | :ref:`bentoml.io.JSON ` | ++------------------------------------------------------------------+-------------------------------------------------------------------------------------------+ +| :ref:`guides/grpc:Complex payload via ``Multipart``` | :ref:`bentoml.io.Multipart ` | ++------------------------------------------------------------------+-------------------------------------------------------------------------------------------+ +| :ref:`guides/grpc:Compact data format via ``serialized_bytes``` | (See below) | ++------------------------------------------------------------------+-------------------------------------------------------------------------------------------+ + +.. note:: + + ``Series`` is currently not yet supported. + +.. _google_protobuf_value: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Value + +.. |google_protobuf_value| replace:: ``google.protobuf.Value`` + +.. _google_protobuf_string_value: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#stringvalue + +.. |google_protobuf_string_value| replace:: ``google.protobuf.StringValue`` + +The ``Response`` message will then return one of the aforementioned types as result. + +:raw-html:`
` + +:bdg-info:`Example:` In the :ref:`quickstart guide`, we defined a ``classify`` API that takes in a :ref:`bentoml.io.NumpyNdarray `. + +Therefore, our ``Request`` message would have the following structure: + +.. tab-set:: + + .. tab-item:: Python + :sync: python + + .. literalinclude:: ./snippets/grpc/python/request.py + :language: python + + .. tab-item:: Go + :sync: golang + + .. literalinclude:: ./snippets/grpc/go/request.go + :language: go + + .. tab-item:: C++ + :sync: cpp + + .. literalinclude:: ./snippets/grpc/cpp/request.cc + :language: cpp + + .. tab-item:: Java + :sync: java + + .. literalinclude:: ./snippets/grpc/java/Request.java + :language: java + + .. tab-item:: Kotlin + :sync: kotlin + + .. literalinclude:: ./snippets/grpc/kotlin/Request.kt + :language: java + + .. tab-item:: Node.js + :sync: nodejs + + .. literalinclude:: ./snippets/grpc/node/request.js + :language: javascript + + .. tab-item:: Swift + :sync: swift + + .. literalinclude:: ./snippets/grpc/swift/Request.swift + :language: swift + + + +Array representation via ``NDArray`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:bdg-info:`Description:` ``NDArray`` represents a flattened n-dimensional array of arbitrary type. It accepts the following fields: + +* `dtype` + + The data type of given input. This is a `Enum `_ field that provides 1-1 mapping with Protobuf data types to NumPy data types: + + +-----------------------+---------------+------------+ + | pb.NDArray.DType | numpy.dtype | Enum value | + +=======================+===============+============+ + | ``DTYPE_UNSPECIFIED`` | ``None`` | 0 | + +-----------------------+---------------+------------+ + | ``DTYPE_FLOAT`` | ``np.float`` | 1 | + +-----------------------+---------------+------------+ + | ``DTYPE_DOUBLE`` | ``np.double`` | 2 | + +-----------------------+---------------+------------+ + | ``DTYPE_BOOL`` | ``np.bool_`` | 3 | + +-----------------------+---------------+------------+ + | ``DTYPE_INT32`` | ``np.int32`` | 4 | + +-----------------------+---------------+------------+ + | ``DTYPE_INT64`` | ``np.int64`` | 5 | + +-----------------------+---------------+------------+ + | ``DTYPE_UINT32`` | ``np.uint32`` | 6 | + +-----------------------+---------------+------------+ + | ``DTYPE_UINT64`` | ``np.uint64`` | 7 | + +-----------------------+---------------+------------+ + | ``DTYPE_STRING`` | ``np.str_`` | 8 | + +-----------------------+---------------+------------+ + +* `shape` + + A list of `int32` that represents the shape of the flattened array. the :ref:`bentoml.io.NumpyNdarray ` will + then reshape the given payload into expected shape. + + Note that this value will always takes precendence over the ``shape`` field in the :ref:`bentoml.io.NumpyNdarray ` descriptor, + meaning the array will be reshaped to this value first if given. Refer to :meth:`bentoml.io.NumpyNdarray.from_proto` for implementation details. + +* `string_values`, `float_values`, `double_values`, `bool_values`, `int32_values`, `int64_values`, `uint32_values`, `unit64_values` + + Each of the fields is a `list` of the corresponding data type. The list is a flattened array, and will be reconstructed + alongside with ``shape`` field to the original payload. + + Per request sent, one message should only contain **ONE** of the aforementioned fields. + + The interaction among the above fields and ``dtype`` are as follows: + + - if ``dtype`` is not present in the message: + * All of the fields are empty, then we return a ``np.empty``. + * We will loop through all of the provided fields, and only allows one field per message. + + If here are more than one field (i.e. ``string_values`` and ``float_values``), then we will raise an error, as we don't know how to deserialize the data. + + - otherwise: + * We will use the provided dtype-to-field map to get the data from the given message. + + +------------------+-------------------+ + | DType | field | + +------------------+-------------------+ + | ``DTYPE_BOOL`` | ``bool_values`` | + +------------------+-------------------+ + | ``DTYPE_DOUBLE`` | ``double_values`` | + +------------------+-------------------+ + | ``DTYPE_FLOAT`` | ``float_values`` | + +------------------+-------------------+ + | ``DTYPE_INT32`` | ``int32_values`` | + +------------------+-------------------+ + | ``DTYPE_INT64`` | ``int64_values`` | + +------------------+-------------------+ + | ``DTYPE_STRING`` | ``string_values`` | + +------------------+-------------------+ + | ``DTYPE_UINT32`` | ``uint32_values`` | + +------------------+-------------------+ + | ``DTYPE_UINT64`` | ``uint64_values`` | + +------------------+-------------------+ + + For example, if ``dtype`` is ``DTYPE_FLOAT``, then the payload expects to have ``float_values`` field. + +.. grid:: 2 + + .. grid-item-card:: ``Python API`` + + .. code-block:: python + + NumpyNdarray.from_sample( + np.array([[5.4, 3.4, 1.5, 0.4]]) + ) + + .. grid-item-card:: ``pb.NDArray`` + + .. code-block:: none + + ndarray { + dtype: DTYPE_FLOAT + shape: 1 + shape: 4 + float_values: 5.4 + float_values: 3.4 + float_values: 1.5 + float_values: 0.4 + } + + +:bdg-primary:`API reference:` :meth:`bentoml.io.NumpyNdarray.from_proto` + +:raw-html:`
` + +Tabular data representation via ``DataFrame`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:bdg-info:`Description:` ``DataFrame`` represents any tabular data type. Currently we only support the columns orientation +since it is best for preserving the input order. + +It accepts the following fields: + +* `column_names` + + A list of `string` that represents the column names of the given tabular data. + +* `column_values` + + A list of `Series` where `Series` represents a series of arbitrary data type. The allowed fields for + `Series` as similar to the ones in `NDArray`: + + * one of [`string_values`, `float_values`, `double_values`, `bool_values`, `int32_values`, `int64_values`, `uint32_values`, `unit64_values`] + +.. grid:: 2 + + .. grid-item-card:: ``Python API`` + + .. code-block:: python + + PandasDataFrame.from_sample( + pd.DataFrame({ + "age": [3, 29], + "height": [94, 170], + "weight": [31, 115] + }), + orient="columns", + ) + + .. grid-item-card:: ``pb.DataFrame`` + + .. code-block:: none + + dataframe { + column_names: "age" + column_names: "height" + column_names: "weight" + columns { + int32_values: 3 + int32_values: 29 + } + columns { + int32_values: 40 + int32_values: 190 + } + columns { + int32_values: 140 + int32_values: 178 + } + } + +:bdg-primary:`API reference:` :meth:`bentoml.io.PandasDataFrame.from_proto` + +File-like object via ``File`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:bdg-info:`Description:` ``File`` represents any arbitrary file type. this can be used +to send in any file type, including images, videos, audio, etc. + +.. note:: + + Currently both :class:`bentoml.io.File` and :class:`bentoml.io.Image` are using + ``pb.File`` + +It accepts the following fields: + +* `content` + + A `bytes` field that represents the content of the file. + +.. TODO:: + + - Document ``kind`` once enum was dropped. + - Demonstrate python API to protobuf representation + + +Complex payload via ``Multipart`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:bdg-info:`Description:` ``Multipart`` represents a complex payload that can contain +multiple different fields. It takes a ``fields``, which is a dictionary of input name to +its coresponding :class:`bentoml.io.IODescriptor` + +.. grid:: 2 + + .. grid-item-card:: ``Python API`` + + .. code-block:: python + + Multipart( + meta=Text(), + arr=NumpyNdarray( + dtype=np.float16, + shape=[2,2] + ) + ) + + .. grid-item-card:: ``pb.Multipart`` + + .. code-block:: none + + multipart { + fields { + key: "arr" + value { + ndarray { + dtype: DTYPE_FLOAT + shape: 2 + shape: 2 + float_values: 1.0 + float_values: 2.0 + float_values: 3.0 + float_values: 4.0 + } + } + } + fields { + key: "meta" + value { + text { + value: "nlp" + } + } + } + } + +:bdg-primary:`API reference:` :meth:`bentoml.io.Multipart.from_proto` + +Compact data format via ``serialized_bytes`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``serialized_bytes`` field in both ``Request`` and ``Response`` is reserved for pre-established protocol encoding between client and server. + +BentoML leverages the field to improve serialization performance between BentoML client and server. Thus the field is not **recommended** for use directly. + +Mounting Servicer +~~~~~~~~~~~~~~~~~ + +gRPC service :ref:`multiplexing ` enables us to mount additional custom servicers alongside with BentoService, +and serve them under the same port. + +.. code-block:: python + :caption: `service.py` + :emphasize-lines: 13 + + import route_guide_pb2 + import route_guide_pb2_grpc + from servicer_impl import RouteGuideServicer + + svc = bentoml.Service("iris_classifier", runners=[iris_clf_runner]) + + services_name = [ + v.full_name for v in route_guide_pb2.DESCRIPTOR.services_by_name.values() + ] + svc.mount_grpc_servicer( + RouteGuideServicer, + add_servicer_fn=add_RouteGuideServicer_to_server, + service_names=services_name, + ) + +Serve your service with :ref:`bentoml serve-grpc ` command: + +.. code-block:: bash + + » bentoml serve-grpc service.py:svc --reload --enable-reflection + +Now your ``RouteGuide`` service can also be accessed through ``localhost:3000``. + +.. note:: + + ``service_names`` is **REQUIRED** here, as this will be used for :github:`server reflection ` + when ``--enable-reflection`` is passed to ``bentoml serve-grpc``. + +Mounting gRPC Interceptors +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Inteceptors are a component of gRPC that allows us to intercept and interact with the +proto message and service context either before - or after - the actual RPC call was +sent/received by client/server. + +Interceptors to gRPC is what middleware is to HTTP. The most common use-case for interceptors +are authentication, :ref:`tracing `, access logs, and more. + +BentoML comes with a sets of built-in *async interceptors* to provide support for access logs, +`OpenTelemetry `_, and `Prometheus `_. + +The following diagrams demonstrates the flow of a gRPC request from client to server: + +.. image:: /_static/img/interceptor-flow.png + :alt: Interceptor Flow + +Since interceptors are executed in the order they are added, users interceptors will be executed after the built-in interceptors. + + Users interceptors shouldn't modify the existing headers and data of the incoming ``Request``. + +BentoML currently only support **async interceptors** (via `grpc.aio.ServerInterceptor `_, as opposed to `grpc.ServerInterceptor `_). This is +because BentoML gRPC server is an async implementation of gRPC server. + +.. note:: + + If you are using ``grpc.ServerInterceptor``, you will need to migrate it over + to use the new ``grpc.aio.ServerInterceptor`` in order to use this feature. + + Feel free to reach out to us at `#support on Slack `_ + +.. dropdown:: A toy implementation ``AppendMetadataInterceptor`` + + .. code-block:: python + :caption: metadata_interceptor.py + + from __future__ import annotations + + import typing as t + import functools + import dataclasses + from typing import TYPE_CHECKING + + from grpc import aio + + if TYPE_CHECKING: + from bentoml.grpc.types import Request + from bentoml.grpc.types import Response + from bentoml.grpc.types import RpcMethodHandler + from bentoml.grpc.types import AsyncHandlerMethod + from bentoml.grpc.types import HandlerCallDetails + from bentoml.grpc.types import BentoServicerContext + + + @dataclasses.dataclass + class Context: + usage: str + accuracy_score: float + + + class AppendMetadataInterceptor(aio.ServerInterceptor): + def __init__(self, *, usage: str, accuracy_score: float) -> None: + self.context = Context(usage=usage, accuracy_score=accuracy_score) + self._record: set[str] = set() + + async def intercept_service( + self, + continuation: t.Callable[[HandlerCallDetails], t.Awaitable[RpcMethodHandler]], + handler_call_details: HandlerCallDetails, + ) -> RpcMethodHandler: + from bentoml.grpc.utils import wrap_rpc_handler + + handler = await continuation(handler_call_details) + + if handler and (handler.response_streaming or handler.request_streaming): + return handler + + def wrapper(behaviour: AsyncHandlerMethod[Response]): + @functools.wraps(behaviour) + async def new_behaviour( + request: Request, context: BentoServicerContext + ) -> Response | t.Awaitable[Response]: + self._record.update( + {f"{self.context.usage}:{self.context.accuracy_score}"} + ) + resp = await behaviour(request, context) + context.set_trailing_metadata( + tuple( + [ + (k, str(v).encode("utf-8")) + for k, v in dataclasses.asdict(self.context).items() + ] + ) + ) + return resp + + return new_behaviour + + return wrap_rpc_handler(wrapper, handler) + +To add your intercptors to existing BentoService, use ``svc.add_grpc_interceptor``: + +.. code-block:: python + :caption: `service.py` + + from custom_interceptor import CustomInterceptor + + svc.add_grpc_interceptor(CustomInterceptor) + +.. note:: + + ``add_grpc_interceptor`` also supports `partial` class as well as multiple arguments + interceptors: + + .. tab-set:: + + .. tab-item:: multiple arguments + + .. code-block:: python + + from metadata_interceptor import AppendMetadataInterceptor + + svc.add_grpc_interceptor(AppendMetadataInterceptor, usage="NLP", accuracy_score=0.867) + + .. tab-item:: partial method + + .. code-block:: python + + from functools import partial + + from metadata_interceptor import AppendMetadataInterceptor + + svc.add_grpc_interceptor(partial(AppendMetadataInterceptor, usage="NLP", accuracy_score=0.867)) + +--------------- + +Recommendations +--------------- + +gRPC is designed to be high performance framework for inter-service communications. This +means that it is a perfect fit for building microservices. The following are some +recommendation we have for using gRPC for model serving: + +:raw-html:`
` + +Demystifying the misconception of gRPC vs. REST +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You might stumble upon articles comparing gRPC to REST, and you might get the impression +that gRPC is a better choice than REST when building services. This is not entirely +true. + +gRPC is built on top of HTTP/2, and it addresses some of the shortcomings of HTTP/1.1, +such as :wiki:`head-of-line blocking `, and :wiki:`HTTP pipelining `. +However, gRPC is not a replacement for REST, and indeed it is not a replacement for +model serving. gRPC comes with its own set of trade-offs, such as: + +* **Limited browser support**: It is impossible to call a gRPC service directly from any + browser. You will end up using tools such as :github:`gRPCUI ` in order to interact + with your service, or having to go through the hassle of implementing a gRPC client in + your language of choice. + +* **Binary protocol format**: While :github:`Protobuf ` is + efficient to send and receive over the wire, it is not human-readable. This means + additional toolin for debugging and analyzing protobuf messages are required. + +* **Knowledge gap**: gRPC comes with its own concepts and learning curve, which requires + teams to invest time in filling those knowledge gap to be effectively use gRPC. This + often leads to a lot of friction and sometimes increase friction to the development + agility. + +* **Lack of support for additional content types**: gRPC depends on protobuf, its content + type are restrictive, in comparison to out-of-the-box support from HTTP+REST. + +.. seealso:: + + `gRPC on HTTP/2 `_ dives into how gRPC is built + on top of HTTP/2, and this `article `_ + goes into more details on how HTTP/2 address the problem from HTTP/1.1 + + For HTTP/2 specification, see `RFC 7540 `_. + +:raw-html:`
` + +Should I use gRPC instead of REST for model serving? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Yes and no. + +If your organization is already using gRPC for inter-service communications, using +your Bento with gRPC is a no-brainer. You will be able to seemlessly integrate your +Bento with your existing gRPC services without having to worry about the overhead of +implementing :github:`grpc-gateway `. + +However, if your organization is not using gRPC, we recommend to keep using REST for +model serving. This is because REST is a well-known and well-understood protocol, +meaning there is no knowledge gap for your team, which will increase developer agility, and +faster go-to-market strategy. + +:raw-html:`
` + +Performance tuning +~~~~~~~~~~~~~~~~~~ + +BentoML allows user to tune the performance of gRPC via :ref:`bentoml_configuration.yaml ` via ``api_server.grpc``. + +A quick overview of the available configuration for gRPC: + +.. code-block:: yaml + :caption: `bentoml_configuration.yaml` + + api_server: + grpc: + host: 0.0.0.0 + port: 3000 + max_concurrent_streams: ~ + maximum_concurrent_rpcs: ~ + max_message_length: -1 + reflection: + enabled: false + metrics: + host: 0.0.0.0 + port: 3001 + +:raw-html:`
` + +``max_concurrent_streams`` +^^^^^^^^^^^^^^^^^^^^^^^^^^ + + :bdg-info:`Definition:` Maximum number of concurrent incoming streams to allow on a HTTP2 connection. + +By default we don't set a limit cap. HTTP/2 connections typically has limit of `maximum concurrent streams `_ +on a connection at one time. + +.. dropdown:: Some notes about fine-tuning ``max_concurrent_streams`` + + Note that a gRPC channel uses a single HTTP/2 connection, and concurrent calls are multiplexed on said connection. + When the number of active calls reaches the connection stream limit, any additional + calls are queued to the client. Queued calls then wait for active calls to complete before being sent. This means that + application will higher load and long running streams could see a performance degradation caused by queuing because of the limit. + + Setting a limit cap on the number of concurrent streams will prevent this from happening, but it also means that + you need to tune the limit cap to the right number. + + * If the limit cap is too low, you will sooner or later running into the issue mentioned above. + + * Not setting a limit cap are also **NOT RECOMMENDED**. Too many streams on a single + HTTP/2 connection introduces `thread contention` between streams trying to write + to the connection, `packet loss` which causes all call to be blocked. + + :bdg-info:`Remarks:` We recommend you to play around with the limit cap, starting with 100, and increase if needed. + +:raw-html:`
` + +``maximum_concurrent_rpcs`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + :bdg-info:`Definition:` The maximum number of concurrent RPCs this server will service before returning ``RESOURCE_EXHAUSTED`` status. + +By default we set to ``None`` to indicate no limit, and let gRPC to decide the limit. + +:raw-html:`
` + +``max_message_length`` +^^^^^^^^^^^^^^^^^^^^^^ + + :bdg-info:`Definition:` The maximum message length in bytes allowed to be received on/can be send to the server. + +By default we set to ``-1`` to indicate no limit. +Message size limits via this options is a way to prevent gRPC from consuming excessive +resources. By default, gRPC uses per-message limits to manage inbound and outbound +message. + +.. dropdown:: Some notes about fine-tuning ``max_message_length`` + + This options sets two values: :github:`grpc.max_receive_message_length ` + and :github:`grpc.max_send_message_length `. + + .. code-block:: cpp + + #define GRPC_ARG_MAX_RECEIVE_MESSAGE_LENGTH "grpc.max_receive_message_length" + + #define GRPC_ARG_MAX_SEND_MESSAGE_LENGTH "grpc.max_send_message_length" + + By default, gRPC sets incoming message to be 4MB, and no limit on outgoing message. + We recommend you to only set this option if you want to limit the size of outcoming message. Otherwise, you should let gRPC to determine the limit. + + +We recommend you to also check out `gRPC performance best practice `_ to learn about best practice for gRPC. + diff --git a/docs/source/guides/index.rst b/docs/source/guides/index.rst index 1b08030566f..b48be2c3d41 100644 --- a/docs/source/guides/index.rst +++ b/docs/source/guides/index.rst @@ -13,6 +13,7 @@ into this part of the documentation. :titlesonly: batching + grpc configuration containerization gpu diff --git a/docs/source/guides/snippets/grpc/additional_setup.rst b/docs/source/guides/snippets/grpc/additional_setup.rst new file mode 100644 index 00000000000..a47202a0b08 --- /dev/null +++ b/docs/source/guides/snippets/grpc/additional_setup.rst @@ -0,0 +1,24 @@ +Since there is no easy way to add additional proto files, we will have to clone some +repositories and copy the proto files into our project: + +1. :github:`protocolbuffers/protobuf` - the official repository for the Protocol Buffers. We will need protobuf files that lives under ``src/google/protobuf``: + +.. code-block:: bash + + » mkdir -p thirdparty && cd thirdparty + » git clone --depth 1 https://github.com/protocolbuffers/protobuf.git + +2. :github:`bentoml/bentoml` - We need the ``service.proto`` under ``bentoml/grpc/`` to build the client, therefore, we will perform + a `sparse checkout `_ to only checkout ``bentoml/grpc`` directory: + +.. code-block:: bash + + » mkdir bentoml && pushd bentoml + » git init + » git remote add -f origin https://github.com/bentoml/BentoML.git + » git config core.sparseCheckout true + » cat <|.git/info/sparse-checkout + bentoml/grpc + EOT + » git pull origin main + » popd diff --git a/docs/source/guides/snippets/grpc/cpp/BUILD.snippet.bzl b/docs/source/guides/snippets/grpc/cpp/BUILD.snippet.bzl new file mode 100644 index 00000000000..d8ea2232c98 --- /dev/null +++ b/docs/source/guides/snippets/grpc/cpp/BUILD.snippet.bzl @@ -0,0 +1,28 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@rules_proto_grpc//cpp:defs.bzl", "cc_grpc_library", "cc_proto_library") + +proto_library( + name = "service_v1alpha1_proto", + srcs = ["bentoml/grpc/v1alpha1/service.proto"], + deps = ["@com_google_protobuf//:struct_proto", "@com_google_protobuf//:wrappers_proto"], +) + +cc_proto_library( + name = "service_cc", + protos = [":service_v1alpha1_proto"], +) + +cc_grpc_library( + name = "service_cc_grpc", + protos = [":service_v1alpha1_proto"], + deps = [":service_cc"], +) + +cc_binary( + name = "client_cc", + srcs = ["client.cc"], + deps = [ + ":service_cc_grpc", + "@com_github_grpc_grpc//:grpc++", + ], +) diff --git a/docs/source/guides/snippets/grpc/cpp/WORKSPACE.snippet.bzl b/docs/source/guides/snippets/grpc/cpp/WORKSPACE.snippet.bzl new file mode 100644 index 00000000000..94d8e198818 --- /dev/null +++ b/docs/source/guides/snippets/grpc/cpp/WORKSPACE.snippet.bzl @@ -0,0 +1,50 @@ +workspace(name = "iris_cc_client") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "rules_proto", + sha256 = "e017528fd1c91c5a33f15493e3a398181a9e821a804eb7ff5acdd1d2d6c2b18d", + strip_prefix = "rules_proto-4.0.0-3.20.0", + urls = [ + "https://github.com/bazelbuild/rules_proto/archive/refs/tags/4.0.0-3.20.0.tar.gz", + ], +) +http_archive( + name = "rules_proto_grpc", + sha256 = "507e38c8d95c7efa4f3b1c0595a8e8f139c885cb41a76cab7e20e4e67ae87731", + strip_prefix = "rules_proto_grpc-4.1.1", + urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/4.1.1.tar.gz"], +) + +load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains") + +rules_proto_dependencies() + +rules_proto_toolchains() + +load("@rules_proto_grpc//:repositories.bzl", "rules_proto_grpc_repos", "rules_proto_grpc_toolchains") + +rules_proto_grpc_toolchains() + +rules_proto_grpc_repos() + +http_archive( + name = "com_github_grpc_grpc", + strip_prefix = "grpc-v1.49.1", + urls = [ + "https://github.com/grpc/grpc/archive/v1.49.1.tar.gz", + ], +) + +load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps") + +grpc_deps() + +load("@com_github_grpc_grpc//bazel:grpc_extra_deps.bzl", "grpc_extra_deps") + +grpc_extra_deps() + +load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") + +protobuf_deps() diff --git a/docs/source/guides/snippets/grpc/cpp/request.cc b/docs/source/guides/snippets/grpc/cpp/request.cc new file mode 100644 index 00000000000..e1bf8257182 --- /dev/null +++ b/docs/source/guides/snippets/grpc/cpp/request.cc @@ -0,0 +1,15 @@ +#include "bentoml/grpc/v1alpha1/service.pb.h" + +using bentoml::grpc::v1alpha1::BentoService; +using bentoml::grpc::v1alpha1::NDArray; +using bentoml::grpc::v1alpha1::Request; + +std::vector data = {3.5, 2.4, 7.8, 5.1}; +std::vector shape = {1, 4}; + +Request request; +request.set_api_name("classify"); + +NDArray *ndarray = request.mutable_ndarray(); +ndarray->mutable_shape()->Assign(shape.begin(), shape.end()); +ndarray->mutable_float_values()->Assign(data.begin(), data.end()); diff --git a/docs/source/guides/snippets/grpc/go/BUILD.snippet.bzl b/docs/source/guides/snippets/grpc/go/BUILD.snippet.bzl new file mode 100644 index 00000000000..46872743ee9 --- /dev/null +++ b/docs/source/guides/snippets/grpc/go/BUILD.snippet.bzl @@ -0,0 +1,27 @@ +load("@rules_proto_grpc//go:defs.bzl", "go_grpc_library") +load("@io_bazel_rules_go//go:def.bzl", "go_binary") + +proto_library( + name = "service_v1alpha1_proto", + srcs = ["bentoml/grpc/v1alpha1/service.proto"], + deps = ["@com_google_protobuf//:struct_proto", "@com_google_protobuf//:wrappers_proto"], +) + +go_grpc_library( + name = "service_go", + importpath = "github.com/bentoml/bentoml/grpc/v1alpha1", + protos = [":service_v1alpha1_proto"], +) + +go_binary( + name = "client_go", + srcs = ["client.go"], + importpath = "github.com/bentoml/bentoml/grpc/v1alpha1", + deps = [ + ":service_go", + "@com_github_golang_protobuf//proto:go_default_library", + "@org_golang_google_grpc//:go_default_library", + "@org_golang_google_grpc//credentials:go_default_library", + "@org_golang_google_grpc//credentials/insecure:go_default_library", + ], +) diff --git a/docs/source/guides/snippets/grpc/go/WORKSPACE.snippet.bzl b/docs/source/guides/snippets/grpc/go/WORKSPACE.snippet.bzl new file mode 100644 index 00000000000..4d8366ee64c --- /dev/null +++ b/docs/source/guides/snippets/grpc/go/WORKSPACE.snippet.bzl @@ -0,0 +1,68 @@ +workspace(name = "iris_go_client") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +# setup rules_proto and rules_proto_grpc +http_archive( + name = "rules_proto", + sha256 = "e017528fd1c91c5a33f15493e3a398181a9e821a804eb7ff5acdd1d2d6c2b18d", + strip_prefix = "rules_proto-4.0.0-3.20.0", + urls = [ + "https://github.com/bazelbuild/rules_proto/archive/refs/tags/4.0.0-3.20.0.tar.gz", + ], +) + +http_archive( + name = "rules_proto_grpc", + sha256 = "507e38c8d95c7efa4f3b1c0595a8e8f139c885cb41a76cab7e20e4e67ae87731", + strip_prefix = "rules_proto_grpc-4.1.1", + urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/4.1.1.tar.gz"], +) + +load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains") +load("@rules_proto_grpc//:repositories.bzl", "rules_proto_grpc_repos", "rules_proto_grpc_toolchains") + +rules_proto_grpc_toolchains() + +rules_proto_grpc_repos() + +rules_proto_dependencies() + +rules_proto_toolchains() + +# We need to load go_grpc rules first +load("@rules_proto_grpc//:repositories.bzl", "bazel_gazelle", "io_bazel_rules_go") # buildifier: disable=same-origin-load + +io_bazel_rules_go() + +bazel_gazelle() + +load("@rules_proto_grpc//go:repositories.bzl", rules_proto_grpc_go_repos = "go_repos") + +rules_proto_grpc_go_repos() + +load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") + +go_rules_dependencies() + +go_register_toolchains(version = "1.19") + +http_archive( + name = "com_github_grpc_grpc", + strip_prefix = "grpc-v1.49.1", + urls = [ + "https://github.com/grpc/grpc/archive/v1.49.1.tar.gz", + ], +) + +load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps") + +grpc_deps() + +load("@com_github_grpc_grpc//bazel:grpc_extra_deps.bzl", "grpc_extra_deps") + +grpc_extra_deps() + +load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") + +protobuf_deps() diff --git a/docs/source/guides/snippets/grpc/go/request.go b/docs/source/guides/snippets/grpc/go/request.go new file mode 100644 index 00000000000..2f0169e942b --- /dev/null +++ b/docs/source/guides/snippets/grpc/go/request.go @@ -0,0 +1,16 @@ +package main + +import ( + pb "github.com/bentoml/bentoml/grpc/v1alpha1" +) + +var req = &pb.Request{ + ApiName: "classify", + Content: &pb.Request_Ndarray{ + Ndarray: &pb.NDArray{ + Dtype: *pb.NDArray_DTYPE_FLOAT.Enum(), + Shape: []int32{1, 4}, + FloatValues: []float32{3.5, 2.4, 7.8, 5.1}, + }, + }, +} diff --git a/docs/source/guides/snippets/grpc/grpc_tools.rst b/docs/source/guides/snippets/grpc/grpc_tools.rst new file mode 100644 index 00000000000..28e3172ece3 --- /dev/null +++ b/docs/source/guides/snippets/grpc/grpc_tools.rst @@ -0,0 +1,72 @@ +.. tab-set:: + + .. tab-item:: gRPCurl + + We will use :github:`fullstorydev/grpcurl` to send a CURL-like request to the gRPC BentoServer. + + Note that we will use `docker `_ to run the ``grpcurl`` command. + + .. tab-set:: + + .. tab-item:: MacOS/Windows + :sync: __fullstorydev_macwin + + .. code-block:: bash + + » docker run -i --rm \ + fullstorydev/grpcurl -d @ -plaintext host.docker.internal:3000 \ + bentoml.grpc.v1alpha1.BentoService/Call <`_ to run the ``grpcui`` command. + + .. tab-set:: + + .. tab-item:: MacOS/Windows + :sync: __fullstorydev_macwin + + .. code-block:: bash + + » docker run --init --rm \ + -p 8080:8080 fullstorydev/grpcui -plaintext host.docker.internal:3000 + + .. tab-item:: Linux + :sync: __fullstorydev_linux + + .. code-block:: bash + + » docker run --init --rm \ + -p 8080:8080 \ + --network=host fullstorydev/grpcui -plaintext 0.0.0.0:3000 + + Proceed to http://127.0.0.1:8080 in your browser and send test request from the web UI. + diff --git a/docs/source/guides/snippets/grpc/java/BUILD.snippet.bzl b/docs/source/guides/snippets/grpc/java/BUILD.snippet.bzl new file mode 100644 index 00000000000..49218260a78 --- /dev/null +++ b/docs/source/guides/snippets/grpc/java/BUILD.snippet.bzl @@ -0,0 +1,52 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "service_v1alpha1_proto", + srcs = ["bentoml/grpc/v1alpha1/service.proto"], + deps = [ + "@com_google_protobuf//:struct_proto", + "@com_google_protobuf//:wrappers_proto", + ], +) + +load("@io_grpc_grpc_java//:java_grpc_library.bzl", "java_grpc_library") + +java_proto_library( + name = "service_java", + deps = [":service_v1alpha1_proto"], +) + +java_grpc_library( + name = "service_java_grpc", + srcs = [":service_v1alpha1_proto"], + deps = [":service_java"], +) + +java_library( + name = "java_library", + srcs = glob(["client/java/src/main/**/*.java"]), + runtime_deps = [ + "@io_grpc_grpc_java//netty", + ], + deps = [ + ":service_java", + ":service_java_grpc", + "@com_google_protobuf//:protobuf_java", + "@com_google_protobuf//:protobuf_java_util", + "@io_grpc_grpc_java//api", + "@io_grpc_grpc_java//protobuf", + "@io_grpc_grpc_java//stub", + "@maven//:com_google_api_grpc_proto_google_common_protos", + "@maven//:com_google_code_findbugs_jsr305", + "@maven//:com_google_code_gson_gson", + "@maven//:com_google_guava_guava", + ], +) + +java_binary( + name = "client_java", + main_class = "com.client.BentoServiceClient", + runtime_deps = [ + ":java_library", + ], +) diff --git a/docs/source/guides/snippets/grpc/java/Request.java b/docs/source/guides/snippets/grpc/java/Request.java new file mode 100644 index 00000000000..019a4c6f136 --- /dev/null +++ b/docs/source/guides/snippets/grpc/java/Request.java @@ -0,0 +1,10 @@ +import java.util.*; + +int shape[] = { 1, 4 }; +Iterable shapeIterable = convert(shape); +Float array[] = { 3.5f, 2.4f, 7.8f, 5.1f }; +Iterable arrayIterable = Arrays.asList(array); + +NDArray.Builder builder = NDArray.newBuilder().addAllShape(shapeIterable).addAllFloatValues(arrayIterable).setDtype(NDArray.DType.DTYPE_FLOAT); + +Request req = Request.newBuilder().setApiName(apiName).setNdarray(builder).build(); diff --git a/docs/source/guides/snippets/grpc/java/WORKSPACE.snippet.bzl b/docs/source/guides/snippets/grpc/java/WORKSPACE.snippet.bzl new file mode 100644 index 00000000000..af0415b108f --- /dev/null +++ b/docs/source/guides/snippets/grpc/java/WORKSPACE.snippet.bzl @@ -0,0 +1,84 @@ +workspace(name = "iris_java_client") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "rules_proto", + sha256 = "e017528fd1c91c5a33f15493e3a398181a9e821a804eb7ff5acdd1d2d6c2b18d", + strip_prefix = "rules_proto-4.0.0-3.20.0", + urls = [ + "https://github.com/bazelbuild/rules_proto/archive/refs/tags/4.0.0-3.20.0.tar.gz", + ], +) +http_archive( + name = "rules_proto_grpc", + sha256 = "507e38c8d95c7efa4f3b1c0595a8e8f139c885cb41a76cab7e20e4e67ae87731", + strip_prefix = "rules_proto_grpc-4.1.1", + urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/4.1.1.tar.gz"], +) + +load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains") + +rules_proto_dependencies() + +rules_proto_toolchains() + +load("@rules_proto_grpc//:repositories.bzl", "rules_proto_grpc_repos", "rules_proto_grpc_toolchains") + +rules_proto_grpc_toolchains() + +rules_proto_grpc_repos() + +http_archive( + name = "com_github_grpc_grpc", + strip_prefix = "grpc-v1.48.1", + urls = [ + "https://github.com/grpc/grpc/archive/v1.48.1.tar.gz", + ], +) + +load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps") + +grpc_deps() + +load("@com_github_grpc_grpc//bazel:grpc_extra_deps.bzl", "grpc_extra_deps") + +grpc_extra_deps() + +load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") + +protobuf_deps() + +# We will be using 1.48.1 for grpc-java +http_archive( + name = "io_grpc_grpc_java", + sha256 = "88b12b2b4e0beb849eddde98d5373f2f932513229dbf9ec86cc8e4912fc75e79", + strip_prefix = "grpc-java-1.48.1", + urls = ["https://github.com/grpc/grpc-java/archive/v1.48.1.tar.gz"], +) + +http_archive( + name = "rules_jvm_external", + sha256 = "c21ce8b8c4ccac87c809c317def87644cdc3a9dd650c74f41698d761c95175f3", + strip_prefix = "rules_jvm_external-1498ac6ccd3ea9cdb84afed65aa257c57abf3e0a", + url = "https://github.com/bazelbuild/rules_jvm_external/archive/1498ac6ccd3ea9cdb84afed65aa257c57abf3e0a.zip", +) + +load("@rules_jvm_external//:defs.bzl", "maven_install") +load("@com_google_protobuf//:protobuf_deps.bzl", "PROTOBUF_MAVEN_ARTIFACTS") +load("@io_grpc_grpc_java//:repositories.bzl", "IO_GRPC_GRPC_JAVA_ARTIFACTS", "IO_GRPC_GRPC_JAVA_OVERRIDE_TARGETS", "grpc_java_repositories") + +grpc_java_repositories() + +maven_install( + artifacts = IO_GRPC_GRPC_JAVA_ARTIFACTS + PROTOBUF_MAVEN_ARTIFACTS, + generate_compat_repositories = True, + override_targets = IO_GRPC_GRPC_JAVA_OVERRIDE_TARGETS, + repositories = [ + "https://repo.maven.apache.org/maven2/", + ], +) + +load("@maven//:compat.bzl", "compat_repositories") + +compat_repositories() diff --git a/docs/source/guides/snippets/grpc/kotlin/BUILD.snippet.bzl b/docs/source/guides/snippets/grpc/kotlin/BUILD.snippet.bzl new file mode 100644 index 00000000000..0cd735cce22 --- /dev/null +++ b/docs/source/guides/snippets/grpc/kotlin/BUILD.snippet.bzl @@ -0,0 +1,31 @@ +load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_binary") +load("@io_grpc_grpc_java//:java_grpc_library.bzl", "java_grpc_library") +load("@com_github_grpc_grpc_kotlin//:kt_jvm_grpc.bzl", "kt_jvm_grpc_library", "kt_jvm_proto_library") + +java_proto_library( + name = "service_java", + deps = ["//:service_v1alpha1_proto"], +) + +kt_jvm_proto_library( + name = "service_kt", + deps = ["//:service_v1alpha1_proto"], +) + +kt_jvm_grpc_library( + name = "service_grpc_kt", + srcs = ["//:service_v1alpha1_proto"], + deps = [":service_java"], +) + +kt_jvm_binary( + name = "client_kt", + srcs = ["src/main/kotlin/com/client/BentoServiceClient.kt"], + main_class = "com.client.BentoServiceClient", + deps = [ + ":service_grpc_kt", + ":service_kt", + "@com_google_protobuf//:protobuf_java_util", + "@io_grpc_grpc_java//netty", + ], +) diff --git a/docs/source/guides/snippets/grpc/kotlin/Request.kt b/docs/source/guides/snippets/grpc/kotlin/Request.kt new file mode 100644 index 00000000000..8c399a18223 --- /dev/null +++ b/docs/source/guides/snippets/grpc/kotlin/Request.kt @@ -0,0 +1,5 @@ +val shape: List = listOf(1, 4) +val data: List = listOf(3.5f, 2.4f, 7.8f, 5.1f) + +val ndarray = NDArray.newBuilder().addAllShape(shape).addAllFloatValues(data).build() +val req = Request.newBuilder().setApiName(apiName).setNdarray(ndarray).build() diff --git a/docs/source/guides/snippets/grpc/kotlin/WORKSPACE.snippet.bzl b/docs/source/guides/snippets/grpc/kotlin/WORKSPACE.snippet.bzl new file mode 100644 index 00000000000..749a70e4295 --- /dev/null +++ b/docs/source/guides/snippets/grpc/kotlin/WORKSPACE.snippet.bzl @@ -0,0 +1,98 @@ +workspace(name = "iris_kotlin_client") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "rules_proto", + sha256 = "e017528fd1c91c5a33f15493e3a398181a9e821a804eb7ff5acdd1d2d6c2b18d", + strip_prefix = "rules_proto-4.0.0-3.20.0", + urls = [ + "https://github.com/bazelbuild/rules_proto/archive/refs/tags/4.0.0-3.20.0.tar.gz", + ], +) +http_archive( + name = "rules_proto_grpc", + sha256 = "507e38c8d95c7efa4f3b1c0595a8e8f139c885cb41a76cab7e20e4e67ae87731", + strip_prefix = "rules_proto_grpc-4.1.1", + urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/4.1.1.tar.gz"], +) + +load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains") + +rules_proto_dependencies() + +rules_proto_toolchains() + +load("@rules_proto_grpc//:repositories.bzl", "rules_proto_grpc_repos", "rules_proto_grpc_toolchains") + +rules_proto_grpc_toolchains() + +rules_proto_grpc_repos() + +# We will be using 1.48.1 for grpc-java +http_archive( + name = "io_grpc_grpc_java", + sha256 = "88b12b2b4e0beb849eddde98d5373f2f932513229dbf9ec86cc8e4912fc75e79", + strip_prefix = "grpc-java-1.48.1", + urls = ["https://github.com/grpc/grpc-java/archive/v1.48.1.tar.gz"], +) + +http_archive( + name = "rules_jvm_external", + sha256 = "c21ce8b8c4ccac87c809c317def87644cdc3a9dd650c74f41698d761c95175f3", + strip_prefix = "rules_jvm_external-1498ac6ccd3ea9cdb84afed65aa257c57abf3e0a", + url = "https://github.com/bazelbuild/rules_jvm_external/archive/1498ac6ccd3ea9cdb84afed65aa257c57abf3e0a.zip", +) + +load("@rules_jvm_external//:defs.bzl", "maven_install") +load("@io_grpc_grpc_java//:repositories.bzl", "IO_GRPC_GRPC_JAVA_ARTIFACTS", "IO_GRPC_GRPC_JAVA_OVERRIDE_TARGETS", "grpc_java_repositories") + +IO_GRPC_GRPC_KOTLIN_ARTIFACTS = [ + "com.google.guava:guava:29.0-android", + "com.squareup:kotlinpoet:1.11.0", + "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2", + "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.2", + "org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.6.2", +] + +maven_install( + artifacts = [ + "com.google.jimfs:jimfs:1.1", + "com.google.truth.extensions:truth-proto-extension:1.0.1", + "com.google.protobuf:protobuf-kotlin:3.18.0", + ] + IO_GRPC_GRPC_KOTLIN_ARTIFACTS + IO_GRPC_GRPC_JAVA_ARTIFACTS, + generate_compat_repositories = True, + override_targets = IO_GRPC_GRPC_JAVA_OVERRIDE_TARGETS, + repositories = [ + "https://repo.maven.apache.org/maven2/", + ], +) + +load("@maven//:compat.bzl", "compat_repositories") + +compat_repositories() + +grpc_java_repositories() + +# loading kotlin rules +# first to load grpc/grpc-kotlin +http_archive( + name = "com_github_grpc_grpc_kotlin", + sha256 = "b1ec1caa5d81f4fa4dca0662f8112711c82d7db6ba89c928ca7baa4de50afbb2", + strip_prefix = "grpc-kotlin-a1659c1b3fb665e01a6854224c7fdcafc8e54d56", + urls = ["https://github.com/grpc/grpc-kotlin/archive/a1659c1b3fb665e01a6854224c7fdcafc8e54d56.tar.gz"], +) + +http_archive( + name = "io_bazel_rules_kotlin", + sha256 = "a57591404423a52bd6b18ebba7979e8cd2243534736c5c94d35c89718ea38f94", + urls = ["https://github.com/bazelbuild/rules_kotlin/releases/download/v1.6.0/rules_kotlin_release.tgz"], +) + +load("@io_bazel_rules_kotlin//kotlin:repositories.bzl", "kotlin_repositories") + +kotlin_repositories() + +load("@io_bazel_rules_kotlin//kotlin:core.bzl", "kt_register_toolchains") + +kt_register_toolchains() diff --git a/docs/source/guides/snippets/grpc/node/request.js b/docs/source/guides/snippets/grpc/node/request.js new file mode 100644 index 00000000000..8800b72c783 --- /dev/null +++ b/docs/source/guides/snippets/grpc/node/request.js @@ -0,0 +1,9 @@ +const pb = require("./bentoml/grpc/v1alpha1/service_pb"); + +var ndarray = new pb.NDArray(); +ndarray + .setDtype(pb.NDArray.DType.DTYPE_FLOAT) + .setShapeList([1, 4]) + .setFloatValuesList([3.5, 2.4, 7.8, 5.1]); +var req = new pb.Request(); +req.setApiName("classify").setNdarray(ndarray); diff --git a/docs/source/guides/snippets/grpc/python/request.py b/docs/source/guides/snippets/grpc/python/request.py new file mode 100644 index 00000000000..6e45fea44a4 --- /dev/null +++ b/docs/source/guides/snippets/grpc/python/request.py @@ -0,0 +1,8 @@ +from bentoml.grpc.v1alpha1 import service_pb2 as pb + +req = pb.Request( + api_name="classify", + ndarray=pb.NDArray( + dtype=pb.NDArray.DTYPE_FLOAT, shape=(1, 4), float_values=[5.9, 3, 5.1, 1.8] + ), +) diff --git a/docs/source/guides/snippets/grpc/swift/Request.swift b/docs/source/guides/snippets/grpc/swift/Request.swift new file mode 100644 index 00000000000..693fba227cd --- /dev/null +++ b/docs/source/guides/snippets/grpc/swift/Request.swift @@ -0,0 +1,16 @@ +import BentoServiceModel + +var shape: [Int32] = [1, 4] +var data: [Float] = [3.5, 2.4, 7.8, 5.1] + +let ndarray: Bentoml_Grpc_V1alpha1_NDArray = .with { + $0.shape = shape + $0.floatValues = data + $0.dtype = Bentoml_Grpc_V1alpha1_NDArray.DType.float +} + +let request: Bentoml_Grpc_V1alpha1_Request = .with { + $0.apiName = apiName + $0.ndarray = ndarray +} + diff --git a/docs/source/reference/api_io_descriptors.rst b/docs/source/reference/api_io_descriptors.rst index b920f114c37..ae27dc0b952 100644 --- a/docs/source/reference/api_io_descriptors.rst +++ b/docs/source/reference/api_io_descriptors.rst @@ -43,6 +43,10 @@ NumPy ``ndarray`` .. autoclass:: bentoml.io.NumpyNdarray .. automethod:: bentoml.io.NumpyNdarray.from_sample +.. automethod:: bentoml.io.NumpyNdarray.from_proto +.. automethod:: bentoml.io.NumpyNdarray.from_http_request +.. automethod:: bentoml.io.NumpyNdarray.to_proto +.. automethod:: bentoml.io.NumpyNdarray.to_http_response Tabular Data with Pandas @@ -89,6 +93,10 @@ To use the IO descriptor, install bentoml with extra ``io-pandas`` dependency: .. autoclass:: bentoml.io.PandasDataFrame .. automethod:: bentoml.io.PandasDataFrame.from_sample +.. automethod:: bentoml.io.PandasDataFrame.from_proto +.. automethod:: bentoml.io.PandasDataFrame.from_http_request +.. automethod:: bentoml.io.PandasDataFrame.to_proto +.. automethod:: bentoml.io.PandasDataFrame.to_http_response .. autoclass:: bentoml.io.PandasSeries @@ -137,6 +145,10 @@ Texts :code:`bentoml.io.Text` is commonly used for NLP Applications: .. autoclass:: bentoml.io.Text +.. automethod:: bentoml.io.Text.from_proto +.. automethod:: bentoml.io.Text.from_http_request +.. automethod:: bentoml.io.Text.to_proto +.. automethod:: bentoml.io.Text.to_http_response Images ------ @@ -181,10 +193,19 @@ To use the IO descriptor, install bentoml with extra ``io-image`` dependency: - Pillow .. autoclass:: bentoml.io.Image +.. automethod:: bentoml.io.Image.from_proto +.. automethod:: bentoml.io.Image.from_http_request +.. automethod:: bentoml.io.Image.to_proto +.. automethod:: bentoml.io.Image.to_http_response Files ----- + .. autoclass:: bentoml.io.File +.. automethod:: bentoml.io.File.from_proto +.. automethod:: bentoml.io.File.from_http_request +.. automethod:: bentoml.io.File.to_proto +.. automethod:: bentoml.io.File.to_http_response Multipart Payloads ------------------ @@ -195,6 +216,10 @@ Multipart Payloads contains a image file and additional metadata in JSON. .. autoclass:: bentoml.io.Multipart +.. automethod:: bentoml.io.Multipart.from_proto +.. automethod:: bentoml.io.Multipart.from_http_request +.. automethod:: bentoml.io.Multipart.to_proto +.. automethod:: bentoml.io.Multipart.to_http_response Custom IODescriptor ------------------- diff --git a/docs/source/reference/cli.rst b/docs/source/reference/cli.rst index e2f5cdfe454..28a61da600f 100644 --- a/docs/source/reference/cli.rst +++ b/docs/source/reference/cli.rst @@ -6,7 +6,7 @@ BentoML CLI BentoML CLI command itself also comes usage documentation. You can learn more from running :code:`bentoml --help`. The :code:`--help` flag also applies to sub-commands - for viewing detailed usage of a command, e.g.: :ccode:`bentoml serve --help`. + for viewing detailed usage of a command, e.g.: :code:`bentoml serve --help`. .. click:: bentoml_cli.cli:cli :prog: bentoml diff --git a/grpc-client/README.md b/grpc-client/README.md new file mode 100644 index 00000000000..da4ade4d5f1 --- /dev/null +++ b/grpc-client/README.md @@ -0,0 +1,55 @@ +# gRPC client + +Contains examples for gRPC clients using for [Serving with gRPC](https://docs.bentoml.org/en/latest/guides/grpc.html) + +We will use [`bazel`](bazel.build) to build and run these examples. + +# Instruction + +All clients are built to run with [quickstart image](https://docs.bentoml.org/en/latest/tutorial.html#setup-for-the-tutorial): + +```bash +docker run -it --rm -p 8888:8888 -p 3000:3000 -p 3001:3001 bentoml/quickstart:latest serve-grpc --production --enable-reflection +``` + +To get all available client rules: + +```bash +bazel query //... --output label_kind | grep ":client" | sort | column -t +``` + +To build all rules for better caching: + +```bash +bazel build ... +``` + +The following table contains command to run clients: + +| Language | Command | +| ------------------ | --------------------------------------- | +| [Python](./python) | `bazel run //grpc-client/python:client` | +| [C++](./cpp) | `bazel run //grpc-client/cpp:client` | +| [Go](./go) | `bazel run //grpc-client/go:client` | +| [Java](./java) | `bazel run //grpc-client/java:client` | +| [Kotlin](./kotlin) | `bazel run //grpc-client/kotlin:client` | +| [Swift](./swift) | `pushd swift && client && popd` | +| [Node.js](./node) | `pushd node && yarn client && popd` | +| [PHP](./php) | See [PHP instruction](./php/README.md) | + +> For Swift client, make sure to compile gRPC Swift `protoc` beforehand to generate the client stubs. + +Note that each of the above client examples are also working standalone if you wish not +to use bazel. + +# Adding new language support + +- Update [gRPC guides](../docs/source/guides/grpc.rst) +- Create a new language directory. Add a `client.` and `BUILD` +- Add new rules to `WORKSPACE` +- `bazel run //:buildifier` for formatting + +### TODO: + +- Write ruleset for compiling Swift +- Write ruleset for running grpc-node diff --git a/grpc-client/bentoml b/grpc-client/bentoml new file mode 120000 index 00000000000..e42a7045df6 --- /dev/null +++ b/grpc-client/bentoml @@ -0,0 +1 @@ +../bentoml \ No newline at end of file diff --git a/grpc-client/cpp/BUILD.bazel b/grpc-client/cpp/BUILD.bazel new file mode 100644 index 00000000000..4de52487d1b --- /dev/null +++ b/grpc-client/cpp/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_proto_grpc//cpp:defs.bzl", "cc_grpc_library") + +cc_grpc_library( + name = "service_grpc", + protos = ["//:service_v1alpha1_proto"], + well_known_protos = True, +) + +cc_binary( + name = "client", + srcs = ["client.cc"], + deps = [ + ":service_grpc", + "@com_github_grpc_grpc//:grpc++", + ], +) diff --git a/grpc-client/cpp/client.cc b/grpc-client/cpp/client.cc new file mode 100644 index 00000000000..d3481d82836 --- /dev/null +++ b/grpc-client/cpp/client.cc @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "bentoml/grpc/v1alpha1/service.grpc.pb.h" +#include "bentoml/grpc/v1alpha1/service.pb.h" + +using bentoml::grpc::v1alpha1::BentoService; +using bentoml::grpc::v1alpha1::NDArray; +using bentoml::grpc::v1alpha1::Request; +using bentoml::grpc::v1alpha1::Response; +using grpc::Channel; +using grpc::ClientAsyncResponseReader; +using grpc::ClientContext; +using grpc::Status; + +int main(int argc, char **argv) { + auto stubs = BentoService::NewStub(grpc::CreateChannel( + "localhost:3000", grpc::InsecureChannelCredentials())); + std::vector data = {3.5, 2.4, 7.8, 5.1}; + std::vector shape = {1, 4}; + + Request request; + request.set_api_name("classify"); + + NDArray *ndarray = request.mutable_ndarray(); + ndarray->mutable_shape()->Assign(shape.begin(), shape.end()); + ndarray->mutable_float_values()->Assign(data.begin(), data.end()); + + Response resp; + ClientContext context; + + // Storage for the status of the RPC upon completion. + Status status = stubs->Call(&context, request, &resp); + + // Act upon the status of the actual RPC. + if (!status.ok()) { + std::cout << status.error_code() << ": " << status.error_message() + << std::endl; + return 1; + } + if (!resp.has_ndarray()) { + std::cout << "Currently only accept output as NDArray." << std::endl; + return 1; + } + std::cout << "response byte size: " << resp.ndarray().ByteSizeLong() + << std::endl; + return 0; +} diff --git a/grpc-client/go/BUILD.bazel b/grpc-client/go/BUILD.bazel new file mode 100644 index 00000000000..060255c5b8e --- /dev/null +++ b/grpc-client/go/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_proto_grpc//go:defs.bzl", "go_grpc_library") +load("@io_bazel_rules_go//go:def.bzl", "go_binary") + +go_grpc_library( + name = "service_grpc", + importpath = "github.com/bentoml/bentoml/grpc/v1alpha1", + protos = ["//:service_v1alpha1_proto"], +) + +go_binary( + name = "client", + srcs = ["client.go"], + importpath = "github.com/bentoml/bentoml/grpc/v1alpha1", + deps = [ + ":service_grpc", + "@com_github_golang_protobuf//proto:go_default_library", + "@org_golang_google_grpc//:go_default_library", + "@org_golang_google_grpc//credentials:go_default_library", + "@org_golang_google_grpc//credentials/insecure:go_default_library", + ], +) diff --git a/grpc-client/go/client b/grpc-client/go/client new file mode 100755 index 00000000000..fa007a19e4f --- /dev/null +++ b/grpc-client/go/client @@ -0,0 +1,28 @@ +#!/bin/bash + +set -e + +pushd .. >/dev/null + +if ! which protoc-gen-go-grpc &>/dev/null; then + echo "protoc-gen-go-grpc is missing. Make sure to install it and add it to your PATH." + exit 1 +fi + +if ! [[ -d "github.com/bentoml/bentoml/grpc" ]]; then + protoc -I. -I ./thirdparty/protobuf/src \ + --go_out . --go_opt paths=import \ + --go-grpc_out . --go-grpc_opt paths=import \ + bentoml/grpc/v1alpha1/service.proto +fi + +popd >/dev/null + +# This script is a hack to test out client.go locally without using bazel. +pushd "$(dirname "$0")/github.com/bentoml/bentoml/grpc/v1alpha1/" >/dev/null +! [[ -f "go.mod" ]] && go mod init v1alpha1 +go mod tidy +popd >/dev/null + +# Then run the client +go run ./client.go diff --git a/grpc-client/go/client.go b/grpc-client/go/client.go new file mode 100644 index 00000000000..d8df6ed465e --- /dev/null +++ b/grpc-client/go/client.go @@ -0,0 +1,45 @@ +package main + +import ( + "context" + "fmt" + "time" + + pb "github.com/bentoml/bentoml/grpc/v1alpha1" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +var opts []grpc.DialOption + +const serverAddr = "localhost:3000" + +func main() { + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) + conn, err := grpc.Dial(serverAddr, opts...) + if err != nil { + panic(err) + } + defer conn.Close() + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + client := pb.NewBentoServiceClient(conn) + + req := &pb.Request{ + ApiName: "classify", + Content: &pb.Request_Ndarray{ + Ndarray: &pb.NDArray{ + Dtype: *pb.NDArray_DTYPE_FLOAT.Enum(), + Shape: []int32{1, 4}, + FloatValues: []float32{3.5, 2.4, 7.8, 5.1}, + }, + }, + } + resp, err := client.Call(ctx, req) + if err != nil { + panic(err) + } + fmt.Print(resp) +} diff --git a/grpc-client/go/go.mod b/grpc-client/go/go.mod new file mode 100644 index 00000000000..11f7c700208 --- /dev/null +++ b/grpc-client/go/go.mod @@ -0,0 +1,20 @@ +module grpc_client_go + +go 1.19 + +require ( + google.golang.org/grpc v1.49.0 + google.golang.org/protobuf v1.28.1 // indirect +) + +require github.com/bentoml/bentoml/grpc/v1alpha1 v0.0.0-unpublished + +replace github.com/bentoml/bentoml/grpc/v1alpha1 v0.0.0-unpublished => ./github.com/bentoml/bentoml/grpc/v1alpha1 + +require ( + github.com/golang/protobuf v1.5.2 // indirect + golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect + golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect + golang.org/x/text v0.3.3 // indirect + google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect +) diff --git a/grpc-client/go/go.sum b/grpc-client/go/go.sum new file mode 100644 index 00000000000..9f0cd57e130 --- /dev/null +++ b/grpc-client/go/go.sum @@ -0,0 +1,83 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/grpc-client/java/.gitattributes b/grpc-client/java/.gitattributes new file mode 100644 index 00000000000..eaf5a40aa54 --- /dev/null +++ b/grpc-client/java/.gitattributes @@ -0,0 +1,2 @@ +# These are explicitly windows files and should use crlf +*.bat text eol=crlf diff --git a/grpc-client/java/BUILD.bazel b/grpc-client/java/BUILD.bazel new file mode 100644 index 00000000000..8742862ee04 --- /dev/null +++ b/grpc-client/java/BUILD.bazel @@ -0,0 +1,43 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@io_grpc_grpc_java//:java_grpc_library.bzl", "java_grpc_library") + +# We need to create a separate proto_library +# to conform with macro java_proto_library rules +proto_library( + name = "service_proto", + srcs = ["src/main/proto/v1alpha1/service.proto"], + deps = [ + "@com_google_protobuf//:struct_proto", + "@com_google_protobuf//:wrappers_proto", + ], +) + +java_proto_library( + name = "service_java_proto", + deps = [":service_proto"], +) + +java_grpc_library( + name = "service_java_grpc", + srcs = [":service_proto"], + deps = [":service_java_proto"], +) + +java_library( + name = "grpc-library", + srcs = glob(["src/main/java/**/*.java"]), + deps = [ + ":service_java_grpc", + ":service_java_proto", + "@io_grpc_grpc_java//api", + ], +) + +java_binary( + name = "client", + main_class = "com.client.BentoServiceClient", + runtime_deps = [ + ":grpc-library", + "@io_grpc_grpc_java//netty", + ], +) diff --git a/grpc-client/java/build.gradle b/grpc-client/java/build.gradle new file mode 100644 index 00000000000..f78aa0ff96d --- /dev/null +++ b/grpc-client/java/build.gradle @@ -0,0 +1,65 @@ +plugins { + id 'application' + // ASSUMES GRADLE 5.6 OR HIGHER. Use plugin version 0.8.10 with earlier gradle versions + id 'com.google.protobuf' version '0.8.18' + // Generate IntelliJ IDEA .idea & .iml project files + id 'idea' +} + +repositories { + // The google mirror is less flaky than mavenCentral() + maven { + url "https://maven-central.storage-download.googleapis.com/maven2/" + } + mavenCentral() + mavenLocal() +} + +def grpcVersion = '1.48.1' +def protobufVersion = '3.19.4' +def protocVersion = protobufVersion + +dependencies { + // This dependency is used internally, and not exposed to consumers on their own compile classpath. + implementation 'com.google.guava:guava:30.1.1-jre' + implementation "io.grpc:grpc-protobuf:${grpcVersion}" + implementation "io.grpc:grpc-stub:${grpcVersion}" + compileOnly "org.apache.tomcat:annotations-api:6.0.53" + + // examples/advanced need this for JsonFormat + implementation "com.google.protobuf:protobuf-java-util:${protobufVersion}" + + runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" +} + +protobuf { + protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" } + plugins { + grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" } + } + generateProtoTasks { + all()*.plugins { grpc {} } + } +} + +// Inform IDEs like IntelliJ IDEA, Eclipse or NetBeans about the generated code. +sourceSets { + main { + java { + srcDirs 'build/generated/source/proto/main/grpc' + srcDirs 'build/generated/source/proto/main/java' + } + } +} + +task bentoServiceClient(type: CreateStartScripts) { + mainClass = 'com.client.BentoServiceClient' + applicationName = 'bento-service-client' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +applicationDistribution.into('bin') { + from(bentoServiceClient) + fileMode = 0755 +} diff --git a/grpc-client/java/gradle/wrapper/gradle-wrapper.jar b/grpc-client/java/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000000..41d9927a4d4 Binary files /dev/null and b/grpc-client/java/gradle/wrapper/gradle-wrapper.jar differ diff --git a/grpc-client/java/gradle/wrapper/gradle-wrapper.properties b/grpc-client/java/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..aa991fceae6 --- /dev/null +++ b/grpc-client/java/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/grpc-client/java/gradlew b/grpc-client/java/gradlew new file mode 100755 index 00000000000..1b6c787337f --- /dev/null +++ b/grpc-client/java/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/grpc-client/java/gradlew.bat b/grpc-client/java/gradlew.bat new file mode 100644 index 00000000000..107acd32c4e --- /dev/null +++ b/grpc-client/java/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/grpc-client/java/settings.gradle b/grpc-client/java/settings.gradle new file mode 100644 index 00000000000..8f4b779dc77 --- /dev/null +++ b/grpc-client/java/settings.gradle @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + maven { // The google mirror is less flaky than mavenCentral() + url "https://maven-central.storage-download.googleapis.com/maven2/" + } + gradlePluginPortal() + } +} + +rootProject.name = 'java' diff --git a/grpc-client/java/src/main/java/com/client/BentoServiceClient.java b/grpc-client/java/src/main/java/com/client/BentoServiceClient.java new file mode 100644 index 00000000000..297e0e7f072 --- /dev/null +++ b/grpc-client/java/src/main/java/com/client/BentoServiceClient.java @@ -0,0 +1,68 @@ +package com.client; + +import io.grpc.Channel; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.bentoml.grpc.v1alpha1.BentoServiceGrpc; +import com.bentoml.grpc.v1alpha1.BentoServiceGrpc.BentoServiceBlockingStub; +import com.bentoml.grpc.v1alpha1.BentoServiceGrpc.BentoServiceStub; +import com.bentoml.grpc.v1alpha1.NDArray; +import com.bentoml.grpc.v1alpha1.Request; +import com.bentoml.grpc.v1alpha1.RequestOrBuilder; +import com.bentoml.grpc.v1alpha1.Response; + +public class BentoServiceClient { + + private static final Logger logger = Logger.getLogger(BentoServiceClient.class.getName()); + + static Iterable convert(int[] array) { + return () -> Arrays.stream(array).iterator(); + } + + public static void main(String[] args) throws Exception { + String apiName = "classify"; + int shape[] = { 1, 4 }; + Iterable shapeIterable = convert(shape); + Float array[] = { 3.5f, 2.4f, 7.8f, 5.1f }; + Iterable arrayIterable = Arrays.asList(array); + // Access a service running on the local machine on port 50051 + String target = "localhost:3000"; + + ManagedChannel channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build(); + try { + BentoServiceBlockingStub blockingStub = BentoServiceGrpc.newBlockingStub(channel); + + NDArray.Builder builder = NDArray.newBuilder().addAllShape(shapeIterable).addAllFloatValues(arrayIterable).setDtype(NDArray.DType.DTYPE_FLOAT); + + Request req = Request.newBuilder().setApiName(apiName).setNdarray(builder).build(); + + try { + Response resp = blockingStub.call(req); + Response.ContentCase contentCase = resp.getContentCase(); + if (contentCase != Response.ContentCase.NDARRAY) { + throw new Exception("Currently only support NDArray response"); + } + NDArray output = resp.getNdarray(); + logger.info("Response: " + resp.toString()); + } catch (StatusRuntimeException e) { + logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); + return; + } + } finally { + // ManagedChannels use resources like threads and TCP connections. To prevent + // leaking these + // resources the channel should be shut down when it will no longer be used. If + // it may be used + // again leave it running. + channel.shutdownNow().awaitTermination(1, TimeUnit.SECONDS); + } + } +} diff --git a/grpc-client/java/src/main/proto/v1alpha1 b/grpc-client/java/src/main/proto/v1alpha1 new file mode 120000 index 00000000000..cfa00eec479 --- /dev/null +++ b/grpc-client/java/src/main/proto/v1alpha1 @@ -0,0 +1 @@ +../../../../bentoml/grpc/v1alpha1 \ No newline at end of file diff --git a/grpc-client/kotlin/.gitattributes b/grpc-client/kotlin/.gitattributes new file mode 100644 index 00000000000..eaf5a40aa54 --- /dev/null +++ b/grpc-client/kotlin/.gitattributes @@ -0,0 +1,2 @@ +# These are explicitly windows files and should use crlf +*.bat text eol=crlf diff --git a/grpc-client/kotlin/BUILD.bazel b/grpc-client/kotlin/BUILD.bazel new file mode 100644 index 00000000000..c0d6a1be24b --- /dev/null +++ b/grpc-client/kotlin/BUILD.bazel @@ -0,0 +1,43 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_binary") +load("@io_grpc_grpc_java//:java_grpc_library.bzl", "java_grpc_library") +load("@com_github_grpc_grpc_kotlin//:kt_jvm_grpc.bzl", "kt_jvm_grpc_library", "kt_jvm_proto_library") + +# We need to create a separate proto_library +# to conform with macro java_proto_library rules +proto_library( + name = "service_proto", + srcs = ["src/main/proto/v1alpha1/service.proto"], + deps = [ + "@com_google_protobuf//:struct_proto", + "@com_google_protobuf//:wrappers_proto", + ], +) + +java_proto_library( + name = "service_java_proto", + deps = [":service_proto"], +) + +kt_jvm_proto_library( + name = "service_kt_proto", + deps = [":service_proto"], +) + +kt_jvm_grpc_library( + name = "service_kt_grpc", + srcs = [":service_proto"], + deps = [":service_java_proto"], +) + +kt_jvm_binary( + name = "client", + srcs = ["src/main/kotlin/com/client/BentoServiceClient.kt"], + main_class = "com.client.BentoServiceClient", + deps = [ + ":service_kt_grpc", + ":service_kt_proto", + "@com_google_protobuf//:protobuf_java_util", + "@io_grpc_grpc_java//netty", + ], +) diff --git a/grpc-client/kotlin/build.gradle.kts b/grpc-client/kotlin/build.gradle.kts new file mode 100644 index 00000000000..54f4acf640c --- /dev/null +++ b/grpc-client/kotlin/build.gradle.kts @@ -0,0 +1,63 @@ +plugins { + id("com.android.application") version "7.0.4" apply false // Older for IntelliJ Support + id("com.google.protobuf") version "0.8.18" apply false + kotlin("jvm") version "1.7.0" apply false + id("org.jlleitschuh.gradle.ktlint") version "10.2.0" + `java-library` +} + +ext["grpcVersion"] = "1.48.0" +ext["grpcKotlinVersion"] = "1.6.0" // CURRENT_GRPC_KOTLIN_VERSION +ext["protobufVersion"] = "3.19.4" +ext["coroutinesVersion"] = "1.6.2" + +allprojects { + repositories { + mavenLocal() + mavenCentral() + google() + } + + apply(plugin = "org.jlleitschuh.gradle.ktlint") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(8)) + } + sourceSets.getByName("main").resources.srcDir("src/main/proto") +} + +dependencies { + implementation(platform("org.jetbrains.kotlin:kotlin-bom")) + + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + + implementation("com.google.guava:guava:30.1.1-jre") + + runtimeOnly("io.grpc:grpc-netty:${rootProject.ext["grpcVersion"]}") + api(kotlin("stdlib-jdk8")) + api("org.jetbrains.kolinx:kotlinx-coroutines-core:${rootProject.ext["coroutinesVersion"]}") + api("io.grpc:grpc-stub:${rootProject.ext["grpcVersion"]}") + api("io.grpc:grpc-protobuf:${rootProject.ext["grpcVersion"]}") + api("com.google.protobuf:protobuf-java-util:${rootProject.ext["protobufVersion"]}") + api("com.google.protobuf:protobuf-kotlin:${rootProject.ext["protobufVersion"]}") + api("io.grpc:grpc-kotlin-stub:${rootProject.ext["grpcKotlinVersion"]}") +} + +tasks.register("BentoServiceClient") { + dependsOn("classes") + classpath = sourceSets["main"].runtimeClasspath + mainClass.set("com.client.BentoServiceClientKt") +} + +val bentoServiceClientStartScripts = tasks.register("bentoServiceClientStartScripts") { + mainClass.set("com.client.BentoServiceClientKt") + applicationName = "bento-service-client" + outputDir = tasks.named("startScripts").get().outputDir + classpath = tasks.named("startScripts").get().classpath +} + +tasks.named("startScripts") { + dependsOn(bentoServiceClientStartScripts) +} diff --git a/grpc-client/kotlin/gradle/wrapper/gradle-wrapper.jar b/grpc-client/kotlin/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000000..41d9927a4d4 Binary files /dev/null and b/grpc-client/kotlin/gradle/wrapper/gradle-wrapper.jar differ diff --git a/grpc-client/kotlin/gradle/wrapper/gradle-wrapper.properties b/grpc-client/kotlin/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..aa991fceae6 --- /dev/null +++ b/grpc-client/kotlin/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/grpc-client/kotlin/gradlew b/grpc-client/kotlin/gradlew new file mode 100755 index 00000000000..1b6c787337f --- /dev/null +++ b/grpc-client/kotlin/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/grpc-client/kotlin/gradlew.bat b/grpc-client/kotlin/gradlew.bat new file mode 100644 index 00000000000..107acd32c4e --- /dev/null +++ b/grpc-client/kotlin/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/grpc-client/kotlin/settings.gradle.kts b/grpc-client/kotlin/settings.gradle.kts new file mode 100644 index 00000000000..c926d9ed63f --- /dev/null +++ b/grpc-client/kotlin/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "kotlin" diff --git a/grpc-client/kotlin/src/main/kotlin/com/client/BentoServiceClient.kt b/grpc-client/kotlin/src/main/kotlin/com/client/BentoServiceClient.kt new file mode 100644 index 00000000000..74a22bf3d48 --- /dev/null +++ b/grpc-client/kotlin/src/main/kotlin/com/client/BentoServiceClient.kt @@ -0,0 +1,34 @@ +package com.client + +import com.bentoml.grpc.v1alpha1.BentoServiceGrpc +import com.bentoml.grpc.v1alpha1.NDArray +import com.bentoml.grpc.v1alpha1.Request +import io.grpc.ManagedChannelBuilder + +class BentoServiceClient { + companion object { + @JvmStatic + fun main(args: Array) { + val apiName: String = "classify" + val shape: List = listOf(1, 4) + val data: List = listOf(3.5f, 2.4f, 7.8f, 5.1f) + + val channel = ManagedChannelBuilder.forAddress("localhost", 3000).usePlaintext().build() + + val client = BentoServiceGrpc.newBlockingStub(channel) + + val ndarray = NDArray.newBuilder().addAllShape(shape).addAllFloatValues(data).build() + val req = Request.newBuilder().setApiName(apiName).setNdarray(ndarray).build() + try { + val resp = client.call(req) + if (!resp.hasNdarray()) { + println("Currently only support NDArray response.") + } else { + println("Response: ${resp.ndarray}") + } + } catch (e: Exception) { + println("Rpc error: ${e.message}") + } + } + } +} diff --git a/grpc-client/kotlin/src/main/proto/v1alpha1 b/grpc-client/kotlin/src/main/proto/v1alpha1 new file mode 120000 index 00000000000..cfa00eec479 --- /dev/null +++ b/grpc-client/kotlin/src/main/proto/v1alpha1 @@ -0,0 +1 @@ +../../../../bentoml/grpc/v1alpha1 \ No newline at end of file diff --git a/grpc-client/node/client.js b/grpc-client/node/client.js new file mode 100644 index 00000000000..7b2ca40b9ec --- /dev/null +++ b/grpc-client/node/client.js @@ -0,0 +1,35 @@ +"use strict"; +const grpc = require("@grpc/grpc-js"); +const pb = require("./bentoml/grpc/v1alpha1/service_pb"); +const services = require("./bentoml/grpc/v1alpha1/service_grpc_pb"); + +function main() { + const target = "localhost:3000"; + const client = new services.BentoServiceClient( + target, + grpc.credentials.createInsecure(), + ); + var ndarray = new pb.NDArray(); + ndarray + .setDtype(pb.NDArray.DType.DTYPE_FLOAT) + .setShapeList([1, 4]) + .setFloatValuesList([3.5, 2.4, 7.8, 5.1]); + var req = new pb.Request(); + req.setApiName("classify").setNdarray(ndarray); + + client.call(req, function (err, resp) { + if (err) { + console.log(err.message); + if (err.code === grpc.status.INVALID_ARGUMENT) { + console.log("Invalid argument", resp); + } + } else { + if (resp.getContentCase() != pb.Response.ContentCase.NDARRAY) { + console.error("Only support NDArray response."); + } + console.log("result: ", resp.getNdarray().toObject()); + } + }); +} + +main(); diff --git a/grpc-client/node/hack b/grpc-client/node/hack new file mode 100755 index 00000000000..307957b320d --- /dev/null +++ b/grpc-client/node/hack @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +pushd .. >/dev/null +ln -s js/node_modules node_modules +yarn +"$(npm bin)"/grpc_tools_node_protoc -I. --js_out=import_style=commonjs,binary:js --grpc_out=grpc_js:js bentoml/grpc/v1alpha1/service.proto +rm node_modules +popd diff --git a/grpc-client/node/package.json b/grpc-client/node/package.json new file mode 100644 index 00000000000..adcfe164225 --- /dev/null +++ b/grpc-client/node/package.json @@ -0,0 +1,19 @@ +{ + "name": "grpc_client_js", + "version": "1.0.0", + "description": "gRPC client in JavaScript", + "main": "client.js", + "keywords": [], + "author": "BentoML Team", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "google-protobuf": "^3.21.0", + "grpc-tools": "^1.11.2", + "ts-protoc-gen": "^0.15.0" + }, + "scripts": { + "compile": "npm_config_target_arch=x64 yarn && bash -x ./hack", + "client": "yarn compile && node client.js" + } +} diff --git a/grpc-client/node/yarn.lock b/grpc-client/node/yarn.lock new file mode 100644 index 00000000000..7b7f5be5066 --- /dev/null +++ b/grpc-client/node/yarn.lock @@ -0,0 +1,587 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@grpc/grpc-js@^1.7.1": + version "1.7.1" + resolved "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.1.tgz" + integrity sha512-GVtMU4oh/TeKkWGzXUEsyZtyvSUIT1z49RtGH1UnEGeL+sLuxKl8QH3KZTlSB329R1sWJmesm5hQ5CxXdYH9dg== + dependencies: + "@grpc/proto-loader" "^0.7.0" + "@types/node" ">=12.12.47" + +"@grpc/proto-loader@^0.7.0": + version "0.7.3" + resolved "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.3.tgz" + integrity sha512-5dAvoZwna2Py3Ef96Ux9jIkp3iZ62TUsV00p3wVBPNX5K178UbNi8Q7gQVqwXT1Yq9RejIGG9G2IPEo93T6RcA== + dependencies: + "@types/long" "^4.0.1" + lodash.camelcase "^4.3.0" + long "^4.0.0" + protobufjs "^7.0.0" + yargs "^16.2.0" + +"@mapbox/node-pre-gyp@^1.0.5": + version "1.0.10" + resolved "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz" + integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + +"@types/long@^4.0.1": + version "4.0.2" + resolved "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== + +"@types/node@>=12.12.47", "@types/node@>=13.7.0": + version "18.7.18" + resolved "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz" + integrity sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg== + +abbrev@1: + version "1.1.1" + resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +agent-base@6: + version "6.0.2" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +console-control-strings@^1.0.0, console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + +debug@4: + version "4.3.4" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +detect-libc@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz" + integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +google-protobuf@^3.15.5, google-protobuf@^3.21.0: + version "3.21.0" + resolved "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.0.tgz" + integrity sha512-byR7MBTK4tZ5PZEb+u5ZTzpt4SfrTxv5682MjPlHN16XeqgZE2/8HOIWeiXe8JKnT9OVbtBGhbq8mtvkK8cd5g== + +grpc-tools@^1.11.2: + version "1.11.2" + resolved "https://registry.npmjs.org/grpc-tools/-/grpc-tools-1.11.2.tgz" + integrity sha512-4+EgpnnkJraamY++oyBCw5Hp9huRYfgakjNVKbiE3PgO9Tv5ydVlRo7ZyGJ0C0SEiA7HhbVc1sNNtIyK7FiEtg== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.5" + +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz" + integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== + +long@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/long/-/long-4.0.0.tgz" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + +long@^5.0.0: + version "5.2.0" + resolved "https://registry.npmjs.org/long/-/long-5.2.0.tgz" + integrity sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minipass@^3.0.0: + version "3.3.4" + resolved "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz" + integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw== + dependencies: + yallist "^4.0.0" + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +node-fetch@^2.6.7: + version "2.6.7" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +protobufjs@^7.0.0: + version "7.1.2" + resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz" + integrity sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + +readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +semver@^6.0.0: + version "6.3.0" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.3.5: + version "7.3.7" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + +signal-exit@^3.0.0: + version "3.0.7" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +tar@^6.1.11: + version "6.1.11" + resolved "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +ts-protoc-gen@^0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/ts-protoc-gen/-/ts-protoc-gen-0.15.0.tgz#2fec5930b46def7dcc9fa73c060d770b7b076b7b" + integrity sha512-TycnzEyrdVDlATJ3bWFTtra3SCiEP0W0vySXReAuEygXCUr1j2uaVyL0DhzjwuUdQoW5oXPwk6oZWeA0955V+g== + dependencies: + google-protobuf "^3.15.5" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" diff --git a/grpc-client/php/BentoServiceClient.php b/grpc-client/php/BentoServiceClient.php new file mode 100644 index 00000000000..68c13361011 --- /dev/null +++ b/grpc-client/php/BentoServiceClient.php @@ -0,0 +1,34 @@ + Grpc\ChannelCredentials::createInsecure(), + ]); + $request = new Request(); + $request->setApiName($apiName); + $payload = new NDArray(); + $payload->setShape($shape); + $payload->setFloatValues($data); + $payload->setDtype(\Bentoml\Grpc\V1alpha1\NDArray\DType::DTYPE_FLOAT); + + list($response, $status) = $client->Call($request)->wait(); + if ($status->code !== Grpc\STATUS_OK) { + echo "ERROR: " . $status->code . ", " . $status->details . PHP_EOL; + exit(1); + } + echo $response->getMessage() . PHP_EOL; +} + +call(); diff --git a/grpc-client/php/README.md b/grpc-client/php/README.md new file mode 100644 index 00000000000..b262b7c2045 --- /dev/null +++ b/grpc-client/php/README.md @@ -0,0 +1,15 @@ +### Instruction + +Make sure to have [`grpc extension`](https://github.com/grpc/grpc/blob/master/src/php/README.md) installed + +Generate the stubs: + +```bash +./codegen +``` + +The run the client: + +```bash +COMPILED_GRPC_SO=/path/to/grpc.so ./client +``` diff --git a/grpc-client/php/client b/grpc-client/php/client new file mode 100755 index 00000000000..f3e9073f5c2 --- /dev/null +++ b/grpc-client/php/client @@ -0,0 +1,29 @@ +#!/bin/bash + +set -e + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" + +cd "$SCRIPT_DIR" || exit 1 + +if ! which php >/dev/null; then + echo "PHP is required." + exit 0 +fi + +if ! which composer >/dev/null; then + echo "composer is required." + exit 0 +fi + +if ! [[ -f $SCRIPT_DIR/composer.lock ]]; then + composer install +else + composer update +fi + +echo "Running PHP client" + +COMPILED_PATH=${COMPILED_GRPC_SO:-grpc} + +php -d extension="${COMPILED_PATH}" -d max_execution_time=300 BentoServiceClient.php diff --git a/grpc-client/php/codegen b/grpc-client/php/codegen new file mode 100755 index 00000000000..254d8e4b8cf --- /dev/null +++ b/grpc-client/php/codegen @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +pushd "$SCRIPT_DIR"/../.. >/dev/null + +# We will use BentoML tools/bazel.rc +echo "Building shared C++ gRPC..." +bazel build @com_github_grpc_grpc//:all +bazel build @com_google_protobuf//:protoc + +if ! [ -f "bazel-bin/src/compiler/grpc_php_plugin" ]; then + echo "We will compile grpc_php_plugin from source." + bazel build @com_github_grpc_grpc//src/compiler:grpc_php_plugin +fi + +PLUGIN=protoc-gen-grpc=$(pwd)/bazel-bin/external/com_github_grpc_grpc/src/compiler/grpc_php_plugin + +echo "Generate PHP stubs." +bazel run @com_google_protobuf//:protoc -- -I ./grpc-client -I ./grpc-client/thirdparty/protobuf/src --php_out=grpc-client/php --grpc_out=grpc-client/php --plugin="$PLUGIN" grpc-client/bentoml/grpc/v1alpha1/service.proto + +popd >/dev/null diff --git a/grpc-client/php/composer.json b/grpc-client/php/composer.json new file mode 100644 index 00000000000..47f126940e7 --- /dev/null +++ b/grpc-client/php/composer.json @@ -0,0 +1,20 @@ +{ + "name": "bentoml/grpc-php-client", + "description": "BentoML gRPC client for PHP", + "require": { + "grpc/grpc": "1.42.0", + "google/protobuf": "3.19.4" + }, + "license": "Apache-2.0", + "autoload": { + "psr-4": { + "GBPMetadata\\": "GPBMetadata/", + "Bentoml\\": "Bentoml" + } + }, + "authors": [ + { + "name": "BentoML Team" + } + ] +} diff --git a/grpc-client/python/BUILD.bazel b/grpc-client/python/BUILD.bazel new file mode 100644 index 00000000000..48e45e5ee08 --- /dev/null +++ b/grpc-client/python/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_python//python:defs.bzl", "py_binary") +load("@bentoml_requirements//:requirements.bzl", "requirement") + +py_binary( + name = "client", + srcs = ["client.py"], + python_version = "PY3", + deps = [ + requirement("grpcio"), + requirement("bentoml"), + requirement("protobuf"), + ], +) diff --git a/grpc-client/python/client.py b/grpc-client/python/client.py new file mode 100644 index 00000000000..7275b7f9253 --- /dev/null +++ b/grpc-client/python/client.py @@ -0,0 +1,32 @@ +import asyncio + +import grpc + +from bentoml.grpc.utils import import_generated_stubs + +pb, services = import_generated_stubs() + + +async def run(): + async with grpc.aio.insecure_channel("localhost:3000") as channel: + stub = services.BentoServiceStub(channel) + req = await stub.Call( + request=pb.Request( + api_name="classify", + ndarray=pb.NDArray( + dtype=pb.NDArray.DTYPE_FLOAT, + shape=(1, 4), + float_values=[5.9, 3, 5.1, 1.8], + ), + ) + ) + print(req) + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + try: + loop.run_until_complete(run()) + finally: + loop.close() + assert loop.is_closed() diff --git a/grpc-client/python/requirement.in b/grpc-client/python/requirement.in new file mode 100644 index 00000000000..755a12b25d4 --- /dev/null +++ b/grpc-client/python/requirement.in @@ -0,0 +1,10 @@ +# We will have to manually generate the lock files +# TODO: bazel rules :) +git+https://github.com/bentoml/bentoml.git@9ac664f27b6d5801a2f0ad46d3fe32f943e6be8f +setuptools==65.3.0 +protobuf==3.19.4 +# Lowest version that support 3.10 +grpcio==1.48.1 +grpcio-health-checking==1.48.1 +grpcio-reflection==1.48.1 +opentelemetry-instrumentation-grpc==0.33b0 diff --git a/grpc-client/python/requirements.lock.txt b/grpc-client/python/requirements.lock.txt new file mode 100644 index 00000000000..0ed175e9741 --- /dev/null +++ b/grpc-client/python/requirements.lock.txt @@ -0,0 +1,205 @@ +# +# This file is autogenerated by pip-compile with python 3.10 +# To update, run: +# +# pip-compile --output-file=python/requirements.lock.txt python/requirement.in +# +aiohttp==3.8.3 + # via bentoml +aiosignal==1.2.0 + # via aiohttp +anyio==3.6.1 + # via + # starlette + # watchfiles +appdirs==1.4.4 + # via fs +asgiref==3.5.2 + # via opentelemetry-instrumentation-asgi +async-timeout==4.0.2 + # via aiohttp +attrs==22.1.0 + # via + # aiohttp + # bentoml + # cattrs +bentoml @ git+https://github.com/bentoml/bentoml.git@9ac664f27b6d5801a2f0ad46d3fe32f943e6be8f + # via -r python/requirement.in +build==0.8.0 + # via pip-tools +cattrs==22.1.0 + # via bentoml +certifi==2022.9.14 + # via requests +charset-normalizer==2.1.1 + # via + # aiohttp + # requests +circus==0.17.1 + # via bentoml +click==8.1.3 + # via + # bentoml + # pip-tools + # uvicorn +cloudpickle==2.2.0 + # via bentoml +commonmark==0.9.1 + # via rich +contextlib2==21.6.0 + # via schema +deepmerge==1.0.1 + # via bentoml +deprecated==1.2.13 + # via opentelemetry-api +exceptiongroup==1.0.0rc9 + # via cattrs +frozenlist==1.3.1 + # via + # aiohttp + # aiosignal +fs==2.4.16 + # via bentoml +grpcio==1.48.1 + # via + # -r python/requirement.in + # grpcio-health-checking + # grpcio-reflection +grpcio-health-checking==1.48.1 + # via -r python/requirement.in +grpcio-reflection==1.48.1 + # via -r python/requirement.in +h11==0.13.0 + # via uvicorn +idna==3.4 + # via + # anyio + # requests + # yarl +jinja2==3.1.2 + # via bentoml +markupsafe==2.1.1 + # via jinja2 +multidict==6.0.2 + # via + # aiohttp + # yarl +numpy==1.23.3 + # via bentoml +opentelemetry-api==1.12.0 + # via + # bentoml + # opentelemetry-instrumentation + # opentelemetry-instrumentation-aiohttp-client + # opentelemetry-instrumentation-asgi + # opentelemetry-instrumentation-grpc + # opentelemetry-sdk +opentelemetry-instrumentation==0.33b0 + # via + # bentoml + # opentelemetry-instrumentation-aiohttp-client + # opentelemetry-instrumentation-asgi + # opentelemetry-instrumentation-grpc +opentelemetry-instrumentation-aiohttp-client==0.33b0 + # via bentoml +opentelemetry-instrumentation-asgi==0.33b0 + # via bentoml +opentelemetry-instrumentation-grpc==0.33b0 + # via -r python/requirement.in +opentelemetry-sdk==1.12.0 + # via + # bentoml + # opentelemetry-instrumentation-grpc +opentelemetry-semantic-conventions==0.33b0 + # via + # bentoml + # opentelemetry-instrumentation-aiohttp-client + # opentelemetry-instrumentation-asgi + # opentelemetry-instrumentation-grpc + # opentelemetry-sdk +opentelemetry-util-http==0.33b0 + # via + # bentoml + # opentelemetry-instrumentation-aiohttp-client + # opentelemetry-instrumentation-asgi +packaging==21.3 + # via + # bentoml + # build +pathspec==0.10.1 + # via bentoml +pep517==0.13.0 + # via build +pip-tools==6.8.0 + # via bentoml +prometheus-client==0.13.1 + # via bentoml +protobuf==3.19.4 + # via + # -r python/requirement.in + # grpcio-health-checking + # grpcio-reflection +psutil==5.9.2 + # via + # bentoml + # circus +pygments==2.13.0 + # via rich +pynvml==11.4.1 + # via bentoml +pyparsing==3.0.9 + # via packaging +python-dateutil==2.8.2 + # via bentoml +python-dotenv==0.21.0 + # via bentoml +python-multipart==0.0.5 + # via bentoml +pyyaml==6.0 + # via bentoml +pyzmq==24.0.1 + # via circus +requests==2.28.1 + # via bentoml +rich==12.5.1 + # via bentoml +schema==0.7.5 + # via bentoml +simple-di==0.1.5 + # via bentoml +six==1.16.0 + # via + # fs + # grpcio + # python-dateutil + # python-multipart +sniffio==1.3.0 + # via anyio +starlette==0.20.4 + # via bentoml +tomli==2.0.1 + # via + # build + # pep517 +tornado==6.2 + # via circus +typing-extensions==4.3.0 + # via opentelemetry-sdk +urllib3==1.26.12 + # via requests +uvicorn==0.18.3 + # via bentoml +watchfiles==0.17.0 + # via bentoml +wheel==0.37.1 + # via pip-tools +wrapt==1.14.1 + # via + # deprecated + # opentelemetry-instrumentation + # opentelemetry-instrumentation-aiohttp-client + # opentelemetry-instrumentation-grpc +yarl==1.8.1 + # via aiohttp +pip==22.1.0 +setuptools==65.3.0 diff --git a/grpc-client/swift/.swiftformat b/grpc-client/swift/.swiftformat new file mode 100644 index 00000000000..ad3452cc684 --- /dev/null +++ b/grpc-client/swift/.swiftformat @@ -0,0 +1,42 @@ +# Language version. +--swiftversion 5.2 + +# Ignore generated files. +--exclude .build/,**/XCTestManifests.swift,**/*.grpc.swift,**/*.pb.swift + +--indent 2 +--maxwidth 100 + +# Require explicit self +--self insert + +# Only remove unused args in closures. +--stripunusedargs closure-only + +# Wrap before the first argument (if wrapping is necessary). +--wraparguments before-first + +# Don't indent #if blocks +--ifdef no-indent + +# Don't turn Optional into Foo? +--shortoptionals except-properties + +# This rule doesn't always work as we'd expect: specifically when we return a +# succeeded future whose type is a closure then that closure is incorrectly +# treated as a trailing closure. This is relevant because the service provider +# API for client streaming RPCs has this exact shape. +--disable trailingClosures + +# Don't wrap the opening brace of multiline statements. +--disable wrapMultilineStatementBraces + +# We used to support 5.0 and return is redundant in more places in 5.1: enabling +# this rule creates a large (and unnecessary) diff. +--disable redundantReturn + +# Don't prefer using key paths for trivial closures. +--disable preferKeyPath + +# Put ACLs on declarations within an extension rather than the extension itself. +--extensionacl on-declarations diff --git a/grpc-client/swift/Package.swift b/grpc-client/swift/Package.swift new file mode 100644 index 00000000000..393b0fc81fb --- /dev/null +++ b/grpc-client/swift/Package.swift @@ -0,0 +1,62 @@ +// swift-tools-version: 5.7 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +// To declare other packages that this package depends on. +let packageDependencies: [Package.Dependency] = [ + .package( + url: "https://github.com/grpc/grpc-swift.git", + from: "1.10.0" + ), + .package( + url: "https://github.com/apple/swift-nio.git", + from: "2.41.1" + ), + .package( + url: "https://github.com/apple/swift-protobuf.git", + from: "1.20.1" + ), +] + +// Defines dependencies for our targets. +extension Target.Dependency { + static let bentoServiceModel: Self = .target(name: "BentoServiceModel") + + static let grpc: Self = .product(name: "GRPC", package: "grpc-swift") + static let nio: Self = .product(name: "NIO", package: "swift-nio") + static let nioCore: Self = .product(name: "NIOCore", package: "swift-nio") + static let nioPosix: Self = .product(name: "NIOPosix", package: "swift-nio") + static let protobuf: Self = .product(name: "SwiftProtobuf", package: "swift-protobuf") +} + +// Targets are the basic building blocks of a package. A target can define a module or a test suite. +// Targets can depend on other targets in this package, and on products in packages this package depends on. +extension Target { + static let bentoServiceModel: Target = .target( + name: "BentoServiceModel", + dependencies: [ + .grpc, + .nio, + .protobuf, + ], + path: "Sources/bentoml/grpc/v1alpha1" + ) + + static let bentoServiceClient: Target = .executableTarget( + name: "BentoServiceClient", + dependencies: [ + .grpc, + .bentoServiceModel, + .nioCore, + .nioPosix, + ], + path: "Sources/BentoServiceClient" + ) +} + +let package = Package( + name: "iris-swift-client", + dependencies: packageDependencies, + targets: [.bentoServiceModel, .bentoServiceClient] +) diff --git a/grpc-client/swift/Sources/BentoServiceClient/main.swift b/grpc-client/swift/Sources/BentoServiceClient/main.swift new file mode 100644 index 00000000000..77527d7fc50 --- /dev/null +++ b/grpc-client/swift/Sources/BentoServiceClient/main.swift @@ -0,0 +1,75 @@ +#if compiler(>=5.6) +#if BAZEL_BUILD +import swift_BentoServiceModel // internal targets +#else +import BentoServiceModel +#endif +import Foundation +import GRPC +import NIOCore +import NIOPosix +import SwiftProtobuf + +// Setup an `EventLoopGroup` for the connection to run on. +// +// See: https://github.com/apple/swift-nio#eventloops-and-eventloopgroups +let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + +var apiName: String = "classify" +var shape: [Int32] = [1, 4] +var data: [Float] = [3.5, 2.4, 7.8, 5.1] + +// Make sure the group is shutdown when we're done with it. +defer { + try! group.syncShutdownGracefully() +} + +// Configure the channel, we're not using TLS so the connection is `insecure`. +let channel = try GRPCChannelPool.with( + target: .host("localhost", port: 3000), + transportSecurity: .plaintext, + eventLoopGroup: group +) + +// Close the connection when we're done with it. +defer { + try! channel.close().wait() +} + +// Provide the connection to the generated client. +let stubs = Bentoml_Grpc_V1alpha1_BentoServiceNIOClient(channel: channel) + +// Form the request with the NDArray, if one was provided. +let ndarray: Bentoml_Grpc_V1alpha1_NDArray = .with { + $0.shape = shape + $0.floatValues = data + $0.dtype = Bentoml_Grpc_V1alpha1_NDArray.DType.float +} + +let request: Bentoml_Grpc_V1alpha1_Request = .with { + $0.apiName = apiName + $0.ndarray = ndarray +} + +let call = stubs.call(request) +do { + let resp = try call.response.wait() + if let content = resp.content { + switch content { + case let .ndarray(ndarray): + print("Response: \(ndarray)") + default: + print("Currently only support NDArray response.") + } + } +} catch { + print("Rpc failed \(try call.status.wait()): \(error)") +} +#else +@main +enum NotAvailable { + static func main() { + print("This example requires Swift >= 5.6") + } +} +#endif // compiler(>=5.6) diff --git a/grpc-client/swift/client b/grpc-client/swift/client new file mode 100755 index 00000000000..aee92be6bae --- /dev/null +++ b/grpc-client/swift/client @@ -0,0 +1,21 @@ +#!/bin/bash + +set -e + +pushd .. >/dev/null + +if ! which protoc-gen-grpc-swift &>/dev/null; then + echo "protoc-gen-grpc-swift is missing. Make sure to compile it first and add it to your PATH." + exit 1 +fi + +if ! [[ -d "Sources/bentoml" ]]; then + protoc -I . -I ./thirdparty/protobuf/src \ + --swift_out=Sources --swift_opt=Visibility=Public \ + --grpc-swift_out=Sources --grpc-swift_opt=Visibility=Public \ + bentoml/grpc/v1alpha1/service.proto +fi + +popd >/dev/null + +swift run BentoServiceClient diff --git a/pyproject.toml b/pyproject.toml index 31dc4532955..8341ef8371f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -176,6 +176,8 @@ exclude = ''' | \.tox | \.venv | _build + | .build + | bazel-* | build | venv | lib diff --git a/requirements/docs-requirements.txt b/requirements/docs-requirements.txt index 38de484f75c..a0dd0c1cb49 100644 --- a/requirements/docs-requirements.txt +++ b/requirements/docs-requirements.txt @@ -1,6 +1,6 @@ # Docs dependencies -r tests-requirements.txt -sphinx==4.5.0 +sphinx>=5 setuptools>=63 # readthedocs doesn't support -r myst-parser sphinx-click>=3.0.2 @@ -15,3 +15,4 @@ sphinx-design pyenchant Jinja2>=3.1 sphinx-autobuild +sphinx-hoverxref diff --git a/scripts/ci/run_tests.sh b/scripts/ci/run_tests.sh index 637e57f3ea3..0fb4765450c 100755 --- a/scripts/ci/run_tests.sh +++ b/scripts/ci/run_tests.sh @@ -6,7 +6,6 @@ # pip install -e . # pip install requirements/tests-requirements.txt - fname=$(basename "$0") dname=$(dirname "$0") @@ -26,32 +25,31 @@ SKIP_DEPS=0 cd "$GIT_ROOT" || exit run_yq() { - need_cmd yq - yq "$@"; + need_cmd yq + yq "$@" } -getval(){ - run_yq eval "$@" "$CONFIG_FILE"; +getval() { + run_yq eval "$@" "$CONFIG_FILE" } validate_yaml() { - # validate YAML file - if ! [ -f "$CONFIG_FILE" ]; then - FAIL "$CONFIG_FILE does not exists" - exit 1 - fi - - if ! (run_yq e --exit-status 'tag == "!!map" or tag== "!!seq"' "$CONFIG_FILE"> /dev/null); then - FAIL "Invalid YAML file" - exit 1 - fi + # validate YAML file + if ! [ -f "$CONFIG_FILE" ]; then + FAIL "$CONFIG_FILE does not exists" + exit 1 + fi + + if ! (run_yq e --exit-status 'tag == "!!map" or tag== "!!seq"' "$CONFIG_FILE" >/dev/null); then + FAIL "Invalid YAML file" + exit 1 + fi } - usage() { - need_cmd cat + need_cmd cat - cat <"$REQ_FILE" || exit + target=$@ + test_dir= + is_dir= + override_name_or_path= + external_scripts= + type_tests= + + test_dir=$(getval ".$target.root_test_dir") + is_dir=$(getval ".$target.is_dir") + override_name_or_path=$(getval ".$target.override_name_or_path") + external_scripts=$(getval ".$target.external_scripts") + type_tests=$(getval ".$target.type_tests") + + # processing file name + if [[ "$override_name_or_path" != "" ]]; then + fname="$override_name_or_path" + elif [[ "$is_dir" == "false" ]]; then + fname="test_""$target""_impl.py" + elif [[ "$is_dir" == "true" ]]; then + fname="" + shift + else + fname="$target" + fi + + # processing dependencies + run_yq eval '.'"$target"'.dependencies[]' "$CONFIG_FILE" >"$REQ_FILE" || exit } install_yq() { - set -ex - target_dir="$HOME/.local/bin" - - mkdir -p "$target_dir" - export PATH=$target_dir:$PATH - - YQ_VERSION=4.16.1 - echo "Trying to install yq..." - shell=$(uname | tr '[:upper:]' '[:lower:]') - extensions=".tar.gz" - if [[ "$shell" =~ "mingw64" ]]; then - shell="windows" - extensions=".zip" - fi - - YQ_BINARY=yq_"$shell"_amd64 - YQ_EXTRACT="./$YQ_BINARY" - if [[ "$shell" == "windows" ]]; then - YQ_EXTRACT="$YQ_BINARY.exe" - fi - curl -fsSLO https://github.com/mikefarah/yq/releases/download/v"$YQ_VERSION"/"$YQ_BINARY""$extensions" - echo "tar $YQ_BINARY$extensions and move to /usr/bin/yq..." - if [[ $(uname | tr '[:upper:]' '[:lower:]') =~ "mingw64" ]]; then - unzip -qq "$YQ_BINARY$extensions" -d yq_dir && cd yq_dir - mv "$YQ_EXTRACT" "$target_dir"/yq && cd .. - rm -rf yq_dir - else - tar -zvxf "$YQ_BINARY$extensions" "$YQ_EXTRACT" && mv "$YQ_EXTRACT" "$target_dir"/yq - fi - rm -f ./"$YQ_BINARY""$extensions" + set -ex + target_dir="$HOME/.local/bin" + + mkdir -p "$target_dir" + export PATH=$target_dir:$PATH + + YQ_VERSION=4.16.1 + echo "Trying to install yq..." + shell=$(uname | tr '[:upper:]' '[:lower:]') + extensions=".tar.gz" + if [[ "$shell" =~ "mingw64" ]]; then + shell="windows" + extensions=".zip" + fi + + YQ_BINARY=yq_"$shell"_amd64 + YQ_EXTRACT="./$YQ_BINARY" + if [[ "$shell" == "windows" ]]; then + YQ_EXTRACT="$YQ_BINARY.exe" + fi + curl -fsSLO https://github.com/mikefarah/yq/releases/download/v"$YQ_VERSION"/"$YQ_BINARY""$extensions" + echo "tar $YQ_BINARY$extensions and move to /usr/bin/yq..." + if [[ $(uname | tr '[:upper:]' '[:lower:]') =~ "mingw64" ]]; then + unzip -qq "$YQ_BINARY$extensions" -d yq_dir && cd yq_dir + mv "$YQ_EXTRACT" "$target_dir"/yq && cd .. + rm -rf yq_dir + else + tar -zvxf "$YQ_BINARY$extensions" "$YQ_EXTRACT" && mv "$YQ_EXTRACT" "$target_dir"/yq + fi + rm -f ./"$YQ_BINARY""$extensions" } - main() { - parse_args "$@" - - need_cmd make - need_cmd curl - need_cmd tr - (need_cmd yq && echo "Using yq via $(which yq)...";) || install_yq - - for args in "$@"; do - if [[ "$args" != "-"* ]]; then - argv="$args" - break; - else - shift; - fi - done - - # validate_yaml - parse_config "$argv" - - OPTS=(--cov=bentoml --cov-config="$GIT_ROOT"/pyproject.toml --cov-report=xml:"$target.xml" --cov-report=term-missing -x) - - if [ -n "${PYTESTARGS[*]}" ]; then - # shellcheck disable=SC2206 - OPTS=( ${OPTS[@]} ${PYTESTARGS[@]} ) - fi - - if [ "$fname" == "test_frameworks.py" ]; then - OPTS=( "--framework" "$target" ${OPTS[@]} ) - fi - - if [ "$SKIP_DEPS" -eq 0 ]; then - # setup tests environment - if [ -f "$REQ_FILE" ]; then - pip install -r "$REQ_FILE" || exit 1 - fi - fi - - if [ -n "$external_scripts" ]; then - eval "$external_scripts" || exit 1 - fi - - if [ "$type_tests" == 'e2e' ]; then - cd "$GIT_ROOT"/"$test_dir"/"$fname" || exit 1 - path="." - else - path="$GIT_ROOT"/"$test_dir"/"$fname" - fi - - # run pytest - python -m pytest "$path" "${OPTS[@]}" || ERR=1 - - # Return non-zero if pytest failed - if ! test $ERR = 0; then - FAIL "$args $type_tests tests failed!" - exit 1 - fi - - PASS "$args $type_tests tests passed!" + parse_args "$@" + + need_cmd make + need_cmd curl + need_cmd tr + (need_cmd yq && echo "Using yq via $(which yq)...") || install_yq + + for args in "$@"; do + if [[ "$args" != "-"* ]]; then + argv="$args" + break + else + shift + fi + done + + # validate_yaml + parse_config "$argv" + + OPTS=(--cov=bentoml --cov-config="$GIT_ROOT"/pyproject.toml --cov-report=xml:"$target.xml" --cov-report=term-missing -x) + + if [ -n "${PYTESTARGS[*]}" ]; then + # shellcheck disable=SC2206 + OPTS=(${OPTS[@]} ${PYTESTARGS[@]}) + fi + + if [ "$fname" == "test_frameworks.py" ]; then + OPTS=("--framework" "$target" ${OPTS[@]}) + fi + + if [ "$SKIP_DEPS" -eq 0 ]; then + # setup tests environment + if [ -f "$REQ_FILE" ]; then + pip install -r "$REQ_FILE" || exit 1 + fi + fi + + if [ -n "$external_scripts" ]; then + eval "$external_scripts" || exit 1 + fi + + if [ "$type_tests" == 'e2e' ]; then + cd "$GIT_ROOT"/"$test_dir"/"$fname" || exit 1 + path="." + else + path="$GIT_ROOT"/"$test_dir"/"$fname" + fi + + # run pytest + python -m pytest "$path" "${OPTS[@]}" || ERR=1 + + # Return non-zero if pytest failed + if ! test $ERR = 0; then + FAIL "$args $type_tests tests failed!" + exit 1 + fi + + PASS "$args $type_tests tests passed!" } main "$@" || exit 1 diff --git a/tools/bazel b/tools/bazel new file mode 100755 index 00000000000..f719d917e4d --- /dev/null +++ b/tools/bazel @@ -0,0 +1,71 @@ +#!/bin/bash + +set -e + +# courtesy of https://github.com/grpc/grpc + +# DISABLE_BAZEL_WRAPPER can be set to eliminate the wrapper logic +if [ "${DISABLE_BAZEL_WRAPPER}" != "" ] && [ "${OVERRIDE_BAZEL_VERSION}" == "" ]; then + if [ "${BAZEL_REAL}" != "" ]; then + # use BAZEL_REAL as set by + # https://github.com/bazelbuild/bazel/blob/master/scripts/packages/bazel.sh + # that originally invoked this script (this is what happens when you + # run "bazel" in our workspace) + exec -a "$0" "${BAZEL_REAL}" "$@" + else + # if BAZEL_REAL is not set, just invoke the default system bazel + exec bazel "$@" + fi +fi + +VERSION=${OVERRIDE_BAZEL_VERSION:-5.3.1} +echo "INFO: Running bazel wrapper (see //tools/bazel for details), bazel version $VERSION will be used instead of system-wide bazel installation." >&2 + +BASEURL_MIRROR="https://storage.googleapis.com/grpc-bazel-mirror/github.com/bazelbuild/bazel/releases/download" +BASEURL="https://github.com/bazelbuild/bazel/releases/download" +pushd "$(dirname "$0")" >/dev/null +# bazel binary will be downloaded to GIT_ROOT/tools directory by default +DOWNLOAD_DIR=${OVERRIDE_BAZEL_WRAPPER_DOWNLOAD_DIR:-$(pwd)} + +case $(uname -sm) in +"Linux x86_64") + suffix=linux-x86_64 + ;; +"Linux aarch64") + suffix=linux-arm64 + ;; +"Darwin x86_64") + suffix=darwin-x86_64 + ;; +"Darwin arm64") + suffix=darwin-arm64 + ;; +"MINGW"* | "MSYS_NT"*) + suffix=windows-x86_64.exe + ;; +*) + echo "Unsupported architecture: $(uname -sm)" >&2 + exit 1 + ;; +esac + +filename="bazel-$VERSION-$suffix" +filename_abs="${DOWNLOAD_DIR}/${filename}" + +if [ ! -x "${filename_abs}" ]; then + # first try to download using mirror, fallback to download from github + echo "Downloading bazel, will try URLs: ${BASEURL_MIRROR}/${VERSION}/${filename} ${BASEURL}/${VERSION}/${filename}" >&2 + curl --fail -L --output "${filename_abs}" "${BASEURL_MIRROR}/${VERSION}/${filename}" || curl --fail -L --output "${filename_abs}" "${BASEURL}/${VERSION}/${filename}" + chmod a+x "${filename_abs}" +fi + +pushd ../grpc-client >/dev/null +! [[ -d "thirdparty" ]] && mkdir -p thirdparty +pushd thirdparty >/dev/null +# We will only need the stubs, not the submodules. +! [[ -d "protobuf" ]] && git clone -b v3.19.4 --depth 1 https://github.com/protocolbuffers/protobuf.git +popd >/dev/null +popd >/dev/null + +popd >/dev/null +exec "${filename_abs}" "$@" diff --git a/tools/bazel.rc b/tools/bazel.rc new file mode 100644 index 00000000000..80af3a078c7 --- /dev/null +++ b/tools/bazel.rc @@ -0,0 +1,17 @@ +build --client_env=CC=clang +build --cxxopt=-std=c++14 --host_cxxopt=-std=c++14 +build --define=use_fast_cpp_protos=true +build:opt --compilation_mode=opt +build:opt --copt=-Wframe-larger-than=16384 + +build:dbg --compilation_mode=dbg +build:dbg --copt=-Werror=return-stack-address + +# Address https://github.com/bazelbuild/rules_swift/issues/776 +build --host_swiftcopt=-wmo --swiftcopt=-wmo +# Dynamic link cause issues like: `dyld: malformed mach-o: load commands size (59272) > 32768` +# https://github.com/bazelbuild/bazel/issues/9190 +build:macos --dynamic_mode=off + +build:windows_opt --compilation_mode=opt +build:windows_dbg --compilation_mode=dbg