docs: def add_data(self, data, table, delimiter='|', bands='', clean_up=True, rename_columns={}, column_fill={}, verbose=False):
          """
          Adds data to the specified database table. Column names must match table fields to insert,
          however order and completeness don't matter.
          
          Parameters
          ----------
          data: str, array-like, astropy.table.Table
              The path to an ascii file, array-like object, or table. The first row or element must
              be the list of column names
          table: str
              The name of the table into which the data should be inserted
          delimiter: str
              The string to use as the delimiter when parsing the ascii file
          bands: sequence
              Sequence of band to look for in the data header when digesting columns of
              multiple photometric measurements (e.g. ['MKO_J','MKO_H','MKO_K']) into individual
              rows of data for database insertion
          clean_up: bool
              Run self.clean_up()
          rename_columns: dict
              A dictionary of the {input_col_name:desired_col_name} for table columns,
              e.g. {'e_Jmag':'J_unc', 'RAJ2000':'ra'}
          column_fill: dict
              A dictionary of the column name and value to fill, e.g. {'instrument_id':2, 'band':'2MASS.J'}
          verbose: bool
            Print diagnostic messages
          """
          # Store raw entry
          entry, del_records = data, []
  
          # Digest the ascii file into table
          if isinstance(data, str) and os.path.isfile(data):
              data = ii.read(data, delimiter=delimiter)
  
          # Or read the sequence of data elements into a table
          elif isinstance(data, (list, tuple, np.ndarray)):
              data = ii.read(['|'.join(map(str, row)) for row in data], data_start=1, delimiter='|')
              
          # Or convert pandas dataframe to astropy table
          elif isinstance(data, pd.core.frame.DataFrame):
              data = at.Table.from_pandas(data)
              
          # Or if it's already an astropy table
          elif isinstance(data, at.Table):
              pass
              
          else:
              data = None
              
          if data:
              
              # Rename columns
              if isinstance(rename_columns,str):
                  rename_columns = astrocat.default_rename_columns(rename_columns)
              for input_name,new_name in rename_columns.items():
                  data.rename_column(input_name,new_name)
                  
              # Add column fills
              if isinstance(column_fill,str):
                  column_fill = astrocat.default_column_fill(column_fill)
              for colname,fill_value in column_fill.items():
                  data[colname] = [fill_value]*len(data)
                  
              # Get list of all columns and make an empty table for new records
              metadata = self.query("PRAGMA table_info({})".format(table), fmt='table')
              columns, types, required = [np.array(metadata[n]) for n in ['name', 'type', 'notnull']]
              new_records = at.Table(names=columns, dtype=[type_dict[t] for t in types])
              
              # Fix column dtypes and blanks
              for col in data.colnames:
                  
                  # Convert data dtypes to those of the existing table
                  try:
                      temp = data[col].astype(new_records[col].dtype)
                      data.replace_column(col, temp)
                  except KeyError:
                      continue
                      
              # If a row contains photometry for multiple bands, use the *multiband argument and execute this
              if bands and table.lower() == 'photometry':
                  
                  # Pull out columns that are band names
                  for b in list(set(bands) & set(data.colnames)):
                      
                      try:
                          # Get the repeated data plus the band data and rename the columns
                          band = data[list(set(columns) & set(data.colnames)) + [b, b + '_unc']]
                          for suf in ['', '_unc']:
                              band.rename_column(b+suf, 'magnitude'+suf)
                              temp = band['magnitude'+suf].astype(new_records['magnitude'+suf].dtype)
                              band.replace_column('magnitude'+suf, temp)
                          band.add_column(at.Column([b] * len(band), name='band', dtype='O'))
                          
                          # Add the band data to the list of new_records
                          new_records = at.vstack([new_records, band])
                          
                      except IOError:
                          pass
                          
              else:
                  # Inject data into full database table format
                  new_records = at.vstack([new_records, data])[new_records.colnames]
                  
              # Reject rows that fail column requirements, e.g. NOT NULL fields like 'source_id'
              for r in columns[np.where(np.logical_and(required, columns != 'id'))]:
                  # Null values...
                  new_records = new_records[np.where(new_records[r])]
                  
                  # Masked values...
                  try:
                      new_records = new_records[~new_records[r].mask]
                  except:
                      pass
                  
                  # NaN values...
                  if new_records.dtype[r] in (int, float):
                      new_records = new_records[~np.isnan(new_records[r])]
                      
              # For spectra, try to populate the table by reading the FITS header
              if table.lower() == 'spectra':
                  for n, new_rec in enumerate(new_records):
                      
                      # Convert relative path to absolute path
                      relpath = new_rec['spectrum']
                      if relpath.startswith('$'):
                          abspath = os.popen('echo {}'.format(relpath.split('/')[0])).read()[:-1]
                          if abspath:
                              new_rec['spectrum'] = relpath.replace(relpath.split('/')[0], abspath)
                              
                      # Test if the file exists and try to pull metadata from the FITS header
                      if os.path.isfile(new_rec['spectrum']):
                          new_records[n]['spectrum'] = relpath
                          new_records[n] = _autofill_spec_record(new_rec)
                      else:
                          print('Error adding the spectrum at {}'.format(new_rec['spectrum']))
                          del_records.append(n)
                          
                  # Remove bad records from the table
                  new_records.remove_rows(del_records)
                  
              # For images, try to populate the table by reading the FITS header
              if table.lower() == 'images':
                  for n, new_rec in enumerate(new_records):
                      
                      # Convert relative path to absolute path
                      relpath = new_rec['image']
                      if relpath.startswith('$'):
                          abspath = os.popen('echo {}'.format(relpath.split('/')[0])).read()[:-1]
                          if abspath:
                              new_rec['image'] = relpath.replace(relpath.split('/')[0], abspath)
  
                      # Test if the file exists and try to pull metadata from the FITS header
                      if os.path.isfile(new_rec['image']):
                          new_records[n]['image'] = relpath
                          new_records[n] = _autofill_spec_record(new_rec)
                      else:
                          print('Error adding the image at {}'.format(new_rec['image']))
                          del_records.append(n)
                          
                  # Remove bad records from the table
                  new_records.remove_rows(del_records)
                  
              # Get some new row ids for the good records
              rowids = self._lowest_rowids(table, len(new_records))
              
              # Add the new records
              keepers, rejects = [], []
              for N, new_rec in enumerate(new_records):
                  new_rec = list(new_rec)
                  new_rec[0] = rowids[N]
                  for n, col in enumerate(new_rec):
                      if type(col) == np.int64 and sys.version_info[0] >= 3:
                          # Fix for Py3 and sqlite3 issue with numpy types
                          new_rec[n] = col.item()
                      if type(col) == np.ma.core.MaskedConstant:
                          new_rec[n] = None
                          
                  try:
                      self.modify("INSERT INTO {} VALUES({})".format(table, ','.join('?'*len(columns))), new_rec, verbose=verbose)
                      keepers.append(N)
                  except IOError:
                      rejects.append(N)
                      
                  new_records[N]['id'] = rowids[N]
                  
              # Make tables of keepers and rejects
              rejected = new_records[rejects]
              new_records = new_records[keepers]
              
              # Print a table of the new records or bad news
              if new_records:
                  print("\033[1;32m{} new records added to the {} table.\033[1;m".format(len(new_records), table.upper()))
                  new_records.pprint()
                  
              if rejected:
                  print("\033[1;31m{} records rejected from the {} table.\033[1;m".format(len(rejected), table.upper()))
                  rejected.pprint()
                  
              # Run table clean up
              if clean_up:
                  self.clean_up(table, verbose)
  
          else:
              print('Please check your input: {}'.format(entry))