Repository: apache/airflow

Issue:
Clearing dag run via UI fails on main branch and 2.5.0rc2
### Apache Airflow version

main (development)

### What happened

Create a simple dag, allow it to completely run through. 

Next, when in grid view, on the left hand side click on the dag run at the top level. 

On the right hand side, then click on "Clear existing tasks". This will error with the following on the web server:

```
[2022-11-29 17:55:05,939] {app.py:1742} ERROR - Exception on /dagrun_clear [POST]
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 2525, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1822, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1820, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1796, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
  File "/opt/airflow/airflow/www/auth.py", line 47, in decorated
    return func(*args, **kwargs)
  File "/opt/airflow/airflow/www/decorators.py", line 83, in wrapper
    return f(*args, **kwargs)
  File "/opt/airflow/airflow/www/views.py", line 2184, in dagrun_clear
    confirmed=confirmed,
  File "/opt/airflow/airflow/www/views.py", line 2046, in _clear_dag_tis
    session=session,
  File "/opt/airflow/airflow/utils/session.py", line 72, in wrapper
    return func(*args, **kwargs)
  File "/opt/airflow/airflow/models/dag.py", line 2030, in clear
    exclude_task_ids=exclude_task_ids,
  File "/opt/airflow/airflow/models/dag.py", line 1619, in _get_task_instances
    tis = session.query(TaskInstance)
AttributeError: 'NoneType' object has no attribute 'query'
```
https://github.com/apache/airflow/blob/527fbce462429fc9836837378f801eed4e9d194f/airflow/models/dag.py#L1619

As per issue title, fails on main branch and `2.5.0rc2`. Works fine on `2.3.3` and `2.4.3`. 


### What you think should happen instead

Tasks within the dag should be cleared as expected. 

### How to reproduce

Run a dag, attempt to clear it within the UI at the top level of the dag. 

### Operating System

Ran via breeze

### Versions of Apache Airflow Providers

N/A

### Deployment

Other 3rd-party Helm chart

### Deployment details

Tested via breeze. 

### Anything else

Happens every time. 

### Are you willing to submit PR?

- [X] Yes I am willing to submit a PR!

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md)



Discussion:
Issue is that this fn is missing the `@provide_session` decorator and/or the `session` argument shouldn't have a default value.

```
    def _clear_dag_tis(
        self,
        dag: DAG,
        start_date: datetime | None,
        end_date: datetime | None,
        origin: str,
        task_ids=None,
        recursive=False,
        confirmed=False,
        only_failed=False,
        session: Session = NEW_SESSION,
    ):
```
Introduced in https://github.com/apache/airflow/pull/26658/file
Since this is internal we should probably remove the default. I’ll also (finally) write a pre-commit hook to make sure `NEW_SESSION` is only used with `provide_session`.

Resolving patch:
diff --git a/airflow/models/dag.py b/airflow/models/dag.py
--- a/airflow/models/dag.py
+++ b/airflow/models/dag.py
@@ -1950,7 +1950,7 @@ def set_dag_runs_state(
     @provide_session
     def clear(
         self,
-        task_ids: Collection[str] | Collection[tuple[str, int]] | None = None,
+        task_ids: Collection[str | tuple[str, int]] | None = None,
         start_date: datetime | None = None,
         end_date: datetime | None = None,
         only_failed: bool = False,
diff --git a/airflow/www/views.py b/airflow/www/views.py
--- a/airflow/www/views.py
+++ b/airflow/www/views.py
@@ -33,7 +33,7 @@
 from functools import wraps
 from json import JSONDecodeError
 from operator import itemgetter
-from typing import Any, Callable
+from typing import Any, Callable, Collection
 from urllib.parse import unquote, urljoin, urlsplit
 
 import configupdater
@@ -2013,11 +2013,12 @@ def _clear_dag_tis(
         dag: DAG,
         start_date: datetime | None,
         end_date: datetime | None,
-        origin: str,
-        task_ids=None,
-        recursive=False,
-        confirmed=False,
-        only_failed=False,
+        *,
+        origin: str | None,
+        task_ids: Collection[str | tuple[str, int]] | None = None,
+        recursive: bool = False,
+        confirmed: bool = False,
+        only_failed: bool = False,
         session: Session = NEW_SESSION,
     ):
         if confirmed:
@@ -2144,7 +2145,7 @@ def clear(self, *, session: Session = NEW_SESSION):
             dag,
             start_date,
             end_date,
-            origin,
+            origin=origin,
             task_ids=task_ids,
             recursive=recursive,
             confirmed=confirmed,
@@ -2164,7 +2165,8 @@ def clear(self, *, session: Session = NEW_SESSION):
         ]
     )
     @action_logging
-    def dagrun_clear(self):
+    @provide_session
+    def dagrun_clear(self, *, session: Session = NEW_SESSION):
         """Clears the DagRun"""
         dag_id = request.form.get("dag_id")
         dag_run_id = request.form.get("dag_run_id")
@@ -2182,6 +2184,7 @@ def dagrun_clear(self):
             origin=None,
             recursive=True,
             confirmed=confirmed,
+            session=session,
         )
 
     @expose("/blocked", methods=["POST"])


Test patch: