Example #1
0
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)
Example #2
0
    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()
Example #3
0
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)
Example #5
0
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)