docs: def enchant(self, list, cache_context=None):
          """ Add any calculated values to each row of a resultset generating a
          new namedtuple
  
          :param list: a list of row results
          :param cache_context: optional extra context for caching
          :return: a list with ingredient.cauldron_extras added for all
                   ingredients
          """
          enchantedlist = []
          if list:
              sample_item = list[0]
  
              # Extra fields to add to each row
              # With extra callables
              extra_fields, extra_callables = [], []
  
              for ingredient in self.values():
                  if not isinstance(ingredient, (Dimension, Metric)):
                      continue
                  if cache_context:
                      ingredient.cache_context += str(cache_context)
                  for extra_field, extra_callable in ingredient.cauldron_extras:
                      extra_fields.append(extra_field)
                      extra_callables.append(extra_callable)
  
              # Mixin the extra fields
              keyed_tuple = lightweight_named_tuple(
                  'result', sample_item._fields + tuple(extra_fields)
              )
  
              # Iterate over the results and build a new namedtuple for each row
              for row in list:
                  values = row + tuple(fn(row) for fn in extra_callables)
                  enchantedlist.append(keyed_tuple(values))
  
          return enchantedlist