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

jscheffl 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 86c0848a30c Fix loading providers hooks fields w/o FAB provider 
installed (#57717)
86c0848a30c is described below

commit 86c0848a30c844917a243477f26ee0c6825dd464
Author: Jens Scheffler <[email protected]>
AuthorDate: Mon Nov 3 21:21:32 2025 +0100

    Fix loading providers hooks fields w/o FAB provider installed (#57717)
---
 .../core_api/services/ui/connections.py            | 98 ++++++++--------------
 1 file changed, 37 insertions(+), 61 deletions(-)

diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/services/ui/connections.py 
b/airflow-core/src/airflow/api_fastapi/core_api/services/ui/connections.py
index 68881ac0f10..ef045813e87 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/services/ui/connections.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/services/ui/connections.py
@@ -17,8 +17,6 @@
 
 from __future__ import annotations
 
-import contextlib
-import importlib
 import logging
 from collections.abc import MutableMapping
 from functools import cache
@@ -132,69 +130,47 @@ class HookMetaService:
             """Mock for wtforms.validators.any_of."""
             return HookMetaService.MockEnum(allowed_values)
 
-        with contextlib.ExitStack() as stack:
+        # Before importing ProvidersManager, we need to mock all FAB and 
WTForms
+        # dependencies to avoid ImportErrors when FAB is not installed.
+        import sys
+        from importlib.util import find_spec
+        from unittest.mock import MagicMock
+
+        for mod_name in [
+            "wtforms",
+            "wtforms.csrf",
+            "wtforms.fields",
+            "wtforms.fields.simple",
+            "wtforms.validators",
+            "flask_babel",
+            "flask_appbuilder",
+            "flask_appbuilder.fieldwidgets",
+        ]:
             try:
-                importlib.import_module("wtforms")
-                stack.enter_context(mock.patch("wtforms.StringField", 
HookMetaService.MockStringField))
-                stack.enter_context(mock.patch("wtforms.fields.StringField", 
HookMetaService.MockStringField))
-                stack.enter_context(
-                    mock.patch("wtforms.fields.simple.StringField", 
HookMetaService.MockStringField)
-                )
-
-                stack.enter_context(mock.patch("wtforms.IntegerField", 
HookMetaService.MockIntegerField))
-                stack.enter_context(
-                    mock.patch("wtforms.fields.IntegerField", 
HookMetaService.MockIntegerField)
-                )
-                stack.enter_context(mock.patch("wtforms.PasswordField", 
HookMetaService.MockPasswordField))
-                stack.enter_context(mock.patch("wtforms.BooleanField", 
HookMetaService.MockBooleanField))
-                stack.enter_context(
-                    mock.patch("wtforms.fields.BooleanField", 
HookMetaService.MockBooleanField)
-                )
-                stack.enter_context(
-                    mock.patch("wtforms.fields.simple.BooleanField", 
HookMetaService.MockBooleanField)
-                )
-                stack.enter_context(mock.patch("wtforms.validators.Optional", 
HookMetaService.MockOptional))
-                stack.enter_context(mock.patch("wtforms.validators.any_of", 
mock_any_of))
-            except ImportError:
-                pass
-
-            try:
-                importlib.import_module("flask_babel")
-                stack.enter_context(mock.patch("flask_babel.lazy_gettext", 
mock_lazy_gettext))
-            except ImportError:
-                pass
-
-            try:
-                importlib.import_module("flask_appbuilder")
-                stack.enter_context(
-                    mock.patch(
-                        "flask_appbuilder.fieldwidgets.BS3TextFieldWidget", 
HookMetaService.MockAnyWidget
-                    )
-                )
-                stack.enter_context(
-                    mock.patch(
-                        
"flask_appbuilder.fieldwidgets.BS3TextAreaFieldWidget", 
HookMetaService.MockAnyWidget
-                    )
-                )
-                stack.enter_context(
-                    mock.patch(
-                        
"flask_appbuilder.fieldwidgets.BS3PasswordFieldWidget", 
HookMetaService.MockAnyWidget
-                    )
-                )
-            except ImportError:
-                pass
-
+                if not find_spec(mod_name):
+                    raise ModuleNotFoundError
+            except ModuleNotFoundError:
+                sys.modules[mod_name] = MagicMock()
+        with (
+            mock.patch("wtforms.StringField", HookMetaService.MockStringField),
+            mock.patch("wtforms.fields.StringField", 
HookMetaService.MockStringField),
+            mock.patch("wtforms.fields.simple.StringField", 
HookMetaService.MockStringField),
+            mock.patch("wtforms.IntegerField", 
HookMetaService.MockIntegerField),
+            mock.patch("wtforms.fields.IntegerField", 
HookMetaService.MockIntegerField),
+            mock.patch("wtforms.PasswordField", 
HookMetaService.MockPasswordField),
+            mock.patch("wtforms.BooleanField", 
HookMetaService.MockBooleanField),
+            mock.patch("wtforms.fields.BooleanField", 
HookMetaService.MockBooleanField),
+            mock.patch("wtforms.fields.simple.BooleanField", 
HookMetaService.MockBooleanField),
+            mock.patch("flask_babel.lazy_gettext", mock_lazy_gettext),
+            mock.patch("flask_appbuilder.fieldwidgets.BS3TextFieldWidget", 
HookMetaService.MockAnyWidget),
+            mock.patch("flask_appbuilder.fieldwidgets.BS3TextAreaFieldWidget", 
HookMetaService.MockAnyWidget),
+            mock.patch("flask_appbuilder.fieldwidgets.BS3PasswordFieldWidget", 
HookMetaService.MockAnyWidget),
+            mock.patch("wtforms.validators.Optional", 
HookMetaService.MockOptional),
+            mock.patch("wtforms.validators.any_of", mock_any_of),
+        ):
             pm = ProvidersManager()
-            pm._cleanup()  # Remove any cached hooks with non mocked FAB
-            pm._init_airflow_core_hooks()  # Initialize core hooks
             return pm.hooks, pm.connection_form_widgets, pm.field_behaviours  
# Will init providers hooks
 
-        return (
-            {},
-            {},
-            {},
-        )  # Make mypy happy, should never been reached 
https://github.com/python/mypy/issues/7726
-
     @staticmethod
     def _make_standard_fields(field_behaviour: dict | None) -> 
StandardHookFields | None:
         if not field_behaviour:

Reply via email to