I am attempting to write a quick decorator to manage logging returns of various functions. I am not super well versed in decorators so any help you can provide would be very helpful!
from functools import update_wrapper
from typing import Any, Optional
from logging import getLogger
from time import perf_counter
from datetime import datetime
class logger:
def __init__(self, func:callable, response:str = "debug"):
self.logger = getLogger()
self.func = func
self.response = response
update_wrapper(self, func)
def __call__(self, *args, **kwargs):
return getattr(self, self.response)
def debug(self, *args, **kwargs):
self.logger.debug(f"Running {__name__} with id: {id(self)} at {datetime.now()}")
start = perf_counter()
value = self.func(*args, **kwargs)
end = perf_counter()
self.logger.debug(f"""Completed {__name__} with id: {id(self)} at {datetime.now()}.
Total Time to run: {end - start:.6f}s""")
return value
def info(self, *args, **kwargs):
self.logger.info(f"Running {__name__} at {datetime.now()}.")
return self.func(*args, **kwargs)
@logger(response="debug")
def stuff(x):
return x*x
stuff(2)
The error I am receiving is:
TypeError: __init__() missing 1 required positional argument: 'func'
,
clearly, it doesn't like the required callable and the response requirement. However, I see in all other class-based decorator setups that func needs to be called as part of the __init__
and I have also seen you can pass decorators addition information. What am I doing wrong here?
EDIT:
The purpose of getattr(self, self.response)
is so that the function returned by __call__
is either the function along with the debug
or info
logging. This allows me to utilize the decorator @logging for both logging and debug, yet yields two different results depending on the response value specified in the decorator (i.e @logging(response="info")).
Solution:
class logger:
def __init__(self, response:str = "debug"):
self.logger = getLogger()
self.response = response
def __call__(self, func:callable):
update_wrapper(self, func)
self.func = func
return getattr(self, self.response)
def debug(self, *args, **kwargs):
self.logger.debug(f"Running {self.func.__name__} (type:{type(self.func)}) with id: {id(self)} at {datetime.now()}")
start = perf_counter()
value = self.func(*args, **kwargs)
end = perf_counter()
self.logger.debug(f"""Completed {self.func.__name__} with id: {id(self)} at {datetime.now()}.
Total Time to run: {end - start:.6f}s""")
return value
def info(self, *args, **kwargs):
self.logger.info(f"Running {self.func.__name__} at {datetime.now()}.")
return self.func(*args, **kwargs)
I don't know what your code should do, in particular it is not clear (to me) which kind of arguments should be passed to getattr(self, self.response)(*args, **kwargs)
. I am saying this to understand the proper workflow of the decorator.
So your code will never work. Here some possible examples of decoration:
the __call__
way: @logger(response="debug")
class logger_1:
def __init__(self, response:str = "debug"):
print(response)
def __call__(self, func):
self.func = func
return self # ? depends on what are you doing
def debug(self, *args, **kwargs):
# ...
def info(self, *args, **kwargs):
#...
@logger_1(response="debug")
def stuff(x):
return x*x
A level more of "abstraction": @logger(response="debug").('some_parameter').debug_method
class logger_2:
def __init__(self, response:str = "debug"):
print(response)
def __call__(self, *args, **kwargs):
self.updated_response = getattr(self, self.response)(*args, **kwargs) # just an example
return self
def debug_method(self, func):
self.func = func
# ...
return func
def debug(self, *args, **kwargs):
# ...
def info(self, *args, **kwargs):
#...
@logger_2(response="debug")('some_parameter').debug_method
def stuff(x):
return x*x
NB: logger_2(response="debug").('some_parameter').debug_method
is not taking argument because it waits to be "feed" with the target function stuff
These are examples of syntax which constraint the workflow, so you need to be careful when design your decorator