Merge pull request #7374 from kpayson64/python_health_check_rename

Polish grpcio_health_checking package
pull/7470/head
kpayson64 8 years ago committed by GitHub
commit 413d302d4b
  1. 2
      include/grpc/impl/codegen/port_platform.h
  2. 2
      setup.cfg
  3. 3
      src/python/grpcio_health_checking/MANIFEST.in
  4. 2
      src/python/grpcio_health_checking/grpc/__init__.py
  5. 0
      src/python/grpcio_health_checking/grpc/health/__init__.py
  6. 0
      src/python/grpcio_health_checking/grpc/health/v1/__init__.py
  7. 19
      src/python/grpcio_health_checking/grpc/health/v1/health.py
  8. 32
      src/python/grpcio_health_checking/grpc_version.py
  9. 29
      src/python/grpcio_health_checking/health_commands.py
  10. 23
      src/python/grpcio_health_checking/setup.py
  11. 2
      src/python/grpcio_tests/commands.py
  12. 2
      src/python/grpcio_tests/setup.py
  13. 52
      src/python/grpcio_tests/tests/health_check/_health_servicer_test.py
  14. 34
      templates/src/python/grpcio_health_checking/grpc_version.py.template
  15. 38
      tools/distrib/python/grpcio_tools/grpc/tools/command.py
  16. 3
      tools/run_tests/artifact_targets.py
  17. 25
      tools/run_tests/build_artifact_python.sh
  18. 3
      tools/run_tests/build_python.sh

@ -119,7 +119,7 @@
// libraries; it should be integrated with the `__linux__` definitions below. // libraries; it should be integrated with the `__linux__` definitions below.
#define GPR_PLATFORM_STRING "manylinux" #define GPR_PLATFORM_STRING "manylinux"
#define GPR_POSIX_CRASH_HANDLER 1 #define GPR_POSIX_CRASH_HANDLER 1
#define GPR_CPU_LINUX 1 #define GPR_CPU_POSIX 1
#define GPR_GCC_ATOMIC 1 #define GPR_GCC_ATOMIC 1
#define GPR_GCC_TLS 1 #define GPR_GCC_TLS 1
#define GPR_LINUX 1 #define GPR_LINUX 1

@ -9,5 +9,5 @@ build_base=python_build
[build_ext] [build_ext]
inplace=1 inplace=1
[build_proto_modules] [build_package_protos]
exclude=.*protoc_plugin/protoc_plugin_test\.proto$ exclude=.*protoc_plugin/protoc_plugin_test\.proto$

@ -1,3 +1,4 @@
include grpc_version.py
include health_commands.py include health_commands.py
graft grpc_health graft grpc
global-exclude *.pyc global-exclude *.pyc

@ -27,4 +27,4 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
__import__('pkg_resources').declare_namespace(__name__)

@ -31,10 +31,12 @@
import threading import threading
from grpc_health.health.v1 import health_pb2 import grpc
from grpc.health.v1 import health_pb2
class HealthServicer(health_pb2.BetaHealthServicer):
class HealthServicer(health_pb2.HealthServicer):
"""Servicer handling RPCs for service statuses.""" """Servicer handling RPCs for service statuses."""
def __init__(self): def __init__(self):
@ -43,14 +45,12 @@ class HealthServicer(health_pb2.BetaHealthServicer):
def Check(self, request, context): def Check(self, request, context):
with self._server_status_lock: with self._server_status_lock:
if request.service not in self._server_status: status = self._server_status.get(request.service)
# TODO(atash): once the Python API has a way of setting the server if status is None:
# status, bring us into conformance with the health check spec by context.set_code(grpc.StatusCode.NOT_FOUND)
# returning the NOT_FOUND status here. return health_pb2.HealthCheckResponse()
raise NotImplementedError()
else: else:
return health_pb2.HealthCheckResponse( return health_pb2.HealthCheckResponse(status=status)
status=self._server_status[request.service])
def set(self, service, status): def set(self, service, status):
"""Sets the status of a service. """Sets the status of a service.
@ -63,4 +63,3 @@ class HealthServicer(health_pb2.BetaHealthServicer):
""" """
with self._server_status_lock: with self._server_status_lock:
self._server_status[service] = status self._server_status[service] = status

@ -0,0 +1,32 @@
# Copyright 2016, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_health_checking/grpc_version.py.template`!!!
VERSION='1.0.0rc1'

@ -29,16 +29,10 @@
"""Provides distutils command classes for the GRPC Python setup process.""" """Provides distutils command classes for the GRPC Python setup process."""
import distutils
import glob
import os import os
import os.path
import shutil import shutil
import subprocess
import sys
import setuptools import setuptools
from setuptools.command import build_py
ROOT_DIR = os.path.abspath(os.path.dirname(os.path.abspath(__file__))) ROOT_DIR = os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
HEALTH_PROTO = os.path.join(ROOT_DIR, '../../proto/grpc/health/v1/health.proto') HEALTH_PROTO = os.path.join(ROOT_DIR, '../../proto/grpc/health/v1/health.proto')
@ -60,12 +54,25 @@ class CopyProtoModules(setuptools.Command):
if os.path.isfile(HEALTH_PROTO): if os.path.isfile(HEALTH_PROTO):
shutil.copyfile( shutil.copyfile(
HEALTH_PROTO, HEALTH_PROTO,
os.path.join(ROOT_DIR, 'grpc_health/health/v1/health.proto')) os.path.join(ROOT_DIR, 'grpc/health/v1/health.proto'))
class BuildPy(build_py.build_py): class BuildPackageProtos(setuptools.Command):
"""Custom project build command.""" """Command to generate project *_pb2.py modules from proto files."""
description = 'build grpc protobuf modules'
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self): def run(self):
self.run_command('build_proto_modules') # due to limitations of the proto generator, we require that only *one*
build_py.build_py.run(self) # directory is provided as an 'include' directory. We assume it's the '' key
# to `self.distribution.package_dir` (and get a key error if it's not
# there).
from grpc.tools import command
command.build_package_protos(self.distribution.package_dir[''])

@ -30,49 +30,42 @@
"""Setup module for the GRPC Python package's optional health checking.""" """Setup module for the GRPC Python package's optional health checking."""
import os import os
import os.path
import sys import sys
from distutils import core as _core
import setuptools import setuptools
import grpc.tools.command
# Ensure we're in the proper directory whether or not we're being used by pip. # Ensure we're in the proper directory whether or not we're being used by pip.
os.chdir(os.path.dirname(os.path.abspath(__file__))) os.chdir(os.path.dirname(os.path.abspath(__file__)))
# Break import-style to ensure we can actually find our commands module. # Break import-style to ensure we can actually find our commands module.
import health_commands import health_commands
import grpc_version
PACKAGES = (
setuptools.find_packages('.')
)
PACKAGE_DIRECTORIES = { PACKAGE_DIRECTORIES = {
'': '.', '': '.',
} }
SETUP_REQUIRES = ( SETUP_REQUIRES = (
'grpcio-tools>=0.14.0', 'grpcio-tools>=0.15.0',
) )
INSTALL_REQUIRES = ( INSTALL_REQUIRES = (
'grpcio>=0.13.1', 'grpcio>=0.15.0',
) )
COMMAND_CLASS = { COMMAND_CLASS = {
# Run preprocess from the repository *before* doing any packaging! # Run preprocess from the repository *before* doing any packaging!
'preprocess': health_commands.CopyProtoModules, 'preprocess': health_commands.CopyProtoModules,
'build_package_protos': health_commands.BuildPackageProtos,
'build_proto_modules': grpc.tools.command.BuildProtoModules,
'build_py': health_commands.BuildPy,
} }
setuptools.setup( setuptools.setup(
name='grpcio-health-checking', name='grpcio-health-checking',
version='0.14.0', version=grpc_version.VERSION,
packages=list(PACKAGES), license='3-clause BSD',
package_dir=PACKAGE_DIRECTORIES, package_dir=PACKAGE_DIRECTORIES,
packages=setuptools.find_packages('.'),
namespace_packages=['grpc'],
install_requires=INSTALL_REQUIRES, install_requires=INSTALL_REQUIRES,
setup_requires=SETUP_REQUIRES, setup_requires=SETUP_REQUIRES,
cmdclass=COMMAND_CLASS cmdclass=COMMAND_CLASS

@ -138,7 +138,7 @@ class BuildPy(build_py.build_py):
def run(self): def run(self):
try: try:
self.run_command('build_proto_modules') self.run_command('build_package_protos')
except CommandError as error: except CommandError as error:
sys.stderr.write('warning: %s\n' % error.message) sys.stderr.write('warning: %s\n' % error.message)
build_py.build_py.run(self) build_py.build_py.run(self)

@ -75,7 +75,7 @@ COMMAND_CLASS = {
# Run `preprocess` *before* doing any packaging! # Run `preprocess` *before* doing any packaging!
'preprocess': commands.GatherProto, 'preprocess': commands.GatherProto,
'build_proto_modules': grpc.tools.command.BuildProtoModules, 'build_package_protos': grpc.tools.command.BuildPackageProtos,
'build_py': commands.BuildPy, 'build_py': commands.BuildPy,
'run_interop': commands.RunInterop, 'run_interop': commands.RunInterop,
'test_lite': commands.TestLite 'test_lite': commands.TestLite

@ -27,48 +27,68 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Tests of grpc_health.health.v1.health.""" """Tests of grpc.health.v1.health."""
import unittest import unittest
from grpc_health.health.v1 import health import grpc
from grpc_health.health.v1 import health_pb2 from grpc.framework.foundation import logging_pool
from grpc.health.v1 import health
from grpc.health.v1 import health_pb2
from tests.unit.framework.common import test_constants
class HealthServicerTest(unittest.TestCase): class HealthServicerTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.servicer = health.HealthServicer() servicer = health.HealthServicer()
self.servicer.set('', health_pb2.HealthCheckResponse.SERVING) servicer.set('', health_pb2.HealthCheckResponse.SERVING)
self.servicer.set('grpc.test.TestServiceServing', servicer.set('grpc.test.TestServiceServing',
health_pb2.HealthCheckResponse.SERVING) health_pb2.HealthCheckResponse.SERVING)
self.servicer.set('grpc.test.TestServiceUnknown', servicer.set('grpc.test.TestServiceUnknown',
health_pb2.HealthCheckResponse.UNKNOWN) health_pb2.HealthCheckResponse.UNKNOWN)
self.servicer.set('grpc.test.TestServiceNotServing', servicer.set('grpc.test.TestServiceNotServing',
health_pb2.HealthCheckResponse.NOT_SERVING) health_pb2.HealthCheckResponse.NOT_SERVING)
server_pool = logging_pool.pool(test_constants.THREAD_CONCURRENCY)
self._server = grpc.server(server_pool)
port = self._server.add_insecure_port('[::]:0')
health_pb2.add_HealthServicer_to_server(servicer, self._server)
self._server.start()
channel = grpc.insecure_channel('localhost:%d' % port)
self._stub = health_pb2.HealthStub(channel)
def test_empty_service(self): def test_empty_service(self):
request = health_pb2.HealthCheckRequest() request = health_pb2.HealthCheckRequest()
resp = self.servicer.Check(request, None) resp = self._stub.Check(request)
self.assertEqual(resp.status, health_pb2.HealthCheckResponse.SERVING) self.assertEqual(health_pb2.HealthCheckResponse.SERVING, resp.status)
def test_serving_service(self): def test_serving_service(self):
request = health_pb2.HealthCheckRequest( request = health_pb2.HealthCheckRequest(
service='grpc.test.TestServiceServing') service='grpc.test.TestServiceServing')
resp = self.servicer.Check(request, None) resp = self._stub.Check(request)
self.assertEqual(resp.status, health_pb2.HealthCheckResponse.SERVING) self.assertEqual(health_pb2.HealthCheckResponse.SERVING, resp.status)
def test_unknown_serivce(self): def test_unknown_serivce(self):
request = health_pb2.HealthCheckRequest( request = health_pb2.HealthCheckRequest(
service='grpc.test.TestServiceUnknown') service='grpc.test.TestServiceUnknown')
resp = self.servicer.Check(request, None) resp = self._stub.Check(request)
self.assertEqual(resp.status, health_pb2.HealthCheckResponse.UNKNOWN) self.assertEqual(health_pb2.HealthCheckResponse.UNKNOWN, resp.status)
def test_not_serving_service(self): def test_not_serving_service(self):
request = health_pb2.HealthCheckRequest( request = health_pb2.HealthCheckRequest(
service='grpc.test.TestServiceNotServing') service='grpc.test.TestServiceNotServing')
resp = self.servicer.Check(request, None) resp = self._stub.Check(request)
self.assertEqual(resp.status, health_pb2.HealthCheckResponse.NOT_SERVING) self.assertEqual(health_pb2.HealthCheckResponse.NOT_SERVING, resp.status)
def test_not_found_service(self):
request = health_pb2.HealthCheckRequest(
service='not-found')
with self.assertRaises(grpc.RpcError) as context:
resp = self._stub.Check(request)
self.assertEqual(grpc.StatusCode.NOT_FOUND, context.exception.code())
if __name__ == '__main__': if __name__ == '__main__':

@ -0,0 +1,34 @@
%YAML 1.2
--- |
# Copyright 2016, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_health_checking/grpc_version.py.template`!!!
VERSION='${settings.python_version.pep440()}'

@ -35,7 +35,26 @@ import setuptools
from grpc.tools import protoc from grpc.tools import protoc
class BuildProtoModules(setuptools.Command): def build_package_protos(package_root):
proto_files = []
inclusion_root = os.path.abspath(package_root)
for root, _, files in os.walk(inclusion_root):
for filename in files:
if filename.endswith('.proto'):
proto_files.append(os.path.abspath(os.path.join(root, filename)))
for proto_file in proto_files:
command = [
'grpc.tools.protoc',
'--proto_path={}'.format(inclusion_root),
'--python_out={}'.format(inclusion_root),
'--grpc_python_out={}'.format(inclusion_root),
] + [proto_file]
if protoc.main(command) != 0:
sys.stderr.write('warning: {} failed'.format(command))
class BuildPackageProtos(setuptools.Command):
"""Command to generate project *_pb2.py modules from proto files.""" """Command to generate project *_pb2.py modules from proto files."""
description = 'build grpc protobuf modules' description = 'build grpc protobuf modules'
@ -52,19 +71,4 @@ class BuildProtoModules(setuptools.Command):
# directory is provided as an 'include' directory. We assume it's the '' key # directory is provided as an 'include' directory. We assume it's the '' key
# to `self.distribution.package_dir` (and get a key error if it's not # to `self.distribution.package_dir` (and get a key error if it's not
# there). # there).
proto_files = [] build_package_protos(self.distribution.package_dir[''])
inclusion_root = os.path.abspath(self.distribution.package_dir[''])
for root, _, files in os.walk(inclusion_root):
for filename in files:
if filename.endswith('.proto'):
proto_files.append(os.path.abspath(os.path.join(root, filename)))
for proto_file in proto_files:
command = [
'grpc.tools.protoc',
'--proto_path={}'.format(inclusion_root),
'--python_out={}'.format(inclusion_root),
'--grpc_python_out={}'.format(inclusion_root),
] + [proto_file]
if protoc.main(command) != 0:
sys.stderr.write('warning: {} failed'.format(command))

@ -113,12 +113,12 @@ class PythonArtifact:
# special places... # special places...
environ['PYTHON'] = '/opt/python/{}/bin/python'.format(self.manylinux_build) environ['PYTHON'] = '/opt/python/{}/bin/python'.format(self.manylinux_build)
environ['PIP'] = '/opt/python/{}/bin/pip'.format(self.manylinux_build) environ['PIP'] = '/opt/python/{}/bin/pip'.format(self.manylinux_build)
environ['SKIP_PIP_INSTALL'] = '1'
# Platform autodetection for the manylinux1 image breaks so we set the # Platform autodetection for the manylinux1 image breaks so we set the
# defines ourselves. # defines ourselves.
# TODO(atash) get better platform-detection support in core so we don't # TODO(atash) get better platform-detection support in core so we don't
# need to do this manually... # need to do this manually...
environ['CFLAGS'] = '-DGPR_MANYLINUX1=1' environ['CFLAGS'] = '-DGPR_MANYLINUX1=1'
environ['BUILD_HEALTH_CHECKING'] = 'TRUE'
return create_docker_jobspec(self.name, return create_docker_jobspec(self.name,
'tools/dockerfile/grpc_artifact_python_manylinux_%s' % self.arch, 'tools/dockerfile/grpc_artifact_python_manylinux_%s' % self.arch,
'tools/run_tests/build_artifact_python.sh', 'tools/run_tests/build_artifact_python.sh',
@ -132,7 +132,6 @@ class PythonArtifact:
], ],
shell=True) shell=True)
else: else:
environ['SKIP_PIP_INSTALL'] = 'TRUE'
environ['PYTHON'] = 'python{}'.format(self.python_version) environ['PYTHON'] = 'python{}'.format(self.python_version)
return create_jobspec(self.name, return create_jobspec(self.name,
['tools/run_tests/build_artifact_python.sh'], ['tools/run_tests/build_artifact_python.sh'],

@ -39,15 +39,6 @@ export PIP=${PIP:-pip}
export AUDITWHEEL=${AUDITWHEEL:-auditwheel} export AUDITWHEEL=${AUDITWHEEL:-auditwheel}
if [ "$SKIP_PIP_INSTALL" == "" ]
then
${PIP} install --upgrade six
# There's a bug in newer versions of setuptools (see
# https://bitbucket.org/pypa/setuptools/issues/503/pkg_resources_vendorpackagingrequirementsi)
${PIP} pip install --upgrade 'setuptools==18'
${PIP} install -rrequirements.txt
fi
# Build the source distribution first because MANIFEST.in cannot override # Build the source distribution first because MANIFEST.in cannot override
# exclusion of built shared objects among package resources (for some # exclusion of built shared objects among package resources (for some
# inexplicable reason). # inexplicable reason).
@ -81,5 +72,21 @@ then
done done
fi fi
# We need to use the built grpcio-tools/grpcio to compile the health proto
# Wheels are not supported by setup_requires/dependency_links, so we
# manually install the dependency. Note we should only do this if we
# are in a docker image or in a virtualenv.
if [ "$BUILD_HEALTH_CHECKING" != "" ]
then
${PIP} install -rrequirements.txt
${PIP} install grpcio --no-index --find-links "file://${PWD}/artifacts/"
${PIP} install grpcio-tools --no-index --find-links "file://${PWD}/artifacts/"
# Build gRPC health check source distribution
${SETARCH_CMD} ${PYTHON} src/python/grpcio_health_checking/setup.py \
preprocess build_package_protos sdist
cp -r src/python/grpcio_health_checking/dist/* artifacts
fi
cp -r dist/* artifacts cp -r dist/* artifacts
cp -r tools/distrib/python/grpcio_tools/dist/* artifacts cp -r tools/distrib/python/grpcio_tools/dist/* artifacts

@ -177,7 +177,8 @@ pip_install_dir $ROOT/tools/distrib/python/grpcio_tools
# etc... # etc...
pip_install_dir $ROOT pip_install_dir $ROOT
$VENV_PYTHON $ROOT/src/python/grpcio_health_checking/setup.py preprocess $VENV_PYTHON $ROOT/src/python/grpcio_health_checking/setup.py preprocess
$VENV_PYTHON $ROOT/src/python/grpcio_health_checking/setup.py build_package_protos
pip_install_dir $ROOT/src/python/grpcio_health_checking pip_install_dir $ROOT/src/python/grpcio_health_checking
$VENV_PYTHON $ROOT/src/python/grpcio_tests/setup.py preprocess $VENV_PYTHON $ROOT/src/python/grpcio_tests/setup.py preprocess
$VENV_PYTHON $ROOT/src/python/grpcio_tests/setup.py build_proto_modules $VENV_PYTHON $ROOT/src/python/grpcio_tests/setup.py build_package_protos
pip_install_dir $ROOT/src/python/grpcio_tests pip_install_dir $ROOT/src/python/grpcio_tests

Loading…
Cancel
Save