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