jscheffl commented on code in PR #60410:
URL: https://github.com/apache/airflow/pull/60410#discussion_r2718646564


##########
airflow-core/src/airflow/providers_manager.py:
##########
@@ -926,6 +927,136 @@ def _get_attr(obj: Any, attr_name: str):
             return None
         return getattr(obj, attr_name)
 
+    def _get_connection_type_config_from_yaml(
+        self, provider_info: ProviderInfo, connection_type: str
+    ) -> dict | None:
+        """Get connection type config from provider.yaml if it exists."""
+        connection_types = provider_info.data.get("connection-types", [])
+        for conn_config in connection_types:
+            if conn_config.get("connection-type") == connection_type:
+                return conn_config
+        return None
+
+    @staticmethod
+    def _get_mock_field_for_field_type(field_type, field_format):
+        from airflow.api_fastapi.core_api.services.ui.connections import 
HookMetaService
+
+        field_type_to_mock_field_map = {
+            ("string", "password"): HookMetaService.MockPasswordField,
+            ("string", None): HookMetaService.MockStringField,
+            ("integer", None): HookMetaService.MockIntegerField,
+            ("boolean", None): HookMetaService.MockBooleanField,
+            ("number", None): HookMetaService.MockIntegerField,
+        }
+
+        field_class = field_type_to_mock_field_map.get((field_type, 
field_format))
+
+        return field_class
+
+    def _create_field_from_yaml(self, field_name: str, field_def: dict) -> Any:
+        """Build mock field from yaml conn-fields definition."""
+        from airflow.api_fastapi.core_api.services.ui.connections import 
HookMetaService
+
+        label = field_def.get("label")
+        description = field_def.get("description")
+        schema = field_def.get("schema", {})
+        field_type = schema.get("type")
+        if isinstance(field_type, list):
+            field_type = next((t for t in field_type if t != "null"), "string")
+
+        field_class = 
ProvidersManager._get_mock_field_for_field_type(field_type, 
schema.get("format"))
+        if not field_class:
+            log.warning("Unknown field type '%s' for field '%s' in 
conn-fields", field_type, field_name)
+            return None
+
+        validators = []
+        if "enum" in schema:
+            validators.append(HookMetaService.MockEnum(schema["enum"]))
+        if (
+            isinstance(schema.get("type"), list)
+            and "null" in schema["type"]
+            or not field_def.get("required", False)
+        ):
+            validators.append(HookMetaService.MockOptional())
+
+        return field_class(
+            label=label,
+            description=description,
+            default=schema.get("default"),
+            validators=validators if validators else None,
+        )
+
+    def _add_widgets_from_yaml(
+        self, package_name: str, hook_class_name: str, connection_type: str, 
conn_fields_yaml: dict
+    ) -> None:
+        """Parse conn-fields from yaml and add to connection_form_widgets."""
+        for field_name, field_def in conn_fields_yaml.items():
+            field = self._create_field_from_yaml(field_name, field_def)
+            if field is None:
+                continue
+
+            prefixed_name = f"extra__{connection_type}__{field_name}"
+            if prefixed_name in self._connection_form_widgets:
+                log.warning(
+                    "Field %s for connection type %s already added, skipping",
+                    field_name,
+                    connection_type,
+                )
+                continue
+
+            self._connection_form_widgets[prefixed_name] = 
ConnectionFormWidgetInfo(
+                hook_class_name=hook_class_name,
+                package_name=package_name,
+                field=field,
+                field_name=field_name,
+                is_sensitive=field_def.get("sensitive", False),
+            )

Review Comment:
   I am wondering, why do we need explicit code to re-parse the YAML into some 
other container classes? The UI on the API just serves a very very similar 
struct out again. I would have expected that after schema validation you just 
need to build a list of the information and can persist or directly serve on 
API endpoint. If not otherwise directly load the JSON into the Pydantic class 
that is served on API server.



##########
airflow-core/src/airflow/provider.yaml.schema.json:
##########
@@ -350,6 +350,111 @@
                     "hook-class-name": {
                         "description": "Hook class name that implements the 
connection type",
                         "type": "string"
+                    },
+                    "ui-field-behaviour": {
+                        "description": "Customizations for standard connection 
form fields",
+                        "type": "object",
+                        "properties": {
+                            "hidden-fields": {
+                                "description": "List of standard fields to 
hide in the UI",
+                                "type": "array",
+                                "items": {
+                                    "type": "string",
+                                    "enum": ["description", "host", "port", 
"login", "password", "schema", "extra"]
+                                },
+                                "default": []
+                            },
+                            "relabeling": {
+                                "description": "Map of field names to custom 
labels",
+                                "type": "object",
+                                "additionalProperties": {
+                                    "type": "string"
+                                },
+                                "default": {}
+                            },
+                            "placeholders": {
+                                "description": "Map of field names to 
placeholder text",
+                                "type": "object",
+                                "additionalProperties": {
+                                    "type": "string"
+                                },
+                                "default": {}
+                            }
+                        },
+                        "additionalProperties": false
+                    },
+                    "conn-fields": {
+                        "description": "Custom connection fields stored in 
Connection.extra JSON",
+                        "type": "object",
+                        "additionalProperties": {
+                            "type": "object",
+                            "properties": {
+                                "label": {
+                                    "type": "string",
+                                    "description": "Display label for the 
field"
+                                },
+                                "description": {
+                                    "type": "string",
+                                    "description": "Help text for the field"
+                                },
+                                "sensitive": {
+                                    "type": "boolean",
+                                    "default": false,
+                                    "description": "Whether to mask field 
value (like password fields)"
+                                },

Review Comment:
   Do we actually need this? Can we not implicitly know it is sensitive if 
`schema.format="password"`?



##########
airflow-core/src/airflow/providers_manager.py:
##########
@@ -926,6 +927,136 @@ def _get_attr(obj: Any, attr_name: str):
             return None
         return getattr(obj, attr_name)
 
+    def _get_connection_type_config_from_yaml(
+        self, provider_info: ProviderInfo, connection_type: str
+    ) -> dict | None:
+        """Get connection type config from provider.yaml if it exists."""
+        connection_types = provider_info.data.get("connection-types", [])
+        for conn_config in connection_types:
+            if conn_config.get("connection-type") == connection_type:
+                return conn_config
+        return None
+
+    @staticmethod
+    def _get_mock_field_for_field_type(field_type, field_format):
+        from airflow.api_fastapi.core_api.services.ui.connections import 
HookMetaService
+
+        field_type_to_mock_field_map = {
+            ("string", "password"): HookMetaService.MockPasswordField,
+            ("string", None): HookMetaService.MockStringField,
+            ("integer", None): HookMetaService.MockIntegerField,
+            ("boolean", None): HookMetaService.MockBooleanField,
+            ("number", None): HookMetaService.MockIntegerField,
+        }
+
+        field_class = field_type_to_mock_field_map.get((field_type, 
field_format))
+
+        return field_class
+
+    def _create_field_from_yaml(self, field_name: str, field_def: dict) -> Any:

Review Comment:
   Please not `Any`return type but some real typing.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to