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 24faa71269a fix(providers/fab): lazily initialize flask_app in FastAPI 
routes (#64908)
24faa71269a is described below

commit 24faa71269adb7799fbc082841d4e027e2645fc3
Author: Pradeep Kalluri <[email protected]>
AuthorDate: Tue Apr 14 12:42:59 2026 +0100

    fix(providers/fab): lazily initialize flask_app in FastAPI routes (#64908)
    
    * fix(providers/fab): lazily initialize flask_app in FastAPI routes
    
    This addresses feedback on PR #64418. In certain execution flows (like 
specific CLI commands or isolated tests), the FabAuthManager might be 
initialized without get_fastapi_app() being called, leaving flask_app as None.
    
    This change ensures that if flask_app is accessed and found to be None, it 
is lazily initialized on demand, preventing HTTP 500 errors.
    
    Closes #64151 follow-up.
---
 .../fab/auth_manager/api_fastapi/routes/login.py   | 19 ++++--
 .../providers/fab/auth_manager/fab_auth_manager.py |  2 +
 .../api_fastapi/routes/test_login_lazy_init.py     | 70 ++++++++++++++++++++++
 3 files changed, 87 insertions(+), 4 deletions(-)

diff --git 
a/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/login.py
 
b/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/login.py
index a59dfa0372f..7d5b9750b7b 100644
--- 
a/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/login.py
+++ 
b/providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/login.py
@@ -16,6 +16,7 @@
 # under the License.
 from __future__ import annotations
 
+import logging
 from typing import Any
 
 from fastapi import Body, HTTPException, Request, status
@@ -35,6 +36,8 @@ if AIRFLOW_V_3_1_8_PLUS:
 else:
     get_cookie_path = lambda: "/"
 
+log = logging.getLogger(__name__)
+
 
 def _get_flask_app():
     from airflow.providers.fab.auth_manager.fab_auth_manager import 
FabAuthManager
@@ -49,10 +52,18 @@ def _get_flask_app():
             ),
         )
     if not auth_manager.flask_app:
-        raise HTTPException(
-            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
-            detail="Flask app is not initialized. Check that FabAuthManager 
started up correctly.",
-        )
+        with auth_manager._flask_app_lock:
+            if not auth_manager.flask_app:
+                try:
+                    from airflow.providers.fab.www.app import create_app
+
+                    auth_manager.flask_app = create_app(enable_plugins=False)
+                except Exception:
+                    log.exception("Failed to lazily initialize Flask app 
context for FabAuthManager")
+                    raise HTTPException(
+                        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+                        detail="Failed to initialize Flask app context. Check 
logs for details.",
+                    )
     return auth_manager.flask_app
 
 
diff --git 
a/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py 
b/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py
index fa8fe2a9edf..61bbf142dc2 100644
--- a/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py
+++ b/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py
@@ -18,6 +18,7 @@
 from __future__ import annotations
 
 import logging
+import threading
 import warnings
 from contextlib import suppress
 from functools import cached_property
@@ -177,6 +178,7 @@ class FabAuthManager(BaseAuthManager[User]):
     cache: TTLCache = TTLCache(maxsize=1024, ttl=CACHE_TTL)
     appbuilder: AirflowAppBuilder | None = None
     flask_app: Flask | None = None
+    _flask_app_lock = threading.Lock()
 
     def init_flask_resources(self) -> None:
         self._sync_appbuilder_roles()
diff --git 
a/providers/fab/tests/unit/fab/auth_manager/api_fastapi/routes/test_login_lazy_init.py
 
b/providers/fab/tests/unit/fab/auth_manager/api_fastapi/routes/test_login_lazy_init.py
new file mode 100644
index 00000000000..d5770f4741d
--- /dev/null
+++ 
b/providers/fab/tests/unit/fab/auth_manager/api_fastapi/routes/test_login_lazy_init.py
@@ -0,0 +1,70 @@
+# 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.mock import MagicMock, patch
+
+import pytest
+from fastapi import HTTPException
+
+from airflow.providers.fab.auth_manager.api_fastapi.routes.login import 
_get_flask_app
+from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager
+
+
+class TestGetFlaskAppLazyInit:
+    
@patch("airflow.providers.fab.auth_manager.api_fastapi.routes.login.get_auth_manager")
+    @patch("airflow.providers.fab.www.app.create_app")
+    def test_get_flask_app_lazy_init(self, mock_create_app, 
mock_get_auth_manager):
+        """Test that _get_flask_app lazily initializes the flask app if it's 
None."""
+        mock_fab_auth_manager = MagicMock(spec=FabAuthManager)
+        mock_fab_auth_manager.flask_app = None
+        mock_get_auth_manager.return_value = mock_fab_auth_manager
+
+        mock_flask_app = MagicMock()
+        mock_create_app.return_value = mock_flask_app
+
+        # First call
+        result = _get_flask_app()
+
+        assert result == mock_flask_app
+        mock_create_app.assert_called_once_with(enable_plugins=False)
+        assert mock_fab_auth_manager.flask_app == mock_flask_app
+
+        # Reset mock and set flask_app (simulate it being stored)
+        mock_create_app.reset_mock()
+
+        # Second call
+        result = _get_flask_app()
+        assert result == mock_flask_app
+        mock_create_app.assert_not_called()
+
+    
@patch("airflow.providers.fab.auth_manager.api_fastapi.routes.login.get_auth_manager")
+    @patch("airflow.providers.fab.www.app.create_app")
+    def test_get_flask_app_lazy_init_error(self, mock_create_app, 
mock_get_auth_manager):
+        """Test that _get_flask_app raises HTTPException if create_app 
fails."""
+        mock_fab_auth_manager = MagicMock(spec=FabAuthManager)
+        mock_fab_auth_manager.flask_app = None
+        mock_get_auth_manager.return_value = mock_fab_auth_manager
+
+        mock_create_app.side_effect = Exception("Config error")
+
+        with pytest.raises(HTTPException) as exc:
+            _get_flask_app()
+
+        assert exc.value.status_code == 500
+        assert "Failed to initialize Flask app context" in exc.value.detail

Reply via email to