2

I have exception handling code that is copied and pasted in multiple places, e.g.

def get(self, id):

    try:
         response = self.client_module_property.get(id)
    except APIItemNotFoundException:
         print("'{}' does not exist.".format(id), file=sys.stderr)
         sys.exit(1)
    except Exception as e:
         print(
             "Unknown error. To debug run with env var LOG_LEVEL=DEBUG",
             file=sys.stderr,
         )
         _log.error(e)
         sys.exit(1)
    ...

And ...

def delete(self, id):

    try:
         self.client_module_property.delete(id=id)
    except APIItemNotFoundException:
         print("'{}' does not exist".format(id), file=sys.stderr)
         sys.exit(1)
    except Exception as e:
         print(
             "Unknown error. To debug run with env var LOG_LEVEL=DEBUG",
             file=sys.stderr,
         )
         _log.error(e)
         sys.exit(1)

I have investigated decorators but they are NOT a good option for me because I'm introspecting the function parameters in some other code and the decorators change the method signature.

Is there another way I can extract the exception handling code to be reusable?

The comments in this question suggest that context manager could be used here, but I'm not clear what this would look like from browsing the python docs.

The solution needs to work on Python 2.7 and 3.x

Chris Snow
  • 23,813
  • 35
  • 144
  • 309
  • For Python 3 you can use functools.wraps to preserve function signature with decorators. [Reference](https://stackoverflow.com/questions/48746567/python-decorator-to-keep-signature-and-user-defined-attribute/48746888) (i.e. only a Python 3 solution not 2.7). – DarrylG Jul 16 '20 at 16:41
  • 1
    [Answer for preserving method signature for Python 2 & 3](https://stackoverflow.com/questions/147816/preserving-signatures-of-decorated-functions). Python 3 uses functools.wraps while Python 2 uses the decorator module. – DarrylG Jul 16 '20 at 16:47

1 Answers1

1

In the end, I got this working with wrapt. I'm posting my solution in case it is useful for anyone else.

In the end, it was actually ok to reuse a decorator as long as I could get the method parameters.

My decorator:

@wrapt.decorator
def intercept_exception(wrapped, instance, args, kwargs):
    """Handle Exceptions."""
    try:
        return wrapped(*args, **kwargs)
    except AssertionError as ae:
        print(ae, file=sys.stderr)
        sys.exit(1)
    except (
        APIException,
        APIItemNotFoundException,
        ContainerPlatformClientException,
    ) as e:
        print(e.message, file=sys.stderr)
        sys.exit(1)
    except Exception as ge:
        print(
            "Unknown error. To debug run with env var LOG_LEVEL=DEBUG",
            file=sys.stderr,
        )
        _log.error(ge)
        sys.exit(1)

A decorated function:

@intercept_exception
def get(self, id):
    response = self.client_module_property.get(id)
    ...

My client code needing to retrieve the method parameters

def get_metadata(self):
    ...
    if six.PY2:
        parameter_names = list(inspect.getargspec(function).args)
    else:
        parameter_names = list(
            inspect.getfullargspec(function).args
        )
    ...
Chris Snow
  • 23,813
  • 35
  • 144
  • 309