diff --git a/core/google/api/core/gapic_v1/__init__.py b/core/google/api/core/gapic_v1/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/core/google/api/core/gapic_v1/config.py b/core/google/api/core/gapic_v1/config.py new file mode 100644 index 000000000000..06d089e67407 --- /dev/null +++ b/core/google/api/core/gapic_v1/config.py @@ -0,0 +1,169 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helpers for loading gapic configuration data. + +The Google API generator creates supplementary configuration for each RPC +method to tell the client library how to deal with retries and timeouts. +""" + +import collections + +import grpc +import six + +from google.api.core import exceptions +from google.api.core import retry +from google.api.core import timeout + + +_MILLIS_PER_SECOND = 1000.0 + + +def _exception_class_for_grpc_status_name(name): + """Returns the Google API exception class for a gRPC error code name. + + Args: + name (str): The name of the gRPC status code, for example, + ``UNAVAILABLE``. + + Returns: + type: The appropriate subclass of + :class:`google.api.core.exceptions.GoogleAPICallError`. + """ + return exceptions.exception_class_for_grpc_status( + getattr(grpc.StatusCode, name)) + + +def _retry_from_retry_config(retry_params, retry_codes): + """Creates a Retry object given a gapic retry configuration. + + Args: + retry_params (dict): The retry parameter values, for example:: + + { + "initial_retry_delay_millis": 1000, + "retry_delay_multiplier": 2.5, + "max_retry_delay_millis": 120000, + "initial_rpc_timeout_millis": 120000, + "rpc_timeout_multiplier": 1.0, + "max_rpc_timeout_millis": 120000, + "total_timeout_millis": 600000 + } + + retry_codes (sequence[str]): The list of retryable gRPC error code + names. + + Returns: + google.api.core.retry.Retry: The default retry object for the method. + """ + exception_classes = [ + _exception_class_for_grpc_status_name(code) for code in retry_codes] + return retry.Retry( + retry.if_exception_type(*exception_classes), + initial=( + retry_params['initial_retry_delay_millis'] / _MILLIS_PER_SECOND), + maximum=( + retry_params['max_retry_delay_millis'] / _MILLIS_PER_SECOND), + multiplier=retry_params['retry_delay_multiplier'], + deadline=retry_params['total_timeout_millis'] / _MILLIS_PER_SECOND) + + +def _timeout_from_retry_config(retry_params): + """Creates a ExponentialTimeout object given a gapic retry configuration. + + Args: + retry_params (dict): The retry parameter values, for example:: + + { + "initial_retry_delay_millis": 1000, + "retry_delay_multiplier": 2.5, + "max_retry_delay_millis": 120000, + "initial_rpc_timeout_millis": 120000, + "rpc_timeout_multiplier": 1.0, + "max_rpc_timeout_millis": 120000, + "total_timeout_millis": 600000 + } + + Returns: + google.api.core.retry.ExponentialTimeout: The default time object for + the method. + """ + return timeout.ExponentialTimeout( + initial=( + retry_params['initial_rpc_timeout_millis'] / _MILLIS_PER_SECOND), + maximum=( + retry_params['max_rpc_timeout_millis'] / _MILLIS_PER_SECOND), + multiplier=retry_params['rpc_timeout_multiplier'], + deadline=( + retry_params['total_timeout_millis'] / _MILLIS_PER_SECOND)) + + +MethodConfig = collections.namedtuple('MethodConfig', ['retry', 'timeout']) + + +def parse_method_configs(interface_config): + """Creates default retry and timeout objects for each method in a gapic + interface config. + + Args: + interface_config (Mapping): The interface config section of the full + gapic library config. For example, If the full configuration has + an interface named ``google.example.v1.ExampleService`` you would + pass in just that interface's configuration, for example + ``gapic_config['interfaces']['google.example.v1.ExampleService']``. + + Returns: + Mapping[str, MethodConfig]: A mapping of RPC method names to their + configuration. + """ + # Grab all the retry codes + retry_codes_map = { + name: retry_codes + for name, retry_codes + in six.iteritems(interface_config.get('retry_codes', {})) + } + + # Grab all of the retry params + retry_params_map = { + name: retry_params + for name, retry_params + in six.iteritems(interface_config.get('retry_params', {})) + } + + # Iterate through all the API methods and create a flat MethodConfig + # instance for each one. + method_configs = {} + + for method_name, method_params in six.iteritems( + interface_config.get('methods', {})): + retry_params_name = method_params.get('retry_params_name') + + if retry_params_name is not None: + retry_params = retry_params_map[retry_params_name] + retry_ = _retry_from_retry_config( + retry_params, + retry_codes_map[method_params['retry_codes_name']]) + timeout_ = _timeout_from_retry_config(retry_params) + + # No retry config, so this is a non-retryable method. + else: + retry_ = None + timeout_ = timeout.ConstantTimeout( + method_params['timeout_millis'] / _MILLIS_PER_SECOND) + + method_configs[method_name] = MethodConfig( + retry=retry_, timeout=timeout_) + + return method_configs diff --git a/core/tests/unit/api_core/gapic/test_config.py b/core/tests/unit/api_core/gapic/test_config.py new file mode 100644 index 000000000000..ece5dd6344de --- /dev/null +++ b/core/tests/unit/api_core/gapic/test_config.py @@ -0,0 +1,89 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from google.api.core import exceptions +from google.api.core.gapic_v1 import config + + +INTERFACE_CONFIG = { + 'retry_codes': { + 'idempotent': ['DEADLINE_EXCEEDED', 'UNAVAILABLE'], + 'other': ['FAILED_PRECONDITION'], + 'non_idempotent': [] + }, + 'retry_params': { + 'default': { + 'initial_retry_delay_millis': 1000, + 'retry_delay_multiplier': 2.5, + 'max_retry_delay_millis': 120000, + 'initial_rpc_timeout_millis': 120000, + 'rpc_timeout_multiplier': 1.0, + 'max_rpc_timeout_millis': 120000, + 'total_timeout_millis': 600000 + }, + 'other': { + 'initial_retry_delay_millis': 1000, + 'retry_delay_multiplier': 1, + 'max_retry_delay_millis': 1000, + 'initial_rpc_timeout_millis': 1000, + 'rpc_timeout_multiplier': 1, + 'max_rpc_timeout_millis': 1000, + 'total_timeout_millis': 1000 + }, + }, + 'methods': { + 'AnnotateVideo': { + 'timeout_millis': 60000, + 'retry_codes_name': 'idempotent', + 'retry_params_name': 'default' + }, + 'Other': { + 'timeout_millis': 60000, + 'retry_codes_name': 'other', + 'retry_params_name': 'other' + }, + 'Plain': { + 'timeout_millis': 30000 + } + } +} + + +def test_create_method_configs(): + method_configs = config.parse_method_configs(INTERFACE_CONFIG) + + retry, timeout = method_configs['AnnotateVideo'] + assert retry._predicate(exceptions.DeadlineExceeded(None)) + assert retry._predicate(exceptions.ServiceUnavailable(None)) + assert retry._initial == 1.0 + assert retry._multiplier == 2.5 + assert retry._maximum == 120.0 + assert retry._deadline == 600.0 + assert timeout._initial == 120.0 + assert timeout._multiplier == 1.0 + assert timeout._maximum == 120.0 + + retry, timeout = method_configs['Other'] + assert retry._predicate(exceptions.FailedPrecondition(None)) + assert retry._initial == 1.0 + assert retry._multiplier == 1.0 + assert retry._maximum == 1.0 + assert retry._deadline == 1.0 + assert timeout._initial == 1.0 + assert timeout._multiplier == 1.0 + assert timeout._maximum == 1.0 + + retry, timeout = method_configs['Plain'] + assert retry is None + assert timeout._timeout == 30.0