From 832ae81b21025c415fc183b1aa18e063c44180a3 Mon Sep 17 00:00:00 2001
From: Masood Malekghassemi <atash@google.com>
Date: Wed, 27 Apr 2016 18:38:54 -0700
Subject: [PATCH] Allow additive changes to protos w/o forcing user
 implementation

---
 src/compiler/python_generator.cc              | 10 ++---
 .../protoc_plugin/beta_python_plugin_test.py  | 41 +++++++++++++++++++
 2 files changed, 46 insertions(+), 5 deletions(-)

diff --git a/src/compiler/python_generator.cc b/src/compiler/python_generator.cc
index 02c032800b1..59137e1c923 100644
--- a/src/compiler/python_generator.cc
+++ b/src/compiler/python_generator.cc
@@ -190,7 +190,7 @@ bool PrintBetaServicer(const ServiceDescriptor* service,
         "Documentation", doc,
       });
   out->Print("\n");
-  out->Print(dict, "class Beta$Service$Servicer(six.with_metaclass(abc.ABCMeta, object)):\n");
+  out->Print(dict, "class Beta$Service$Servicer(object):\n");
   {
     IndentScope raii_class_indent(out);
     out->Print(dict, "\"\"\"$Documentation$\"\"\"\n");
@@ -198,12 +198,11 @@ bool PrintBetaServicer(const ServiceDescriptor* service,
       auto meth = service->method(i);
       grpc::string arg_name = meth->client_streaming() ?
           "request_iterator" : "request";
-      out->Print("@abc.abstractmethod\n");
       out->Print("def $Method$(self, $ArgName$, context):\n",
                  "Method", meth->name(), "ArgName", arg_name);
       {
         IndentScope raii_method_indent(out);
-        out->Print("raise NotImplementedError()\n");
+        out->Print("context.code(beta_interfaces.StatusCode.UNIMPLEMENTED)\n");
       }
     }
   }
@@ -218,7 +217,7 @@ bool PrintBetaStub(const ServiceDescriptor* service,
         "Documentation", doc,
       });
   out->Print("\n");
-  out->Print(dict, "class Beta$Service$Stub(six.with_metaclass(abc.ABCMeta, object)):\n");
+  out->Print(dict, "class Beta$Service$Stub(object):\n");
   {
     IndentScope raii_class_indent(out);
     out->Print(dict, "\"\"\"$Documentation$\"\"\"\n");
@@ -227,7 +226,6 @@ bool PrintBetaStub(const ServiceDescriptor* service,
       grpc::string arg_name = meth->client_streaming() ?
           "request_iterator" : "request";
       auto methdict = ListToDict({"Method", meth->name(), "ArgName", arg_name});
-      out->Print("@abc.abstractmethod\n");
       out->Print(methdict, "def $Method$(self, $ArgName$, timeout):\n");
       {
         IndentScope raii_method_indent(out);
@@ -450,6 +448,8 @@ bool PrintPreamble(const FileDescriptor* file,
   out->Print("import six\n");
   out->Print("from $Package$ import implementations as beta_implementations\n",
              "Package", config.beta_package_root);
+  out->Print("from $Package$ import interfaces as beta_interfaces\n",
+             "Package", config.beta_package_root);
   out->Print("from grpc.framework.common import cardinality\n");
   out->Print("from grpc.framework.interfaces.face import utilities as face_utilities\n");
   return true;
diff --git a/src/python/grpcio/tests/protoc_plugin/beta_python_plugin_test.py b/src/python/grpcio/tests/protoc_plugin/beta_python_plugin_test.py
index 6fba3d4271b..3dc3042e38b 100644
--- a/src/python/grpcio/tests/protoc_plugin/beta_python_plugin_test.py
+++ b/src/python/grpcio/tests/protoc_plugin/beta_python_plugin_test.py
@@ -45,6 +45,7 @@ import unittest
 from six import moves
 
 from grpc.beta import implementations
+from grpc.beta import interfaces
 from grpc.framework.foundation import future
 from grpc.framework.interfaces.face import face
 from tests.unit.framework.common import test_constants
@@ -178,6 +179,36 @@ def _CreateService(test_pb2):
   server.stop(0)
 
 
+@contextlib.contextmanager
+def _CreateIncompleteService(test_pb2):
+  """Provides a servicer backend that fails to implement methods and its stub.
+
+  The servicer is just the implementation of the actual servicer passed to the
+  face player of the python RPC implementation; the two are detached.
+
+  Args:
+    test_pb2: The test_pb2 module generated by this test.
+
+  Yields:
+    A (servicer_methods, stub) pair where servicer_methods is the back-end of
+      the service bound to the stub and and stub is the stub on which to invoke
+      RPCs.
+  """
+  servicer_methods = _ServicerMethods(test_pb2)
+
+  class Servicer(getattr(test_pb2, SERVICER_IDENTIFIER)):
+    pass
+
+  servicer = Servicer()
+  server = getattr(test_pb2, SERVER_FACTORY_IDENTIFIER)(servicer)
+  port = server.add_insecure_port('[::]:0')
+  server.start()
+  channel = implementations.insecure_channel('localhost', port)
+  stub = getattr(test_pb2, STUB_FACTORY_IDENTIFIER)(channel)
+  yield servicer_methods, stub
+  server.stop(0)
+
+
 def _streaming_input_request_iterator(test_pb2):
   for _ in range(3):
     request = test_pb2.StreamingInputCallRequest()
@@ -264,6 +295,16 @@ class PythonPluginTest(unittest.TestCase):
     with _CreateService(test_pb2) as (servicer, stub):
       request = test_pb2.SimpleRequest(response_size=13)
 
+  def testIncompleteServicer(self):
+    import protoc_plugin_test_pb2 as test_pb2
+    moves.reload_module(test_pb2)
+    with _CreateIncompleteService(test_pb2) as (servicer, stub):
+      request = test_pb2.SimpleRequest(response_size=13)
+      try:
+        response = stub.UnaryCall(request, test_constants.LONG_TIMEOUT)
+      except face.AbortionError as error:
+        self.assertEqual(interfaces.StatusCode.UNIMPLEMENTED, error.code)
+
   def testUnaryCall(self):
     import protoc_plugin_test_pb2 as test_pb2  # pylint: disable=g-import-not-at-top
     moves.reload_module(test_pb2)