def getargspec(func): import sys if sys.version_info < (3, 5): return inspect.getargspec(func) sig = inspect._signature_from_callable(func, follow_wrapper_chains=False, skip_bound_arg=False, sigcls=inspect.Signature) args = [ p.name for p in sig.parameters.values() if p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD ] varargs = [ p.name for p in sig.parameters.values() if p.kind == inspect.Parameter.VAR_POSITIONAL ] varargs = varargs[0] if varargs else None varkw = [ p.name for p in sig.parameters.values() if p.kind == inspect.Parameter.VAR_KEYWORD ] varkw = varkw[0] if varkw else None defaults = [ p.default for p in sig.parameters.values() if (p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD and p.default is not p.empty) ] or None if defaults is not None: defaults = tuple(defaults) from collections import namedtuple ArgSpec = namedtuple('ArgSpec', 'args varargs keywords defaults') return ArgSpec(args, varargs, varkw, defaults)
def __init__(self, func, owner=_UNSET_ARG, func_name=None): """Initialize the signature. Subclasses must override this to parse function types/ownership and available arguments. Args: func (callable): The function to use for the signature. owner (type, optional): The owning class, as provided when spying on the function. This is used only when spying on unbound or slippery methods. func_name (str, optional): An explicit name for the function. This will be used instead of the function's specified name, and is usually a sign of a bad decorator. Version Added: 7.0 """ super(FunctionSigPy3, self).__init__(func=func, owner=owner, func_name=func_name) if not hasattr(inspect, '_signature_from_callable'): raise InternalKGBError( 'Python %s.%s does not have inspect._signature_from_callable, ' 'which is needed in order to generate a Signature from a ' 'function.' % sys.version_info[:2]) func_name = self.func_name # Figure out the owner and method type. # # Python 3 does not officially have unbound methods. Methods on # instances are easily identified as types.MethodType, but # unbound methods are just standard functions without something # like __self__ to point to the parent class. # # However, the owner can generally be inferred (but not always!). # Python 3.3 introduced __qualname__, which is a string # identifying the path to the class within the containing module. # The path is expected to be traversable, unless it contains # "<locals>" in it, in which case it's defined somewhere you can't # get to it (like in a function). # # So to determine if it's an unbound method, we check to see what # __qualname__ looks like, and then we try to find it. If we can, # we grab the owner and identify it as an unbound method. If not, # it stays as a standard function. if inspect.ismethod(func): self.func_type = self.TYPE_BOUND_METHOD self.owner = func.__self__ elif '.' in func.__qualname__: if owner is not _UNSET_ARG: self.owner = owner try: self.is_slippery = (owner is not _UNSET_ARG and getattr( owner, func_name) is not func) except AttributeError: if '<locals>' in func.__qualname__: logger.warning( "%r doesn't have a function named \"%s\". This " "appears to be a decorator that doesn't " "preserve function names. Try passing " "func_name= when setting up the spy.", owner, func_name) else: logger.warning( "%r doesn't have a function named \"%s\". It's " "not clear why this is. Try passing func_name= " "when setting up the spy.", owner, func_name) if owner is _UNSET_ARG or inspect.isclass(owner): self.func_type = self.TYPE_UNBOUND_METHOD else: self.func_type = self.TYPE_BOUND_METHOD elif '<locals>' in func.__qualname__: # We can only assume this is a function. It might not be. self.func_type = self.TYPE_FUNCTION else: real_func = self.real_func method_owner = inspect.getmodule(real_func) for part in real_func.__qualname__.split('.')[:-1]: try: method_owner = getattr(method_owner, part) except AttributeError: method_owner = None break if method_owner is not None: self.func_type = self.TYPE_UNBOUND_METHOD self.owner = method_owner logger.warning( 'Determined the owner of %r to be %r, ' 'but it may be wrong. Please pass ' 'owner= to spy_on() to set a specific ' 'owner.', func, self.owner) # Load information on the arguments. sig = inspect._signature_from_callable(func, follow_wrapper_chains=False, skip_bound_arg=False, sigcls=inspect.Signature) all_args = [] args = [] kwargs = [] for param in sig.parameters.values(): kind = param.kind name = param.name if kind is param.POSITIONAL_OR_KEYWORD: # Standard arguments -- either positional or keyword. all_args.append(name) if param.default is param.empty: args.append(name) else: kwargs.append(name) elif kind is param.POSITIONAL_ONLY: # Positional-only arguments (Python 3.8+). all_args.append(name) args.append(name) elif kind is param.KEYWORD_ONLY: # Keyword-only arguments (Python 3+). kwargs.append(name) elif kind is param.VAR_POSITIONAL: # *args self.args_param_name = name elif kind is param.VAR_KEYWORD: # **kwargs self.kwargs_param_name = name self.all_arg_names = all_args self.arg_names = args self.kwarg_names = kwargs self._sig = sig self.finalize_state()
def Py3GetFullArgSpec(fn): """A alternative to the builtin getfullargspec. The builtin inspect.getfullargspec uses: `skip_bound_args=False, follow_wrapped_chains=False` in order to be backwards compatible. This function instead skips bound args (self) and follows wrapped chains. Args: fn: The function or class of interest. Returns: An inspect.FullArgSpec namedtuple with the full arg spec of the function. """ # pylint: disable=no-member # pytype: disable=module-attr try: sig = inspect._signature_from_callable( # pylint: disable=protected-access fn, skip_bound_arg=True, follow_wrapper_chains=True, sigcls=inspect.Signature) except Exception: # 'signature' can raise ValueError (most common), AttributeError, and # possibly others. We catch all exceptions here, and reraise a TypeError. raise TypeError('Unsupported callable.') args = [] varargs = None varkw = None kwonlyargs = [] defaults = () annotations = {} defaults = () kwdefaults = {} if sig.return_annotation is not sig.empty: annotations['return'] = sig.return_annotation for param in sig.parameters.values(): kind = param.kind name = param.name # pylint: disable=protected-access if kind is inspect._POSITIONAL_ONLY: args.append(name) elif kind is inspect._POSITIONAL_OR_KEYWORD: args.append(name) if param.default is not param.empty: defaults += (param.default, ) elif kind is inspect._VAR_POSITIONAL: varargs = name elif kind is inspect._KEYWORD_ONLY: kwonlyargs.append(name) if param.default is not param.empty: kwdefaults[name] = param.default elif kind is inspect._VAR_KEYWORD: varkw = name if param.annotation is not param.empty: annotations[name] = param.annotation # pylint: enable=protected-access if not kwdefaults: # compatibility with 'func.__kwdefaults__' kwdefaults = None if not defaults: # compatibility with 'func.__defaults__' defaults = None return inspect.FullArgSpec(args, varargs, varkw, defaults, kwonlyargs, kwdefaults, annotations)
def from_callable(cls, obj, *, follow_wrapped=True): """Constructs Signature for the given callable object.""" return inspect._signature_from_callable( obj, sigcls=cls, follow_wrapper_chains=follow_wrapped)
def custom_getfullargspec(func): """Taken from CPython inspect.getfullargspec: The method is deprecated and uses skip_bound_args=False and follow_wrapped_chains=False because it was legacy behavior We need the opposite to follow the wrapped methods, and skip the bound arguments instead of manually removing them """ try: sig = inspect._signature_from_callable(func, skip_bound_arg=True, follow_wrapper_chains=True, sigcls=inspect.Signature) except Exception as ex: # Most of the times 'signature' will raise ValueError. # But, it can also raise AttributeError, and, maybe something # else. So to be fully backwards compatible, we catch all # possible exceptions here, and reraise a TypeError. raise TypeError('unsupported callable') from ex args = [] varargs = None varkw = None kwonlyargs = [] defaults = () annotations = {} defaults = () kwdefaults = {} if sig.return_annotation is not sig.empty: annotations['return'] = sig.return_annotation for param in sig.parameters.values(): kind = param.kind name = param.name if kind is inspect._POSITIONAL_ONLY: args.append(name) elif kind is inspect._POSITIONAL_OR_KEYWORD: args.append(name) if param.default is not param.empty: defaults += (param.default, ) elif kind is inspect._VAR_POSITIONAL: varargs = name elif kind is inspect._KEYWORD_ONLY: kwonlyargs.append(name) if param.default is not param.empty: kwdefaults[name] = param.default elif kind is inspect._VAR_KEYWORD: varkw = name if param.annotation is not param.empty: annotations[name] = param.annotation if not kwdefaults: # compatibility with 'func.__kwdefaults__' kwdefaults = None if not defaults: # compatibility with 'func.__defaults__' defaults = None return inspect.FullArgSpec(args, varargs, varkw, defaults, kwonlyargs, kwdefaults, annotations)