docs: def run_task(task, workspace):
      """
      Runs the task and updates the workspace with results.
  
      Parameters
      ----------
      task - dict
          Task Description
  
      Examples:
      {'task': task_func, 'inputs': ['a', 'b'], 'outputs': 'c'}
      {'task': task_func, 'inputs': '*', 'outputs': '*'}
      {'task': task_func, 'inputs': ['*','a'], 'outputs': 'b'}
  
      Returns a new workspace with results
      """
      data = copy.copy(workspace)
  
      task = validate_task(task)
  
      # Prepare input to task
      inputs = [input_parser(key, data) for key in task.inputs]
  
      if inspect.isgeneratorfunction(task.fn):
          # Multiple output task
          # Assuming number of outputs are equal to number of return values
          data.update(zip(task.outputs, task.fn(*inputs)))
      else:
          # Single output task
          results = task.fn(*inputs)
          if task.outputs[0] != '*':
              results = {task.outputs[0]: results}
          elif not isinstance(results, dict):
              raise TypeError('Result should be a dict for output type *')
          data.update(results)
  
      return data