This is an automated email from the ASF dual-hosted git repository.

vincbeck pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 0f21f0ab426 Move `airflow.www.auth` to 
`airflow.providers.fab.www.auth` (#47307)
0f21f0ab426 is described below

commit 0f21f0ab426257d2258a886194591973d7e1e36b
Author: Vincent <[email protected]>
AuthorDate: Mon Mar 3 15:18:32 2025 -0500

    Move `airflow.www.auth` to `airflow.providers.fab.www.auth` (#47307)
---
 dev/breeze/tests/test_selective_checks.py          | 12 +++++-----
 .../administration-and-deployment/plugins.rst      |  2 +-
 docs/apache-airflow/empty_plugin/empty_plugin.py   |  2 +-
 generated/provider_dependencies.json               |  7 ++++--
 newsfragments/aip-79.significant.rst               |  2 ++
 providers/databricks/README.rst                    |  1 +
 providers/databricks/pyproject.toml                |  3 +++
 .../providers/databricks/get_provider_info.py      |  1 +
 .../databricks/plugins/databricks_workflow.py      |  7 +++++-
 providers/edge/README.rst                          | 19 +++++++++++++++
 providers/edge/pyproject.toml                      |  7 ++++++
 .../airflow/providers/edge/get_provider_info.py    |  1 +
 .../providers/edge/plugins/edge_executor_plugin.py |  6 ++++-
 .../fab/src/airflow/providers/fab}/www/auth.py     |  2 +-
 .../fab/tests/unit/fab}/www/test_auth.py           | 28 +++++++++++++---------
 15 files changed, 76 insertions(+), 24 deletions(-)

diff --git a/dev/breeze/tests/test_selective_checks.py 
b/dev/breeze/tests/test_selective_checks.py
index 1fcf0e7aa20..bd5e8572c6f 100644
--- a/dev/breeze/tests/test_selective_checks.py
+++ b/dev/breeze/tests/test_selective_checks.py
@@ -153,7 +153,7 @@ def assert_outputs_are_printed(expected_outputs: dict[str, 
str], stderr: str):
             pytest.param(
                 ("airflow/api/file.py",),
                 {
-                    "selected-providers-list-as-string": "amazon common.compat 
fab",
+                    "selected-providers-list-as-string": "amazon common.compat 
databricks edge fab",
                     "all-python-versions": "['3.9']",
                     "all-python-versions-list-as-string": "3.9",
                     "python-versions": "['3.9']",
@@ -168,8 +168,8 @@ def assert_outputs_are_printed(expected_outputs: dict[str, 
str], stderr: str):
                     
"mypy-docs,mypy-providers,mypy-task-sdk,ts-compile-format-lint-ui",
                     "upgrade-to-newer-dependencies": "false",
                     "core-test-types-list-as-string": "API Always",
-                    "providers-test-types-list-as-string": "Providers[amazon] 
Providers[common.compat,fab]",
-                    "individual-providers-test-types-list-as-string": 
"Providers[amazon] Providers[common.compat] Providers[fab]",
+                    "providers-test-types-list-as-string": "Providers[amazon] 
Providers[common.compat,databricks,edge,fab]",
+                    "individual-providers-test-types-list-as-string": 
"Providers[amazon] Providers[common.compat] Providers[databricks] 
Providers[edge] Providers[fab]",
                     "testable-core-integrations": "['celery', 'kerberos']",
                     "testable-providers-integrations": "['cassandra', 'drill', 
'kafka', 'mongo', 'pinot', 'qdrant', 'redis', 'trino', 'ydb']",
                     "needs-mypy": "true",
@@ -318,7 +318,7 @@ def assert_outputs_are_printed(expected_outputs: dict[str, 
str], stderr: str):
                     "providers/postgres/tests/unit/postgres/file.py",
                 ),
                 {
-                    "selected-providers-list-as-string": "amazon common.compat 
common.sql fab google openlineage "
+                    "selected-providers-list-as-string": "amazon common.compat 
common.sql databricks edge fab google openlineage "
                     "pgvector postgres",
                     "all-python-versions": "['3.9']",
                     "all-python-versions-list-as-string": "3.9",
@@ -335,9 +335,9 @@ def assert_outputs_are_printed(expected_outputs: dict[str, 
str], stderr: str):
                     "upgrade-to-newer-dependencies": "false",
                     "core-test-types-list-as-string": "API Always",
                     "providers-test-types-list-as-string": "Providers[amazon] "
-                    
"Providers[common.compat,common.sql,fab,openlineage,pgvector,postgres] 
Providers[google]",
+                    
"Providers[common.compat,common.sql,databricks,edge,fab,openlineage,pgvector,postgres]
 Providers[google]",
                     "individual-providers-test-types-list-as-string": 
"Providers[amazon] Providers[common.compat] Providers[common.sql] "
-                    "Providers[fab] Providers[google] Providers[openlineage] 
Providers[pgvector] "
+                    "Providers[databricks] Providers[edge] Providers[fab] 
Providers[google] Providers[openlineage] Providers[pgvector] "
                     "Providers[postgres]",
                     "needs-mypy": "true",
                     "mypy-checks": "['mypy-airflow', 'mypy-providers']",
diff --git a/docs/apache-airflow/administration-and-deployment/plugins.rst 
b/docs/apache-airflow/administration-and-deployment/plugins.rst
index bcfc4acf238..983842c133d 100644
--- a/docs/apache-airflow/administration-and-deployment/plugins.rst
+++ b/docs/apache-airflow/administration-and-deployment/plugins.rst
@@ -163,7 +163,7 @@ definitions in Airflow.
     # This is the class you derive to create a plugin
     from airflow.plugins_manager import AirflowPlugin
     from airflow.security import permissions
-    from airflow.www.auth import has_access
+    from airflow.providers.fab.www.auth import has_access
 
     from fastapi import FastAPI
     from flask import Blueprint
diff --git a/docs/apache-airflow/empty_plugin/empty_plugin.py 
b/docs/apache-airflow/empty_plugin/empty_plugin.py
index 6cfb9c2196c..ed5a99f1c91 100644
--- a/docs/apache-airflow/empty_plugin/empty_plugin.py
+++ b/docs/apache-airflow/empty_plugin/empty_plugin.py
@@ -24,7 +24,7 @@ from flask_appbuilder import BaseView, expose
 
 from airflow.auth.managers.models.resource_details import AccessView
 from airflow.plugins_manager import AirflowPlugin
-from airflow.www.auth import has_access_view
+from airflow.providers.fab.www.auth import has_access_view
 
 
 class EmptyPluginView(BaseView):
diff --git a/generated/provider_dependencies.json 
b/generated/provider_dependencies.json
index b935b9c7d03..864b3e252e7 100644
--- a/generated/provider_dependencies.json
+++ b/generated/provider_dependencies.json
@@ -466,7 +466,8 @@
       }
     ],
     "cross-providers-deps": [
-      "common.sql"
+      "common.sql",
+      "fab"
     ],
     "excluded-python-versions": [],
     "state": "ready"
@@ -551,7 +552,9 @@
         "plugin-class": 
"airflow.providers.edge.plugins.edge_executor_plugin.EdgeExecutorPlugin"
       }
     ],
-    "cross-providers-deps": [],
+    "cross-providers-deps": [
+      "fab"
+    ],
     "excluded-python-versions": [],
     "state": "not-ready"
   },
diff --git a/newsfragments/aip-79.significant.rst 
b/newsfragments/aip-79.significant.rst
index ec58d6d2228..97d26bee0f1 100644
--- a/newsfragments/aip-79.significant.rst
+++ b/newsfragments/aip-79.significant.rst
@@ -34,6 +34,8 @@ As part of this change the following breaking changes have 
occurred:
     - ``batch_is_authorized_pool``
     - ``batch_is_authorized_variable``
 
+- The module ``airflow.www.auth`` has been moved to 
``airflow.providers.fab.www.auth``
+
 * Types of change
 
   * [ ] Dag changes
diff --git a/providers/databricks/README.rst b/providers/databricks/README.rst
index a5cada60bbd..68fbc4adb12 100644
--- a/providers/databricks/README.rst
+++ b/providers/databricks/README.rst
@@ -80,6 +80,7 @@ You can install such cross-provider dependencies when 
installing from PyPI. For
 Dependent package                                                              
                               Extra
 
============================================================================================================
  ==============
 `apache-airflow-providers-common-sql 
<https://airflow.apache.org/docs/apache-airflow-providers-common-sql>`_  
``common.sql``
+`apache-airflow-providers-fab 
<https://airflow.apache.org/docs/apache-airflow-providers-fab>`_                
``fab``
 
============================================================================================================
  ==============
 
 The changelog for the provider package can be found in the
diff --git a/providers/databricks/pyproject.toml 
b/providers/databricks/pyproject.toml
index 9c3c0b8cbf5..e8ce96c43e6 100644
--- a/providers/databricks/pyproject.toml
+++ b/providers/databricks/pyproject.toml
@@ -77,6 +77,9 @@ dependencies = [
 "azure-identity" = [
     "azure-identity>=1.3.1",
 ]
+"fab" = [
+    "apache-airflow-providers-fab"
+]
 
 # The dependency groups should be modified in place in the generated file
 # Any change in the dependencies is preserved when the file is regenerated
diff --git 
a/providers/databricks/src/airflow/providers/databricks/get_provider_info.py 
b/providers/databricks/src/airflow/providers/databricks/get_provider_info.py
index 8d91a53a670..c322a8a18be 100644
--- a/providers/databricks/src/airflow/providers/databricks/get_provider_info.py
+++ b/providers/databricks/src/airflow/providers/databricks/get_provider_info.py
@@ -189,6 +189,7 @@ def get_provider_info():
         "optional-dependencies": {
             "sdk": ["databricks-sdk==0.10.0"],
             "azure-identity": ["azure-identity>=1.3.1"],
+            "fab": ["apache-airflow-providers-fab"],
         },
         "devel-dependencies": ["deltalake>=0.12.0"],
     }
diff --git 
a/providers/databricks/src/airflow/providers/databricks/plugins/databricks_workflow.py
 
b/providers/databricks/src/airflow/providers/databricks/plugins/databricks_workflow.py
index 8146f4de976..05da1ed5609 100644
--- 
a/providers/databricks/src/airflow/providers/databricks/plugins/databricks_workflow.py
+++ 
b/providers/databricks/src/airflow/providers/databricks/plugins/databricks_workflow.py
@@ -34,11 +34,16 @@ from airflow.models.taskinstance import TaskInstance, 
TaskInstanceKey
 from airflow.models.xcom import XCom
 from airflow.plugins_manager import AirflowPlugin
 from airflow.providers.databricks.hooks.databricks import DatabricksHook
+from airflow.providers.databricks.version_compat import AIRFLOW_V_3_0_PLUS
+
+if AIRFLOW_V_3_0_PLUS:
+    from airflow.providers.fab.www import auth
+else:
+    from airflow.www import auth  # type: ignore
 from airflow.utils.log.logging_mixin import LoggingMixin
 from airflow.utils.session import NEW_SESSION, provide_session
 from airflow.utils.state import TaskInstanceState
 from airflow.utils.task_group import TaskGroup
-from airflow.www import auth
 
 if TYPE_CHECKING:
     from sqlalchemy.orm.session import Session
diff --git a/providers/edge/README.rst b/providers/edge/README.rst
index 6f727510133..5a61219a5fa 100644
--- a/providers/edge/README.rst
+++ b/providers/edge/README.rst
@@ -58,5 +58,24 @@ PIP package         Version required
 ``retryhttp``       ``>=1.2.0,!=1.3.0``
 ==================  ===================
 
+Cross provider package dependencies
+-----------------------------------
+
+Those are dependencies that might be needed in order to use all the features 
of the package.
+You need to install the specified provider packages in order to use them.
+
+You can install such cross-provider dependencies when installing from PyPI. 
For example:
+
+.. code-block:: bash
+
+    pip install apache-airflow-providers-edge[fab]
+
+
+==============================================================================================
  =======
+Dependent package                                                              
                 Extra
+==============================================================================================
  =======
+`apache-airflow-providers-fab 
<https://airflow.apache.org/docs/apache-airflow-providers-fab>`_  ``fab``
+==============================================================================================
  =======
+
 The changelog for the provider package can be found in the
 `changelog 
<https://airflow.apache.org/docs/apache-airflow-providers-edge/0.20.0pre0/changelog.html>`_.
diff --git a/providers/edge/pyproject.toml b/providers/edge/pyproject.toml
index 1d5c05416eb..264f996e6a3 100644
--- a/providers/edge/pyproject.toml
+++ b/providers/edge/pyproject.toml
@@ -62,6 +62,13 @@ dependencies = [
     "retryhttp>=1.2.0,!=1.3.0",
 ]
 
+# The optional dependencies should be modified in place in the generated file
+# Any change in the dependencies is preserved when the file is regenerated
+[project.optional-dependencies]
+"fab" = [
+    "apache-airflow-providers-fab"
+]
+
 [project.urls]
 "Documentation" = 
"https://airflow.apache.org/docs/apache-airflow-providers-edge/0.20.0pre0";
 "Changelog" = 
"https://airflow.apache.org/docs/apache-airflow-providers-edge/0.20.0pre0/changelog.html";
diff --git a/providers/edge/src/airflow/providers/edge/get_provider_info.py 
b/providers/edge/src/airflow/providers/edge/get_provider_info.py
index aeaaaf1d71f..ea75a8c5710 100644
--- a/providers/edge/src/airflow/providers/edge/get_provider_info.py
+++ b/providers/edge/src/airflow/providers/edge/get_provider_info.py
@@ -100,4 +100,5 @@ def get_provider_info():
             }
         },
         "dependencies": ["apache-airflow>=2.10.0", "pydantic>=2.10.2", 
"retryhttp>=1.2.0,!=1.3.0"],
+        "optional-dependencies": {"fab": ["apache-airflow-providers-fab"]},
     }
diff --git 
a/providers/edge/src/airflow/providers/edge/plugins/edge_executor_plugin.py 
b/providers/edge/src/airflow/providers/edge/plugins/edge_executor_plugin.py
index 507066ffafb..3382249e4ba 100644
--- a/providers/edge/src/airflow/providers/edge/plugins/edge_executor_plugin.py
+++ b/providers/edge/src/airflow/providers/edge/plugins/edge_executor_plugin.py
@@ -32,10 +32,14 @@ from airflow.exceptions import AirflowConfigException
 from airflow.models.taskinstance import TaskInstanceState
 from airflow.plugins_manager import AirflowPlugin
 from airflow.providers.edge.version_compat import AIRFLOW_V_3_0_PLUS
+
+if AIRFLOW_V_3_0_PLUS:
+    from airflow.providers.fab.www.auth import has_access_view
+else:
+    from airflow.www.auth import has_access_view  # type: ignore
 from airflow.utils.session import NEW_SESSION, provide_session
 from airflow.utils.yaml import safe_load
 from airflow.www import utils as wwwutils
-from airflow.www.auth import has_access_view
 
 if TYPE_CHECKING:
     from sqlalchemy.orm import Session
diff --git a/airflow/www/auth.py 
b/providers/fab/src/airflow/providers/fab/www/auth.py
similarity index 99%
rename from airflow/www/auth.py
rename to providers/fab/src/airflow/providers/fab/www/auth.py
index a128fd5c47f..40c5dc51f0c 100644
--- a/airflow/www/auth.py
+++ b/providers/fab/src/airflow/providers/fab/www/auth.py
@@ -155,7 +155,7 @@ def _has_access(*, is_authorized: bool, func: Callable, 
args, kwargs):
     else:
         access_denied = get_access_denied_message()
         flash(access_denied, "danger")
-    return redirect(url_for("Airflow.index"))
+    return redirect(url_for("FabIndexView.index"))
 
 
 def has_access_configuration(method: ResourceMethod) -> Callable[[T], T]:
diff --git a/tests/www/test_auth.py 
b/providers/fab/tests/unit/fab/www/test_auth.py
similarity index 90%
rename from tests/www/test_auth.py
rename to providers/fab/tests/unit/fab/www/test_auth.py
index 929b12966d9..de69129fa2c 100644
--- a/tests/www/test_auth.py
+++ b/providers/fab/tests/unit/fab/www/test_auth.py
@@ -21,13 +21,19 @@ from unittest.mock import Mock, patch
 
 import pytest
 
-import airflow.www.auth as auth
+import airflow.providers.fab.www.auth as auth
 from airflow.auth.managers.models.resource_details import DagAccessEntity
 from airflow.models import Connection, Pool, Variable
+from airflow.providers.fab.www import app as application
 
 mock_call = Mock()
 
 
[email protected]
+def app():
+    return application.create_app(enable_plugins=False)
+
+
 @pytest.mark.parametrize(
     "decorator_name, is_authorized_method_name",
     [
@@ -44,7 +50,7 @@ class TestHasAccessNoDetails:
         mock_call()
         return True
 
-    @patch("airflow.www.auth.get_auth_manager")
+    @patch("airflow.providers.fab.www.auth.get_auth_manager")
     def test_has_access_no_details_when_authorized(
         self, mock_get_auth_manager, decorator_name, is_authorized_method_name
     ):
@@ -59,8 +65,8 @@ class TestHasAccessNoDetails:
         mock_call.assert_called_once()
         assert result is True
 
-    @patch("airflow.www.auth.get_auth_manager")
-    @patch("airflow.www.auth.render_template")
+    @patch("airflow.providers.fab.www.auth.get_auth_manager")
+    @patch("airflow.providers.fab.www.auth.render_template")
     def test_has_access_no_details_when_no_permission(
         self, mock_render_template, mock_get_auth_manager, decorator_name, 
is_authorized_method_name
     ):
@@ -78,7 +84,7 @@ class TestHasAccessNoDetails:
         mock_render_template.assert_called_once()
 
     @pytest.mark.db_test
-    @patch("airflow.www.auth.get_auth_manager")
+    @patch("airflow.providers.fab.www.auth.get_auth_manager")
     def test_has_access_no_details_when_not_logged_in(
         self, mock_get_auth_manager, app, decorator_name, 
is_authorized_method_name
     ):
@@ -132,7 +138,7 @@ class TestHasAccessWithDetails:
         mock_call()
         return True
 
-    @patch("airflow.www.auth.get_auth_manager")
+    @patch("airflow.providers.fab.www.auth.get_auth_manager")
     def test_has_access_with_details_when_authorized(
         self, mock_get_auth_manager, decorator_name, 
is_authorized_method_name, items, request
     ):
@@ -149,7 +155,7 @@ class TestHasAccessWithDetails:
         assert result is True
 
     @pytest.mark.db_test
-    @patch("airflow.www.auth.get_auth_manager")
+    @patch("airflow.providers.fab.www.auth.get_auth_manager")
     def test_has_access_with_details_when_unauthorized(
         self, mock_get_auth_manager, app, decorator_name, 
is_authorized_method_name, items, request
     ):
@@ -184,7 +190,7 @@ class TestHasAccessDagEntities:
         mock_call()
         return True
 
-    @patch("airflow.www.auth.get_auth_manager")
+    @patch("airflow.providers.fab.www.auth.get_auth_manager")
     def test_has_access_dag_entities_when_authorized(self, 
mock_get_auth_manager, dag_access_entity):
         auth_manager = Mock()
         auth_manager.batch_is_authorized_dag.return_value = True
@@ -197,7 +203,7 @@ class TestHasAccessDagEntities:
         assert result is True
 
     @pytest.mark.db_test
-    @patch("airflow.www.auth.get_auth_manager")
+    @patch("airflow.providers.fab.www.auth.get_auth_manager")
     def test_has_access_dag_entities_when_unauthorized(self, 
mock_get_auth_manager, app, dag_access_entity):
         auth_manager = Mock()
         auth_manager.batch_is_authorized_dag.return_value = False
@@ -208,10 +214,10 @@ class TestHasAccessDagEntities:
             result = auth.has_access_dag_entities("GET", 
dag_access_entity)(self.method_test)(None, items)
 
         mock_call.assert_not_called()
-        assert result.headers["Location"] == "/home"
+        assert result.headers["Location"] == "/"
 
     @pytest.mark.db_test
-    @patch("airflow.www.auth.get_auth_manager")
+    @patch("airflow.providers.fab.www.auth.get_auth_manager")
     def test_has_access_dag_entities_when_logged_out(self, 
mock_get_auth_manager, app, dag_access_entity):
         auth_manager = Mock()
         auth_manager.batch_is_authorized_dag.return_value = False

Reply via email to