use functools.wraps to preserve function signature