Просмотр исходного кода

feat: rollback when catch an exception during import

trgiangdo 1 год назад
Родитель
Сommit
edac21d979
3 измененных файлов с 90 добавлено и 46 удалено
  1. 4 4
      taipy/core/job/_job_manager.py
  2. 49 34
      taipy/core/taipy.py
  3. 37 8
      tests/core/test_taipy/test_import.py

+ 4 - 4
taipy/core/job/_job_manager.py

@@ -58,11 +58,11 @@ class _JobManager(_Manager[Job], _VersionMixin):
         return job
 
     @classmethod
-    def _delete(cls, job: Job, force=False):
-        if cls._is_deletable(job) or force:
-            super()._delete(job.id)
+    def _delete(cls, job_id: JobId, force=False):
+        if cls._is_deletable(job_id) or force:
+            super()._delete(job_id)
         else:
-            err = JobNotDeletedException(job.id)
+            err = JobNotDeletedException(job_id)
             cls._logger.error(err)
             raise err
 

+ 49 - 34
taipy/core/taipy.py

@@ -1003,7 +1003,7 @@ def export_scenario(
     _VersionManagerFactory._build_manager()._export(scenario.version, folder_path)
 
 
-def import_scenario(folder_path: Union[str, pathlib.Path], override: bool = False):
+def import_scenario(folder_path: Union[str, pathlib.Path], override: bool = False) -> Optional[Scenario]:
     """Import a folder containing an exported scenario into the current Taipy application.
 
     The folder should contain all related entities of the scenario, and all entities should
@@ -1015,12 +1015,11 @@ def import_scenario(folder_path: Union[str, pathlib.Path], override: bool = Fals
         override (bool): If True, override the entities if existed. Default value is False.
 
     Return:
-        The imported scenario.
+        The imported scenario if the import is successful.
 
     Raises:
         FileNotFoundError: If the import folder path does not exist.
         ImportFolderDoesntContainAnyScenario: If the import folder doesn't contain any scenario.
-        EntitiesToBeImportAlredyExist: If there is any entity in the import folder that has already existed.
         ConflictedConfigurationError: If the configuration of the imported scenario is conflicted with the current one.
     """
     if isinstance(folder_path, str):
@@ -1052,49 +1051,65 @@ def import_scenario(folder_path: Union[str, pathlib.Path], override: bool = Fals
         "version": _VersionManagerFactory._build_manager,
     }
 
+    # Import the version to check for compatibility
     entity_managers["version"]()._import(next((folder / "version").iterdir()), "")
 
     valid_entity_folders = list(entity_managers.keys())
     valid_data_folder = Config.core.storage_folder
 
     imported_scenario = None
-    imported_entities = []
+    imported_entities: Dict[str, List] = {}
 
     for entity_folder in folder.iterdir():
         if not entity_folder.is_dir() or entity_folder.name not in valid_entity_folders + [valid_data_folder]:
             __logger.warning(f"{entity_folder} is not a valid Taipy folder and will not be imported.")
             continue
 
-    for entity_type in valid_entity_folders:
-        # Skip the version folder as it is already handled
-        if entity_type == "version":
-            continue
-
-        entity_folder = folder / entity_type
-        if not entity_folder.exists():
-            continue
-
-        manager = entity_managers[entity_type]()
-        for entity_file in entity_folder.iterdir():
-            # Check if the to-be-imported entity already exists
-            entity_id = entity_file.stem
-            if manager._exists(entity_id):
-                if override:
-                    __logger.warning(f"{entity_id} already exists and will be overridden.")
-                else:
-                    __logger.error(f"{entity_id} already exists. Please use the 'override' parameter to override it.")
-                    raise EntitiesToBeImportAlredyExist(folder_path)
-
-            # Import the entity
-            imported_entity = manager._import(
-                entity_file,
-                version=_VersionManagerFactory._build_manager()._get_latest_version(),
-                data_folder=folder / valid_data_folder,
-            )
-
-            imported_entities.append(imported_entity)
-            if entity_type in ["scenario", "scenarios"]:
-                imported_scenario = imported_entity
+    try:
+        for entity_type in valid_entity_folders:
+            # Skip the version folder as it is already handled
+            if entity_type == "version":
+                continue
+
+            entity_folder = folder / entity_type
+            if not entity_folder.exists():
+                continue
+
+            manager = entity_managers[entity_type]()
+            imported_entities[entity_type] = []
+
+            for entity_file in entity_folder.iterdir():
+                # Check if the to-be-imported entity already exists
+                entity_id = entity_file.stem
+                if manager._exists(entity_id):
+                    if override:
+                        __logger.warning(f"{entity_id} already exists and will be overridden.")
+                    else:
+                        __logger.error(
+                            f"{entity_id} already exists. Please use the 'override' parameter to override it."
+                        )
+                        raise EntitiesToBeImportAlredyExist(folder_path)
+
+                # Import the entity
+                imported_entity = manager._import(
+                    entity_file,
+                    version=_VersionManagerFactory._build_manager()._get_latest_version(),
+                    data_folder=folder / valid_data_folder,
+                )
+
+                imported_entities[entity_type].append(imported_entity.id)
+                if entity_type in ["scenario", "scenarios"]:
+                    imported_scenario = imported_entity
+    except Exception as err:
+        __logger.error(f"An error occurred during the import: {err}. Rollback the import.")
+
+        # Rollback the import
+        for entity_type, entity_ids in list(imported_entities.items())[::-1]:
+            manager = entity_managers[entity_type]()
+            for entity_id in entity_ids:
+                if manager._exists(entity_id):
+                    manager._delete(entity_id)
+        return None
 
     __logger.info(f"Scenario {imported_scenario.id} has been successfully imported.")  # type: ignore[union-attr]
     return imported_scenario

+ 37 - 8
tests/core/test_taipy/test_import.py

@@ -22,7 +22,6 @@ from taipy.core.cycle._cycle_manager import _CycleManager
 from taipy.core.data._data_manager import _DataManager
 from taipy.core.exceptions.exceptions import (
     ConflictedConfigurationError,
-    EntitiesToBeImportAlredyExist,
     ImportFolderDoesntContainAnyScenario,
     ImportScenarioDoesntHaveAVersion,
 )
@@ -113,26 +112,56 @@ def test_import_scenario_with_data(init_managers):
     assert all(os.path.exists(dn.path) for dn in imported_scenario.data_nodes.values())
 
 
-def test_import_scenario_when_entities_are_already_existed(caplog):
+def test_import_scenario_when_entities_are_already_existed_should_rollback(caplog):
     scenario_cfg = configure_test_scenario(1, frequency=Frequency.DAILY)
     export_test_scenario(scenario_cfg)
 
     caplog.clear()
 
+    _CycleManager._delete_all()
+    _TaskManager._delete_all()
+    _DataManager._delete_all()
+    _JobManager._delete_all()
+    _ScenarioManager._delete_all()
+
+    assert len(_CycleManager._get_all()) == 0
+    assert len(_TaskManager._get_all()) == 0
+    assert len(_DataManager._get_all()) == 0
+    assert len(_JobManager._get_all()) == 0
+    assert len(_SubmissionManager._get_all()) == 1  # Keep the submission entity to test the rollback
+    submission_id = _SubmissionManager._get_all()[0].id
+    assert len(_ScenarioManager._get_all()) == 0
+
     # Import the scenario when the old entities still exist
-    with pytest.raises(EntitiesToBeImportAlredyExist):
-        tp.import_scenario("./tmp/exp_scenario")
-    assert all(log.levelname == "ERROR" for log in caplog.records[1:])
+    imported_entity = tp.import_scenario("./tmp/exp_scenario")
+    assert imported_entity is None
+    assert all(log.levelname in ["ERROR", "INFO"] for log in caplog.records)
+    assert "An error occurred during the import" in caplog.text
+    assert f"{submission_id} already exists. Please use the 'override' parameter to override it" in caplog.text
+
+    # No entity should be imported and the old entities should be kept
+    assert len(_CycleManager._get_all()) == 0
+    assert len(_TaskManager._get_all()) == 0
+    assert len(_DataManager._get_all()) == 0
+    assert len(_JobManager._get_all()) == 0
+    assert len(_SubmissionManager._get_all()) == 1  # Keep the submission entity to test the rollback
+    assert len(_ScenarioManager._get_all()) == 0
 
     caplog.clear()
 
     # Import with override flag
-    assert len(_ScenarioManager._get_all()) == 1
     tp.import_scenario("./tmp/exp_scenario", override=True)
-    assert all(log.levelname == "WARNING" for log in caplog.records[1:])
+    assert all(log.levelname in ["WARNING", "INFO"] for log in caplog.records)
+    assert f"{submission_id} already exists and will be overridden" in caplog.text
 
-    # The scenario is overridden
+    # The scenario is imported and overridden the old one
     assert len(_ScenarioManager._get_all()) == 1
+    assert len(_CycleManager._get_all()) == 1
+    assert len(_TaskManager._get_all()) == 4
+    assert len(_DataManager._get_all()) == 5
+    assert len(_JobManager._get_all()) == 4
+    assert len(_SubmissionManager._get_all()) == 1
+    assert len(_VersionManager._get_all()) == 1
 
 
 def test_import_incompatible_scenario(init_managers):