<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def propagate_cols_up(self, cols, target_df_name, source_df_name):
 """ Take values from source table, compile them into a colon-delimited list, and apply them to the target table. This method won't overwrite values in the target table, it will only supply values where they are missing. Parameters cols : list-like list of columns to propagate target_df_name : str name of table to propagate values into source_df_name: name of table to propagate values from Returns --------- target_df : MagicDataFrame updated MagicDataFrame with propagated values """

        print("-I- Trying to propagate {} columns from {} table into {} table".format(cols,
                                                                                      source_df_name,
                                                                                      target_df_name))
        # make sure target table is read in
        if target_df_name not in self.tables:
            self.add_magic_table(target_df_name)
        if target_df_name not in self.tables:
            print("-W- Couldn't read in {} table".format(target_df_name))
            return
        # make sure source table is read in
        if source_df_name not in self.tables:
            self.add_magic_table(source_df_name)
            print("-W- Couldn't read in {} table".format(source_df_name))
            return
        target_df = self.tables[target_df_name]
        source_df = self.tables[source_df_name]
        target_name = target_df_name[:-1]
        # make sure source_df has relevant columns
        for col in cols:
            if col not in source_df.df.columns:
                source_df.df[col] = None
        # if target_df has info, propagate that into all rows
        target_df.front_and_backfill(cols)
        # make sure target_name is in source_df for merging
        if target_name not in source_df.df.columns:
            print("-W- You can't merge data from {} table into {} table".format(source_df_name, target_df_name))
            print("    Your {} table is missing {} column".format(source_df_name, target_name))
            self.tables[target_df_name] = target_df
            return target_df
        source_df.front_and_backfill([target_name])
        # group source df by target_name
        grouped = source_df.df.groupby(source_df.df[target_name])
        if not len(grouped):
            print("-W- Couldn't propagate from {} to {}".format(source_df_name, target_df_name))
            return target_df
        # function to generate capitalized, sorted, colon-delimited list
        # of unique, non-null values from a column
        def func(group, col_name):
            lst = group[col_name][group[col_name].notnull()].unique()
            split_lst = [col.split(':') for col in lst if col]
            sorted_lst = sorted(np.unique([item.capitalize() for sublist in split_lst for item in sublist]))
            group_col = ":".join(sorted_lst)
            return group_col
        # apply func to each column
        for col in cols:
            res = grouped.apply(func, col)
            target_df.df['new_' + col] = res
            target_df.df[col] = np.where(target_df.df[col], target_df.df[col], target_df.df['new_' + col])
            target_df.df.drop(['new_' + col], axis='columns', inplace=True)
        # set table
        self.tables[target_df_name] = target_df
        return target_df