Reputation: 192
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
The decorator
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 *args
and **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 inspect.signature(callback).parameters
the_other_decorator
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 solution 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!
Upvotes: 3
Views: 908
Reputation: 624
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[0]
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 *args
and **kwargs
as if the first decorator did not exist thanks to @functools.wraps
Upvotes: 1