xref: /aosp_15_r20/external/grpc-grpc/src/compiler/python_generator.cc (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
1 /*
2  *
3  * Copyright 2015 gRPC authors.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */
18 
19 #include "src/compiler/python_generator.h"
20 
21 #include <algorithm>
22 #include <cassert>
23 #include <cctype>
24 #include <cstring>
25 #include <fstream>
26 #include <iostream>
27 #include <map>
28 #include <memory>
29 #include <ostream>
30 #include <set>
31 #include <sstream>
32 #include <tuple>
33 #include <vector>
34 
35 #include "src/compiler/config.h"
36 #include "src/compiler/generator_helpers.h"
37 #include "src/compiler/protobuf_plugin.h"
38 #include "src/compiler/python_generator_helpers.h"
39 #include "src/compiler/python_private_generator.h"
40 
41 using grpc::protobuf::FileDescriptor;
42 using grpc::protobuf::compiler::GeneratorContext;
43 using grpc::protobuf::io::CodedOutputStream;
44 using grpc::protobuf::io::ZeroCopyOutputStream;
45 using std::make_pair;
46 using std::map;
47 using std::pair;
48 using std::replace;
49 using std::set;
50 using std::tuple;
51 using std::vector;
52 
53 namespace grpc_python_generator {
54 
55 std::string generator_file_name;
56 
57 namespace {
58 
59 typedef map<std::string, std::string> StringMap;
60 typedef vector<std::string> StringVector;
61 typedef tuple<std::string, std::string> StringPair;
62 typedef set<StringPair> StringPairSet;
63 
64 // Provides RAII indentation handling. Use as:
65 // {
66 //   IndentScope raii_my_indent_var_name_here(my_py_printer);
67 //   // constructor indented my_py_printer
68 //   ...
69 //   // destructor called at end of scope, un-indenting my_py_printer
70 // }
71 class IndentScope {
72  public:
IndentScope(grpc_generator::Printer * printer)73   explicit IndentScope(grpc_generator::Printer* printer) : printer_(printer) {
74     // NOTE(rbellevi): Two-space tabs are hard-coded in the protocol compiler.
75     // Doubling our indents and outdents guarantees compliance with PEP8.
76     printer_->Indent();
77     printer_->Indent();
78   }
79 
~IndentScope()80   ~IndentScope() {
81     printer_->Outdent();
82     printer_->Outdent();
83   }
84 
85  private:
86   grpc_generator::Printer* printer_;
87 };
88 
PrivateGenerator(const GeneratorConfiguration & config,const grpc_generator::File * file)89 PrivateGenerator::PrivateGenerator(const GeneratorConfiguration& config,
90                                    const grpc_generator::File* file)
91     : config(config), file(file) {}
92 
PrintAllComments(StringVector comments,grpc_generator::Printer * out)93 void PrivateGenerator::PrintAllComments(StringVector comments,
94                                         grpc_generator::Printer* out) {
95   if (comments.empty()) {
96     // Python requires code structures like class and def to have
97     // a body, even if it is just "pass" or a docstring.  We need
98     // to ensure not to generate empty bodies. We could do something
99     // smarter and more sophisticated, but at the moment, if there is
100     // no docstring to print, we simply emit "pass" to ensure validity
101     // of the generated code.
102     out->Print(
103         "\"\"\"Missing associated documentation comment in .proto "
104         "file.\"\"\"\n");
105     return;
106   }
107   out->Print("\"\"\"");
108   for (StringVector::iterator it = comments.begin(); it != comments.end();
109        ++it) {
110     size_t start_pos = it->find_first_not_of(' ');
111     if (start_pos != std::string::npos) {
112       out->PrintRaw(it->c_str() + start_pos);
113     }
114     out->Print("\n");
115   }
116   out->Print("\"\"\"\n");
117 }
118 
PrintBetaServicer(const grpc_generator::Service * service,grpc_generator::Printer * out)119 bool PrivateGenerator::PrintBetaServicer(const grpc_generator::Service* service,
120                                          grpc_generator::Printer* out) {
121   StringMap service_dict;
122   service_dict["Service"] = service->name();
123   out->Print("\n\n");
124   out->Print(service_dict, "class Beta$Service$Servicer(object):\n");
125   {
126     IndentScope raii_class_indent(out);
127     out->Print(
128         "\"\"\"The Beta API is deprecated for 0.15.0 and later.\n"
129         "\nIt is recommended to use the GA API (classes and functions in this\n"
130         "file not marked beta) for all further purposes. This class was "
131         "generated\n"
132         "only to ease transition from grpcio<0.15.0 to "
133         "grpcio>=0.15.0.\"\"\"\n");
134     StringVector service_comments = service->GetAllComments();
135     PrintAllComments(service_comments, out);
136     for (int i = 0; i < service->method_count(); ++i) {
137       auto method = service->method(i);
138       std::string arg_name =
139           method->ClientStreaming() ? "request_iterator" : "request";
140       StringMap method_dict;
141       method_dict["Method"] = method->name();
142       method_dict["ArgName"] = arg_name;
143       out->Print(method_dict, "def $Method$(self, $ArgName$, context):\n");
144       {
145         IndentScope raii_method_indent(out);
146         StringVector method_comments = method->GetAllComments();
147         PrintAllComments(method_comments, out);
148         out->Print("context.code(beta_interfaces.StatusCode.UNIMPLEMENTED)\n");
149       }
150     }
151   }
152   return true;
153 }
154 
PrintBetaStub(const grpc_generator::Service * service,grpc_generator::Printer * out)155 bool PrivateGenerator::PrintBetaStub(const grpc_generator::Service* service,
156                                      grpc_generator::Printer* out) {
157   StringMap service_dict;
158   service_dict["Service"] = service->name();
159   out->Print("\n\n");
160   out->Print(service_dict, "class Beta$Service$Stub(object):\n");
161   {
162     IndentScope raii_class_indent(out);
163     out->Print(
164         "\"\"\"The Beta API is deprecated for 0.15.0 and later.\n"
165         "\nIt is recommended to use the GA API (classes and functions in this\n"
166         "file not marked beta) for all further purposes. This class was "
167         "generated\n"
168         "only to ease transition from grpcio<0.15.0 to "
169         "grpcio>=0.15.0.\"\"\"\n");
170     StringVector service_comments = service->GetAllComments();
171     PrintAllComments(service_comments, out);
172     for (int i = 0; i < service->method_count(); ++i) {
173       auto method = service->method(i);
174       std::string arg_name =
175           method->ClientStreaming() ? "request_iterator" : "request";
176       StringMap method_dict;
177       method_dict["Method"] = method->name();
178       method_dict["ArgName"] = arg_name;
179       out->Print(method_dict,
180                  "def $Method$(self, $ArgName$, timeout, metadata=None, "
181                  "with_call=False, protocol_options=None):\n");
182       {
183         IndentScope raii_method_indent(out);
184         StringVector method_comments = method->GetAllComments();
185         PrintAllComments(method_comments, out);
186         out->Print("raise NotImplementedError()\n");
187       }
188       if (!method->ServerStreaming()) {
189         out->Print(method_dict, "$Method$.future = None\n");
190       }
191     }
192   }
193   return true;
194 }
195 
PrintBetaServerFactory(const std::string & package_qualified_service_name,const grpc_generator::Service * service,grpc_generator::Printer * out)196 bool PrivateGenerator::PrintBetaServerFactory(
197     const std::string& package_qualified_service_name,
198     const grpc_generator::Service* service, grpc_generator::Printer* out) {
199   StringMap service_dict;
200   service_dict["Service"] = service->name();
201   out->Print("\n\n");
202   out->Print(service_dict,
203              "def beta_create_$Service$_server(servicer, pool=None, "
204              "pool_size=None, default_timeout=None, maximum_timeout=None):\n");
205   {
206     IndentScope raii_create_server_indent(out);
207     out->Print(
208         "\"\"\"The Beta API is deprecated for 0.15.0 and later.\n"
209         "\nIt is recommended to use the GA API (classes and functions in this\n"
210         "file not marked beta) for all further purposes. This function was\n"
211         "generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0"
212         "\"\"\"\n");
213     StringMap method_implementation_constructors;
214     StringMap input_message_modules_and_classes;
215     StringMap output_message_modules_and_classes;
216     for (int i = 0; i < service->method_count(); ++i) {
217       auto method = service->method(i);
218       const std::string method_implementation_constructor =
219           std::string(method->ClientStreaming() ? "stream_" : "unary_") +
220           std::string(method->ServerStreaming() ? "stream_" : "unary_") +
221           "inline";
222       std::string input_message_module_and_class;
223       if (!method->get_module_and_message_path_input(
224               &input_message_module_and_class, generator_file_name,
225               generate_in_pb2_grpc, config.import_prefix,
226               config.prefixes_to_filter)) {
227         return false;
228       }
229       std::string output_message_module_and_class;
230       if (!method->get_module_and_message_path_output(
231               &output_message_module_and_class, generator_file_name,
232               generate_in_pb2_grpc, config.import_prefix,
233               config.prefixes_to_filter)) {
234         return false;
235       }
236       method_implementation_constructors.insert(
237           make_pair(method->name(), method_implementation_constructor));
238       input_message_modules_and_classes.insert(
239           make_pair(method->name(), input_message_module_and_class));
240       output_message_modules_and_classes.insert(
241           make_pair(method->name(), output_message_module_and_class));
242     }
243     StringMap method_dict;
244     method_dict["PackageQualifiedServiceName"] = package_qualified_service_name;
245     out->Print("request_deserializers = {\n");
246     for (StringMap::iterator name_and_input_module_class_pair =
247              input_message_modules_and_classes.begin();
248          name_and_input_module_class_pair !=
249          input_message_modules_and_classes.end();
250          name_and_input_module_class_pair++) {
251       method_dict["MethodName"] = name_and_input_module_class_pair->first;
252       method_dict["InputTypeModuleAndClass"] =
253           name_and_input_module_class_pair->second;
254       IndentScope raii_indent(out);
255       out->Print(method_dict,
256                  "(\'$PackageQualifiedServiceName$\', \'$MethodName$\'): "
257                  "$InputTypeModuleAndClass$.FromString,\n");
258     }
259     out->Print("}\n");
260     out->Print("response_serializers = {\n");
261     for (StringMap::iterator name_and_output_module_class_pair =
262              output_message_modules_and_classes.begin();
263          name_and_output_module_class_pair !=
264          output_message_modules_and_classes.end();
265          name_and_output_module_class_pair++) {
266       method_dict["MethodName"] = name_and_output_module_class_pair->first;
267       method_dict["OutputTypeModuleAndClass"] =
268           name_and_output_module_class_pair->second;
269       IndentScope raii_indent(out);
270       out->Print(method_dict,
271                  "(\'$PackageQualifiedServiceName$\', \'$MethodName$\'): "
272                  "$OutputTypeModuleAndClass$.SerializeToString,\n");
273     }
274     out->Print("}\n");
275     out->Print("method_implementations = {\n");
276     for (StringMap::iterator name_and_implementation_constructor =
277              method_implementation_constructors.begin();
278          name_and_implementation_constructor !=
279          method_implementation_constructors.end();
280          name_and_implementation_constructor++) {
281       method_dict["Method"] = name_and_implementation_constructor->first;
282       method_dict["Constructor"] = name_and_implementation_constructor->second;
283       IndentScope raii_descriptions_indent(out);
284       const std::string method_name =
285           name_and_implementation_constructor->first;
286       out->Print(method_dict,
287                  "(\'$PackageQualifiedServiceName$\', \'$Method$\'): "
288                  "face_utilities.$Constructor$(servicer.$Method$),\n");
289     }
290     out->Print("}\n");
291     out->Print(
292         "server_options = beta_implementations.server_options("
293         "request_deserializers=request_deserializers, "
294         "response_serializers=response_serializers, "
295         "thread_pool=pool, thread_pool_size=pool_size, "
296         "default_timeout=default_timeout, "
297         "maximum_timeout=maximum_timeout)\n");
298     out->Print(
299         "return beta_implementations.server(method_implementations, "
300         "options=server_options)\n");
301   }
302   return true;
303 }
304 
PrintBetaStubFactory(const std::string & package_qualified_service_name,const grpc_generator::Service * service,grpc_generator::Printer * out)305 bool PrivateGenerator::PrintBetaStubFactory(
306     const std::string& package_qualified_service_name,
307     const grpc_generator::Service* service, grpc_generator::Printer* out) {
308   StringMap dict;
309   dict["Service"] = service->name();
310   out->Print("\n\n");
311   out->Print(dict,
312              "def beta_create_$Service$_stub(channel, host=None,"
313              " metadata_transformer=None, pool=None, pool_size=None):\n");
314   {
315     IndentScope raii_create_server_indent(out);
316     out->Print(
317         "\"\"\"The Beta API is deprecated for 0.15.0 and later.\n"
318         "\nIt is recommended to use the GA API (classes and functions in this\n"
319         "file not marked beta) for all further purposes. This function was\n"
320         "generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0"
321         "\"\"\"\n");
322     StringMap method_cardinalities;
323     StringMap input_message_modules_and_classes;
324     StringMap output_message_modules_and_classes;
325     for (int i = 0; i < service->method_count(); ++i) {
326       auto method = service->method(i);
327       const std::string method_cardinality =
328           std::string(method->ClientStreaming() ? "STREAM" : "UNARY") + "_" +
329           std::string(method->ServerStreaming() ? "STREAM" : "UNARY");
330       std::string input_message_module_and_class;
331       if (!method->get_module_and_message_path_input(
332               &input_message_module_and_class, generator_file_name,
333               generate_in_pb2_grpc, config.import_prefix,
334               config.prefixes_to_filter)) {
335         return false;
336       }
337       std::string output_message_module_and_class;
338       if (!method->get_module_and_message_path_output(
339               &output_message_module_and_class, generator_file_name,
340               generate_in_pb2_grpc, config.import_prefix,
341               config.prefixes_to_filter)) {
342         return false;
343       }
344       method_cardinalities.insert(
345           make_pair(method->name(), method_cardinality));
346       input_message_modules_and_classes.insert(
347           make_pair(method->name(), input_message_module_and_class));
348       output_message_modules_and_classes.insert(
349           make_pair(method->name(), output_message_module_and_class));
350     }
351     StringMap method_dict;
352     method_dict["PackageQualifiedServiceName"] = package_qualified_service_name;
353     out->Print("request_serializers = {\n");
354     for (StringMap::iterator name_and_input_module_class_pair =
355              input_message_modules_and_classes.begin();
356          name_and_input_module_class_pair !=
357          input_message_modules_and_classes.end();
358          name_and_input_module_class_pair++) {
359       method_dict["MethodName"] = name_and_input_module_class_pair->first;
360       method_dict["InputTypeModuleAndClass"] =
361           name_and_input_module_class_pair->second;
362       IndentScope raii_indent(out);
363       out->Print(method_dict,
364                  "(\'$PackageQualifiedServiceName$\', \'$MethodName$\'): "
365                  "$InputTypeModuleAndClass$.SerializeToString,\n");
366     }
367     out->Print("}\n");
368     out->Print("response_deserializers = {\n");
369     for (StringMap::iterator name_and_output_module_class_pair =
370              output_message_modules_and_classes.begin();
371          name_and_output_module_class_pair !=
372          output_message_modules_and_classes.end();
373          name_and_output_module_class_pair++) {
374       method_dict["MethodName"] = name_and_output_module_class_pair->first;
375       method_dict["OutputTypeModuleAndClass"] =
376           name_and_output_module_class_pair->second;
377       IndentScope raii_indent(out);
378       out->Print(method_dict,
379                  "(\'$PackageQualifiedServiceName$\', \'$MethodName$\'): "
380                  "$OutputTypeModuleAndClass$.FromString,\n");
381     }
382     out->Print("}\n");
383     out->Print("cardinalities = {\n");
384     for (StringMap::iterator name_and_cardinality =
385              method_cardinalities.begin();
386          name_and_cardinality != method_cardinalities.end();
387          name_and_cardinality++) {
388       method_dict["Method"] = name_and_cardinality->first;
389       method_dict["Cardinality"] = name_and_cardinality->second;
390       IndentScope raii_descriptions_indent(out);
391       out->Print(method_dict,
392                  "\'$Method$\': cardinality.Cardinality.$Cardinality$,\n");
393     }
394     out->Print("}\n");
395     out->Print(
396         "stub_options = beta_implementations.stub_options("
397         "host=host, metadata_transformer=metadata_transformer, "
398         "request_serializers=request_serializers, "
399         "response_deserializers=response_deserializers, "
400         "thread_pool=pool, thread_pool_size=pool_size)\n");
401     out->Print(method_dict,
402                "return beta_implementations.dynamic_stub(channel, "
403                "\'$PackageQualifiedServiceName$\', "
404                "cardinalities, options=stub_options)\n");
405   }
406   return true;
407 }
408 
PrintStub(const std::string & package_qualified_service_name,const grpc_generator::Service * service,grpc_generator::Printer * out)409 bool PrivateGenerator::PrintStub(
410     const std::string& package_qualified_service_name,
411     const grpc_generator::Service* service, grpc_generator::Printer* out) {
412   StringMap dict;
413   dict["Service"] = service->name();
414   out->Print("\n\n");
415   out->Print(dict, "class $Service$Stub(object):\n");
416   {
417     IndentScope raii_class_indent(out);
418     StringVector service_comments = service->GetAllComments();
419     PrintAllComments(service_comments, out);
420     out->Print("\n");
421     out->Print("def __init__(self, channel):\n");
422     {
423       IndentScope raii_init_indent(out);
424       out->Print("\"\"\"Constructor.\n");
425       out->Print("\n");
426       out->Print("Args:\n");
427       {
428         IndentScope raii_args_indent(out);
429         out->Print("channel: A grpc.Channel.\n");
430       }
431       out->Print("\"\"\"\n");
432       for (int i = 0; i < service->method_count(); ++i) {
433         auto method = service->method(i);
434         std::string multi_callable_constructor =
435             std::string(method->ClientStreaming() ? "stream" : "unary") + "_" +
436             std::string(method->ServerStreaming() ? "stream" : "unary");
437         std::string request_module_and_class;
438         if (!method->get_module_and_message_path_input(
439                 &request_module_and_class, generator_file_name,
440                 generate_in_pb2_grpc, config.import_prefix,
441                 config.prefixes_to_filter)) {
442           return false;
443         }
444         std::string response_module_and_class;
445         if (!method->get_module_and_message_path_output(
446                 &response_module_and_class, generator_file_name,
447                 generate_in_pb2_grpc, config.import_prefix,
448                 config.prefixes_to_filter)) {
449           return false;
450         }
451         StringMap method_dict;
452         method_dict["Method"] = method->name();
453         method_dict["MultiCallableConstructor"] = multi_callable_constructor;
454         out->Print(method_dict,
455                    "self.$Method$ = channel.$MultiCallableConstructor$(\n");
456         {
457           method_dict["PackageQualifiedService"] =
458               package_qualified_service_name;
459           method_dict["RequestModuleAndClass"] = request_module_and_class;
460           method_dict["ResponseModuleAndClass"] = response_module_and_class;
461           IndentScope raii_first_attribute_indent(out);
462           IndentScope raii_second_attribute_indent(out);
463           out->Print(method_dict, "'/$PackageQualifiedService$/$Method$',\n");
464           out->Print(method_dict,
465                      "request_serializer=$RequestModuleAndClass$."
466                      "SerializeToString,\n");
467           out->Print(
468               method_dict,
469               "response_deserializer=$ResponseModuleAndClass$.FromString,\n");
470           out->Print("_registered_method=True)\n");
471         }
472       }
473     }
474   }
475   return true;
476 }
477 
PrintServicer(const grpc_generator::Service * service,grpc_generator::Printer * out)478 bool PrivateGenerator::PrintServicer(const grpc_generator::Service* service,
479                                      grpc_generator::Printer* out) {
480   StringMap service_dict;
481   service_dict["Service"] = service->name();
482   out->Print("\n\n");
483   out->Print(service_dict, "class $Service$Servicer(object):\n");
484   {
485     IndentScope raii_class_indent(out);
486     StringVector service_comments = service->GetAllComments();
487     PrintAllComments(service_comments, out);
488     for (int i = 0; i < service->method_count(); ++i) {
489       auto method = service->method(i);
490       std::string arg_name =
491           method->ClientStreaming() ? "request_iterator" : "request";
492       StringMap method_dict;
493       method_dict["Method"] = method->name();
494       method_dict["ArgName"] = arg_name;
495       out->Print("\n");
496       out->Print(method_dict, "def $Method$(self, $ArgName$, context):\n");
497       {
498         IndentScope raii_method_indent(out);
499         StringVector method_comments = method->GetAllComments();
500         PrintAllComments(method_comments, out);
501         out->Print("context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n");
502         out->Print("context.set_details('Method not implemented!')\n");
503         out->Print("raise NotImplementedError('Method not implemented!')\n");
504       }
505     }
506   }
507   return true;
508 }
509 
PrintAddServicerToServer(const std::string & package_qualified_service_name,const grpc_generator::Service * service,grpc_generator::Printer * out)510 bool PrivateGenerator::PrintAddServicerToServer(
511     const std::string& package_qualified_service_name,
512     const grpc_generator::Service* service, grpc_generator::Printer* out) {
513   StringMap service_dict;
514   service_dict["Service"] = service->name();
515   out->Print("\n\n");
516   out->Print(service_dict,
517              "def add_$Service$Servicer_to_server(servicer, server):\n");
518   {
519     IndentScope raii_class_indent(out);
520     out->Print("rpc_method_handlers = {\n");
521     {
522       IndentScope raii_dict_first_indent(out);
523       IndentScope raii_dict_second_indent(out);
524       for (int i = 0; i < service->method_count(); ++i) {
525         auto method = service->method(i);
526         std::string method_handler_constructor =
527             std::string(method->ClientStreaming() ? "stream" : "unary") + "_" +
528             std::string(method->ServerStreaming() ? "stream" : "unary") +
529             "_rpc_method_handler";
530         std::string request_module_and_class;
531         if (!method->get_module_and_message_path_input(
532                 &request_module_and_class, generator_file_name,
533                 generate_in_pb2_grpc, config.import_prefix,
534                 config.prefixes_to_filter)) {
535           return false;
536         }
537         std::string response_module_and_class;
538         if (!method->get_module_and_message_path_output(
539                 &response_module_and_class, generator_file_name,
540                 generate_in_pb2_grpc, config.import_prefix,
541                 config.prefixes_to_filter)) {
542           return false;
543         }
544         StringMap method_dict;
545         method_dict["Method"] = method->name();
546         method_dict["MethodHandlerConstructor"] = method_handler_constructor;
547         method_dict["RequestModuleAndClass"] = request_module_and_class;
548         method_dict["ResponseModuleAndClass"] = response_module_and_class;
549         out->Print(method_dict,
550                    "'$Method$': grpc.$MethodHandlerConstructor$(\n");
551         {
552           IndentScope raii_call_first_indent(out);
553           IndentScope raii_call_second_indent(out);
554           out->Print(method_dict, "servicer.$Method$,\n");
555           out->Print(
556               method_dict,
557               "request_deserializer=$RequestModuleAndClass$.FromString,\n");
558           out->Print(
559               method_dict,
560               "response_serializer=$ResponseModuleAndClass$.SerializeToString,"
561               "\n");
562         }
563         out->Print("),\n");
564       }
565     }
566     StringMap method_dict;
567     method_dict["PackageQualifiedServiceName"] = package_qualified_service_name;
568     out->Print("}\n");
569     out->Print("generic_handler = grpc.method_handlers_generic_handler(\n");
570     {
571       IndentScope raii_call_first_indent(out);
572       IndentScope raii_call_second_indent(out);
573       out->Print(method_dict,
574                  "'$PackageQualifiedServiceName$', rpc_method_handlers)\n");
575     }
576     out->Print("server.add_generic_rpc_handlers((generic_handler,))\n");
577   }
578   return true;
579 }
580 
581 /* Prints out a service class used as a container for static methods pertaining
582  * to a class. This class has the exact name of service written in the ".proto"
583  * file, with no suffixes. Since this class merely acts as a namespace, it
584  * should never be instantiated.
585  */
PrintServiceClass(const std::string & package_qualified_service_name,const grpc_generator::Service * service,grpc_generator::Printer * out)586 bool PrivateGenerator::PrintServiceClass(
587     const std::string& package_qualified_service_name,
588     const grpc_generator::Service* service, grpc_generator::Printer* out) {
589   StringMap dict;
590   dict["Service"] = service->name();
591   out->Print("\n\n");
592   out->Print(" # This class is part of an EXPERIMENTAL API.\n");
593   out->Print(dict, "class $Service$(object):\n");
594   {
595     IndentScope class_indent(out);
596     StringVector service_comments = service->GetAllComments();
597     PrintAllComments(service_comments, out);
598     for (int i = 0; i < service->method_count(); ++i) {
599       const auto& method = service->method(i);
600       std::string request_module_and_class;
601       if (!method->get_module_and_message_path_input(
602               &request_module_and_class, generator_file_name,
603               generate_in_pb2_grpc, config.import_prefix,
604               config.prefixes_to_filter)) {
605         return false;
606       }
607       std::string response_module_and_class;
608       if (!method->get_module_and_message_path_output(
609               &response_module_and_class, generator_file_name,
610               generate_in_pb2_grpc, config.import_prefix,
611               config.prefixes_to_filter)) {
612         return false;
613       }
614       out->Print("\n");
615       StringMap method_dict;
616       method_dict["Method"] = method->name();
617       out->Print("@staticmethod\n");
618       out->Print(method_dict, "def $Method$(");
619       std::string request_parameter(
620           method->ClientStreaming() ? "request_iterator" : "request");
621       StringMap args_dict;
622       args_dict["RequestParameter"] = request_parameter;
623       {
624         IndentScope args_indent(out);
625         IndentScope args_double_indent(out);
626         out->Print(args_dict, "$RequestParameter$,\n");
627         out->Print("target,\n");
628         out->Print("options=(),\n");
629         out->Print("channel_credentials=None,\n");
630         out->Print("call_credentials=None,\n");
631         out->Print("insecure=False,\n");
632         out->Print("compression=None,\n");
633         out->Print("wait_for_ready=None,\n");
634         out->Print("timeout=None,\n");
635         out->Print("metadata=None):\n");
636       }
637       {
638         IndentScope method_indent(out);
639         std::string arity_method_name =
640             std::string(method->ClientStreaming() ? "stream" : "unary") + "_" +
641             std::string(method->ServerStreaming() ? "stream" : "unary");
642         args_dict["ArityMethodName"] = arity_method_name;
643         args_dict["PackageQualifiedService"] = package_qualified_service_name;
644         args_dict["Method"] = method->name();
645         out->Print(args_dict, "return grpc.experimental.$ArityMethodName$(\n");
646         {
647           IndentScope continuation_indent(out);
648           StringMap serializer_dict;
649           out->Print(args_dict, "$RequestParameter$,\n");
650           out->Print("target,\n");
651           out->Print(args_dict, "'/$PackageQualifiedService$/$Method$',\n");
652           serializer_dict["RequestModuleAndClass"] = request_module_and_class;
653           serializer_dict["ResponseModuleAndClass"] = response_module_and_class;
654           out->Print(serializer_dict,
655                      "$RequestModuleAndClass$.SerializeToString,\n");
656           out->Print(serializer_dict, "$ResponseModuleAndClass$.FromString,\n");
657           out->Print("options,\n");
658           out->Print("channel_credentials,\n");
659           out->Print("insecure,\n");
660           out->Print("call_credentials,\n");
661           out->Print("compression,\n");
662           out->Print("wait_for_ready,\n");
663           out->Print("timeout,\n");
664           out->Print("metadata,\n");
665           out->Print("_registered_method=True)\n");
666         }
667       }
668     }
669   }
670   // TODO(rbellevi): Add methods pertinent to the server side as well.
671   return true;
672 }
673 
PrintBetaPreamble(grpc_generator::Printer * out)674 bool PrivateGenerator::PrintBetaPreamble(grpc_generator::Printer* out) {
675   StringMap var;
676   var["Package"] = config.beta_package_root;
677   out->Print(var,
678              "from $Package$ import implementations as beta_implementations\n");
679   out->Print(var, "from $Package$ import interfaces as beta_interfaces\n");
680   out->Print("from grpc.framework.common import cardinality\n");
681   out->Print(
682       "from grpc.framework.interfaces.face import utilities as "
683       "face_utilities\n");
684   return true;
685 }
686 
PrintPreamble(grpc_generator::Printer * out)687 bool PrivateGenerator::PrintPreamble(grpc_generator::Printer* out) {
688   StringMap var;
689   var["Package"] = config.grpc_package_root;
690   out->Print(var, "import $Package$\n");
691   if (config.grpc_tools_version.size() > 0) {
692     out->Print(var, "import warnings\n");
693   }
694   if (generate_in_pb2_grpc) {
695     out->Print("\n");
696     StringPairSet imports_set;
697     for (int i = 0; i < file->service_count(); ++i) {
698       auto service = file->service(i);
699       for (int j = 0; j < service->method_count(); ++j) {
700         auto method = service.get()->method(j);
701 
702         std::string input_type_file_name = method->get_input_type_name();
703         std::string input_module_name =
704             ModuleName(input_type_file_name, config.import_prefix,
705                        config.prefixes_to_filter);
706         std::string input_module_alias =
707             ModuleAlias(input_type_file_name, config.import_prefix,
708                         config.prefixes_to_filter);
709         imports_set.insert(
710             std::make_tuple(input_module_name, input_module_alias));
711 
712         std::string output_type_file_name = method->get_output_type_name();
713         std::string output_module_name =
714             ModuleName(output_type_file_name, config.import_prefix,
715                        config.prefixes_to_filter);
716         std::string output_module_alias =
717             ModuleAlias(output_type_file_name, config.import_prefix,
718                         config.prefixes_to_filter);
719         imports_set.insert(
720             std::make_tuple(output_module_name, output_module_alias));
721       }
722     }
723 
724     for (StringPairSet::iterator it = imports_set.begin();
725          it != imports_set.end(); ++it) {
726       auto module_name = std::get<0>(*it);
727       var["ModuleAlias"] = std::get<1>(*it);
728       const size_t last_dot_pos = module_name.rfind('.');
729       if (last_dot_pos == std::string::npos) {
730         var["ImportStatement"] = "import " + module_name;
731       } else {
732         var["ImportStatement"] = "from " + module_name.substr(0, last_dot_pos) +
733                                  " import " +
734                                  module_name.substr(last_dot_pos + 1);
735       }
736       out->Print(var, "$ImportStatement$ as $ModuleAlias$\n");
737     }
738 
739     // Checks if generate code is used with a supported grpcio version.
740     if (config.grpc_tools_version.size() > 0) {
741       var["ToolsVersion"] = config.grpc_tools_version;
742       out->Print(var, "\nGRPC_GENERATED_VERSION = '$ToolsVersion$'\n");
743       out->Print("GRPC_VERSION = grpc.__version__\n");
744       out->Print("EXPECTED_ERROR_RELEASE = '1.65.0'\n");
745       out->Print("SCHEDULED_RELEASE_DATE = 'June 25, 2024'\n");
746       out->Print("_version_not_supported = False\n\n");
747       out->Print("try:\n");
748       {
749         IndentScope raii_import_indent(out);
750         out->Print(
751             "from grpc._utilities import first_version_is_lower\n"
752             "_version_not_supported = first_version_is_lower(GRPC_VERSION, "
753             "GRPC_GENERATED_VERSION)\n");
754       }
755       out->Print("except ImportError:\n");
756       {
757         IndentScope raii_import_error_indent(out);
758         out->Print("_version_not_supported = True\n");
759       }
760       out->Print("\nif _version_not_supported:\n");
761       {
762         IndentScope raii_warning_indent(out);
763         out->Print("warnings.warn(\n");
764         {
765           IndentScope raii_warning_string_indent(out);
766           std::string filename_without_ext = file->filename_without_ext();
767           std::replace(filename_without_ext.begin(), filename_without_ext.end(),
768                        '-', '_');
769           var["Pb2GrpcFileName"] = filename_without_ext;
770           out->Print(
771               var,
772               "f'The grpc package installed is at version {GRPC_VERSION},'\n"
773               "+ f' but the generated code in $Pb2GrpcFileName$_pb2_grpc.py "
774               "depends on'\n"
775               "+ f' grpcio>={GRPC_GENERATED_VERSION}.'\n"
776               "+ f' Please upgrade your grpc module to "
777               "grpcio>={GRPC_GENERATED_VERSION}'\n"
778               "+ f' or downgrade your generated code using "
779               "grpcio-tools<={GRPC_VERSION}.'\n"
780               "+ f' This warning will become an error in "
781               "{EXPECTED_ERROR_RELEASE},'\n"
782               "+ f' scheduled for release on {SCHEDULED_RELEASE_DATE}.',\n"
783               "RuntimeWarning\n");
784         }
785         out->Print(")\n");
786       }
787     }
788   }
789   return true;
790 }
791 
PrintGAServices(grpc_generator::Printer * out)792 bool PrivateGenerator::PrintGAServices(grpc_generator::Printer* out) {
793   std::string package = file->package();
794   if (!package.empty()) {
795     package = package.append(".");
796   }
797   for (int i = 0; i < file->service_count(); ++i) {
798     auto service = file->service(i);
799     std::string package_qualified_service_name = package + service->name();
800     if (!(PrintStub(package_qualified_service_name, service.get(), out) &&
801           PrintServicer(service.get(), out) &&
802           PrintAddServicerToServer(package_qualified_service_name,
803                                    service.get(), out) &&
804           PrintServiceClass(package_qualified_service_name, service.get(),
805                             out))) {
806       return false;
807     }
808   }
809   return true;
810 }
811 
PrintBetaServices(grpc_generator::Printer * out)812 bool PrivateGenerator::PrintBetaServices(grpc_generator::Printer* out) {
813   std::string package = file->package();
814   if (!package.empty()) {
815     package = package.append(".");
816   }
817   for (int i = 0; i < file->service_count(); ++i) {
818     auto service = file->service(i);
819     std::string package_qualified_service_name = package + service->name();
820     if (!(PrintBetaServicer(service.get(), out) &&
821           PrintBetaStub(service.get(), out) &&
822           PrintBetaServerFactory(package_qualified_service_name, service.get(),
823                                  out) &&
824           PrintBetaStubFactory(package_qualified_service_name, service.get(),
825                                out))) {
826       return false;
827     }
828   }
829   return true;
830 }
831 
GetGrpcServices()832 pair<bool, std::string> PrivateGenerator::GetGrpcServices() {
833   std::string output;
834   {
835     // Scope the output stream so it closes and finalizes output to the string.
836     auto out = file->CreatePrinter(&output);
837     if (generate_in_pb2_grpc) {
838       out->Print(
839           "# Generated by the gRPC Python protocol compiler plugin. "
840           "DO NOT EDIT!\n\"\"\""
841           "Client and server classes corresponding to protobuf-defined "
842           "services.\"\"\"\n");
843       if (!PrintPreamble(out.get())) {
844         return make_pair(false, "");
845       }
846       if (!PrintGAServices(out.get())) {
847         return make_pair(false, "");
848       }
849     } else {
850       out->Print("try:\n");
851       {
852         IndentScope raii_dict_try_indent(out.get());
853         out->Print(
854             "# THESE ELEMENTS WILL BE DEPRECATED.\n"
855             "# Please use the generated *_pb2_grpc.py files instead.\n");
856         if (!PrintPreamble(out.get())) {
857           return make_pair(false, "");
858         }
859         if (!PrintBetaPreamble(out.get())) {
860           return make_pair(false, "");
861         }
862         if (!PrintGAServices(out.get())) {
863           return make_pair(false, "");
864         }
865         if (!PrintBetaServices(out.get())) {
866           return make_pair(false, "");
867         }
868       }
869       out->Print("except ImportError:\n");
870       {
871         IndentScope raii_dict_except_indent(out.get());
872         out->Print("pass");
873       }
874     }
875   }
876   return make_pair(true, std::move(output));
877 }
878 
879 }  // namespace
880 
GeneratorConfiguration()881 GeneratorConfiguration::GeneratorConfiguration()
882     : grpc_package_root("grpc"),
883       beta_package_root("grpc.beta"),
884       import_prefix(""),
885       grpc_tools_version("") {}
886 
GeneratorConfiguration(std::string version)887 GeneratorConfiguration::GeneratorConfiguration(std::string version)
888     : grpc_package_root("grpc"),
889       beta_package_root("grpc.beta"),
890       import_prefix(""),
891       grpc_tools_version(version) {}
892 
PythonGrpcGenerator(const GeneratorConfiguration & config)893 PythonGrpcGenerator::PythonGrpcGenerator(const GeneratorConfiguration& config)
894     : config_(config) {}
895 
~PythonGrpcGenerator()896 PythonGrpcGenerator::~PythonGrpcGenerator() {}
897 
GenerateGrpc(GeneratorContext * context,PrivateGenerator & generator,std::string file_name,bool generate_in_pb2_grpc)898 static bool GenerateGrpc(GeneratorContext* context, PrivateGenerator& generator,
899                          std::string file_name, bool generate_in_pb2_grpc) {
900   bool success;
901   std::unique_ptr<ZeroCopyOutputStream> output;
902   std::unique_ptr<CodedOutputStream> coded_output;
903   std::string grpc_code;
904 
905   if (generate_in_pb2_grpc) {
906     output.reset(context->Open(file_name));
907     generator.generate_in_pb2_grpc = true;
908   } else {
909     output.reset(context->OpenForInsert(file_name, "module_scope"));
910     generator.generate_in_pb2_grpc = false;
911   }
912 
913   coded_output.reset(new CodedOutputStream(output.get()));
914   tie(success, grpc_code) = generator.GetGrpcServices();
915 
916   if (success) {
917     coded_output->WriteRaw(grpc_code.data(), grpc_code.size());
918     return true;
919   } else {
920     return false;
921   }
922 }
923 
ParseParameters(const std::string & parameter,std::string * grpc_version,std::vector<std::string> * strip_prefixes,std::string * error)924 static bool ParseParameters(const std::string& parameter,
925                             std::string* grpc_version,
926                             std::vector<std::string>* strip_prefixes,
927                             std::string* error) {
928   std::vector<std::string> comma_delimited_parameters;
929   grpc_python_generator::Split(parameter, ',', &comma_delimited_parameters);
930   if (comma_delimited_parameters.size() == 1 &&
931       comma_delimited_parameters[0].empty()) {
932     *grpc_version = "grpc_2_0";
933   } else if (comma_delimited_parameters.size() == 1) {
934     *grpc_version = comma_delimited_parameters[0];
935   } else if (comma_delimited_parameters.size() == 2) {
936     *grpc_version = comma_delimited_parameters[0];
937     std::copy(comma_delimited_parameters.begin() + 1,
938               comma_delimited_parameters.end(),
939               std::back_inserter(*strip_prefixes));
940   } else {
941     *error = "--grpc_python_out received too many comma-delimited parameters.";
942     return false;
943   }
944   return true;
945 }
946 
Generate(const FileDescriptor * file,const std::string & parameter,GeneratorContext * context,std::string * error) const947 bool PythonGrpcGenerator::Generate(const FileDescriptor* file,
948                                    const std::string& parameter,
949                                    GeneratorContext* context,
950                                    std::string* error) const {
951   // Get output file name.
952   std::string pb2_file_name;
953   std::string pb2_grpc_file_name;
954   static const int proto_suffix_length = strlen(".proto");
955   if (file->name().size() > static_cast<size_t>(proto_suffix_length) &&
956       file->name().find_last_of(".proto") == file->name().size() - 1) {
957     std::string base =
958         file->name().substr(0, file->name().size() - proto_suffix_length);
959     std::replace(base.begin(), base.end(), '-', '_');
960     pb2_file_name = base + "_pb2.py";
961     pb2_grpc_file_name = base + "_pb2_grpc.py";
962   } else {
963     *error = "Invalid proto file name. Proto file must end with .proto";
964     return false;
965   }
966   generator_file_name = file->name();
967 
968   ProtoBufFile pbfile(file);
969   std::string grpc_version;
970   GeneratorConfiguration extended_config(config_);
971   bool success = ParseParameters(parameter, &grpc_version,
972                                  &(extended_config.prefixes_to_filter), error);
973   PrivateGenerator generator(extended_config, &pbfile);
974   if (!success) return false;
975   if (grpc_version == "grpc_2_0") {
976     return GenerateGrpc(context, generator, pb2_grpc_file_name, true);
977   } else if (grpc_version == "grpc_1_0") {
978     return GenerateGrpc(context, generator, pb2_grpc_file_name, true) &&
979            GenerateGrpc(context, generator, pb2_file_name, false);
980   } else {
981     *error = "Invalid grpc version '" + grpc_version + "'.";
982     return false;
983   }
984 }
985 
986 }  // namespace grpc_python_generator
987