From 6b06bf4938561f47c7ce693529a61e9a2641a6c7 Mon Sep 17 00:00:00 2001 From: Richard Belleville Date: Mon, 14 Dec 2020 17:27:24 -0800 Subject: [PATCH 1/4] Lazily import grpc_tools --- src/python/grpcio/grpc/_runtime_protos.py | 67 ++++++++++++++--------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/src/python/grpcio/grpc/_runtime_protos.py b/src/python/grpcio/grpc/_runtime_protos.py index 7f555ccd9e4..d90148c3277 100644 --- a/src/python/grpcio/grpc/_runtime_protos.py +++ b/src/python/grpcio/grpc/_runtime_protos.py @@ -15,6 +15,39 @@ import sys _REQUIRED_SYMBOLS = ("_protos", "_services", "_protos_and_services") +_MINIMUM_VERSION = (3, 5, 0) + + +def _has_runtime_proto_symbols(mod): + return all(hasattr(mod, sym) for sym in _REQUIRED_SYMBOLS) + + +def _is_grpc_tools_importable(): + try: + import grpc_tools # pylint: disable=unused-import + return True + except ImportError as e: + # NOTE: It's possible that we're encountering a transitive ImportError, so + # we check for that and re-raise if so. + if "grpc_tools" not in e.args[0]: + raise + return False + + +def _call_with_lazy_import(fn_name, version_fn, uninstalled_fn, protobuf_path): + # TODO: Docstring. + if sys.version_info < _MINIMUM_VERSION: + return version_fn(protobuf_path) + else: + if not _is_grpc_tools_importable(): + return uninstalled_fn(protobuf_path) + import grpc_tools.protoc + if _has_runtime_proto_symbols(grpc_tools.protoc): + fn = getattr(grpc_tools.protoc, fn_name) + return fn(protobuf_path) + else: + return uninstalled_fn(protobuf_path) + def _uninstalled_protos(*args, **kwargs): @@ -85,6 +118,9 @@ def protos(protobuf_path): # pylint: disable=unused-argument A module object corresponding to the message code for the indicated .proto file. Equivalent to a generated _pb2.py file. """ + return _call_with_lazy_import("_protos", _interpreter_version_protos, + _uninstalled_protos, protobuf_path) + def services(protobuf_path): # pylint: disable=unused-argument @@ -121,6 +157,8 @@ def services(protobuf_path): # pylint: disable=unused-argument A module object corresponding to the stub/service code for the indicated .proto file. Equivalent to a generated _pb2_grpc.py file. """ + return _call_with_lazy_import("_services", _interpreter_version_services, + _uninstalled_services, protobuf_path) def protos_and_services(protobuf_path): # pylint: disable=unused-argument @@ -142,30 +180,5 @@ def protos_and_services(protobuf_path): # pylint: disable=unused-argument Returns: A 2-tuple of module objects corresponding to (protos(path), services(path)). """ - - -if sys.version_info < (3, 5, 0): - protos = _interpreter_version_protos - services = _interpreter_version_services - protos_and_services = _interpreter_version_protos_and_services -else: - try: - import grpc_tools # pylint: disable=unused-import - except ImportError as e: - # NOTE: It's possible that we're encountering a transitive ImportError, so - # we check for that and re-raise if so. - if "grpc_tools" not in e.args[0]: - raise - protos = _uninstalled_protos - services = _uninstalled_services - protos_and_services = _uninstalled_protos_and_services - else: - import grpc_tools.protoc # pylint: disable=unused-import - if all(hasattr(grpc_tools.protoc, sym) for sym in _REQUIRED_SYMBOLS): - from grpc_tools.protoc import _protos as protos # pylint: disable=unused-import - from grpc_tools.protoc import _services as services # pylint: disable=unused-import - from grpc_tools.protoc import _protos_and_services as protos_and_services # pylint: disable=unused-import - else: - protos = _uninstalled_protos - services = _uninstalled_services - protos_and_services = _uninstalled_protos_and_services + return _call_with_lazy_import("_protos_and_services", _interpreter_version_protos_and_services, + _uninstalled_protos_and_services, protobuf_path) From faa4ea2863a724f0baf0979a808fec14eff851a6 Mon Sep 17 00:00:00 2001 From: Richard Belleville Date: Mon, 14 Dec 2020 17:38:34 -0800 Subject: [PATCH 2/4] Docstring. Formatting --- src/python/grpcio/grpc/_runtime_protos.py | 24 ++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/python/grpcio/grpc/_runtime_protos.py b/src/python/grpcio/grpc/_runtime_protos.py index d90148c3277..765afd95696 100644 --- a/src/python/grpcio/grpc/_runtime_protos.py +++ b/src/python/grpcio/grpc/_runtime_protos.py @@ -35,7 +35,17 @@ def _is_grpc_tools_importable(): def _call_with_lazy_import(fn_name, version_fn, uninstalled_fn, protobuf_path): - # TODO: Docstring. + """Calls one of the three functions, lazily importing grpc_tools. + + Args: + fn_name: The name of the function to import from grpc_tools.protoc. + version_fn: A function to call in case the Python version is insufficient. + uninstalled_fn: A function to call in case grpc_tools is not installed. + protobuf_path: The path to import. + + Returns: + The appropriate module object. + """ if sys.version_info < _MINIMUM_VERSION: return version_fn(protobuf_path) else: @@ -49,7 +59,6 @@ def _call_with_lazy_import(fn_name, version_fn, uninstalled_fn, protobuf_path): return uninstalled_fn(protobuf_path) - def _uninstalled_protos(*args, **kwargs): raise NotImplementedError( "Install the grpcio-tools package (1.32.0+) to use the protos function." @@ -119,8 +128,7 @@ def protos(protobuf_path): # pylint: disable=unused-argument .proto file. Equivalent to a generated _pb2.py file. """ return _call_with_lazy_import("_protos", _interpreter_version_protos, - _uninstalled_protos, protobuf_path) - + _uninstalled_protos, protobuf_path) def services(protobuf_path): # pylint: disable=unused-argument @@ -158,7 +166,7 @@ def services(protobuf_path): # pylint: disable=unused-argument .proto file. Equivalent to a generated _pb2_grpc.py file. """ return _call_with_lazy_import("_services", _interpreter_version_services, - _uninstalled_services, protobuf_path) + _uninstalled_services, protobuf_path) def protos_and_services(protobuf_path): # pylint: disable=unused-argument @@ -180,5 +188,7 @@ def protos_and_services(protobuf_path): # pylint: disable=unused-argument Returns: A 2-tuple of module objects corresponding to (protos(path), services(path)). """ - return _call_with_lazy_import("_protos_and_services", _interpreter_version_protos_and_services, - _uninstalled_protos_and_services, protobuf_path) + return _call_with_lazy_import("_protos_and_services", + _interpreter_version_protos_and_services, + _uninstalled_protos_and_services, + protobuf_path) From 3da0a9a68d42840eaf7c34b488588f4e7db76e95 Mon Sep 17 00:00:00 2001 From: Richard Belleville Date: Mon, 14 Dec 2020 18:04:38 -0800 Subject: [PATCH 3/4] Remove trailing whitespace --- src/python/grpcio/grpc/_runtime_protos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/grpcio/grpc/_runtime_protos.py b/src/python/grpcio/grpc/_runtime_protos.py index 765afd95696..daa4249f2bf 100644 --- a/src/python/grpcio/grpc/_runtime_protos.py +++ b/src/python/grpcio/grpc/_runtime_protos.py @@ -36,7 +36,7 @@ def _is_grpc_tools_importable(): def _call_with_lazy_import(fn_name, version_fn, uninstalled_fn, protobuf_path): """Calls one of the three functions, lazily importing grpc_tools. - + Args: fn_name: The name of the function to import from grpc_tools.protoc. version_fn: A function to call in case the Python version is insufficient. From 416f8f6d5c60e08813fde2db63efc7a37be27498 Mon Sep 17 00:00:00 2001 From: Richard Belleville Date: Tue, 15 Dec 2020 13:22:08 -0800 Subject: [PATCH 4/4] Factor out error functions --- src/python/grpcio/grpc/_runtime_protos.py | 61 ++++------------------- 1 file changed, 11 insertions(+), 50 deletions(-) diff --git a/src/python/grpcio/grpc/_runtime_protos.py b/src/python/grpcio/grpc/_runtime_protos.py index daa4249f2bf..2a3e1d459a2 100644 --- a/src/python/grpcio/grpc/_runtime_protos.py +++ b/src/python/grpcio/grpc/_runtime_protos.py @@ -17,6 +17,9 @@ import sys _REQUIRED_SYMBOLS = ("_protos", "_services", "_protos_and_services") _MINIMUM_VERSION = (3, 5, 0) +_UNINSTALLED_TEMPLATE = "Install the grpcio-tools package (1.32.0+) to use the {} function." +_VERSION_ERROR_TEMPLATE = "The {} function is only on available on Python 3.X interpreters." + def _has_runtime_proto_symbols(mod): return all(hasattr(mod, sym) for sym in _REQUIRED_SYMBOLS) @@ -34,64 +37,27 @@ def _is_grpc_tools_importable(): return False -def _call_with_lazy_import(fn_name, version_fn, uninstalled_fn, protobuf_path): +def _call_with_lazy_import(fn_name, protobuf_path): """Calls one of the three functions, lazily importing grpc_tools. Args: fn_name: The name of the function to import from grpc_tools.protoc. - version_fn: A function to call in case the Python version is insufficient. - uninstalled_fn: A function to call in case grpc_tools is not installed. protobuf_path: The path to import. Returns: The appropriate module object. """ if sys.version_info < _MINIMUM_VERSION: - return version_fn(protobuf_path) + raise NotImplementedError(_VERSION_ERROR_TEMPLATE.format(fn_name)) else: if not _is_grpc_tools_importable(): - return uninstalled_fn(protobuf_path) + raise NotImplementedError(_UNINSTALLED_TEMPLATE.format(fn_name)) import grpc_tools.protoc if _has_runtime_proto_symbols(grpc_tools.protoc): - fn = getattr(grpc_tools.protoc, fn_name) + fn = getattr(grpc_tools.protoc, '_' + fn_name) return fn(protobuf_path) else: - return uninstalled_fn(protobuf_path) - - -def _uninstalled_protos(*args, **kwargs): - raise NotImplementedError( - "Install the grpcio-tools package (1.32.0+) to use the protos function." - ) - - -def _uninstalled_services(*args, **kwargs): - raise NotImplementedError( - "Install the grpcio-tools package (1.32.0+) to use the services function." - ) - - -def _uninstalled_protos_and_services(*args, **kwargs): - raise NotImplementedError( - "Install the grpcio-tools package (1.32.0+) to use the protos_and_services function." - ) - - -def _interpreter_version_protos(*args, **kwargs): - raise NotImplementedError( - "The protos function is only on available on Python 3.X interpreters.") - - -def _interpreter_version_services(*args, **kwargs): - raise NotImplementedError( - "The services function is only on available on Python 3.X interpreters." - ) - - -def _interpreter_version_protos_and_services(*args, **kwargs): - raise NotImplementedError( - "The protos_and_services function is only on available on Python 3.X interpreters." - ) + raise NotImplementedError(_UNINSTALLED_TEMPLATE.format(fn_name)) def protos(protobuf_path): # pylint: disable=unused-argument @@ -127,8 +93,7 @@ def protos(protobuf_path): # pylint: disable=unused-argument A module object corresponding to the message code for the indicated .proto file. Equivalent to a generated _pb2.py file. """ - return _call_with_lazy_import("_protos", _interpreter_version_protos, - _uninstalled_protos, protobuf_path) + return _call_with_lazy_import("protos", protobuf_path) def services(protobuf_path): # pylint: disable=unused-argument @@ -165,8 +130,7 @@ def services(protobuf_path): # pylint: disable=unused-argument A module object corresponding to the stub/service code for the indicated .proto file. Equivalent to a generated _pb2_grpc.py file. """ - return _call_with_lazy_import("_services", _interpreter_version_services, - _uninstalled_services, protobuf_path) + return _call_with_lazy_import("services", protobuf_path) def protos_and_services(protobuf_path): # pylint: disable=unused-argument @@ -188,7 +152,4 @@ def protos_and_services(protobuf_path): # pylint: disable=unused-argument Returns: A 2-tuple of module objects corresponding to (protos(path), services(path)). """ - return _call_with_lazy_import("_protos_and_services", - _interpreter_version_protos_and_services, - _uninstalled_protos_and_services, - protobuf_path) + return _call_with_lazy_import("protos_and_services", protobuf_path)