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

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