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: