callable_util.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  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. import abc
  16. import collections
  17. import enum
  18. import functools
  19. import logging
  20. import six
  21. _LOGGER = logging.getLogger(__name__)
  22. class Outcome(six.with_metaclass(abc.ABCMeta)):
  23. """A sum type describing the outcome of some call.
  24. Attributes:
  25. kind: One of Kind.RETURNED or Kind.RAISED respectively indicating that the
  26. call returned a value or raised an exception.
  27. return_value: The value returned by the call. Must be present if kind is
  28. Kind.RETURNED.
  29. exception: The exception raised by the call. Must be present if kind is
  30. Kind.RAISED.
  31. """
  32. @enum.unique
  33. class Kind(enum.Enum):
  34. """Identifies the general kind of the outcome of some call."""
  35. RETURNED = object()
  36. RAISED = object()
  37. class _EasyOutcome(
  38. collections.namedtuple('_EasyOutcome',
  39. ['kind', 'return_value', 'exception']), Outcome):
  40. """A trivial implementation of Outcome."""
  41. def _call_logging_exceptions(behavior, message, *args, **kwargs):
  42. try:
  43. return _EasyOutcome(Outcome.Kind.RETURNED, behavior(*args, **kwargs),
  44. None)
  45. except Exception as e: # pylint: disable=broad-except
  46. _LOGGER.exception(message)
  47. return _EasyOutcome(Outcome.Kind.RAISED, None, e)
  48. def with_exceptions_logged(behavior, message):
  49. """Wraps a callable in a try-except that logs any exceptions it raises.
  50. Args:
  51. behavior: Any callable.
  52. message: A string to log if the behavior raises an exception.
  53. Returns:
  54. A callable that when executed invokes the given behavior. The returned
  55. callable takes the same arguments as the given behavior but returns a
  56. future.Outcome describing whether the given behavior returned a value or
  57. raised an exception.
  58. """
  59. @functools.wraps(behavior)
  60. def wrapped_behavior(*args, **kwargs):
  61. return _call_logging_exceptions(behavior, message, *args, **kwargs)
  62. return wrapped_behavior
  63. def call_logging_exceptions(behavior, message, *args, **kwargs):
  64. """Calls a behavior in a try-except that logs any exceptions it raises.
  65. Args:
  66. behavior: Any callable.
  67. message: A string to log if the behavior raises an exception.
  68. *args: Positional arguments to pass to the given behavior.
  69. **kwargs: Keyword arguments to pass to the given behavior.
  70. Returns:
  71. An Outcome describing whether the given behavior returned a value or raised
  72. an exception.
  73. """
  74. return _call_logging_exceptions(behavior, message, *args, **kwargs)