diff --git a/src/pyedb/dotnet/edb.py b/src/pyedb/dotnet/edb.py index 28a8bc5cff..9b2c5c9266 100644 --- a/src/pyedb/dotnet/edb.py +++ b/src/pyedb/dotnet/edb.py @@ -174,15 +174,16 @@ class Edb(Database): def __init__( self, - edbpath=None, - cellname=None, - isreadonly=False, - edbversion=None, - isaedtowned=False, + edbpath: str = None, + cellname: str = None, + isreadonly: bool = False, + edbversion: str = None, + isaedtowned: bool = False, oproject=None, - student_version=False, - use_ppe=False, - technology_file=None, + student_version: bool = False, + use_ppe: bool = False, + technology_file: str = None, + remove_existing_aedt: bool = False, ): edbversion = get_string_version(edbversion) self._clean_variables() @@ -213,17 +214,8 @@ def __init__( os.path.dirname(edbpath), "pyedb_" + os.path.splitext(os.path.split(edbpath)[-1])[0] + ".log", ) - aedt_file = os.path.splitext(edbpath)[0] + ".aedt" - files = [aedt_file, aedt_file + ".lock"] - for file in files: - if os.path.isfile(file): - try: - shutil.rmtree(file) - self.logger.info(f"Removing {file} to allow loading EDB file.") - except: - self.logger.info( - f"Failed to delete {file} which is located at the same location as the EDB file." - ) + if not isreadonly: + self._check_remove_project_files(edbpath, remove_existing_aedt) if isaedtowned and (inside_desktop or settings.remote_rpc_session): self.open_edb_inside_aedt() @@ -311,6 +303,22 @@ def __setitem__(self, variable_name, variable_value): if description: # Add the variable description if a two-item list is passed for variable_value. self.__getitem__(variable_name).description = description + def _check_remove_project_files(self, edbpath: str, remove_existing_aedt: bool) -> None: + aedt_file = os.path.splitext(edbpath)[0] + ".aedt" + files = [aedt_file, aedt_file + ".lock"] + for file in files: + if os.path.isfile(file): + if not remove_existing_aedt: + self.logger.warning( + f"AEDT project-related file {file} exists and may need to be deleted before opening the EDB in HFSS 3D Layout." # noqa: E501 + ) + else: + try: + os.unlink(file) + self.logger.info(f"Deleted AEDT project-related file {file}.") + except: + self.logger.info(f"Failed to delete AEDT project-related file {file}.") + def _clean_variables(self): """Initialize internal variables and perform garbage collection.""" self._materials = None diff --git a/tests/legacy/unit/test_edb.py b/tests/legacy/unit/test_edb.py index b28e94bc4f..b74f24cbb6 100644 --- a/tests/legacy/unit/test_edb.py +++ b/tests/legacy/unit/test_edb.py @@ -39,7 +39,10 @@ def init(self, local_scratch): def test_create_edb(self): """Create EDB.""" - edb = Edb(os.path.join(self.local_scratch.path, "temp.aedb"), edbversion=desktop_version) + edb = Edb( + os.path.join(self.local_scratch.path, "temp.aedb"), + edbversion=desktop_version, + ) assert edb assert edb.active_layout edb.close() @@ -56,7 +59,10 @@ def test_variables_value(self): """Evaluate variables value.""" from pyedb.generic.general_methods import check_numeric_equivalence - edb = Edb(os.path.join(self.local_scratch.path, "temp.aedb"), edbversion=desktop_version) + edb = Edb( + os.path.join(self.local_scratch.path, "temp.aedb"), + edbversion=desktop_version, + ) edb["var1"] = 0.01 edb["var2"] = "10um" edb["var3"] = [0.03, "test description"] @@ -73,7 +79,10 @@ def test_variables_value(self): def test_add_design_variable(self): """Add a variable value.""" - edb = Edb(os.path.join(self.local_scratch.path, "temp.aedb"), edbversion=desktop_version) + edb = Edb( + os.path.join(self.local_scratch.path, "temp.aedb"), + edbversion=desktop_version, + ) is_added, _ = edb.add_design_variable("ant_length", "1cm") assert is_added is_added, _ = edb.add_design_variable("ant_length", "1cm") @@ -89,14 +98,20 @@ def test_add_design_variable(self): def test_add_design_variable_with_setitem(self): """Add a variable value.""" - edb = Edb(os.path.join(self.local_scratch.path, "temp.aedb"), edbversion=desktop_version) + edb = Edb( + os.path.join(self.local_scratch.path, "temp.aedb"), + edbversion=desktop_version, + ) edb["ant_length"] = "1cm" assert edb.variable_exists("ant_length")[0] assert edb["ant_length"].value == 0.01 def test_change_design_variable_value(self): """Change a variable value.""" - edb = Edb(os.path.join(self.local_scratch.path, "temp.aedb"), edbversion=desktop_version) + edb = Edb( + os.path.join(self.local_scratch.path, "temp.aedb"), + edbversion=desktop_version, + ) edb.add_design_variable("ant_length", "1cm") edb.add_design_variable("my_parameter_default", "1mm", is_parameter=True) edb.add_design_variable("$my_project_variable", "1mm") @@ -114,7 +129,10 @@ def test_change_design_variable_value(self): def test_change_design_variable_value_with_setitem(self): """Change a variable value.""" - edb = Edb(os.path.join(self.local_scratch.path, "temp.aedb"), edbversion=desktop_version) + edb = Edb( + os.path.join(self.local_scratch.path, "temp.aedb"), + edbversion=desktop_version, + ) edb["ant_length"] = "1cm" assert edb["ant_length"].value == 0.01 edb["ant_length"] = "2cm" @@ -122,7 +140,10 @@ def test_change_design_variable_value_with_setitem(self): def test_create_padstack_instance(self): """Create padstack instances.""" - edb = Edb(os.path.join(self.local_scratch.path, "temp.aedb"), edbversion=desktop_version) + edb = Edb( + os.path.join(self.local_scratch.path, "temp.aedb"), + edbversion=desktop_version, + ) pad_name = edb.padstacks.create( pad_shape="Rectangle", @@ -151,9 +172,12 @@ def test_create_padstack_instance(self): edb.close() @patch("os.path.isfile") - @patch("shutil.rmtree") - @patch("pyedb.dotnet.edb_core.dotnet.database.EdbDotNet.logger", new_callable=PropertyMock) - def test_conflict_files_removal_success(self, mock_logger, mock_rmtree, mock_isfile): + @patch("os.unlink") + @patch( + "pyedb.dotnet.edb_core.dotnet.database.EdbDotNet.logger", + new_callable=PropertyMock, + ) + def test_conflict_files_removal_success(self, mock_logger, mock_unlink, mock_isfile): logger_mock = MagicMock() mock_logger.return_value = logger_mock mock_isfile.side_effect = lambda file: file.endswith((".aedt", ".aedt.lock")) @@ -161,28 +185,52 @@ def test_conflict_files_removal_success(self, mock_logger, mock_rmtree, mock_isf edbpath = "file.edb" aedt_file = os.path.splitext(edbpath)[0] + ".aedt" files = [aedt_file, aedt_file + ".lock"] - _ = Edb(edbpath) + _ = Edb(edbpath, remove_existing_aedt=True) + + for file in files: + mock_unlink.assert_any_call(file) + logger_mock.info.assert_any_call(f"Deleted AEDT project-related file {file}.") + + @patch("os.path.isfile") + @patch("os.unlink") + @patch( + "pyedb.dotnet.edb_core.dotnet.database.EdbDotNet.logger", + new_callable=PropertyMock, + ) + def test_conflict_files_removal_failure(self, mock_logger, mock_unlink, mock_isfile): + logger_mock = MagicMock() + mock_logger.return_value = logger_mock + mock_isfile.side_effect = lambda file: file.endswith((".aedt", ".aedt.lock")) + mock_unlink.side_effect = Exception("Could not delete file") + + edbpath = "file.edb" + aedt_file = os.path.splitext(edbpath)[0] + ".aedt" + files = [aedt_file, aedt_file + ".lock"] + _ = Edb(edbpath, remove_existing_aedt=True) for file in files: - mock_rmtree.assert_any_call(file) - logger_mock.info.assert_any_call(f"Removing {file} to allow loading EDB file.") + mock_unlink.assert_any_call(file) + logger_mock.info.assert_any_call(f"Failed to delete AEDT project-related file {file}.") @patch("os.path.isfile") - @patch("shutil.rmtree") - @patch("pyedb.dotnet.edb_core.dotnet.database.EdbDotNet.logger", new_callable=PropertyMock) - def test_conflict_files_removal_failure(self, mock_logger, mock_rmtree, mock_isfile): + @patch("os.unlink") + @patch( + "pyedb.dotnet.edb_core.dotnet.database.EdbDotNet.logger", + new_callable=PropertyMock, + ) + def test_conflict_files_leave_in_place(self, mock_logger, mock_unlink, mock_isfile): logger_mock = MagicMock() mock_logger.return_value = logger_mock mock_isfile.side_effect = lambda file: file.endswith((".aedt", ".aedt.lock")) - mock_rmtree.side_effect = Exception("Could not delete file") + mock_unlink.side_effect = Exception("Could not delete file") edbpath = "file.edb" aedt_file = os.path.splitext(edbpath)[0] + ".aedt" files = [aedt_file, aedt_file + ".lock"] _ = Edb(edbpath) + mock_unlink.assert_not_called() for file in files: - mock_rmtree.assert_any_call(file) - logger_mock.info.assert_any_call( - f"Failed to delete {file} which is located at the same location as the EDB file." + logger_mock.warning.assert_any_call( + f"AEDT project-related file {file} exists and may need to be deleted before opening the EDB in HFSS 3D Layout." # noqa: E501 )