You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by el...@apache.org on 2026/08/18 16:46:18 UTC

(superset) 01/01: fix(import): catch KeyError for missing uuid/ssh_tunnel in load_configs

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

eschutho pushed a commit to branch fix-keyerror-import-config-uuid
in repository https://gitbox.apache.org/repos/asf/superset.git

commit d746e42332be35487509decd2afa577e10506f77
Author: Elizabeth Thompson <es...@gmail.com>
AuthorDate: Tue Aug 18 16:43:37 2026 +0000

    fix(import): catch KeyError for missing uuid/ssh_tunnel in load_configs
    
    A hand-edited or third-party database export YAML that omits the `uuid`
    key (or `ssh_tunnel`) hit `config["uuid"]`/`config["ssh_tunnel"]`
    indexing in load_configs() before schema.load() ran, raising a raw
    KeyError that escaped the enclosing `except ValidationError` and
    surfaced as an opaque 500 from the *//import/ endpoints instead of a
    clean validation error. Add a sibling `except KeyError` that logs and
    appends a ValidationError, routing the failure into the same aggregated
    per-file error path as every other validation failure.
---
 superset/commands/importers/v1/utils.py            | 19 +++++
 .../unit_tests/commands/importers/v1/utils_test.py | 82 ++++++++++++++++++++++
 2 files changed, 101 insertions(+)

diff --git a/superset/commands/importers/v1/utils.py b/superset/commands/importers/v1/utils.py
index 28936341f13..98e7a2d6e43 100644
--- a/superset/commands/importers/v1/utils.py
+++ b/superset/commands/importers/v1/utils.py
@@ -247,6 +247,25 @@ def load_configs(
                     )
                 exc.messages = {file_name: exc.messages}
                 exceptions.append(exc)
+            except KeyError as exc:
+                # Some config fields (e.g. `uuid`, `ssh_tunnel`) are read
+                # directly from the imported YAML before schema validation runs;
+                # a config missing one of these keys raises a raw KeyError
+                # instead of failing validation cleanly like every other
+                # per-file error. Convert it into a ValidationError so it flows
+                # into the same aggregated error path.
+                field = str(exc).strip("'\"")
+                logger.error(
+                    "Missing required key %s in config for %s (prefix: %s)",
+                    exc,
+                    file_name,
+                    prefix,
+                )
+                exceptions.append(
+                    ValidationError(
+                        {file_name: {field: ["Missing data for required field."]}}
+                    )
+                )
 
     return configs
 
diff --git a/tests/unit_tests/commands/importers/v1/utils_test.py b/tests/unit_tests/commands/importers/v1/utils_test.py
index 58def5bebef..0e0419e693e 100644
--- a/tests/unit_tests/commands/importers/v1/utils_test.py
+++ b/tests/unit_tests/commands/importers/v1/utils_test.py
@@ -138,3 +138,85 @@ class TestLoadYaml:
 
         with pytest.raises(ValidationError):
             load_yaml("test.yaml", 'key: "unterminated string')
+
+
+class TestLoadConfigs:
+    def _database_schemas(self) -> dict[str, object]:
+        from marshmallow import fields, Schema
+
+        class DatabaseSchema(Schema):
+            uuid = fields.UUID(required=True)
+            database_name = fields.String(required=True)
+            sqlalchemy_uri = fields.String(required=True)
+            password = fields.String(required=False, allow_none=True)
+
+        return {"databases/": DatabaseSchema()}
+
+    @patch("superset.commands.importers.v1.utils.db")
+    def test_missing_uuid_appends_validation_error(self, mock_db: object) -> None:
+        """A databases config missing `uuid` must not raise a raw KeyError;
+        it should be excluded from the returned configs and a ValidationError
+        appended to the exceptions list instead."""
+        from marshmallow.exceptions import ValidationError
+
+        from superset.commands.importers.v1.utils import load_configs
+
+        mock_db.session.query.return_value.all.return_value = []
+
+        # No `uuid` and no `password`, so the code reaches
+        # `config["uuid"] in db_passwords` and would raise KeyError pre-fix.
+        contents = {
+            "databases/bad.yaml": (
+                "database_name: bad\nsqlalchemy_uri: postgres://localhost\n"
+            ),
+        }
+        exceptions: list[ValidationError] = []
+
+        configs = load_configs(
+            contents,
+            self._database_schemas(),
+            {},
+            exceptions,
+            {},
+            {},
+            {},
+            {},
+        )
+
+        assert "databases/bad.yaml" not in configs
+        assert len(exceptions) == 1
+        assert isinstance(exceptions[0], ValidationError)
+        assert "databases/bad.yaml" in exceptions[0].messages
+
+    @patch("superset.commands.importers.v1.utils.db")
+    def test_uuid_present_loads_successfully(self, mock_db: object) -> None:
+        """Control: a well-formed databases config loads with no exceptions."""
+        from marshmallow.exceptions import ValidationError
+
+        from superset.commands.importers.v1.utils import load_configs
+
+        mock_db.session.query.return_value.all.return_value = []
+
+        contents = {
+            "databases/good.yaml": (
+                "uuid: 6ff1d5b3-4b0f-4c6a-9d2f-9c8b7a6e5d4c\n"
+                "database_name: good\n"
+                "sqlalchemy_uri: postgres://localhost\n"
+                "password: secret\n"
+            ),
+        }
+        exceptions: list[ValidationError] = []
+
+        configs = load_configs(
+            contents,
+            self._database_schemas(),
+            {},
+            exceptions,
+            {},
+            {},
+            {},
+            {},
+        )
+
+        assert "databases/good.yaml" in configs
+        assert exceptions == []