The order of command decorator with other decorators matters when using argument type annotations
The pytango tutorial, the order the @command decorator with respect to other decorators is not strict.
However, if I am using type annotations to provide the dtype_in for my command, then standard decorators will break the determination of dtype_in based on type annotations. Even if the decorator is using functools.wraps.
For example, consider the following device:
import tango
from tango.server import Device, run, command
import functools
import inspect
def my_decorator(f):
@functools.wraps(f)
def wrapped(*args, **kwargs):
print("before")
result = f(*args, **kwargs)
print("after")
return result
return wrapped
class MyDevice(Device):
@command
@my_decorator
def MyCmdFirst(self, argin: str) -> str:
return argin
@my_decorator
@command
def MyCmdSecond(self, argin: str) -> str:
return argin
if __name__ == '__main__':
run((MyDevice,))
I can successfully invoke MyCmdSecond("foo"), however, when I invoke MyCmdFirst("foo") I get a TypeError:
TypeError: Invalid input argument for command MyCmdFirst: 'foo' cannot be converted t
o type DevVoid
It is interesting to look at the introspection information for these the functions being passed to @command. If I add the following to my_decorator:
def my_decorator(f):
@functools.wraps(f)
def wrapped(*args, **kwargs):
print("before")
result = f(*args, **kwargs)
print("after")
return result
print(f"f signature: {inspect.signature(f)}")
print(f"wrapped signature: {inspect.signature(wrapped)}")
print(f"f fullargspec: {inspect.getfullargspec(f)}")
print(f"wrapped fullargspec: {inspect.getfullargspec(wrapped)}")
return wrapped
I get the following output when MyCmdFirst is passed to my_decorator:
f signature: (self, argin: str) -> str
wrapped signature: (self, argin: str) -> str
f fullargspec: FullArgSpec(args=['self', 'argin'], varargs=None, varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={'return': <class 'str'>, 'argin
': <class 'str'>})
wrapped fullargspec: FullArgSpec(args=[], varargs='args', varkw='kwargs', defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={'return': <class 'str'>})
So, functools.wraps() is updating things so that the inspect.signature()s match, however, @command is using the inspect.getfullargspec() and these differ.
If I add wrapped.__siganture__ = inspect.signature(f) to my_decorator then the inspect.getfullargspec() match and both commands work.
I would expect that my_decorator is pretty typical in that it is a decorator which doesn't care about the arguments and return type of the function it is wrapping. I would expect this to be re-orderable with @command provided the decorator is using functools.wraps and I would not expect it to have to provide a __signature__ as this is pretty unusual.
Would it be possible to only use inspect.signature() in @command to support this case?