**Description**

Similarly to `@dag` and `@task` decorators, we can add a decorator to easily generate `TaskGroup` instances. 

**Use case / motivation**

This would be used for reusable pieces of DAGs. An example may be to hyperparameter tune an ML pipeline. You may want to generate several parallel subpipelines with different settings/parameters to be executed in parallel. At the end pull the result and decide the best parametrization.  

```py
@task
collect_dataset(...)
  pass

@task
def train_model(...)
   pass

@task_group
def movielens_model_pipeline(learning_rate: int, feature_list: list, dataset: XComArg):  
  dataset = filter_dataset(dataset, feature_list)
  train_model(dataset, learning_rate)

@dag
def movielens_hpt(dataset_path: str, feature_list:list=['year', 'director']):
  dataset = load_dataset(dataset_path)
  for i in range(0.1, 0.9, step=0.1):
    movielens_model_pipeline(i, feature_list, dataset)
  
  decide_best_model(...)
```




**Related Issues**

https://github.com/apache/airflow/issues?q=is%3Aopen+is%3Aissue+label%3AAIP-31


airflow/utils/task_group.py
"""Set a TaskGroup/task/list of task downstream of this TaskGroup."""
        self._set_relative(task_or_task_list, upstream=False)
    def set_upstream(self, task_or_task_list: Union[TaskMixin, Sequence[TaskMixin]]) -> None:
        """Set a TaskGroup/task/list of task upstream of this TaskGroup."""
        self._set_relative(task_or_task_list, upstream=True)
    def __enter__(self):
        TaskGroupContext.push_context_managed_task_group(self)
        return self
    def __exit__(self, _type, _value, _tb):
        TaskGroupContext.pop_context_managed_task_group()
    def has_task(self, task: "BaseOperator") -> bool:
        """Returns True if this TaskGroup or its children TaskGroups contains the given task."""
        if task.task_id in self.children:
            return True
        return any(child.has_task(task) for child in self.children.values() if isinstance(child, TaskGroup))
    @property
    def roots(self) -> List["BaseOperator"]:
        """Required by TaskMixin"""
        return list(self.get_roots())
    @property
    def leaves(self) -> List["BaseOperator"]: