Merge pull request #22218 from gnossen/simple_stubs_codegen

Generate simple stubs code
pull/22305/head
Richard Belleville 5 years ago committed by GitHub
commit df1761ce50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 104
      src/compiler/python_generator.cc
  2. 3
      src/compiler/python_private_generator.h
  3. 35
      src/python/grpcio/grpc/_simple_stubs.py
  4. 1
      src/python/grpcio_tests/commands.py
  5. 114
      src/python/grpcio_tests/tests/protoc_plugin/_python_plugin_test.py
  6. 1
      src/python/grpcio_tests/tests/tests.json
  7. 7
      src/python/grpcio_tests/tests_py3_only/unit/_simple_stubs_test.py

@ -70,10 +70,16 @@ typedef set<StringPair> StringPairSet;
class IndentScope {
public:
explicit IndentScope(grpc_generator::Printer* printer) : printer_(printer) {
// NOTE(rbellevi): Two-space tabs are hard-coded in the protocol compiler.
// Doubling our indents and outdents guarantees compliance with PEP8.
printer_->Indent();
printer_->Indent();
}
~IndentScope() { printer_->Outdent(); }
~IndentScope() {
printer_->Outdent();
printer_->Outdent();
}
private:
grpc_generator::Printer* printer_;
@ -92,8 +98,9 @@ void PrivateGenerator::PrintAllComments(StringVector comments,
// smarter and more sophisticated, but at the moment, if there is
// no docstring to print, we simply emit "pass" to ensure validity
// of the generated code.
out->Print("# missing associated documentation comment in .proto file\n");
out->Print("pass\n");
out->Print(
"\"\"\"Missing associated documentation comment in .proto "
"file\"\"\"\n");
return;
}
out->Print("\"\"\"");
@ -570,6 +577,93 @@ bool PrivateGenerator::PrintAddServicerToServer(
return true;
}
/* Prints out a service class used as a container for static methods pertaining
* to a class. This class has the exact name of service written in the ".proto"
* file, with no suffixes. Since this class merely acts as a namespace, it
* should never be instantiated.
*/
bool PrivateGenerator::PrintServiceClass(
const grpc::string& package_qualified_service_name,
const grpc_generator::Service* service, grpc_generator::Printer* out) {
StringMap dict;
dict["Service"] = service->name();
out->Print("\n\n");
out->Print(" # This class is part of an EXPERIMENTAL API.\n");
out->Print(dict, "class $Service$(object):\n");
{
IndentScope class_indent(out);
StringVector service_comments = service->GetAllComments();
PrintAllComments(service_comments, out);
for (int i = 0; i < service->method_count(); ++i) {
const auto& method = service->method(i);
grpc::string request_module_and_class;
if (!method->get_module_and_message_path_input(
&request_module_and_class, generator_file_name,
generate_in_pb2_grpc, config.import_prefix,
config.prefixes_to_filter)) {
return false;
}
grpc::string response_module_and_class;
if (!method->get_module_and_message_path_output(
&response_module_and_class, generator_file_name,
generate_in_pb2_grpc, config.import_prefix,
config.prefixes_to_filter)) {
return false;
}
out->Print("\n");
StringMap method_dict;
method_dict["Method"] = method->name();
out->Print("@staticmethod\n");
out->Print(method_dict, "def $Method$(");
grpc::string request_parameter(
method->ClientStreaming() ? "request_iterator" : "request");
StringMap args_dict;
args_dict["RequestParameter"] = request_parameter;
{
IndentScope args_indent(out);
IndentScope args_double_indent(out);
out->Print(args_dict, "$RequestParameter$,\n");
out->Print("target,\n");
out->Print("options=(),\n");
out->Print("channel_credentials=None,\n");
out->Print("call_credentials=None,\n");
out->Print("compression=None,\n");
out->Print("wait_for_ready=None,\n");
out->Print("timeout=None,\n");
out->Print("metadata=None):\n");
}
{
IndentScope method_indent(out);
grpc::string arity_method_name =
grpc::string(method->ClientStreaming() ? "stream" : "unary") + "_" +
grpc::string(method->ServerStreaming() ? "stream" : "unary");
args_dict["ArityMethodName"] = arity_method_name;
args_dict["PackageQualifiedService"] = package_qualified_service_name;
args_dict["Method"] = method->name();
out->Print(args_dict,
"return "
"grpc.experimental.$ArityMethodName$($RequestParameter$, "
"target, '/$PackageQualifiedService$/$Method$',\n");
{
IndentScope continuation_indent(out);
StringMap serializer_dict;
serializer_dict["RequestModuleAndClass"] = request_module_and_class;
serializer_dict["ResponseModuleAndClass"] = response_module_and_class;
out->Print(serializer_dict,
"$RequestModuleAndClass$.SerializeToString,\n");
out->Print(serializer_dict, "$ResponseModuleAndClass$.FromString,\n");
out->Print("options, channel_credentials,\n");
out->Print(
"call_credentials, compression, wait_for_ready, timeout, "
"metadata)\n");
}
}
}
}
// TODO(rbellevi): Add methods pertinent to the server side as well.
return true;
}
bool PrivateGenerator::PrintBetaPreamble(grpc_generator::Printer* out) {
StringMap var;
var["Package"] = config.beta_package_root;
@ -646,7 +740,9 @@ bool PrivateGenerator::PrintGAServices(grpc_generator::Printer* out) {
if (!(PrintStub(package_qualified_service_name, service.get(), out) &&
PrintServicer(service.get(), out) &&
PrintAddServicerToServer(package_qualified_service_name,
service.get(), out))) {
service.get(), out) &&
PrintServiceClass(package_qualified_service_name, service.get(),
out))) {
return false;
}
}

@ -59,6 +59,9 @@ struct PrivateGenerator {
const grpc_generator::Service* service,
grpc_generator::Printer* out);
bool PrintServiceClass(const grpc::string& package_qualified_service_name,
const grpc_generator::Service* service,
grpc_generator::Printer* out);
bool PrintBetaServicer(const grpc_generator::Service* service,
grpc_generator::Printer* out);
bool PrintBetaServerFactory(

@ -53,8 +53,10 @@ else:
def _create_channel(target: str, options: Sequence[Tuple[str, str]],
channel_credentials: Optional[grpc.ChannelCredentials],
compression: Optional[grpc.Compression]) -> grpc.Channel:
channel_credentials = channel_credentials or grpc.local_channel_credentials(
)
# TODO(rbellevi): Revisit the default value for this.
if channel_credentials is None:
raise NotImplementedError(
"channel_credentials must be supplied explicitly.")
if channel_credentials._credentials is grpc.experimental._insecure_channel_credentials:
_LOGGER.debug(f"Creating insecure channel with options '{options}' " +
f"and compression '{compression}'")
@ -156,26 +158,13 @@ class ChannelCache:
return len(self._mapping)
# TODO(rbellevi): Consider a credential type that has the
# following functionality matrix:
#
# +----------+-------+--------+
# | | local | remote |
# |----------+-------+--------+
# | secure | o | o |
# | insecure | o | x |
# +----------+-------+--------+
#
# Make this the default option.
@experimental_api
def unary_unary(
request: RequestType,
target: str,
method: str,
request_serializer: Optional[Callable[[Any], bytes]] = None,
request_deserializer: Optional[Callable[[bytes], Any]] = None,
response_deserializer: Optional[Callable[[bytes], Any]] = None,
options: Sequence[Tuple[AnyStr, AnyStr]] = (),
channel_credentials: Optional[grpc.ChannelCredentials] = None,
call_credentials: Optional[grpc.CallCredentials] = None,
@ -232,7 +221,7 @@ def unary_unary(
channel = ChannelCache.get().get_channel(target, options,
channel_credentials, compression)
multicallable = channel.unary_unary(method, request_serializer,
request_deserializer)
response_deserializer)
return multicallable(request,
metadata=metadata,
wait_for_ready=wait_for_ready,
@ -246,7 +235,7 @@ def unary_stream(
target: str,
method: str,
request_serializer: Optional[Callable[[Any], bytes]] = None,
request_deserializer: Optional[Callable[[bytes], Any]] = None,
response_deserializer: Optional[Callable[[bytes], Any]] = None,
options: Sequence[Tuple[AnyStr, AnyStr]] = (),
channel_credentials: Optional[grpc.ChannelCredentials] = None,
call_credentials: Optional[grpc.CallCredentials] = None,
@ -302,7 +291,7 @@ def unary_stream(
channel = ChannelCache.get().get_channel(target, options,
channel_credentials, compression)
multicallable = channel.unary_stream(method, request_serializer,
request_deserializer)
response_deserializer)
return multicallable(request,
metadata=metadata,
wait_for_ready=wait_for_ready,
@ -316,7 +305,7 @@ def stream_unary(
target: str,
method: str,
request_serializer: Optional[Callable[[Any], bytes]] = None,
request_deserializer: Optional[Callable[[bytes], Any]] = None,
response_deserializer: Optional[Callable[[bytes], Any]] = None,
options: Sequence[Tuple[AnyStr, AnyStr]] = (),
channel_credentials: Optional[grpc.ChannelCredentials] = None,
call_credentials: Optional[grpc.CallCredentials] = None,
@ -372,7 +361,7 @@ def stream_unary(
channel = ChannelCache.get().get_channel(target, options,
channel_credentials, compression)
multicallable = channel.stream_unary(method, request_serializer,
request_deserializer)
response_deserializer)
return multicallable(request_iterator,
metadata=metadata,
wait_for_ready=wait_for_ready,
@ -386,7 +375,7 @@ def stream_stream(
target: str,
method: str,
request_serializer: Optional[Callable[[Any], bytes]] = None,
request_deserializer: Optional[Callable[[bytes], Any]] = None,
response_deserializer: Optional[Callable[[bytes], Any]] = None,
options: Sequence[Tuple[AnyStr, AnyStr]] = (),
channel_credentials: Optional[grpc.ChannelCredentials] = None,
call_credentials: Optional[grpc.CallCredentials] = None,
@ -442,7 +431,7 @@ def stream_stream(
channel = ChannelCache.get().get_channel(target, options,
channel_credentials, compression)
multicallable = channel.stream_stream(method, request_serializer,
request_deserializer)
response_deserializer)
return multicallable(request_iterator,
metadata=metadata,
wait_for_ready=wait_for_ready,

@ -193,6 +193,7 @@ class TestGevent(setuptools.Command):
'unit._server_ssl_cert_config_test',
# TODO(https://github.com/grpc/grpc/issues/14901) enable this test
'protoc_plugin._python_plugin_test.PythonPluginTest',
'protoc_plugin._python_plugin_test.SimpleStubsPluginTest',
# Beta API is unsupported for gevent
'protoc_plugin.beta_python_plugin_test',
'unit.beta._beta_features_test',

@ -27,6 +27,7 @@ import unittest
from six import moves
import grpc
import grpc.experimental
from tests.unit import test_common
from tests.unit.framework.common import test_constants
@ -503,5 +504,118 @@ class PythonPluginTest(unittest.TestCase):
service.server.stop(None)
@unittest.skipIf(sys.version_info[0] < 3, "Unsupported on Python 2.")
class SimpleStubsPluginTest(unittest.TestCase):
servicer_methods = _ServicerMethods()
class Servicer(service_pb2_grpc.TestServiceServicer):
def UnaryCall(self, request, context):
return SimpleStubsPluginTest.servicer_methods.UnaryCall(
request, context)
def StreamingOutputCall(self, request, context):
return SimpleStubsPluginTest.servicer_methods.StreamingOutputCall(
request, context)
def StreamingInputCall(self, request_iterator, context):
return SimpleStubsPluginTest.servicer_methods.StreamingInputCall(
request_iterator, context)
def FullDuplexCall(self, request_iterator, context):
return SimpleStubsPluginTest.servicer_methods.FullDuplexCall(
request_iterator, context)
def HalfDuplexCall(self, request_iterator, context):
return SimpleStubsPluginTest.servicer_methods.HalfDuplexCall(
request_iterator, context)
def setUp(self):
super(SimpleStubsPluginTest, self).setUp()
self._server = test_common.test_server()
service_pb2_grpc.add_TestServiceServicer_to_server(
self.Servicer(), self._server)
self._port = self._server.add_insecure_port('[::]:0')
self._server.start()
self._target = 'localhost:{}'.format(self._port)
def tearDown(self):
self._server.stop(None)
super(SimpleStubsPluginTest, self).tearDown()
def testUnaryCall(self):
request = request_pb2.SimpleRequest(response_size=13)
response = service_pb2_grpc.TestService.UnaryCall(
request,
self._target,
channel_credentials=grpc.experimental.insecure_channel_credentials(
),
wait_for_ready=True)
expected_response = self.servicer_methods.UnaryCall(
request, 'not a real context!')
self.assertEqual(expected_response, response)
def testStreamingOutputCall(self):
request = _streaming_output_request()
expected_responses = self.servicer_methods.StreamingOutputCall(
request, 'not a real RpcContext!')
responses = service_pb2_grpc.TestService.StreamingOutputCall(
request,
self._target,
channel_credentials=grpc.experimental.insecure_channel_credentials(
),
wait_for_ready=True)
for expected_response, response in moves.zip_longest(
expected_responses, responses):
self.assertEqual(expected_response, response)
def testStreamingInputCall(self):
response = service_pb2_grpc.TestService.StreamingInputCall(
_streaming_input_request_iterator(),
self._target,
channel_credentials=grpc.experimental.insecure_channel_credentials(
),
wait_for_ready=True)
expected_response = self.servicer_methods.StreamingInputCall(
_streaming_input_request_iterator(), 'not a real RpcContext!')
self.assertEqual(expected_response, response)
def testFullDuplexCall(self):
responses = service_pb2_grpc.TestService.FullDuplexCall(
_full_duplex_request_iterator(),
self._target,
channel_credentials=grpc.experimental.insecure_channel_credentials(
),
wait_for_ready=True)
expected_responses = self.servicer_methods.FullDuplexCall(
_full_duplex_request_iterator(), 'not a real RpcContext!')
for expected_response, response in moves.zip_longest(
expected_responses, responses):
self.assertEqual(expected_response, response)
def testHalfDuplexCall(self):
def half_duplex_request_iterator():
request = request_pb2.StreamingOutputCallRequest()
request.response_parameters.add(size=1, interval_us=0)
yield request
request = request_pb2.StreamingOutputCallRequest()
request.response_parameters.add(size=2, interval_us=0)
request.response_parameters.add(size=3, interval_us=0)
yield request
responses = service_pb2_grpc.TestService.HalfDuplexCall(
half_duplex_request_iterator(),
self._target,
channel_credentials=grpc.experimental.insecure_channel_credentials(
),
wait_for_ready=True)
expected_responses = self.servicer_methods.HalfDuplexCall(
half_duplex_request_iterator(), 'not a real RpcContext!')
for expected_response, response in moves.zip_longest(
expected_responses, responses):
self.assertEqual(expected_response, response)
if __name__ == '__main__':
unittest.main(verbosity=2)

@ -7,6 +7,7 @@
"interop._insecure_intraop_test.InsecureIntraopTest",
"interop._secure_intraop_test.SecureIntraopTest",
"protoc_plugin._python_plugin_test.PythonPluginTest",
"protoc_plugin._python_plugin_test.SimpleStubsPluginTest",
"protoc_plugin._split_definitions_test.SameProtoGrpcBeforeProtoProtocStyleTest",
"protoc_plugin._split_definitions_test.SameProtoMid2016ProtocStyleTest",
"protoc_plugin._split_definitions_test.SameProtoProtoBeforeGrpcProtocStyleTest",

@ -174,13 +174,6 @@ class SimpleStubsTest(unittest.TestCase):
channel_credentials=grpc.local_channel_credentials())
self.assertEqual(_REQUEST, response)
def test_channel_credentials_default(self):
with _server(grpc.local_server_credentials()) as port:
target = f'localhost:{port}'
response = grpc.experimental.unary_unary(_REQUEST, target,
_UNARY_UNARY)
self.assertEqual(_REQUEST, response)
def test_channels_cached(self):
with _server(grpc.local_server_credentials()) as port:
target = f'localhost:{port}'

Loading…
Cancel
Save