blob: 3fcf7ea40942f31f4bff8b7db62bc9646a5aeede [file] [log] [blame]
Austin Schuh272c6132020-11-14 16:37:52 -08001/*
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 <algorithm>
20#include <cassert>
21#include <cctype>
22#include <cstring>
23#include <fstream>
24#include <iostream>
25#include <map>
26#include <memory>
27#include <ostream>
28#include <set>
29#include <sstream>
30#include <tuple>
31#include <vector>
32
33#include "flatbuffers/util.h"
34#include "src/compiler/python_generator.h"
35#include "src/compiler/python_private_generator.h"
36
37using std::make_pair;
38using std::map;
39using std::pair;
40using std::replace;
41using std::tuple;
42using std::vector;
43using std::set;
44
45namespace grpc_python_generator {
46
47grpc::string generator_file_name;
48
49typedef map<grpc::string, grpc::string> StringMap;
50typedef vector<grpc::string> StringVector;
51typedef tuple<grpc::string, grpc::string> StringPair;
52typedef set<StringPair> StringPairSet;
53
54// Provides RAII indentation handling. Use as:
55// {
56// IndentScope raii_my_indent_var_name_here(my_py_printer);
57// // constructor indented my_py_printer
58// ...
59// // destructor called at end of scope, un-indenting my_py_printer
60// }
61class IndentScope {
62 public:
63 explicit IndentScope(grpc_generator::Printer* printer) : printer_(printer) {
64 printer_->Indent();
65 }
66
67 ~IndentScope() { printer_->Outdent(); }
68
69 private:
70 grpc_generator::Printer* printer_;
71};
72
73inline grpc::string StringReplace(grpc::string str, const grpc::string& from,
74 const grpc::string& to, bool replace_all) {
75 size_t pos = 0;
76
77 do {
78 pos = str.find(from, pos);
79 if (pos == grpc::string::npos) {
80 break;
81 }
82 str.replace(pos, from.length(), to);
83 pos += to.length();
84 } while (replace_all);
85
86 return str;
87}
88
89inline grpc::string StringReplace(grpc::string str, const grpc::string& from,
90 const grpc::string& to) {
91 return StringReplace(str, from, to, true);
92}
93
94grpc::string ModuleName(const grpc::string& filename,
95 const grpc::string& import_prefix) {
96 grpc::string basename = flatbuffers::StripExtension(filename);
97 basename = StringReplace(basename, "-", "_");
98 basename = StringReplace(basename, "/", ".");
99 return import_prefix + basename + "_fb";
100}
101
102grpc::string ModuleAlias(const grpc::string& filename,
103 const grpc::string& import_prefix) {
104 grpc::string module_name = ModuleName(filename, import_prefix);
105 // We can't have dots in the module name, so we replace each with _dot_.
106 // But that could lead to a collision between a.b and a_dot_b, so we also
107 // duplicate each underscore.
108 module_name = StringReplace(module_name, "_", "__");
109 module_name = StringReplace(module_name, ".", "_dot_");
110 return module_name;
111}
112
113PrivateGenerator::PrivateGenerator(const GeneratorConfiguration& config_,
114 const grpc_generator::File* file_)
115 : config(config_), file(file_) {}
116
117void PrivateGenerator::PrintBetaServicer(const grpc_generator::Service* service,
118 grpc_generator::Printer* out) {
119 StringMap service_dict;
120 service_dict["Service"] = service->name();
121 out->Print("\n\n");
122 out->Print(service_dict, "class Beta$Service$Servicer(object):\n");
123 {
124 IndentScope raii_class_indent(out);
125 out->Print(
126 "\"\"\"The Beta API is deprecated for 0.15.0 and later.\n"
127 "\nIt is recommended to use the GA API (classes and functions in this\n"
128 "file not marked beta) for all further purposes. This class was "
129 "generated\n"
130 "only to ease transition from grpcio<0.15.0 to "
131 "grpcio>=0.15.0.\"\"\"\n");
132 for (int i = 0; i < service->method_count(); ++i) {
133 auto method = service->method(i);
134 grpc::string arg_name =
135 method->ClientStreaming() ? "request_iterator" : "request";
136 StringMap method_dict;
137 method_dict["Method"] = method->name();
138 method_dict["ArgName"] = arg_name;
139 out->Print(method_dict, "def $Method$(self, $ArgName$, context):\n");
140 {
141 IndentScope raii_method_indent(out);
142 out->Print("context.code(beta_interfaces.StatusCode.UNIMPLEMENTED)\n");
143 }
144 }
145 }
146}
147
148void PrivateGenerator::PrintBetaStub(const grpc_generator::Service* service,
149 grpc_generator::Printer* out) {
150 StringMap service_dict;
151 service_dict["Service"] = service->name();
152 out->Print("\n\n");
153 out->Print(service_dict, "class Beta$Service$Stub(object):\n");
154 {
155 IndentScope raii_class_indent(out);
156 out->Print(
157 "\"\"\"The Beta API is deprecated for 0.15.0 and later.\n"
158 "\nIt is recommended to use the GA API (classes and functions in this\n"
159 "file not marked beta) for all further purposes. This class was "
160 "generated\n"
161 "only to ease transition from grpcio<0.15.0 to "
162 "grpcio>=0.15.0.\"\"\"\n");
163 for (int i = 0; i < service->method_count(); ++i) {
164 auto method = service->method(i);
165 grpc::string arg_name =
166 method->ClientStreaming() ? "request_iterator" : "request";
167 StringMap method_dict;
168 method_dict["Method"] = method->name();
169 method_dict["ArgName"] = arg_name;
170 out->Print(method_dict,
171 "def $Method$(self, $ArgName$, timeout, metadata=None, "
172 "with_call=False, protocol_options=None):\n");
173 {
174 IndentScope raii_method_indent(out);
175 out->Print("raise NotImplementedError()\n");
176 }
177 if (!method->ServerStreaming()) {
178 out->Print(method_dict, "$Method$.future = None\n");
179 }
180 }
181 }
182}
183
184void PrivateGenerator::PrintBetaServerFactory(
185 const grpc::string& package_qualified_service_name,
186 const grpc_generator::Service* service, grpc_generator::Printer* out) {
187 StringMap service_dict;
188 service_dict["Service"] = service->name();
189 out->Print("\n\n");
190 out->Print(service_dict,
191 "def beta_create_$Service$_server(servicer, pool=None, "
192 "pool_size=None, default_timeout=None, maximum_timeout=None):\n");
193 {
194 IndentScope raii_create_server_indent(out);
195 out->Print(
196 "\"\"\"The Beta API is deprecated for 0.15.0 and later.\n"
197 "\nIt is recommended to use the GA API (classes and functions in this\n"
198 "file not marked beta) for all further purposes. This function was\n"
199 "generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0"
200 "\"\"\"\n");
201 StringMap method_implementation_constructors;
202 StringMap input_message_modules_and_classes;
203 StringMap output_message_modules_and_classes;
204 for (int i = 0; i < service->method_count(); ++i) {
205 auto method = service->method(i);
206 const grpc::string method_implementation_constructor =
207 grpc::string(method->ClientStreaming() ? "stream_" : "unary_") +
208 grpc::string(method->ServerStreaming() ? "stream_" : "unary_") +
209 "inline";
210 grpc::string input_message_module_and_class = method->get_fb_builder();
211 grpc::string output_message_module_and_class = method->get_fb_builder();
212 method_implementation_constructors.insert(
213 make_pair(method->name(), method_implementation_constructor));
214 input_message_modules_and_classes.insert(
215 make_pair(method->name(), input_message_module_and_class));
216 output_message_modules_and_classes.insert(
217 make_pair(method->name(), output_message_module_and_class));
218 }
219 StringMap method_dict;
220 method_dict["PackageQualifiedServiceName"] = package_qualified_service_name;
221// out->Print("request_deserializers = {\n");
222// for (StringMap::iterator name_and_input_module_class_pair =
223// input_message_modules_and_classes.begin();
224// name_and_input_module_class_pair !=
225// input_message_modules_and_classes.end();
226// name_and_input_module_class_pair++) {
227// method_dict["MethodName"] = name_and_input_module_class_pair->first;
228// method_dict["InputTypeModuleAndClass"] =
229// name_and_input_module_class_pair->second;
230// IndentScope raii_indent(out);
231// out->Print(method_dict,
232// "(\'$PackageQualifiedServiceName$\', \'$MethodName$\'): "
233// "$InputTypeModuleAndClass$.FromString,\n");
234// }
235// out->Print("}\n");
236// out->Print("response_serializers = {\n");
237// for (StringMap::iterator name_and_output_module_class_pair =
238// output_message_modules_and_classes.begin();
239// name_and_output_module_class_pair !=
240// output_message_modules_and_classes.end();
241// name_and_output_module_class_pair++) {
242// method_dict["MethodName"] = name_and_output_module_class_pair->first;
243// method_dict["OutputTypeModuleAndClass"] =
244// name_and_output_module_class_pair->second;
245// IndentScope raii_indent(out);
246// out->Print(method_dict,
247// "(\'$PackageQualifiedServiceName$\', \'$MethodName$\'): "
248// "$OutputTypeModuleAndClass$.SerializeToString,\n");
249// }
250// out->Print("}\n");
251 out->Print("method_implementations = {\n");
252 for (StringMap::iterator name_and_implementation_constructor =
253 method_implementation_constructors.begin();
254 name_and_implementation_constructor !=
255 method_implementation_constructors.end();
256 name_and_implementation_constructor++) {
257 method_dict["Method"] = name_and_implementation_constructor->first;
258 method_dict["Constructor"] = name_and_implementation_constructor->second;
259 IndentScope raii_descriptions_indent(out);
260 const grpc::string method_name =
261 name_and_implementation_constructor->first;
262 out->Print(method_dict,
263 "(\'$PackageQualifiedServiceName$\', \'$Method$\'): "
264 "face_utilities.$Constructor$(servicer.$Method$),\n");
265 }
266 out->Print("}\n");
267 out->Print(
268 "server_options = beta_implementations.server_options("
269 "thread_pool=pool, thread_pool_size=pool_size, "
270 "default_timeout=default_timeout, "
271 "maximum_timeout=maximum_timeout)\n");
272 out->Print(
273 "return beta_implementations.server(method_implementations, "
274 "options=server_options)\n");
275 //"request_deserializers=request_deserializers, "
276 //"response_serializers=response_serializers, "
277 }
278}
279
280void PrivateGenerator::PrintBetaStubFactory(
281 const grpc::string& package_qualified_service_name,
282 const grpc_generator::Service* service, grpc_generator::Printer* out) {
283 StringMap dict;
284 dict["Service"] = service->name();
285 out->Print("\n\n");
286 out->Print(dict,
287 "def beta_create_$Service$_stub(channel, host=None,"
288 " metadata_transformer=None, pool=None, pool_size=None):\n");
289 {
290 IndentScope raii_create_server_indent(out);
291 out->Print(
292 "\"\"\"The Beta API is deprecated for 0.15.0 and later.\n"
293 "\nIt is recommended to use the GA API (classes and functions in this\n"
294 "file not marked beta) for all further purposes. This function was\n"
295 "generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0"
296 "\"\"\"\n");
297 StringMap method_cardinalities;
298 StringMap input_message_modules_and_classes;
299 StringMap output_message_modules_and_classes;
300 for (int i = 0; i < service->method_count(); ++i) {
301 auto method = service->method(i);
302 const grpc::string method_cardinality =
303 grpc::string(method->ClientStreaming() ? "STREAM" : "UNARY") +
304 "_" +
305 grpc::string(method->ServerStreaming() ? "STREAM" : "UNARY");
306 grpc::string input_message_module_and_class = method->get_fb_builder();
307 grpc::string output_message_module_and_class = method->get_fb_builder();
308 method_cardinalities.insert(
309 make_pair(method->name(), method_cardinality));
310 input_message_modules_and_classes.insert(
311 make_pair(method->name(), input_message_module_and_class));
312 output_message_modules_and_classes.insert(
313 make_pair(method->name(), output_message_module_and_class));
314 }
315 StringMap method_dict;
316 method_dict["PackageQualifiedServiceName"] = package_qualified_service_name;
317// out->Print("request_serializers = {\n");
318// for (StringMap::iterator name_and_input_module_class_pair =
319// input_message_modules_and_classes.begin();
320// name_and_input_module_class_pair !=
321// input_message_modules_and_classes.end();
322// name_and_input_module_class_pair++) {
323// method_dict["MethodName"] = name_and_input_module_class_pair->first;
324// method_dict["InputTypeModuleAndClass"] =
325// name_and_input_module_class_pair->second;
326// IndentScope raii_indent(out);
327// out->Print(method_dict,
328// "(\'$PackageQualifiedServiceName$\', \'$MethodName$\'): "
329// "$InputTypeModuleAndClass$.SerializeToString,\n");
330// }
331// out->Print("}\n");
332// out->Print("response_deserializers = {\n");
333// for (StringMap::iterator name_and_output_module_class_pair =
334// output_message_modules_and_classes.begin();
335// name_and_output_module_class_pair !=
336// output_message_modules_and_classes.end();
337// name_and_output_module_class_pair++) {
338// method_dict["MethodName"] = name_and_output_module_class_pair->first;
339// method_dict["OutputTypeModuleAndClass"] =
340// name_and_output_module_class_pair->second;
341// IndentScope raii_indent(out);
342// out->Print(method_dict,
343// "(\'$PackageQualifiedServiceName$\', \'$MethodName$\'): "
344// "$OutputTypeModuleAndClass$.FromString,\n");
345// }
346// out->Print("}\n");
347 out->Print("cardinalities = {\n");
348 for (StringMap::iterator name_and_cardinality =
349 method_cardinalities.begin();
350 name_and_cardinality != method_cardinalities.end();
351 name_and_cardinality++) {
352 method_dict["Method"] = name_and_cardinality->first;
353 method_dict["Cardinality"] = name_and_cardinality->second;
354 IndentScope raii_descriptions_indent(out);
355 out->Print(method_dict,
356 "\'$Method$\': cardinality.Cardinality.$Cardinality$,\n");
357 }
358 out->Print("}\n");
359 out->Print(
360 "stub_options = beta_implementations.stub_options("
361 "host=host, metadata_transformer=metadata_transformer, "
362 "thread_pool=pool, thread_pool_size=pool_size)\n");
363 out->Print(method_dict,
364 "return beta_implementations.dynamic_stub(channel, "
365 "\'$PackageQualifiedServiceName$\', "
366 "cardinalities, options=stub_options)\n");
367 // "request_serializers=request_serializers, "
368 //"response_deserializers=response_deserializers, "
369 }
370}
371
372void PrivateGenerator::PrintStub(
373 const grpc::string& package_qualified_service_name,
374 const grpc_generator::Service* service, grpc_generator::Printer* out) {
375 StringMap dict;
376 dict["Service"] = service->name();
377 out->Print("\n\n");
378 out->Print(dict, "class $Service$Stub(object):\n");
379 {
380 IndentScope raii_class_indent(out);
381 out->Print("\n");
382 out->Print("def __init__(self, channel):\n");
383 {
384 IndentScope raii_init_indent(out);
385 out->Print("\"\"\"Constructor.\n");
386 out->Print("\n");
387 out->Print("Args:\n");
388 {
389 IndentScope raii_args_indent(out);
390 out->Print("channel: A grpc.Channel.\n");
391 }
392 out->Print("\"\"\"\n");
393 for (int i = 0; i < service->method_count(); ++i) {
394 auto method = service->method(i);
395 grpc::string multi_callable_constructor =
396 grpc::string(method->ClientStreaming() ? "stream" : "unary") +
397 "_" +
398 grpc::string(method->ServerStreaming() ? "stream" : "unary");
399 grpc::string request_module_and_class = method->get_fb_builder();
400 grpc::string response_module_and_class = method->get_fb_builder();
401 StringMap method_dict;
402 method_dict["Method"] = method->name();
403 method_dict["MultiCallableConstructor"] = multi_callable_constructor;
404 out->Print(method_dict,
405 "self.$Method$ = channel.$MultiCallableConstructor$(\n");
406 {
407 method_dict["PackageQualifiedService"] =
408 package_qualified_service_name;
409 method_dict["RequestModuleAndClass"] = request_module_and_class;
410 method_dict["ResponseModuleAndClass"] = response_module_and_class;
411 IndentScope raii_first_attribute_indent(out);
412 IndentScope raii_second_attribute_indent(out);
413 out->Print(method_dict, "'/$PackageQualifiedService$/$Method$',\n");
414 out->Print(method_dict,"\n");
415 out->Print(
416 method_dict,"\n");
417 out->Print(")\n");
418 }
419 }
420 }
421 }
422}
423
424void PrivateGenerator::PrintServicer(const grpc_generator::Service* service,
425 grpc_generator::Printer* out) {
426 StringMap service_dict;
427 service_dict["Service"] = service->name();
428 out->Print("\n\n");
429 out->Print(service_dict, "class $Service$Servicer(object):\n");
430 {
431 IndentScope raii_class_indent(out);
432 for (int i = 0; i < service->method_count(); ++i) {
433 auto method = service->method(i);
434 grpc::string arg_name =
435 method->ClientStreaming() ? "request_iterator" : "request";
436 StringMap method_dict;
437 method_dict["Method"] = method->name();
438 method_dict["ArgName"] = arg_name;
439 out->Print("\n");
440 out->Print(method_dict, "def $Method$(self, $ArgName$, context):\n");
441 {
442 IndentScope raii_method_indent(out);
443 out->Print("context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n");
444 out->Print("context.set_details('Method not implemented!')\n");
445 out->Print("raise NotImplementedError('Method not implemented!')\n");
446 }
447 }
448 }
449}
450
451void PrivateGenerator::PrintAddServicerToServer(
452 const grpc::string& package_qualified_service_name,
453 const grpc_generator::Service* service, grpc_generator::Printer* out) {
454 StringMap service_dict;
455 service_dict["Service"] = service->name();
456 out->Print("\n\n");
457 out->Print(service_dict,
458 "def add_$Service$Servicer_to_server(servicer, server):\n");
459 {
460 IndentScope raii_class_indent(out);
461 out->Print("rpc_method_handlers = {\n");
462 {
463 IndentScope raii_dict_first_indent(out);
464 IndentScope raii_dict_second_indent(out);
465 for (int i = 0; i < service->method_count(); ++i) {
466 auto method = service->method(i);
467 grpc::string method_handler_constructor =
468 grpc::string(method->ClientStreaming() ? "stream" : "unary") +
469 "_" +
470 grpc::string(method->ServerStreaming() ? "stream" : "unary") +
471 "_rpc_method_handler";
472 grpc::string request_module_and_class = method->get_fb_builder();
473 grpc::string response_module_and_class = method->get_fb_builder();
474 StringMap method_dict;
475 method_dict["Method"] = method->name();
476 method_dict["MethodHandlerConstructor"] = method_handler_constructor;
477 method_dict["RequestModuleAndClass"] = request_module_and_class;
478 method_dict["ResponseModuleAndClass"] = response_module_and_class;
479 out->Print(method_dict,
480 "'$Method$': grpc.$MethodHandlerConstructor$(\n");
481 {
482 IndentScope raii_call_first_indent(out);
483 IndentScope raii_call_second_indent(out);
484 out->Print(method_dict, "servicer.$Method$,\n");
485 out->Print(
486 method_dict,"\n");
487 out->Print(
488 method_dict,
489 "\n");
490 }
491 out->Print("),\n");
492 }
493 }
494 StringMap method_dict;
495 method_dict["PackageQualifiedServiceName"] = package_qualified_service_name;
496 out->Print("}\n");
497 out->Print("generic_handler = grpc.method_handlers_generic_handler(\n");
498 {
499 IndentScope raii_call_first_indent(out);
500 IndentScope raii_call_second_indent(out);
501 out->Print(method_dict,
502 "'$PackageQualifiedServiceName$', rpc_method_handlers)\n");
503 }
504 out->Print("server.add_generic_rpc_handlers((generic_handler,))\n");
505 }
506}
507
508void PrivateGenerator::PrintBetaPreamble(grpc_generator::Printer* out) {
509 StringMap var;
510 var["Package"] = config.beta_package_root;
511 out->Print(var,
512 "from $Package$ import implementations as beta_implementations\n");
513 out->Print(var, "from $Package$ import interfaces as beta_interfaces\n");
514 out->Print("from grpc.framework.common import cardinality\n");
515 out->Print(
516 "from grpc.framework.interfaces.face import utilities as "
517 "face_utilities\n");
518}
519
520void PrivateGenerator::PrintPreamble(grpc_generator::Printer* out) {
521 StringMap var;
522 var["Package"] = config.grpc_package_root;
523 out->Print(var, "import $Package$\n");
524 out->Print("\n");
525 StringPairSet imports_set;
526 for (int i = 0; i < file->service_count(); ++i) {
527 auto service = file->service(i);
528 for (int j = 0; j < service->method_count(); ++j) {
529 auto method = service.get()->method(j);
530
531 grpc::string input_type_file_name = method->get_fb_builder();
532 grpc::string input_module_name =
533 ModuleName(input_type_file_name, config.import_prefix);
534 grpc::string input_module_alias =
535 ModuleAlias(input_type_file_name, config.import_prefix);
536 imports_set.insert(
537 std::make_tuple(input_module_name, input_module_alias));
538
539 grpc::string output_type_file_name = method->get_fb_builder();
540 grpc::string output_module_name =
541 ModuleName(output_type_file_name, config.import_prefix);
542 grpc::string output_module_alias =
543 ModuleAlias(output_type_file_name, config.import_prefix);
544 imports_set.insert(
545 std::make_tuple(output_module_name, output_module_alias));
546 }
547 }
548
549 for (StringPairSet::iterator it = imports_set.begin();
550 it != imports_set.end(); ++it) {
551 var["ModuleName"] = std::get<0>(*it);
552 var["ModuleAlias"] = std::get<1>(*it);
553 out->Print(var, "import $ModuleName$ as $ModuleAlias$\n");
554 }
555}
556
557void PrivateGenerator::PrintGAServices(grpc_generator::Printer* out) {
558 grpc::string package = file->package();
559 if (!package.empty()) {
560 package = package.append(".");
561 }
562
563 out->Print(file->additional_headers().c_str());
564
565 for (int i = 0; i < file->service_count(); ++i) {
566 auto service = file->service(i);
567
568 grpc::string package_qualified_service_name = package + service->name();
569 PrintStub(package_qualified_service_name, service.get(), out);
570 PrintServicer(service.get(), out);
571 PrintAddServicerToServer(package_qualified_service_name, service.get(),
572 out);
573 }
574}
575
576void PrivateGenerator::PrintBetaServices(grpc_generator::Printer* out) {
577 grpc::string package = file->package();
578 if (!package.empty()) {
579 package = package.append(".");
580 }
581 for (int i = 0; i < file->service_count(); ++i) {
582 auto service = file->service(i);
583
584 grpc::string package_qualified_service_name = package + service->name();
585 PrintBetaServicer(service.get(), out);
586 PrintBetaStub(service.get(), out);
587 PrintBetaServerFactory(package_qualified_service_name, service.get(), out);
588 PrintBetaStubFactory(package_qualified_service_name, service.get(), out);
589 }
590}
591
592grpc::string PrivateGenerator::GetGrpcServices() {
593 grpc::string output;
594 {
595 // Scope the output stream so it closes and finalizes output to the string.
596 auto out = file->CreatePrinter(&output);
597 out->Print(
598 "# Generated by the gRPC Python protocol compiler plugin. "
599 "DO NOT EDIT!\n");
600 StringMap var;
601 var["Package"] = config.grpc_package_root;
602 out->Print(var, "import $Package$\n");
603 PrintGAServices(out.get());
604 out->Print("try:\n");
605 {
606 IndentScope raii_dict_try_indent(out.get());
607 out->Print(
608 "# THESE ELEMENTS WILL BE DEPRECATED.\n"
609 "# Please use the generated *_pb2_grpc.py files instead.\n");
610 out->Print(var, "import $Package$\n");
611 PrintBetaPreamble(out.get());
612 PrintGAServices(out.get());
613 PrintBetaServices(out.get());
614 }
615 out->Print("except ImportError:\n");
616 {
617 IndentScope raii_dict_except_indent(out.get());
618 out->Print("pass");
619 }
620 }
621 return output;
622}
623
624} // namespace grpc_python_generator