Reputation: 916
I am trying to create a generic function lifecycle handler in python. Working in brief:
The issue which I encountered while doing the cleanup is as follows: Function-based decorator:
def handler(exception=Exception,cleanup=None):
def func_wrapper(func):
def wrapper(*args,**kwargs):
response=None
try:
print(args)
response=func(*args,**kwargs)
except exception as ex:
print(f'Exception occurred:{str(ex)}\nCleaning up resources')
#Passing the object for cleanup, it fails for class based decorators as it does not passes self as argument
cleanup(args[0])
return response
return wrapper
return func_wrapper
The data which is supposed to be cleaned up is stored in the class instance and is cleaned based on the method provided. For example:
Store some information using third party API.
In case of exception, the cleanup operation passed would invoke a delete API.
class Test:
def __init__(self,data):
self.main_data=data
@handler(exception=Exception,cleanup=lambda x:print(f"Cleaning data:{x.main_data}"))
def test_data(self):
print(f'Data is :{self.main_data}')
i=1/0
Output:
Exception occurred:division by zero
Cleaning up resources
Cleaning:John Doe
I was more inclinded towards Class based decorator.
class LifeCycleHandler:
def __init__(self,*,func=None,exception=Exception,cleanup=None):
self.__exception=exception
self.__cleanup=cleanup
self.__func=func
def __call__(self,*args,**kwargs):
response=None
try:
print(args)
response=self.__func(*args,**kwargs)
except self.__exception as ex:
print(f'Exception occurred:{str(ex)}\n cleaning up resources')
#Passing the object for cleanup
self.__cleanup(args[0])
return response
def lifecycle_handler(exception=Exception,cleanup=None):
def wrapper(func):
response=LifeCycleHandler(func=func,exception=exception,cleanup=cleanup)
return response
return wrapper
With class based decorator with similar functionality i faced the following error:
()
Exception occurred:test_data() missing 1 required positional argument: 'self'
cleaning up resources
Traceback (most recent call last):
File "test.py", line 27, in __call__
response=self.__func(*args,**kwargs)
TypeError: test_data() missing 1 required positional argument: 'self'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "test.py", line 54, in <module>
test.test_data()
File "test.py", line 31, in __call__
self.__cleanup(args[0])
IndexError: tuple index out of range
Can someone guide me regarding the the argument interpretation for callable classes?
Upvotes: 2
Views: 65
Reputation: 987
If my comment correct, you could add __get__
to LifeCycleHandler
.
def __get__(self, obj, type=None):
return functools.partial(self.__call__, obj)
This will make test_data
become a non-data descriptor. I assume you already know descriptor. If not, it's definitely worth to check it.
Back to your question, from trace back, your assumed that python will help you pass the caller instance which is instance of Test
as second argument to __call__
. That's not true. However, that's true in __get__
.
Your core logic (try/except block) needs are:
Test
, because you need access to main_data
.LifeCycleHandler
in, because you need access to your self.__func
.__get__
, but you could have them in __call__
.For example, you have test code below:
t = Test(123)
t.test_data()
t.test_data
will invoke __get__
. In its arguments, self
is an instance of LifeCycleHandler
and obj
is t
(instance of Test
). __get__
returned a callable function(__call__
) in which its first argument is partially feed by obj
.
Upvotes: 1