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

rusackas 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 c9e2c7037e feat: Implement sparse import for ImportAssetsCommand 
(#32670)
c9e2c7037e is described below

commit c9e2c7037e81ffe31e83bba70f78f865f52f973d
Author: Paul Rhodes <[email protected]>
AuthorDate: Mon Mar 17 14:44:15 2025 +0000

    feat: Implement sparse import for ImportAssetsCommand (#32670)
---
 superset/commands/importers/v1/assets.py  | 25 +++++++++++++++++++++----
 superset/commands/importers/v1/utils.py   | 18 +++++++++++++++++-
 superset/importexport/api.py              |  5 +++++
 tests/unit_tests/importexport/api_test.py |  1 +
 4 files changed, 44 insertions(+), 5 deletions(-)

diff --git a/superset/commands/importers/v1/assets.py 
b/superset/commands/importers/v1/assets.py
index 41895f84d7..3b0442157f 100644
--- a/superset/commands/importers/v1/assets.py
+++ b/superset/commands/importers/v1/assets.py
@@ -34,17 +34,21 @@ from superset.commands.database.importers.v1.utils import 
import_database
 from superset.commands.dataset.importers.v1.utils import import_dataset
 from superset.commands.exceptions import CommandInvalidError, ImportFailedError
 from superset.commands.importers.v1.utils import (
+    get_resource_mappings_batched,
     load_configs,
     load_metadata,
     validate_metadata_type,
 )
 from superset.commands.query.importers.v1.utils import import_saved_query
 from superset.commands.utils import update_chart_config_dataset
+from superset.connectors.sqla.models import SqlaTable
 from superset.dashboards.schemas import ImportV1DashboardSchema
 from superset.databases.schemas import ImportV1DatabaseSchema
 from superset.datasets.schemas import ImportV1DatasetSchema
 from superset.migrations.shared.native_filters import migrate_dashboard
+from superset.models.core import Database
 from superset.models.dashboard import dashboard_slices
+from superset.models.slice import Slice
 from superset.queries.saved_queries.schemas import ImportV1SavedQuerySchema
 from superset.utils.decorators import on_error, transaction
 
@@ -79,12 +83,27 @@ class ImportAssetsCommand(BaseCommand):
             kwargs.get("ssh_tunnel_priv_key_passwords") or {}
         )
         self._configs: dict[str, Any] = {}
+        self.sparse = kwargs.get("sparse", False)
 
     # pylint: disable=too-many-locals
     @staticmethod
-    def _import(configs: dict[str, Any]) -> None:  # noqa: C901
+    def _import(configs: dict[str, Any], sparse: bool = False) -> None:  # 
noqa: C901
         # import databases first
         database_ids: dict[str, int] = {}
+        dataset_info: dict[str, dict[str, Any]] = {}
+        chart_ids: dict[str, int] = {}
+        if sparse:
+            chart_ids = get_resource_mappings_batched(Slice)
+            database_ids = get_resource_mappings_batched(Database)
+            dataset_info = get_resource_mappings_batched(
+                SqlaTable,
+                value_func=lambda x: {
+                    "datasource_id": x.id,
+                    "datasource_type": x.datasource_type,
+                    "datasource_name": x.datasource_name,
+                },
+            )
+
         for file_name, config in configs.items():
             if file_name.startswith("databases/"):
                 database = import_database(config, overwrite=True)
@@ -97,7 +116,6 @@ class ImportAssetsCommand(BaseCommand):
                 import_saved_query(config, overwrite=True)
 
         # import datasets
-        dataset_info: dict[str, dict[str, Any]] = {}
         for file_name, config in configs.items():
             if file_name.startswith("datasets/"):
                 config["database_id"] = database_ids[config["database_uuid"]]
@@ -110,7 +128,6 @@ class ImportAssetsCommand(BaseCommand):
 
         # import charts
         charts = []
-        chart_ids: dict[str, int] = {}
         for file_name, config in configs.items():
             if file_name.startswith("charts/"):
                 dataset_dict = dataset_info[config["dataset_uuid"]]
@@ -161,7 +178,7 @@ class ImportAssetsCommand(BaseCommand):
     )
     def run(self) -> None:
         self.validate()
-        self._import(self._configs)
+        self._import(self._configs, self.sparse)
 
     def validate(self) -> None:
         exceptions: list[ValidationError] = []
diff --git a/superset/commands/importers/v1/utils.py 
b/superset/commands/importers/v1/utils.py
index ec5387708f..7d4e590913 100644
--- a/superset/commands/importers/v1/utils.py
+++ b/superset/commands/importers/v1/utils.py
@@ -15,7 +15,7 @@
 
 import logging
 from pathlib import Path, PurePosixPath
-from typing import Any, Optional
+from typing import Any, Callable, Dict, Optional, Type
 from zipfile import ZipFile
 
 import yaml
@@ -214,3 +214,19 @@ def get_contents_from_bundle(bundle: ZipFile) -> dict[str, 
str]:
         for file_name in bundle.namelist()
         if is_valid_config(file_name)
     }
+
+
+def get_resource_mappings_batched(
+    model_class: Type[Any],
+    batch_size: int = 1000,
+    value_func: Callable[[Any], Any] = lambda x: x.id,
+) -> Dict[str, Any]:
+    offset = 0
+    mapping = {}
+    while True:
+        batch = 
db.session.query(model_class).limit(batch_size).offset(offset).all()
+        if not batch:
+            break
+        mapping.update({str(x.uuid): value_func(x) for x in batch})
+        offset += batch_size
+    return mapping
diff --git a/superset/importexport/api.py b/superset/importexport/api.py
index 0d3906b1a1..0a6a27dd58 100644
--- a/superset/importexport/api.py
+++ b/superset/importexport/api.py
@@ -147,6 +147,9 @@ class ImportExportRestApi(BaseSupersetApi):
                         the private_key should be provided in the following 
format:
                         `{"databases/MyDatabase.yaml": 
"my_private_key_password"}`.
                       type: string
+                    sparse:
+                      description: allow sparse update of resources
+                      type: boolean
           responses:
             200:
               description: Assets import result
@@ -177,6 +180,7 @@ class ImportExportRestApi(BaseSupersetApi):
 
         if not contents:
             raise NoValidFilesFoundError()
+        sparse = request.form.get("sparse") == "true"
 
         passwords = (
             json.loads(request.form["passwords"])
@@ -201,6 +205,7 @@ class ImportExportRestApi(BaseSupersetApi):
 
         command = ImportAssetsCommand(
             contents,
+            sparse=sparse,
             passwords=passwords,
             ssh_tunnel_passwords=ssh_tunnel_passwords,
             ssh_tunnel_private_keys=ssh_tunnel_private_keys,
diff --git a/tests/unit_tests/importexport/api_test.py 
b/tests/unit_tests/importexport/api_test.py
index 0c125c2ccf..1458631e5d 100644
--- a/tests/unit_tests/importexport/api_test.py
+++ b/tests/unit_tests/importexport/api_test.py
@@ -108,6 +108,7 @@ def test_import_assets(
     passwords = {"assets_export/databases/imported_database.yaml": "SECRET"}
     ImportAssetsCommand.assert_called_with(
         mocked_contents,
+        sparse=False,
         passwords=passwords,
         ssh_tunnel_passwords=None,
         ssh_tunnel_private_keys=None,

Reply via email to