I have a function looking like following:
async def callback(ctx, first_arg, second_arg=None) ...
Where the parameter names, the count and everything can always be different.
In the first paremeter is a method called
respond(), and I want to make a decorator that returns the modified callback, where
ctx.respond() is called.
I thought of backing up the original callback and then creating a “fake” callback where the ctx.respond() method is called and then the real callback, but it won’t work because after my decorator there is another decorator that will check on the functions parameters
def auto_respond(): def wraper(callback): # this is what I thought of b_callback = callback async def auto_callback(ctx, here comes the problem): await ctx.respond() return await b_callback(ctx, the problem is here too) return auto_callback return wraper
The problem is, that I cannot set the right parameters of the function, because I never now how what they are going to be.
Originally, I thought of maybe using
**kwargs to receive the params and directly pass them over, but then the check wouldn’t work
Let’s say I have this example
@the_other_decorator() @auto_respond() async def callback(ctx, user=None): ...
And in the other decorator, the paremeters will be checked with
def the_other_decorator(): def wraper(callback): params = inspect.signature(callback).parameters for name in params: param = params.get(name) if param.annotation != param.empty: ... elif param.default != inspect._empty: ... ... return wraper
So my next soloution was that if I somehow could “inject” a line of code to the callback function, get the first argument and use the
respond method of it, I would “bypass” the param check
Note that the params checks are needed because I need to get some information about them in the decorator
So now there are two options left, the first one is to inject a line of code as I said or somehow “clone” the parameters of the callback function and set them to the “fake” callback function
By the way, sorry for the bad English, if I didn’t express myself right or some information are missing in this problem, please tell me so I can improve the question!
I think you want something like this (I removed the async stuff for simplicity, feel free to add them back):
import functools import inspect def auto_respond(func): # This will preserve the signature of func @functools.wraps(func) def wraper(*args, **kwargs): ctx = args ctx.respond() return func(*args, **kwargs) return wraper class Context: def respond(self): print("Response") my_context = Context() @auto_respond def my_func_1(ctx, arg1, arg2): print(arg1, arg2) my_func_1(my_context, "a", "b") print(inspect.signature(my_func_1).parameters) @auto_respond def my_func_2(ctx, arg1, arg2, arg3, arg4=None): print(arg1, arg2, arg3, arg4) my_func_2(my_context, "a", "b", "c", "d") print(inspect.signature(my_func_2).parameters) >>>>>> Response a b OrderedDict([('ctx', <Parameter "ctx">), ('arg1', <Parameter "arg1">), ('arg2', <Parameter "arg2">)]) Response a b c d OrderedDict([('ctx', <Parameter "ctx">), ('arg1', <Parameter "arg1">), ('arg2', <Parameter "arg2">), ('arg3', <Parameter "arg3">), ('arg4', <Parameter "arg4=None">)])
If you want to get the function and store it somewhere, you can also just do this:
def my_func_3(ctx, arg1, arg2): print(arg1, arg2) my_auto_respond_func = auto_respond(my_func_3) my_auto_respond_func(my_context, "a", "b")
To answer the second part of your question, you can chain another decorator on top of this one by using the same structure as auto_respond, and you’ll be able to check the parameters as these are passed through
**kwargs as if the first decorator did not exist thanks to