예제 #1
0
    def __call__(self, wrapped_function):

        # Extract the function signature for the function we are wrapping.
        wrapped_signature = funcsigs.signature(wrapped_function)

        # Define a new function to return in place of the wrapped one
        @wraps(wrapped_function)
        def wrapper(*func_args, **func_kwargs):
            # Bind the arguments to our new function to the
            # signature of the original.
            bound_args = wrapped_signature.bind(*func_args, **func_kwargs)

            # Iterate through the parameters of the original signature
            for param in wrapped_signature.parameters.values():
                # We do not support variable arguments (*args, **kwargs)
                if param.kind in (funcsigs.Parameter.VAR_KEYWORD,
                                  funcsigs.Parameter.VAR_POSITIONAL):
                    continue
                # Catch the (never triggered) case where bind relied on
                #  a default value.
                if (param.name not in bound_args.arguments
                        and param.default is not param.empty):
                    bound_args.arguments[param.name] = param.default

                # Get the value of this parameter (argument to new function)
                arg = bound_args.arguments[param.name]

                # Get target unit, either from decorator kwargs or annotations
                if param.name in self.decorator_kwargs:
                    (target_min, target_max,
                     target_unit) = self.decorator_kwargs[param.name]
                else:
                    continue

                # If the target unit is empty, then no unit was specified
                # so we move past it
                if target_unit is not funcsigs.Parameter.empty:

                    # skip over None values, if desired
                    if arg is None and self.allow_none:
                        continue

                    try:
                        equivalent = arg.unit.is_equivalent(
                            target_unit, equivalencies=self.equivalencies)

                        if not equivalent:
                            raise UnitsError("Argument '{0}' to function '{1}'"
                                             " must be in units convertible to"
                                             " '{2}'.".format(
                                                 param.name,
                                                 wrapped_function.__name__,
                                                 target_unit.to_string()))

                    # Either there is no .unit or no .is_equivalent
                    except AttributeError:
                        if hasattr(arg, "unit"):
                            error_msg = ("a 'unit' attribute without an "
                                         "'is_equivalent' method")
                        else:
                            error_msg = "no 'unit' attribute"
                        raise TypeError(
                            "Argument '{0}' to function '{1}' has {2}. You "
                            "may want to pass in an astropy Quantity "
                            "instead.".format(param.name,
                                              wrapped_function.__name__,
                                              error_msg))

                    # test value range
                    if target_min is not None:
                        quantity = bound_args.arguments[param.name]
                        value = quantity.to(target_unit).value
                        if np.any(value < target_min):
                            raise ValueError(
                                "Argument '{0}' to function '{1}' out of "
                                "range (allowed {2} to {3} {4}).".format(
                                    param.name,
                                    wrapped_function.__name__,
                                    target_min,
                                    target_max,
                                    target_unit,
                                ))

                    if target_max is not None:
                        quantity = bound_args.arguments[param.name]
                        value = quantity.to(target_unit).value
                        if np.any(value > target_max):
                            raise ValueError(
                                "Argument '{0}' to function '{1}' out of "
                                "range (allowed {2} to {3} {4}).".format(
                                    param.name,
                                    wrapped_function.__name__,
                                    target_min,
                                    target_max,
                                    target_unit,
                                ))
                    if self.strip_input_units:
                        bound_args.arguments[param.name] = (
                            bound_args.arguments[param.name].to(
                                target_unit).value)

            # Call the original function with any equivalencies in force.
            with add_enabled_equivalencies(self.equivalencies):
                # result = wrapped_function(*func_args, **func_kwargs)
                result = wrapped_function(*bound_args.args,
                                          **bound_args.kwargs)

            if self.output_unit is not None:
                # test, if return values are tuple-like
                try:
                    # make namedtuples work (as well as tuples)
                    if hasattr(result, '_fields'):
                        cls = result.__class__
                        return cls(*(
                            # r if u is None else Quantity(r, u, subok=True)
                            r if u is None else r * u  # deal with astropy bug
                            for r, u in zip(result, self.output_unit)))
                    else:
                        return tuple(
                            # r if u is None else Quantity(r, u, subok=True)
                            r if u is None else r * u  # deal with astropy bug
                            for r, u in zip(result, self.output_unit))
                except TypeError:

                    return (result if self.output_unit is None else
                            # Quantity(result, self.output_unit, subok=True)
                            result * self.output_unit  # deal with astropy bug
                            )
            else:
                return result

        return wrapper
예제 #2
0
    def support_nddata_decorator(func):

        # Find out args and kwargs
        sig = signature(func)
        func_args = []
        func_kwargs = []
        for param in sig.parameters.values():
            if param.kind in (param.VAR_POSITIONAL,
                              param.VAR_KEYWORD):  # pragma: no cover
                raise ValueError("func may not have *args or **kwargs")
            elif param.default == param.empty:
                func_args.append(param.name)
            else:
                func_kwargs.append(param.name)

        # First argument should be data
        if len(func_args) == 0 or func_args[0] != 'data':
            raise ValueError("Can only wrap functions whose first positional "
                             "argument is `data`")

        supported_properties = ['uncertainty', 'mask', 'meta', 'unit', 'wcs',
                                'flags']

        @wraps(func)
        def wrapper(data, *args, **kwargs):

            unpack = isinstance(data, accepts)
            input_data = data

            if not unpack and isinstance(data, NDDataBase):
                raise TypeError("Only NDDataBase sub-classes that inherit from"
                                " {0} can be used by this function".format(
                                    accepts.__name__))

            # If data is an NDData instance, we can try and find properties
            # that can be passed as kwargs.
            if unpack:

                ignored = []

                # We loop over a list of pre-defined properties
                for prop in supported_properties:
                    msg = ("Property {0} has been passed explicitly and as an "
                           "NDData property, using explicitly specified "
                           "value.")

                    # We only need to do something if the property exists on
                    # the NDData object
                    if hasattr(data, prop):
                        value = getattr(data, prop)
                        if ((prop == 'meta' and len(value) > 0) or
                                (prop != 'meta' and value is not None)):
                            if prop in func_kwargs:
                                if prop in kwargs and kwargs[prop] is not None:
                                    warnings.warn(msg.format(prop),
                                                  AstropyUserWarning)
                                else:
                                    kwargs[prop] = value
                            else:
                                ignored.append(prop)

                if ignored:
                    warnings.warn("The following attributes were set on the "
                                  "data object, but will be ignored by the "
                                  "function: " + ", ".join(ignored),
                                  AstropyUserWarning)

                # Finally, replace data by the data itself
                data = data.data

            result = func(data, *args, **kwargs)

            if unpack:

                if repack:
                    if (len(returns) > 1 and
                            len(returns) != len(result)):  # pragma: no cover
                        raise ValueError("Function did not return the expected"
                                         " number of arguments")
                    elif len(returns) == 1:
                        result = [result]
                    return input_data.__class__(**dict(zip(returns, result)))
                else:
                    return result

            else:

                return result

        return wrapper
        def decorator(function):
            # Lazy import to avoid cyclic imports
            from astropy.utils.compat.funcsigs import signature

            # The named arguments of the function.
            arguments = signature(function).parameters
            keys = list(arguments.keys())
            position = [None] * n

            for i in range(n):
                # Determine the position of the argument.
                if arg_in_kwargs[i]:
                    pass
                else:
                    if new_name[i] is None:
                        continue
                    elif new_name[i] in arguments:
                        param = arguments[new_name[i]]
                        # In case the argument is not found in the list of arguments
                        # the only remaining possibility is that it should be caught
                        # by some kind of **kwargs argument.
                        # This case has to be explicitly specified, otherwise throw
                        # an exception!
                    else:
                        raise TypeError(
                            '"{}" was not specified in the function '
                            'signature. If it was meant to be part of '
                            '"**kwargs" then set "arg_in_kwargs" to "True"'
                            '.'.format(new_name[i]))

                    # There are several possibilities now:

                    # 1.) Positional or keyword argument:
                    if param.kind == param.POSITIONAL_OR_KEYWORD:
                        position[i] = keys.index(new_name[i])

                    # 2.) Keyword only argument (Python 3 only):
                    elif param.kind == param.KEYWORD_ONLY:
                        # These cannot be specified by position.
                        position[i] = None

                    # 3.) positional-only argument, varargs, varkwargs or some
                    #     unknown type:
                    else:
                        raise TypeError(
                            'cannot replace argument "{0}" of kind '
                            '{1!r}.'.format(new_name[i], param.kind))

            @functools.wraps(function)
            def wrapper(*args, **kwargs):
                for i in range(n):
                    # The only way to have oldkeyword inside the function is
                    # that it is passed as kwarg because the oldkeyword
                    # parameter was renamed to newkeyword.
                    if old_name[i] in kwargs:
                        value = kwargs.pop(old_name[i])
                        # Display the deprecation warning only when it's not
                        # pending.
                        if not pending[i]:
                            message = (
                                '"{0}" was deprecated in version {1} '
                                'and will be removed in a future version. '.
                                format(old_name[i], since[i]))
                            if new_name[i] is not None:
                                message += (
                                    'Use argument "{}" instead.'.format(
                                        new_name[i]))
                            elif alternative:
                                message += ('\n        Use {} instead.'.format(
                                    alternative))
                            warnings.warn(message, warning_type, stacklevel=2)

                        # Check if the newkeyword was given as well.
                        newarg_in_args = (position[i] is not None
                                          and len(args) > position[i])
                        newarg_in_kwargs = new_name[i] in kwargs

                        if newarg_in_args or newarg_in_kwargs:
                            if not pending[i]:
                                # If both are given print a Warning if relax is
                                # True or raise an Exception is relax is False.
                                if relax[i]:
                                    warnings.warn(
                                        '"{0}" and "{1}" keywords were set. '
                                        'Using the value of "{1}".'
                                        ''.format(old_name[i], new_name[i]),
                                        AstropyUserWarning)
                                else:
                                    raise TypeError(
                                        'cannot specify both "{}" and "{}"'
                                        '.'.format(old_name[i], new_name[i]))
                        else:
                            # Pass the value of the old argument with the
                            # name of the new argument to the function
                            if new_name[i] is not None:
                                kwargs[new_name[i]] = value

                return function(*args, **kwargs)

            return wrapper
예제 #4
0
        def decorator(function):
            # Lazy import to avoid cyclic imports
            from astropy.utils.compat.funcsigs import signature

            # The named arguments of the function.
            arguments = signature(function).parameters
            keys = list(arguments.keys())
            position = [None] * n

            for i in range(n):
                # Determine the position of the argument.
                if arg_in_kwargs[i]:
                    pass
                else:
                    if new_name[i] is None:
                        continue
                    elif new_name[i] in arguments:
                        param = arguments[new_name[i]]
                        # In case the argument is not found in the list of arguments
                        # the only remaining possibility is that it should be caught
                        # by some kind of **kwargs argument.
                        # This case has to be explicitly specified, otherwise throw
                        # an exception!
                    else:
                        raise TypeError('"{}" was not specified in the function '
                                        'signature. If it was meant to be part of '
                                        '"**kwargs" then set "arg_in_kwargs" to "True"'
                                        '.'.format(new_name[i]))

                    # There are several possibilities now:

                    # 1.) Positional or keyword argument:
                    if param.kind == param.POSITIONAL_OR_KEYWORD:
                        position[i] = keys.index(new_name[i])

                    # 2.) Keyword only argument (Python 3 only):
                    elif param.kind == param.KEYWORD_ONLY:
                        # These cannot be specified by position.
                        position[i] = None

                    # 3.) positional-only argument, varargs, varkwargs or some
                    #     unknown type:
                    else:
                        raise TypeError('cannot replace argument "{0}" of kind '
                                        '{1!r}.'.format(new_name[i], param.kind))

            @functools.wraps(function)
            def wrapper(*args, **kwargs):
                for i in range(n):
                    # The only way to have oldkeyword inside the function is
                    # that it is passed as kwarg because the oldkeyword
                    # parameter was renamed to newkeyword.
                    if old_name[i] in kwargs:
                        value = kwargs.pop(old_name[i])
                        # Display the deprecation warning only when it's not
                        # pending.
                        if not pending[i]:
                            message = ('"{0}" was deprecated in version {1} '
                                       'and will be removed in a future version. '
                                       .format(old_name[i], since[i]))
                            if new_name[i] is not None:
                                message += ('Use argument "{}" instead.'
                                            .format(new_name[i]))
                            elif alternative:
                                message += ('\n        Use {} instead.'
                                            .format(alternative))
                            warnings.warn(message, warning_type, stacklevel=2)

                        # Check if the newkeyword was given as well.
                        newarg_in_args = (position[i] is not None and
                                          len(args) > position[i])
                        newarg_in_kwargs = new_name[i] in kwargs

                        if newarg_in_args or newarg_in_kwargs:
                            if not pending[i]:
                                # If both are given print a Warning if relax is
                                # True or raise an Exception is relax is False.
                                if relax[i]:
                                    warnings.warn(
                                        '"{0}" and "{1}" keywords were set. '
                                        'Using the value of "{1}".'
                                        ''.format(old_name[i], new_name[i]),
                                        AstropyUserWarning)
                                else:
                                    raise TypeError(
                                        'cannot specify both "{}" and "{}"'
                                        '.'.format(old_name[i], new_name[i]))
                        else:
                            # Pass the value of the old argument with the
                            # name of the new argument to the function
                            if new_name[i] is not None:
                                kwargs[new_name[i]] = value

                return function(*args, **kwargs)

            return wrapper