def __update_return_type(self): """ Updates the return types within self.kwargs['compss_retvalue'] """ from pycompss.api.parameter import Parameter from pycompss.api.parameter import DIRECTION # This condition is interesting, because a user can write returns=list # However, lists have the attribute __len__ but raise an exception. # Since the user does not indicate the length, it will be managed # as a single return. # When the user specifies the length, it is possible to manage the # elements independently. if not hasattr(self.kwargs['returns'], '__len__') or type(self.kwargs['returns']) is type: # Simple return retType = get_COMPSs_type(self.kwargs['returns']) self.kwargs['compss_retvalue'] = Parameter( p_type=retType, p_direction=DIRECTION.OUT) else: # Multi return self.has_multireturn = True returns = [] for r in self.kwargs['returns']: retType = get_COMPSs_type(r) returns.append( Parameter(p_type=retType, p_direction=DIRECTION.OUT)) self.kwargs['compss_retvalue'] = tuple(returns)
def fromDictToParameter(d): ''' Convert a Dict defined by a user for a parameter into a real Parameter object. :param d: Dictionary (mandatory to have 'Type' key). :return: Parameter object. ''' from pycompss.api.parameter import Parameter from pycompss.api.parameter import Type from pycompss.api.parameter import Direction from pycompss.api.parameter import Stream from pycompss.api.parameter import Prefix if Type not in d: # If no Type specified => IN d[Type] = Parameter() p = d[Type] if Direction in d: p.setDirection(d[Direction]) if Stream in d: p.setStream(d[Stream]) if Prefix in d: p.setPrefix(d[Prefix]) return p
def _build_return_objects(f_returns): """ Build the return object from the f_returns dictionary and include their filename in f_returns. WARNING: Updates f_returns dictionary :param f_returns: Dictionary which contains the return objects and Parameters. :return: future object/s """ fo = None if len(f_returns) == 0: # No return return fo elif len(f_returns) == 1: # Simple return if __debug__: logger.debug("Simple object return found.") # Build the appropriate future object ret_value = f_returns['compss_retvalue'].object if ret_value in _python_to_compss: # primitives, string, dic, list, tuple fo = Future() elif inspect.isclass(ret_value): # For objects: # type of future has to be specified to allow o = func; o.func try: fo = ret_value() except TypeError: if __debug__: logger.warning( "Type {0} does not have an empty constructor, building generic future object" .format(ret_value)) fo = Future() else: fo = Future() # modules, functions, methods obj_id = get_object_id(fo, True) if __debug__: logger.debug("Setting object %s of %s as a future" % (obj_id, type(fo))) ret_filename = temp_dir + _temp_obj_prefix + str(obj_id) objid_to_filename[obj_id] = ret_filename pending_to_synchronize[obj_id] = fo f_returns['compss_retvalue'] = Parameter(p_type=TYPE.FILE, p_direction=DIRECTION.OUT, p_prefix="#") f_returns['compss_retvalue'].file_name = ret_filename else: # Multireturn fo = [] if __debug__: logger.debug("Multiple objects return found.") for k, v in f_returns.items(): # Build the appropriate future object if v.object in _python_to_compss: # primitives, string, dic, list, tuple foe = Future() elif inspect.isclass(v.object): # For objects: # type of future has to be specified to allow o = func; o.func try: foe = v.object() except TypeError: if __debug__: logger.warning( "Type {0} does not have an empty constructor, building generic future object" .format(v['Value'])) foe = Future() else: foe = Future() # modules, functions, methods fo.append(foe) obj_id = get_object_id(foe, True) if __debug__: logger.debug("Setting object %s of %s as a future" % (obj_id, type(foe))) ret_filename = temp_dir + _temp_obj_prefix + str(obj_id) objid_to_filename[obj_id] = ret_filename pending_to_synchronize[obj_id] = foe # Once determined the filename where the returns are going to # be stored, create a new Parameter object for each return object f_returns[k] = Parameter(p_type=TYPE.FILE, p_direction=DIRECTION.OUT, p_prefix="#") f_returns[k].file_name = ret_filename return fo
def reveal_objects(values, spec_args, deco_kwargs, compss_types, process_name, has_return, is_multi_return, num_return): """ Function that goes through all parameters in order to find and open the files. :param values: <List> - The value of each parameter. :param spec_args: <List> - Specific arguments. :param deco_kwargs: <List> - The decoratos. :param compss_types: <List> - The types of the values. :param process_name: <String> - Process name (id). :param has_returns: <Boolean> - If the function returns a value. :param is_multi_return: <Boolean> - If the return is a multireturn. :param num_return: <Int> - Number of returns :return: a list with the real values """ from pycompss.api.parameter import Parameter from pycompss.api.parameter import TYPE from pycompss.api.parameter import DIRECTION num_pars = len(spec_args) real_values = [] to_serialize = [] if has_return: num_pars -= num_return # return value must not be passed to the function call # print('Spec args: %s'%(str(spec_args))) # print('COMPSs Types: %s'%(str(compss_types))) # print('Values: %s'%(str(values))) for i in range(num_pars): spec_arg = spec_args[i] compss_type = compss_types[i] value = values[i] if i == 0: if spec_arg == 'self': # callee object if deco_kwargs['isModifier']: d = DIRECTION.INOUT else: d = DIRECTION.IN deco_kwargs[spec_arg] = Parameter(p_type=TYPE.OBJECT, p_direction=d) elif inspect.isclass(value): # class (it's a class method) real_values.append(value) continue p = deco_kwargs.get(spec_arg) if p is None: # decoration not present, using default p = deco_kwargs['varargsType'] if spec_arg.startswith( '*args') else Parameter() if compss_type == TYPE.FILE and p.type != TYPE.FILE: # Getting ids and file names from passed files and objects pattern # is: "originalDataID:destinationDataID;flagToPreserveOriginalData:flagToWrite:PathToFile" complete_fname = value.split(':') if len(complete_fname) > 1: # In NIO we get more information forig = complete_fname[0] fdest = complete_fname[1] preserve = complete_fname[2] write_final = complete_fname[3] fname = complete_fname[4] preserve, write_final = list( map(lambda x: x == "true", [preserve, write_final])) suffix_name = forig else: # In GAT we only get the name fname = complete_fname[0] value = fname # For COMPSs it is a file, but it is actually a Python object logger.debug("Processing a hidden object in parameter %d", i) obj = deserialize_from_file(value) real_values.append(obj) if p.direction != DIRECTION.IN: to_serialize.append((obj, value)) else: # print('compss_type' + str(compss_type) + ' type' + str(p.type)) if compss_type == TYPE.FILE: complete_fname = value.split(':') if len(complete_fname) > 1: # In NIO we get more information forig = complete_fname[0] fdest = complete_fname[1] preserve = complete_fname[2] write_final = complete_fname[3] fname = complete_fname[4] else: # In GAT we only get the name fname = complete_fname[0] value = fname real_values.append(value) return real_values, to_serialize
def __init__(self, *args, **kwargs): """ If there are decorator arguments, the function to be decorated is not passed to the constructor! """ logger.debug("Init @task decorator...") # Defaults self.args = args # Not used self.kwargs = kwargs # The only ones actually used: (decorators) self.is_instance = False # Pre-process decorator arguments from pycompss.api.parameter import Parameter from pycompss.api.parameter import IN from pycompss.api.parameter import TYPE from pycompss.api.parameter import DIRECTION # Reserved PyCOMPSs keywords and default values reserved_keywords = { 'isModifier': True, 'returns': False, 'priority': False, 'isReplicated': False, 'isDistributed': False, 'varargsType': IN } for (reserved_keyword, default_value) in reserved_keywords.items(): if reserved_keyword not in self.kwargs: self.kwargs[reserved_keyword] = default_value # Remove old args for old_vararg in [ x for x in self.kwargs.keys() if x.startswith('*args') ]: self.kwargs.pop(old_vararg) import copy if i_am_at_master(): for arg_name in self.kwargs.keys(): if arg_name not in [ 'isModifier', 'returns', 'priority', 'isReplicated', 'isDistributed' ]: # Prevent p.value from being overwritten later by ensuring # each Parameter is a separate object p = self.kwargs[arg_name] pcopy = copy.copy(p) # shallow copy self.kwargs[arg_name] = pcopy if self.kwargs['isModifier']: direction = DIRECTION.INOUT else: direction = DIRECTION.IN # Add callee object parameter self.kwargs['self'] = Parameter(p_type=TYPE.OBJECT, p_direction=direction) # Check the return type: if self.kwargs['returns']: # This condition is interesting, because a user can write # returns=list # However, lists have the attribute __len__ but raise an exception. # Since the user does not indicate the length, it will be managed # as a single return. # When the user specifies the length, it is possible to manage the # elements independently. if not hasattr(self.kwargs['returns'], '__len__') or type(self.kwargs['returns']) is type: # Simple return retType = getCOMPSsType(self.kwargs['returns']) self.kwargs['compss_retvalue'] = Parameter( p_type=retType, p_direction=DIRECTION.OUT) else: # Multi return i = 0 returns = [] for r in self.kwargs['returns']: # This adds only one? - yep retType = getCOMPSsType(r) returns.append( Parameter(p_type=retType, p_direction=DIRECTION.OUT)) self.kwargs['compss_retvalue'] = tuple(returns) logger.debug("Init @task decorator finished.")
def infer_types_and_serialize_objects(spec_args, first_par, num_pars, fileNames, self_kwargs, args): ''' Infer COMPSs types for the task parameters and serialize them. :param spec_args: <List of strings> - Names of the task arguments :param first_par: <Integer> - First parameter :param num_pars: <Integer> - Number of parameters :param fileNames: <Dictionary> - Return objects filenames :param self_kwargs: <Dictionary> - Decorator arguments :param args: <List> - Unnamed arguments :return: Tuple of self_kwargs updated and a dictionary containing if the objects are future elements. ''' is_future = {} max_obj_arg_size = 320000 for i in range(first_par, num_pars): spec_arg = spec_args[i] p = self_kwargs.get(spec_arg) if p is None: if __debug__: logger.debug('Adding default decoration for param %s' % spec_arg) p = Parameter() self_kwargs[spec_arg] = p elif type(p) is dict: # The user has provided some information about a parameter within # the @task parameter if __debug__: logger.debug('Checking decoration for param %s' % spec_arg) p = fromDictToParameter(p) if spec_args[0] != 'self': # It is a function if i < len(args): p.value = args[i] else: p.value = fileNames[spec_arg] else: # It is a class function if spec_arg == 'self': p.value = args[0] # Check if self is a persistent object and set its type if it really is. if is_PSCO(p.value): p.type = TYPE.EXTERNAL_PSCO elif spec_arg.startswith('compss_retvalue'): p.value = fileNames[spec_arg] else: p.value = args[i] # Update the parameter type if changed between task calls # Respect p.type if none to be inferred later. # Respect p.type == TYPE.FILE since may be updated wrongly to str. new_type = python_to_compss.get(type(p.value)) if p.type is not None and p.type != TYPE.FILE and p.type != new_type: p.type = new_type val_type = type(p.value) is_future[i] = (val_type == Future) if __debug__: logger.debug('Parameter ' + spec_arg) logger.debug('\t- Value type: ' + str(val_type)) logger.debug('\t- User-defined type: ' + str(p.type)) # Infer type if necessary if p.type is None: if __debug__: logger.debug('Inferring type due to None pType.') p.type = python_to_compss.get(val_type) if p.type is None: if is_PSCO(p.value): p.type = TYPE.EXTERNAL_PSCO else: p.type = TYPE.OBJECT if __debug__: logger.debug('\t- Inferred type: %d' % p.type) # Convert small objects to string if object_conversion enabled # Check if the object is small in order not to serialize it. if object_conversion: p, bytes = convert_object_to_string(p, is_future.get(i), max_obj_arg_size, policy='objectSize') max_obj_arg_size -= bytes # Serialize objects into files p = serialize_object_into_file(p, is_future, i, val_type) if __debug__: logger.debug('Final type for parameter %s: %d' % (spec_arg, p.type)) return self_kwargs, is_future
def build_return_objects(self_kwargs, spec_args): ''' Build the return object and updates the self_kwargs and spec_args structures (as tuple in the multireturn case). :param self_kwargs: :param spec_args: :return: ''' # Check if the returns statement contains an integer value. # In such case, build a list of objects of value length and set it in ret_type. if isinstance(self_kwargs['returns'], int): num_rets = self_kwargs['returns'] # Assume all as objects (generic type). # It will not work properly when using user defined classes, since # the future object built will not be of the same type as expected # and may cause "AttributeError" since the 'object' does not have # the attributes of the class if num_rets > 1: ret_type = [object for _ in range(num_rets)] else: ret_type = object else: ret_type = self_kwargs['returns'] fileNames = {} # return files locations if ret_type: fu = [] # Create future for return value if isinstance(ret_type, list) or isinstance(ret_type, tuple): # MULTIRETURN if __debug__: logger.debug('Multiple objects return found.') # This condition fixes the multiple calls to a function with multireturn bug. if 'compss_retvalue' in spec_args: spec_args.remove('compss_retvalue') # remove single return... it contains more than one if 'compss_retvalue' in self_kwargs: self_kwargs.pop('compss_retvalue') else: assert 'compss_retvalue0' in self_kwargs, 'Inconsistent state: multireturn detected, but there is no compss_retvalue0' pos = 0 for i in ret_type: # Build the appropriate future object if i in python_to_compss: # primitives, string, dic, list, tuple fue = Future() elif inspect.isclass(i): # For objects: # type of future has to be specified to allow o = func; o.func try: fue = i() except TypeError: if __debug__: logger.warning('Type {0} does not have an empty constructor, building generic future object'.format(ret_type)) fue = Future() else: fue = Future() # modules, functions, methods fu.append(fue) obj_id = get_object_id(fue, True) if __debug__: logger.debug('Setting object %s of %s as a future' % (obj_id, type(fue))) ret_filename = temp_dir + temp_obj_prefix + str(obj_id) objid_to_filename[obj_id] = ret_filename pending_to_synchronize[obj_id] = fue fileNames['compss_retvalue' + str(pos)] = ret_filename # Once determined the filename where the returns are going to # be stored, create a new Parameter object for each return # object self_kwargs['compss_retvalue' + str(pos)] = Parameter(p_type=TYPE.FILE, p_direction=DIRECTION.OUT, p_prefix="#") spec_args.append('compss_retvalue' + str(pos)) pos += 1 self_kwargs['num_returns'] = pos # Update the amount of objects to be returned else: # SIMPLE RETURN # Build the appropriate future object if ret_type in python_to_compss: # primitives, string, dic, list, tuple fu = Future() elif inspect.isclass(ret_type): # For objects: # type of future has to be specified to allow o = func; o.func try: fu = ret_type() except TypeError: if __debug__: logger.warning('Type {0} does not have an empty constructor, building generic future object'.format(ret_type)) fu = Future() else: fu = Future() # modules, functions, methods obj_id = get_object_id(fu, True) if __debug__: logger.debug('Setting object %s of %s as a future' % (obj_id, type(fu))) ret_filename = temp_dir + temp_obj_prefix + str(obj_id) objid_to_filename[obj_id] = ret_filename pending_to_synchronize[obj_id] = fu fileNames['compss_retvalue'] = ret_filename self_kwargs['compss_retvalue'] = Parameter(p_type=TYPE.FILE, p_direction=DIRECTION.OUT, p_prefix="#") else: fu = None return fu, fileNames, self_kwargs, spec_args
def __update_return_if_no_returns(self, f): """ Checks the code looking for return statements if no returns is specified in @task decorator. :param f: Function to check """ from pycompss.api.parameter import Parameter from pycompss.api.parameter import DIRECTION from pycompss.api.parameter import TYPE source_code = get_wrapped_source(f).strip() if self.is_instance or source_code.startswith( '@classmethod'): # TODO: WHAT IF IS CLASSMETHOD FROM BOOLEAN? # It is a task defined within a class (can not parse the code with ast since the class does not # exist yet. Alternatively, the only way I see is to parse it manually line by line. retMask = [] code = source_code.split('\n') for line in code: if 'return ' in line: retMask.append(True) else: retMask.append(False) else: code = [node for node in ast.walk(ast.parse(source_code))] retMask = [isinstance(node, ast.Return) for node in code] if any(retMask): print("INFO! Return found in function " + f.__name__ + " without 'returns' statement at task definition.") self.has_return = True lines = [i for i, li in enumerate(retMask) if li] max_num_returns = 0 if self.is_instance or source_code.startswith('@classmethod'): # Parse code as string (it is a task defined within a class) def _has_multireturn(statement): v = ast.parse(statement.strip()) try: if len(v.body[0].value.elts) > 1: return True else: return False except: # KeyError: 'elts' means that it is a multiple return. # "Ask forgiveness not permission" return False def _get_return_elements(statement): v = ast.parse(statement.strip()) return len(v.body[0].value.elts) for i in lines: if _has_multireturn(code[i]): self.has_multireturn = True num_returns = _get_return_elements(code[i]) if num_returns > max_num_returns: max_num_returns = num_returns else: # Parse code AST (it is not a task defined within a class) for i in lines: try: if 'elts' in code[i].value.__dict__: self.has_multireturn = True num_returns = len(code[i].value.__dict__['elts']) if num_returns > max_num_returns: max_num_returns = num_returns except: # KeyError: 'elts' means that it is a multiple return. # "Ask forgiveness not permission" pass if self.has_multireturn: if __debug__: logger.debug("Multireturn found: %s" % str(max_num_returns)) self.kwargs['returns'] = [] returns = [] for _ in range(max_num_returns): self.kwargs['returns'].append(object()) returns.append( Parameter(p_type=TYPE.FILE, p_direction=DIRECTION.OUT)) self.kwargs['compss_retvalue'] = tuple(returns) else: if __debug__: logger.debug("Return found") self.kwargs['returns'] = object() self.kwargs['compss_retvalue'] = Parameter( p_type=TYPE.FILE, p_direction=DIRECTION.OUT) self.f_argspec.args.append('compss_retvalue')
def __call__(self, f): """ If there are decorator arguments, __call__() is only called once, as part of the decoration process! You can only give it a single argument, which is the function object. """ # Check if under the PyCOMPSs scope if not self.scope: return self.__not_under_pycompss_scope(f) # Imports from pycompss.api.parameter import Parameter from pycompss.api.parameter import TYPE from pycompss.api.parameter import DIRECTION from pycompss.util.interactive_helpers import updateTasksCodeFile from pycompss.util.location import i_am_at_master if __debug__: logger.debug("Call in @task decorator...") # Assume it is an instance method if the first parameter of the # function is called 'self' # "I would rely on the convention that functions that will become # methods have a first argument named self, and other functions don't. # Fragile, but then, there's no really solid way." self.f_argspec = inspect.getargspec(f) # Set default booleans self.is_instance = False self.is_classmethod = False self.has_varargs = False self.has_keywords = False self.has_defaults = False self.has_return = False self.has_multireturn = False # Step 1.- Check if it is an instance method. # Question: Will the first condition evaluate to false? spec_args will # always be a named tuple, so it will always return true if evaluated # as a bool # Answer: The first condition evaluates if args exists (a list) and is # not empty in the spec_args. The second checks if the first argument # in that list is 'self'. In case that the args list exists and its # first element is self, then the function is considered as an instance # function (task defined within a class). if self.f_argspec.args and self.f_argspec.args[0] == 'self': self.is_instance = True if self.kwargs['isModifier']: direction = DIRECTION.INOUT else: direction = DIRECTION.IN # Add callee object parameter self.kwargs['self'] = Parameter(p_type=TYPE.OBJECT, p_direction=direction) # Step 2.- Check if it is a class method. # The check of 'cls' may be weak but it is PEP8 style agreements. if self.f_argspec.args and self.f_argspec.args[0] == 'cls': self.is_classmethod = True # Add class object parameter self.kwargs['self'] = Parameter(p_type=TYPE.OBJECT, p_direction=DIRECTION.IN) # Step 3.- Check if it has varargs (contains *args?) # Check if contains *args if self.f_argspec.varargs is not None: self.has_varargs = True # Step 4.- Check if it has keyword arguments (contains **kwargs?) # Check if contains **kwargs if self.f_argspec.keywords is not None: self.has_keywords = True # Step 5.- Check if it has default values # Check if has default values if self.f_argspec.defaults is not None: self.has_defaults = True # Step 6.- Check if the keyword returns has been specified by the user. # Check if the keyword returns has been specified by the user. if self.kwargs['returns']: self.has_return = True self.f_argspec.args.append( 'compss_retvalue' ) # TODO: WHY THIS VARIABLE? THE INFORMATION IS IN SELF.KWARGS['COMPSS_RETVALUE'] self.__update_return_type() else: # If no returns statement found, double check to see if the user has specified a return statement. self.__update_return_if_no_returns(f) # Get module (for invocation purposes in the worker) mod = inspect.getmodule(f) self.module = mod.__name__ if self.module == '__main__' or self.module == 'pycompss.runtime.launch': # the module where the function is defined was run as __main__, # we need to find out the real module name # Get the real module name from our launch.py app_path global variable try: path = getattr(mod, "app_path") except AttributeError: # This exception is raised when the runtime is not running and the @task decorator is used. # The runtime has not been started yet. return self.__not_under_pycompss_scope(f) # Get the file name file_name = os.path.splitext(os.path.basename(path))[0] # Do any necessary preprocessing action before executing any code if file_name.startswith('InteractiveMode'): # If the file_name starts with 'InteractiveMode' means that # the user is using PyCOMPSs from jupyter-notebook. # Convention between this file and interactive.py # In this case it is necessary to do a pre-processing step # that consists of putting all user code that may be executed # in the worker on a file. # This file has to be visible for all workers. updateTasksCodeFile(f, path) else: # work as always pass # Get the module self.module = get_module_name(path, file_name) # The registration needs to be done only in the master node if i_am_at_master(): self.__register_task(f) # Modified variables until now that will be used later: # - self.f_argspec : Function argspect (Named tuple) # e.g. ArgSpec(args=['a', 'b', 'compss_retvalue'], varargs=None, keywords=None, defaults=None) # - self.is_instance : Boolean - if the function is an instance (contains self in the f_argspec) # - self.is_classmethod : Boolean - if the function is a classmethod (contains cls in the f_argspec) # - self.has_varargs : Boolean - if the function has *args # - self.has_keywords : Boolean - if the function has **kwargs # - self.has_defaults : Boolean - if the function has default values # - self.has_return : Boolean - if the function has return # - self.has_multireturn : Boolean - if the function has multireturn # - self.module_name : String - Module name (e.g. test.kmeans) # - is_replicated : Boolean - if the task is replicated # - is_distributed : Boolean - if the task is distributed # Other variables that will be used: # - f : Decorated function # - self.args : Decorator args tuple (usually empty) # - self.kwargs : Decorator keywords dictionary. # e.g. {'priority': True, 'isModifier': True, 'returns': <type 'dict'>, # 'self': <pycompss.api.parameter.Parameter instance at 0xXXXXXXXXX>, # 'compss_retvalue': <pycompss.api.parameter.Parameter instance at 0xXXXXXXXX>} if __debug__: logger.debug("Call in @task decorator finished.") @wraps(f) def wrapped_f(*args, **kwargs): # args - <Tuple> - Contains the objects that the function has been called with (positional). # kwargs - <Dictionary> - Contains the named objects that the function has been called with. is_replicated = self.kwargs['isReplicated'] is_distributed = self.kwargs['isDistributed'] # By default, each task is set to use one core. computingNodes = 1 if 'computingNodes' in kwargs: # There is a @mpi decorator over task that overrides the # default value of computing nodes computingNodes = kwargs['computingNodes'] del kwargs['computingNodes'] # Check if this call is nested using the launch_pycompss_module # function from launch.py. is_nested = False istack = inspect.stack() for i_s in istack: if i_s[3] == 'launch_pycompss_module': is_nested = True if i_s[3] == 'launch_pycompss_application': is_nested = True if not i_am_at_master() and (not is_nested): # Task decorator worker body code. newTypes, newValues = self.worker_code(f, args, kwargs) return newTypes, newValues else: # Task decorator master body code. # Returns the future object that will be used instead of the # actual function return. fo = self.master_code(f, is_replicated, is_distributed, computingNodes, args, kwargs) return fo return wrapped_f