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
 """,

Reply via email to