pierrejeambrun commented on code in PR #44624:
URL: https://github.com/apache/airflow/pull/44624#discussion_r1878316280


##########
tests/api_fastapi/core_api/routes/ui/test_backfills.py:
##########
@@ -0,0 +1,153 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from unittest import mock
+
+import pytest
+
+from airflow.models import DagModel
+from airflow.models.backfill import Backfill
+from airflow.utils import timezone
+from airflow.utils.session import provide_session
+
+from tests_common.test_utils.db import (
+    clear_db_backfills,
+    clear_db_dags,
+    clear_db_runs,
+    clear_db_serialized_dags,
+)
+from tests_common.test_utils.format_datetime import from_datetime_to_zulu
+
+pytestmark = pytest.mark.db_test
+
+DAG_ID = "test_dag"
+TASK_ID = "op1"
+DAG2_ID = "test_dag2"
+DAG3_ID = "test_dag3"
+
+
+def _clean_db():
+    clear_db_backfills()
+    clear_db_runs()
+    clear_db_dags()
+    clear_db_serialized_dags()
+
+
[email protected](autouse=True)
+def clean_db():
+    _clean_db()
+    yield
+    _clean_db()
+
+
+class TestBackfillEndpoint:
+    @provide_session
+    def _create_dag_models(self, *, count=3, dag_id_prefix="TEST_DAG", 
is_paused=False, session=None):
+        dags = []
+        for num in range(1, count + 1):
+            dag_model = DagModel(
+                dag_id=f"{dag_id_prefix}_{num}",
+                fileloc=f"/tmp/dag_{num}.py",
+                is_active=True,
+                timetable_summary="0 0 * * *",
+                is_paused=is_paused,
+            )
+            session.add(dag_model)
+            dags.append(dag_model)
+        return dags
+
+
+class TestListBackfills(TestBackfillEndpoint):
+    @pytest.mark.parametrize(
+        "test_params, response_params, total_entries",
+        [
+            ({}, ["backfill1", "backfill2", "backfill3"], 3),
+            ({"only_active": True}, ["backfill2", "backfill3"], 2),
+            ({"only_active": False}, ["backfill1", "backfill2", "backfill3"], 
3),
+            ({"dag_id": "", "only_active": True}, ["backfill2", "backfill3"], 
2),
+            ({"dag_id": "", "only_active": False}, ["backfill1", "backfill2", 
"backfill3"], 3),
+            ({"dag_id": ""}, ["backfill1", "backfill2", "backfill3"], 3),
+            ({"dag_id": "TEST_DAG_1", "only_active": True}, [], 0),
+            ({"dag_id": "TEST_DAG_1", "only_active": False}, ["backfill1"], 1),
+            ({"dag_id": "TEST_DAG_1"}, ["backfill1"], 1),
+        ],
+    )
+    @provide_session

Review Comment:
   ```suggestion
   ```



##########
airflow/api_fastapi/common/parameters.py:
##########
@@ -112,6 +112,34 @@ def depends(self, only_active: bool = True) -> 
_OnlyActiveFilter:
         return self.set_value(only_active)
 
 
+class _NoneFilter(BaseParam[bool]):
+    """check if a column is none or not."""
+
+    def __init__(self, attribute: ColumnElement, value: bool | None = None, 
skip_none: bool = True) -> None:
+        super().__init__(value, skip_none)
+        self.attribute: ColumnElement = attribute
+        self.value: bool | None = value
+
+    def to_orm(self, select: Select) -> Select:
+        if not self.value and self.skip_none:
+            return select
+        return select.where(self.attribute.is_(None))
+
+    def depends(self, *args: Any, **kwargs: Any) -> Self:
+        raise NotImplementedError("Use search_param_factory instead , depends 
is not implemented.")
+
+
+def none_param_factory(
+    attribute: ColumnElement,
+    none_filter_name: str,
+    skip_none: bool = True,
+) -> Callable[[bool | None], _NoneFilter]:
+    def depends_none(value: bool | None = Query(alias=none_filter_name, 
default=None)) -> _NoneFilter:

Review Comment:
   ```suggestion
       def depends_none(value: bool = Query(alias=none_filter_name, 
default=False)) -> _NoneFilter:
   ```
   
   `only_active=False` return `all`, same as `only_active=None`. We don't need 
both `None` and `False` I think



##########
tests/api_fastapi/core_api/routes/ui/test_backfills.py:
##########
@@ -0,0 +1,153 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from unittest import mock
+
+import pytest
+
+from airflow.models import DagModel
+from airflow.models.backfill import Backfill
+from airflow.utils import timezone
+from airflow.utils.session import provide_session
+
+from tests_common.test_utils.db import (
+    clear_db_backfills,
+    clear_db_dags,
+    clear_db_runs,
+    clear_db_serialized_dags,
+)
+from tests_common.test_utils.format_datetime import from_datetime_to_zulu
+
+pytestmark = pytest.mark.db_test
+
+DAG_ID = "test_dag"
+TASK_ID = "op1"
+DAG2_ID = "test_dag2"
+DAG3_ID = "test_dag3"
+
+
+def _clean_db():
+    clear_db_backfills()
+    clear_db_runs()
+    clear_db_dags()
+    clear_db_serialized_dags()
+
+
[email protected](autouse=True)
+def clean_db():
+    _clean_db()
+    yield
+    _clean_db()
+
+
+class TestBackfillEndpoint:
+    @provide_session
+    def _create_dag_models(self, *, count=3, dag_id_prefix="TEST_DAG", 
is_paused=False, session=None):
+        dags = []
+        for num in range(1, count + 1):
+            dag_model = DagModel(
+                dag_id=f"{dag_id_prefix}_{num}",
+                fileloc=f"/tmp/dag_{num}.py",
+                is_active=True,
+                timetable_summary="0 0 * * *",
+                is_paused=is_paused,
+            )
+            session.add(dag_model)
+            dags.append(dag_model)
+        return dags
+
+
+class TestListBackfills(TestBackfillEndpoint):
+    @pytest.mark.parametrize(
+        "test_params, response_params, total_entries",
+        [
+            ({}, ["backfill1", "backfill2", "backfill3"], 3),
+            ({"only_active": True}, ["backfill2", "backfill3"], 2),
+            ({"only_active": False}, ["backfill1", "backfill2", "backfill3"], 
3),
+            ({"dag_id": "", "only_active": True}, ["backfill2", "backfill3"], 
2),
+            ({"dag_id": "", "only_active": False}, ["backfill1", "backfill2", 
"backfill3"], 3),
+            ({"dag_id": ""}, ["backfill1", "backfill2", "backfill3"], 3),
+            ({"dag_id": "TEST_DAG_1", "only_active": True}, [], 0),
+            ({"dag_id": "TEST_DAG_1", "only_active": False}, ["backfill1"], 1),
+            ({"dag_id": "TEST_DAG_1"}, ["backfill1"], 1),
+        ],
+    )
+    @provide_session

Review Comment:
   session is a fixture now



##########
airflow/api_fastapi/common/parameters.py:
##########
@@ -112,6 +112,34 @@ def depends(self, only_active: bool = True) -> 
_OnlyActiveFilter:
         return self.set_value(only_active)
 
 
+class _NoneFilter(BaseParam[bool]):
+    """check if a column is none or not."""
+
+    def __init__(self, attribute: ColumnElement, value: bool | None = None, 
skip_none: bool = True) -> None:
+        super().__init__(value, skip_none)
+        self.attribute: ColumnElement = attribute
+        self.value: bool | None = value
+
+    def to_orm(self, select: Select) -> Select:
+        if not self.value and self.skip_none:
+            return select
+        return select.where(self.attribute.is_(None))
+
+    def depends(self, *args: Any, **kwargs: Any) -> Self:
+        raise NotImplementedError("Use search_param_factory instead , depends 
is not implemented.")
+
+
+def none_param_factory(
+    attribute: ColumnElement,
+    none_filter_name: str,
+    skip_none: bool = True,
+) -> Callable[[bool | None], _NoneFilter]:

Review Comment:
   Something like (in your case):
   ```python
           if self.filter_option == FilterOptionEnum.IS_NONE:
               if self.value == False or self.value is None:
                   return select
               if self.value == True:
                   return select.where(self.attribute.is_(None))
   ```
   
   Also this particular enum option is only available with `bool | None` / 
`bool` type. 



##########
airflow/api_fastapi/common/parameters.py:
##########
@@ -112,6 +112,34 @@ def depends(self, only_active: bool = True) -> 
_OnlyActiveFilter:
         return self.set_value(only_active)
 
 
+class _NoneFilter(BaseParam[bool]):
+    """check if a column is none or not."""
+
+    def __init__(self, attribute: ColumnElement, value: bool | None = None, 
skip_none: bool = True) -> None:
+        super().__init__(value, skip_none)
+        self.attribute: ColumnElement = attribute
+        self.value: bool | None = value
+
+    def to_orm(self, select: Select) -> Select:
+        if not self.value and self.skip_none:
+            return select
+        return select.where(self.attribute.is_(None))
+
+    def depends(self, *args: Any, **kwargs: Any) -> Self:
+        raise NotImplementedError("Use search_param_factory instead , depends 
is not implemented.")
+
+
+def none_param_factory(
+    attribute: ColumnElement,
+    none_filter_name: str,
+    skip_none: bool = True,
+) -> Callable[[bool | None], _NoneFilter]:
+    def depends_none(value: bool | None = Query(alias=none_filter_name, 
default=None)) -> _NoneFilter:

Review Comment:
   Maybe a more general approach is to `active=True` `active=False` 
`active=None`. 
   
   - None is ignored and no filtering happen, returning all resources
   - True, only take `active` ones, i.e end date is None
   - False only take `inactive` ones, i.e end data is not None
   
   This will allow retrieving only `inactive` backfills (which is not possible 
at the moment)



##########
airflow/api_fastapi/common/parameters.py:
##########
@@ -112,6 +112,34 @@ def depends(self, only_active: bool = True) -> 
_OnlyActiveFilter:
         return self.set_value(only_active)
 
 
+class _NoneFilter(BaseParam[bool]):
+    """check if a column is none or not."""
+
+    def __init__(self, attribute: ColumnElement, value: bool | None = None, 
skip_none: bool = True) -> None:
+        super().__init__(value, skip_none)
+        self.attribute: ColumnElement = attribute
+        self.value: bool | None = value
+
+    def to_orm(self, select: Select) -> Select:
+        if not self.value and self.skip_none:
+            return select
+        return select.where(self.attribute.is_(None))
+
+    def depends(self, *args: Any, **kwargs: Any) -> Self:
+        raise NotImplementedError("Use search_param_factory instead , depends 
is not implemented.")
+
+
+def none_param_factory(
+    attribute: ColumnElement,
+    none_filter_name: str,
+    skip_none: bool = True,
+) -> Callable[[bool | None], _NoneFilter]:

Review Comment:
   I think all that could be implemented with a `is_none` operator in the 
`FilterOptionEnum` ? And then using the generic `filter_param_factory` ? Might 
limit code addition.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to