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
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
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