This is an automated email from the ASF dual-hosted git repository.
vavila pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 3f37cdbf9c fix(database): include `configuration_method` in the DB
export/import flow (#36958)
3f37cdbf9c is described below
commit 3f37cdbf9c6443a5306f21ca94dfe0c17c925378
Author: isaac-jaynes-imperva <[email protected]>
AuthorDate: Fri Jan 23 14:34:52 2026 -0800
fix(database): include `configuration_method` in the DB export/import flow
(#36958)
Co-authored-by: codeant-ai-for-open-source[bot]
<244253245+codeant-ai-for-open-source[bot]@users.noreply.github.com>
---
superset/databases/api.py | 1 +
superset/databases/schemas.py | 7 +
superset/models/core.py | 1 +
tests/integration_tests/databases/api_tests.py | 1 +
.../integration_tests/databases/commands_tests.py | 1 +
tests/unit_tests/databases/api_test.py | 149 +++++++++++++++++++++
tests/unit_tests/datasets/commands/export_test.py | 1 +
7 files changed, 161 insertions(+)
diff --git a/superset/databases/api.py b/superset/databases/api.py
index b0f29c5247..e9178d1f24 100644
--- a/superset/databases/api.py
+++ b/superset/databases/api.py
@@ -218,6 +218,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
"changed_by.last_name",
"created_by.first_name",
"created_by.last_name",
+ "configuration_method",
"database_name",
"explore_database_id",
"expose_in_sqllab",
diff --git a/superset/databases/schemas.py b/superset/databases/schemas.py
index 105496efa4..199da14b63 100644
--- a/superset/databases/schemas.py
+++ b/superset/databases/schemas.py
@@ -888,6 +888,13 @@ class ImportV1DatabaseSchema(Schema):
is_managed_externally = fields.Boolean(allow_none=True, dump_default=False)
external_url = fields.String(allow_none=True)
ssh_tunnel = fields.Nested(DatabaseSSHTunnel, allow_none=True)
+ configuration_method = fields.Enum(
+ ConfigurationMethod,
+ by_value=True,
+ required=False,
+ allow_none=True,
+ load_default=ConfigurationMethod.SQLALCHEMY_FORM,
+ )
@validates_schema
def validate_password(self, data: dict[str, Any], **kwargs: Any) -> None:
diff --git a/superset/models/core.py b/superset/models/core.py
index d13c14b65a..2de0a05391 100755
--- a/superset/models/core.py
+++ b/superset/models/core.py
@@ -197,6 +197,7 @@ class Database(CoreDatabase, AuditMixinNullable,
ImportExportMixin): # pylint:
"allow_file_upload",
"extra",
"impersonate_user",
+ "configuration_method",
]
extra_import_fields = [
"password",
diff --git a/tests/integration_tests/databases/api_tests.py
b/tests/integration_tests/databases/api_tests.py
index 94b24896a5..f77a47c8a3 100644
--- a/tests/integration_tests/databases/api_tests.py
+++ b/tests/integration_tests/databases/api_tests.py
@@ -189,6 +189,7 @@ class TestDatabaseApi(SupersetTestCase):
"changed_by",
"changed_on",
"changed_on_delta_humanized",
+ "configuration_method",
"created_by",
"database_name",
"disable_data_preview",
diff --git a/tests/integration_tests/databases/commands_tests.py
b/tests/integration_tests/databases/commands_tests.py
index 529e4807e7..27c1ce5654 100644
--- a/tests/integration_tests/databases/commands_tests.py
+++ b/tests/integration_tests/databases/commands_tests.py
@@ -371,6 +371,7 @@ class TestExportDatabasesCommand(SupersetTestCase):
"allow_csv_upload",
"extra",
"impersonate_user",
+ "configuration_method",
"uuid",
"version",
]
diff --git a/tests/unit_tests/databases/api_test.py
b/tests/unit_tests/databases/api_test.py
index 0785d762e5..7b77f5099d 100644
--- a/tests/unit_tests/databases/api_test.py
+++ b/tests/unit_tests/databases/api_test.py
@@ -2274,3 +2274,152 @@ def test_schemas_with_oauth2(
}
]
}
+
+
+def test_export_includes_configuration_method(
+ mocker: MockerFixture, client: Any, full_api_access: None
+) -> None:
+ """
+ Test that exporting a database
+ includes the 'configuration_method' field in the YAML.
+ """
+ import zipfile
+
+ import prison
+
+ from superset.models.core import Database
+
+ # Create a database with a non-default configuration_method
+ db_obj = Database(
+ database_name="export_test_db",
+ sqlalchemy_uri="bigquery://gcp-project-id/",
+ configuration_method="dynamic_form",
+ uuid=UUID("12345678-1234-5678-1234-567812345678"),
+ )
+ db.session.add(db_obj)
+ db.session.commit()
+
+ rison_ids = prison.dumps([db_obj.id])
+ response = client.get(f"/api/v1/database/export/?q={rison_ids}")
+ assert response.status_code == 200
+
+ # Read the zip file from the response
+ buf = BytesIO(response.data)
+ with zipfile.ZipFile(buf) as zf:
+ # Find the database yaml file
+ db_yaml_path = None
+ for name in zf.namelist():
+ if (
+ name.endswith(".yaml")
+ and name.startswith("database_export_")
+ and "/databases/" in name
+ ):
+ db_yaml_path = name
+ break
+ assert db_yaml_path, "Database YAML not found in export zip"
+ with zf.open(db_yaml_path) as f:
+ db_yaml = yaml.safe_load(f.read())
+ # Assert configuration_method is present and correct
+ assert "configuration_method" in db_yaml
+ assert db_yaml["configuration_method"] == "dynamic_form"
+
+
+def test_import_includes_configuration_method(
+ mocker: MockerFixture,
+ client: Any,
+ full_api_access: None,
+) -> None:
+ """
+ Test that importing a database YAML with configuration_method
+ sets the value on the imported DB connection.
+ """
+ from io import BytesIO
+ from unittest.mock import patch
+
+ import yaml
+ from flask import g, has_app_context, has_request_context
+
+ from superset import db, security_manager
+ from superset.databases.api import DatabaseRestApi
+ from superset.models.core import Database
+
+ DatabaseRestApi.datamodel._session = db.session
+ Database.metadata.create_all(db.session.get_bind())
+
+ def find_by_id_side_effect(db_id):
+ return db.session.query(Database).filter_by(id=db_id).first()
+
+ DatabaseDAO = mocker.patch("superset.databases.api.DatabaseDAO") # noqa:
N806
+ DatabaseDAO.find_by_id.side_effect = find_by_id_side_effect
+
+ metadata = {
+ "version": "1.0.0",
+ "type": "Database",
+ "timestamp": "2025-12-08T18:06:31.356738+00:00",
+ }
+ db_yaml = {
+ "database_name": "Test_Import_Configuration_Method",
+ "sqlalchemy_uri": "bigquery://gcp-project-id/",
+ "cache_timeout": 0,
+ "expose_in_sqllab": True,
+ "allow_run_async": False,
+ "allow_ctas": False,
+ "allow_cvas": False,
+ "allow_dml": False,
+ "allow_csv_upload": False,
+ "extra": {"allows_virtual_table_explore": True},
+ "impersonate_user": False,
+ "uuid": "87654321-4321-8765-4321-876543218765",
+ "configuration_method": "dynamic_form",
+ "version": "1.0.0",
+ }
+ contents = {
+ "metadata.yaml": yaml.safe_dump(metadata),
+ "databases/test.yaml": yaml.safe_dump(db_yaml),
+ }
+
+ with (
+ patch("superset.databases.api.is_zipfile", return_value=True),
+ patch("superset.databases.api.ZipFile"),
+ patch("superset.databases.api.get_contents_from_bundle",
return_value=contents),
+ ):
+ form_data = {"formData": (BytesIO(b"test"), "test.zip")}
+ response = client.post(
+ "/api/v1/database/import/",
+ data=form_data,
+ content_type="multipart/form-data",
+ )
+ db.session.commit()
+ db.session.remove()
+ assert response.status_code == 200, response.data
+
+ db_obj = (
+ db.session.query(Database)
+ .filter_by(database_name="Test_Import_Configuration_Method")
+ .first()
+ )
+ assert db_obj is not None, "Database not found in SQLAlchemy session after
import"
+ assert hasattr(db_obj, "configuration_method"), (
+ "'configuration_method' not found on model"
+ )
+ assert db_obj.configuration_method == "dynamic_form", (
+ "Expected configuration_method 'dynamic_form', got "
+ f"{db_obj.configuration_method}"
+ )
+
+ user = None
+ if has_request_context() or has_app_context():
+ user = getattr(g, "user", None)
+ if user and getattr(user, "is_authenticated", False) and hasattr(user,
"id"):
+ db_obj.created_by = security_manager.get_user_by_id(user.id)
+ db.session.commit()
+ get_resp = client.get(
+
"/api/v1/database/?q=(filters:!((col:database_name,opr:eq,value:'Test_Import_Configuration_Method')))"
+ )
+ result = get_resp.json["result"]
+ assert result, "No database returned from API after import."
+ db_obj_api = result[0]
+ assert "configuration_method" in db_obj_api, (
+ f"'configuration_method' not found in database list response:
{db_obj_api}"
+ )
+ assert db_obj_api["configuration_method"] == "dynamic_form"
diff --git a/tests/unit_tests/datasets/commands/export_test.py
b/tests/unit_tests/datasets/commands/export_test.py
index d2bb3a66cc..a449b76408 100644
--- a/tests/unit_tests/datasets/commands/export_test.py
+++ b/tests/unit_tests/datasets/commands/export_test.py
@@ -298,6 +298,7 @@ extra:
metadata_cache_timeout: {{}}
schemas_allowed_for_file_upload: []
impersonate_user: false
+configuration_method: sqlalchemy_form
uuid: {database.uuid}
version: 1.0.0
""",