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]