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

eladkal pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-1-test by this push:
     new 4bc9068b521 [v3-1-test] Fix `TypeError` crashes on `/users/list` and 
`/roles/list` in FAB UI caused by concurrent API schema requests (#64156)
4bc9068b521 is described below

commit 4bc9068b521d6f26f5f49af07c2a42c90ff46cf9
Author: Subham <[email protected]>
AuthorDate: Wed Mar 25 12:42:32 2026 +0530

    [v3-1-test] Fix `TypeError` crashes on `/users/list` and `/roles/list` in 
FAB UI caused by concurrent API schema requests (#64156)
    
    * Fix `TypeError` crashes on `/users/list` and `/roles/list` in FAB UI 
caused by concurrent API schema requests (#63986)
    
    * Fix TypeError crashes on /users/list and /roles/list in FAB UI caused by 
concurrent API schema requests
    
    * Fix TypeError in FAB UI by isolating ProvidersManager discovery and 
making MockOptional callable
    
    * Fix unrelated Elasticsearch test failure in FAB UI PR branch
    
    * Revert unrelated Elasticsearch test changes
    
    * Fix OpenAPI constraint generation by addressing Singleton patching, 
restoring YAML extra fields logic, and safely resolving callable defaults in 
Param object
---
 .../core_api/services/ui/connections.py            | 107 +++++++++++++++++----
 1 file changed, 87 insertions(+), 20 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 1f2d8f659d3..ca295e880e2 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
@@ -49,10 +49,11 @@ class HookMetaService:
             pass
 
         def __call__(self, form, field):
-            pass
+            """No-op call to satisfy WTForms validator protocol."""
+            return None
 
     class MockEnum:
-        """Mock for wtforms.validators.Optional."""
+        """Mock for wtforms.validators.AnyOf."""
 
         def __init__(self, allowed_values):
             self.allowed_values = allowed_values
@@ -154,23 +155,40 @@ class HookMetaService:
                     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),
-        ):
+
+        # We conditionally inject mock classes for missing dependencies
+        # to ensure `ProvidersManager` can initialize hook connection widgets
+        # without crashing when FAB/WTForms are not installed.
+        if "wtforms.StringField" not in sys.modules:
+            # Only apply mocks if the actual module wasn't loaded beforehand.
+            # This avoids thread-safety issues caused by `unittest.mock.patch` 
mutating global states.
+            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),
+                # Prevent poisoning the global ProvidersManager singleton with 
mocks
+                
mock.patch.dict("airflow.utils.singleton.Singleton._instances", clear=True),
+                
mock.patch("airflow.providers_manager.ProvidersManager.initialized", 
return_value=False),
+            ):
+                pm = ProvidersManager()
+                return pm.hooks, pm.connection_form_widgets, 
pm.field_behaviours  # Will init providers hooks
+        else:
             pm = ProvidersManager()
             return pm.hooks, pm.connection_form_widgets, pm.field_behaviours  
# Will init providers hooks
 
@@ -205,10 +223,59 @@ class HookMetaService:
         result: dict[str, MutableMapping] = {}
         for key, form_widget in form_widgets.items():
             hook_key = key.split("__")[1]
-            if isinstance(form_widget.field, HookMetaService.MockBaseField):
+            if isinstance(form_widget.field, dict):
+                hook_widgets = result.get(hook_key, {})
+                hook_widgets[form_widget.field_name] = form_widget.field
+                result[hook_key] = hook_widgets
+            elif isinstance(form_widget.field, HookMetaService.MockBaseField):
                 hook_widgets = result.get(hook_key, {})
                 hook_widgets[form_widget.field_name] = 
form_widget.field.param.dump()
                 result[hook_key] = hook_widgets
+            elif type(form_widget.field).__name__ == "UnboundField":
+                # handle real WTForms fields gracefully without needing mock 
patches
+                field_class_name = getattr(form_widget.field.field_class, 
"__name__", "")
+                param_type = "string"
+                param_format = None
+                if field_class_name == "BooleanField":
+                    param_type = "boolean"
+                elif field_class_name == "IntegerField":
+                    param_type = "integer"
+                elif field_class_name == "PasswordField":
+                    param_format = "password"
+
+                label = (
+                    form_widget.field.args[0]
+                    if len(form_widget.field.args) > 0
+                    else form_widget.field.kwargs.get("label")
+                )
+                validators = form_widget.field.kwargs.get("validators", [])
+                description = form_widget.field.kwargs.get("description", "")
+                default = form_widget.field.kwargs.get("default", None)
+                if callable(default):
+                    try:
+                        default = default()
+                    except Exception:
+                        default = None
+
+                enum = {}
+                for v in validators:
+                    if type(v).__name__ == "AnyOf":
+                        enum["enum"] = getattr(v, "values", [])
+
+                types = [param_type, "null"]
+                format_dict = {"format": param_format} if param_format else {}
+
+                param = Param(
+                    default=default,
+                    title=str(label) if label is not None else None,
+                    description=str(description) if description else None,
+                    type=types,
+                    **format_dict,
+                    **enum,
+                ).dump()
+                hook_widgets = result.get(hook_key, {})
+                hook_widgets[form_widget.field_name] = param
+                result[hook_key] = hook_widgets
             else:
                 log.error("Unknown form widget in %s: %s", hook_key, 
form_widget)
         return result

Reply via email to