wrapping class method in try / except using decorator - python

I have a general purpose function that sends info about exceptions to an application log.
I use the exception_handler function from within methods in classes. The app log handler that is passed into and called by the exception_handler creates a JSON string that is what actually gets sent to the logfile. This all works fine.
def exception_handler(log, terminate=False):
exc_type, exc_value, exc_tb = sys.exc_info()
filename, line_num, func_name, text = traceback.extract_tb(exc_tb)[-1]
log.error('{0} Thrown from module: {1} in {2} at line: {3} ({4})'.format(exc_value, filename, func_name, line_num, text))
del (filename, line_num, func_name, text)
if terminate:
sys.exit()
I use it as follows: (a hyper-simplified example)
from utils import exception_handler
class Demo1(object):
def __init__(self):
self.log = {a class that implements the application log}
def demo(self, name):
try:
print(name)
except Exception:
exception_handler(self.log, True)
I would like to alter exception_handler for use as a decorator for a large number of methods, i.e.:
#handle_exceptions
def func1(self, name)
{some code that gets wrapped in a try / except by the decorator}
I've looked at a number of articles about decorators, but I haven't yet figured out how to implement what I want to do. I need to pass a reference to the active log object and also pass 0 or more arguments to the wrapped function. I'd be happy to convert exception_handler to a method in a class if that makes things easier.

Such a decorator would simply be:
def handle_exceptions(f):
def wrapper(*args, **kw):
try:
return f(*args, **kw)
except Exception:
self = args[0]
exception_handler(self.log, True)
return wrapper
This decorator simply calls the wrapped function inside a try suite.
This can be applied to methods only, as it assumes the first argument is self.

Thanks to Martijn for pointing me in the right direction.
I couldn't get his suggested solution to work but after a little more searching based on his example the following works fine:
def handle_exceptions(fn):
from functools import wraps
#wraps(fn)
def wrapper(self, *args, **kw):
try:
return fn(self, *args, **kw)
except Exception:
exception_handler(self.log)
return wrapper

Related

Why are there warnings in these decorated methods?

I am string with decorators and the first use I have is to wrap a HTTP call to account for failed connections. The working code is as follows:
import requests
class Gotify:
def __init__(self):
self.url = "https://postman-echo.com/status/200"
def ensure_safe_call(caller):
def wrapper(*args, **kwargs):
try:
r = caller(*args, **kwargs)
r.raise_for_status()
except Exception as e:
try:
print(f"cannot reach gotify: {e}: {r.text}")
except NameError:
print(f"cannot reach gotify: {e} (the response r does not exist)")
else:
print("OK notified gotify of result change")
return wrapper
#ensure_safe_call
def send(self, title, message):
return requests.get(self.url)
Gotify().send("hello", "world")
This correct displays OK notified gotify of result change.
When editing this in PyCharm, I get two warning which I do not understand:
and
What do they mean in the context of my decorators (there are none when I do not use decorators)
class Gotify:
def __init__(self):
self.url = "https://postman-echo.com/status/200"
def ensure_safe_call(caller):
Because ensure_safe_call is a class method, the first argument (in your case caller) is actually the self argument, the instance of the clas object--Gotify.
Hence the warning message about the Gotify object not being callable (it's not callable because you have not overridden the __call__ class method in your Gotify class)
Function ensure_safe_call lacks a positional argument -- this is because ensure_safe_call only takes in the self argument, and doesn't specify any actual input arguments (recall that caller == self given the way you have it defined). Thus, your decorator ensure_safe_call cannot wrap anything, because it's accepting no position arguments.
You need to define a positional argument
def ensure_safe_call(self, caller):
...

Python: How do you intercept a method call to change function parameters?

What I am trying to do is write a wrapper around another module so that I can transform the parameters that are being passed to the methods of the other module. That was fairly confusing, so here is an example:
import somemodule
class Wrapper:
def __init__(self):
self.transforms = {}
self.transforms["t"] = "test"
# This next function is the one I want to exist
# Please understand the lines below will not compile and are not real code
def __intercept__(self, item, *args, **kwargs):
if "t" in args:
args[args.index("t")] = self.transforms["t"]
return somemodule.item(*args, **kwargs)
The goal is to allow users of the wrapper class to make simplified calls to the underlying module without having to rewrite all of the functions in the module. So in this case if somemodule had a function called print_uppercase then the user could do
w = Wrapper()
w.print_uppercase("t")
and get the output
TEST
I believe the answer lies in __getattr__ but I'm not totally sure how to use it for this application.
__getattr__ combined with defining a function on the fly should work:
# somemodule
def print_uppercase(x):
print(x.upper())
Now:
from functools import wraps
import somemodule
class Wrapper:
def __init__(self):
self.transforms = {}
self.transforms["t"] = "test"
def __getattr__(self, attr):
func = getattr(somemodule, attr)
#wraps(func)
def _wrapped(*args, **kwargs):
if "t" in args:
args = list(args)
args[args.index("t")] = self.transforms["t"]
return func(*args, **kwargs)
return _wrapped
w = Wrapper()
w.print_uppercase('Hello')
w.print_uppercase('t')
Output:
HELLO
TEST
I would approach this by calling the intercept method, and entering the desired method to execute, as a parameter for intercept. Then, in the intercept method, you can search for a method with that name and execute it.
Since your Wrapper object doesn't have any mutable state, it'd be easier to implement without a class. Example wrapper.py:
def func1(*args, **kwargs):
# do your transformations
return somemodule.func1(*args, **kwargs)
Then call it like:
import wrapper as w
print w.func1('somearg')

Accessing original function variables in decorators

I'm making a logging module in python which reports every exception that happens in run-time to a server, so in every function I have to write:
def a_func():
try:
#stuff here
pass
except:
Logger.writeError(self.__class__.__name__, inspect.stack()[1][3],\
tracer(self, vars()))
As you can see I'm using vars() function to get the variables which caused the exception. I read about decorators and I decided to use them:
def flog(func):
def flog_wrapper(*args, **kwargs):
try:
func(*args, **kwargs)
except Exception as e:
print "At flog:", e
#self.myLogger.writeError(self.__class__.__name__, inspect.stack()[1][3], tracer(self, vars()))
return flog_wrapper
The problem is I don't have access to the original function's (func) variables (vars()) here. Is there a way to access them in the decorator function?
You don't need to use vars(). The traceback of an exception has everything you need:
import sys
def flog(func):
def flog_wrapper(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
except Exception:
exc_type, exc_value, tb = sys.exc_info()
print "At flog:", exc_value
locals = tb.tb_frame.f_locals
self.myLogger.writeError(type(self).__name__, inspect.stack()[1][3], tracer(self, locals))
del tb
return flog_wrapper
The traceback contains a chained series of execution frames; each frame has a reference to the locals used in that frame.
You do very much want to clean up the reference to the traceback; because the traceback includes the wrapper function frame, you have a circular reference and that is best broken early.

A dry run decorator

I'm trying to write a function decorator that wraps functions. If the function is called with dry=True, the function should simply print its name and arguments. If it's called with dry=False, or without dry as an argument, it should run normally.
I have a toy working with:
from functools import wraps
def drywrap(func):
#wraps(func)
def dryfunc(*args, **kwargs):
dry = kwargs.get('dry')
if dry is not None and dry:
print("Dry run {} with args {} {}".format(func.func_name, args, kwargs))
else:
if dry is not None:
del kwargs['dry']
return func(*args, **kwargs)
return dryfunc
#drywrap
def a(something):
# import pdb; pdb.set_trace()
print("a")
print(something)
a('print no dry')
a('print me', dry=False)
a('print me too', dry=True)
...but when I put this into my application, the print statement is never run even when dry=True in the arguments. I've also tried:
from functools import wraps
def drywrap(func):
#wraps(func)
def dryfunc(*args, **kwargs):
def printfunc(*args, **kwargs):
print("Dry run {} with args {} {}".format(func.func_name,args,kwargs))
dry = kwargs.get('dry')
if dry is not None and dry:
return printfunc(*args, **kwargs)
else:
if dry is not None:
del kwargs['dry']
return func(*args, **kwargs)
return dryfunc
I should note that as part of my applications, the drywrap function is part of utils file that is imported before being used...
src/build/utils.py <--- has drywrap
src/build/build.py
src/build/plugins/subclass_build.py <--- use drywrap via "from build.utils import drywrap"
The following github project provides "a useful dry-run decorator for python" that allows checking "the basic logic of our programs without running certain operations that are lengthy and cause side effects."
see https://github.com/haarcuba/dryable
This feels like a solution.
def drier(dry=False):
def wrapper(func):
def inner_wrapper(*arg,**kwargs):
if dry:
print(func.__name__)
print(arg)
print(kwargs)
else:
return func(*arg,**kwargs)
return inner_wrapper
return wrapper
#drier(True)
def test(name):
print("hello "+name)
test("girish")
#drier()
def test2(name,last):
print("hello {} {}".format(name,last))
But as you can see even when you dont have any arguments you have to give #drier() instead of #drier .
Note : Using python 3.4.1

How can I mix decorators with the #contextmanager decorator?

Here is the code I'm working with:
from contextlib import contextmanager
from functools import wraps
class with_report_status(object):
def __init__(self, message):
self.message = message
def __call__(self, f):
#wraps(f)
def wrapper(_self, *a, **kw):
try:
return f(_self, *a, **kw)
except:
log.exception("Handling exception in reporting operation")
if not (hasattr(_self, 'report_status') and _self.report_status):
_self.report_status = self.message
raise
return wrapper
class MyClass(object):
#contextmanager
#with_report_status('unable to create export workspace')
def make_workspace(self):
temp_dir = tempfile.mkdtemp()
log.debug("Creating working directory in %s", temp_dir)
self.workspace = temp_dir
yield self.workspace
log.debug("Cleaning up working directory in %s", temp_dir)
shutil.rmtree(temp_dir)
#with_report_status('working on step 1')
def step_one(self):
# do something that isn't a context manager
The problem is, #with_report_status does not yield, as expected by #contextmanager. However, I can't wrap it the other way around either, because #contextmanager returns a generator object (i think!) instead of the value itself.
How can I make #contextmanager play nice with decorators?
Try moving #contextmanager at the bottom of the decorator list.
That is kind of a weird question: #contextmanager returns a context manager, not a generator. But for some reason you want to treat that context manager like a function? That's not something you can make work, they have nothing in common.
I think what you want is a MyClass.make_workspace that is context manager and also has a report_status field in case of exceptions. For that you need to write a context manager yourself that sets this field in it's __exit__ method, #contextmanager can't help you here.
You can subclass contextlib.GeneratorContextManager to avoid most of the work. It's not documented, so use the source, Luke.

Categories