• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2015 gRPC authors.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Utilities for working with callables."""
15
16from abc import ABC
17import collections
18import enum
19import functools
20import logging
21
22_LOGGER = logging.getLogger(__name__)
23
24
25class Outcome(ABC):
26    """A sum type describing the outcome of some call.
27
28    Attributes:
29      kind: One of Kind.RETURNED or Kind.RAISED respectively indicating that the
30        call returned a value or raised an exception.
31      return_value: The value returned by the call. Must be present if kind is
32        Kind.RETURNED.
33      exception: The exception raised by the call. Must be present if kind is
34        Kind.RAISED.
35    """
36
37    @enum.unique
38    class Kind(enum.Enum):
39        """Identifies the general kind of the outcome of some call."""
40
41        RETURNED = object()
42        RAISED = object()
43
44
45class _EasyOutcome(
46    collections.namedtuple(
47        "_EasyOutcome", ["kind", "return_value", "exception"]
48    ),
49    Outcome,
50):
51    """A trivial implementation of Outcome."""
52
53
54def _call_logging_exceptions(behavior, message, *args, **kwargs):
55    try:
56        return _EasyOutcome(
57            Outcome.Kind.RETURNED, behavior(*args, **kwargs), None
58        )
59    except Exception as e:  # pylint: disable=broad-except
60        _LOGGER.exception(message)
61        return _EasyOutcome(Outcome.Kind.RAISED, None, e)
62
63
64def with_exceptions_logged(behavior, message):
65    """Wraps a callable in a try-except that logs any exceptions it raises.
66
67    Args:
68      behavior: Any callable.
69      message: A string to log if the behavior raises an exception.
70
71    Returns:
72      A callable that when executed invokes the given behavior. The returned
73        callable takes the same arguments as the given behavior but returns a
74        future.Outcome describing whether the given behavior returned a value or
75        raised an exception.
76    """
77
78    @functools.wraps(behavior)
79    def wrapped_behavior(*args, **kwargs):
80        return _call_logging_exceptions(behavior, message, *args, **kwargs)
81
82    return wrapped_behavior
83
84
85def call_logging_exceptions(behavior, message, *args, **kwargs):
86    """Calls a behavior in a try-except that logs any exceptions it raises.
87
88    Args:
89      behavior: Any callable.
90      message: A string to log if the behavior raises an exception.
91      *args: Positional arguments to pass to the given behavior.
92      **kwargs: Keyword arguments to pass to the given behavior.
93
94    Returns:
95      An Outcome describing whether the given behavior returned a value or raised
96        an exception.
97    """
98    return _call_logging_exceptions(behavior, message, *args, **kwargs)
99