From 043bae853128a358c9a24fac5687a063b979866f Mon Sep 17 00:00:00 2001 From: Antti Soininen Date: Tue, 14 Jan 2025 10:59:36 +0200 Subject: [PATCH] Migrate to SQLAlchemy 1.4 - low-level queries now require DatabaseMapping to be used inside a 'with' block. - Fixed unit tests, some bonus Tracebacks as well. Re spine-tools/Spine-Database-API#121 --- spine_items/data_store/data_store.py | 2 +- .../widgets/class_tree_widget.py | 20 ++++++++--------- .../widgets/parameter_tree_widget.py | 22 +++++++++---------- spine_items/utils.py | 4 ++-- .../test_specification_editor_window.py | 13 +++++++++++ 5 files changed, 36 insertions(+), 25 deletions(-) diff --git a/spine_items/data_store/data_store.py b/spine_items/data_store/data_store.py index f3e53262..49a79c35 100644 --- a/spine_items/data_store/data_store.py +++ b/spine_items/data_store/data_store.py @@ -334,7 +334,7 @@ def copy_url(self, checked=False): sa_url = convert_to_sqlalchemy_url(self._url, self.name, self._logger) if sa_url is None: return - sa_url.password = None + sa_url = sa_url.set(password=None) QApplication.clipboard().setText(str(sa_url)) self._logger.msg.emit(f"Database url {sa_url} copied to clipboard") diff --git a/spine_items/data_transformer/widgets/class_tree_widget.py b/spine_items/data_transformer/widgets/class_tree_widget.py index 028b4726..7dd748c6 100644 --- a/spine_items/data_transformer/widgets/class_tree_widget.py +++ b/spine_items/data_transformer/widgets/class_tree_widget.py @@ -34,21 +34,19 @@ def load_data(self, url): Args: url (str): database URL """ + classes = [] try: - db_map = DatabaseMapping(url) + with DatabaseMapping(url) as db_map: + try: + for class_row in db_map.query(db_map.entity_class_sq): + classes.append(class_row.name) + except SpineDBAPIError as error: + QMessageBox.information( + self, "Error while reading database", f"Could not read from database {url}:\n{error}" + ) except SpineDBAPIError as error: QMessageBox.information(self, "Error while opening database", f"Could not open database {url}:\n{error}") return - classes = [] - try: - for class_row in db_map.query(db_map.entity_class_sq): - classes.append(class_row.name) - except SpineDBAPIError as error: - QMessageBox.information( - self, "Error while reading database", f"Could not read from database {url}:\n{error}" - ) - finally: - db_map.close() self.clear() for class_name in classes: self.addTopLevelItem(QTreeWidgetItem([class_name])) diff --git a/spine_items/data_transformer/widgets/parameter_tree_widget.py b/spine_items/data_transformer/widgets/parameter_tree_widget.py index 56da4261..1c481d53 100644 --- a/spine_items/data_transformer/widgets/parameter_tree_widget.py +++ b/spine_items/data_transformer/widgets/parameter_tree_widget.py @@ -40,21 +40,21 @@ def load_data(self, url): Args: url (str): database URL """ + parameters = {} try: - db_map = DatabaseMapping(url) + with DatabaseMapping(url) as db_map: + try: + for definition_row in db_map.query(db_map.entity_parameter_definition_sq): + parameters.setdefault(definition_row.entity_class_name, []).append( + definition_row.parameter_name + ) + except SpineDBAPIError as error: + QMessageBox.information( + self, "Error while reading database", f"Could not read from database {url}:\n{error}" + ) except SpineDBAPIError as error: QMessageBox.information(self, "Error while opening database", f"Could not open database {url}:\n{error}") return - parameters = {} - try: - for definition_row in db_map.query(db_map.entity_parameter_definition_sq): - parameters.setdefault(definition_row.entity_class_name, []).append(definition_row.parameter_name) - except SpineDBAPIError as error: - QMessageBox.information( - self, "Error while reading database", f"Could not read from database {url}:\n{error}" - ) - finally: - db_map.close() self.clear() for class_name, parameter_names in parameters.items(): class_item = QTreeWidgetItem([class_name]) diff --git a/spine_items/utils.py b/spine_items/utils.py index 33746fd8..41c15337 100644 --- a/spine_items/utils.py +++ b/spine_items/utils.py @@ -85,12 +85,12 @@ def _convert_url(url): database = url.get("database", "") if database: url["database"] = os.path.normcase(os.path.abspath(database)) - return URL("sqlite", **url) # pylint: disable=unexpected-keyword-arg + return URL.create("sqlite", **url) # pylint: disable=unexpected-keyword-arg db_api = spinedb_api.SUPPORTED_DIALECTS.get(dialect) if db_api is None: db_api = spinedb_api.helpers.UNSUPPORTED_DIALECTS[dialect] driver_name = f"{dialect}+{db_api}" - return URL(driver_name, **url) # pylint: disable=unexpected-keyword-arg + return URL.create(driver_name, **url) # pylint: disable=unexpected-keyword-arg except Exception as error: raise URLError(str(error)) from error diff --git a/tests/exporter/widgets/test_specification_editor_window.py b/tests/exporter/widgets/test_specification_editor_window.py index 47ee7c54..59145554 100644 --- a/tests/exporter/widgets/test_specification_editor_window.py +++ b/tests/exporter/widgets/test_specification_editor_window.py @@ -43,6 +43,7 @@ def test_empty_editor(self): editor = SpecificationEditorWindow(self._toolbox) self.assertEqual(editor._ui.mappings_table.model().rowCount(), 1) self.assertEqual(editor._ui.mappings_table.model().index(0, 0).data(), "Mapping (1)") + editor.tear_down() def test_mapping_in_table_name_position_disables_fixed_table_name_widgets(self): editor = SpecificationEditorWindow(self._toolbox) @@ -57,6 +58,11 @@ def test_mapping_in_table_name_position_disables_fixed_table_name_widgets(self): self.assertFalse(editor._ui.fix_table_name_check_box.isChecked()) self.assertFalse(editor._ui.fix_table_name_line_edit.isEnabled()) self.assertEqual(editor._ui.fix_table_name_line_edit.text(), "") + with mock.patch( + "spinetoolbox.project_item.specification_editor_window.SpecificationEditorWindowBase.tear_down" + ) as tear_down_window: + tear_down_window.return_value = True + editor.tear_down() def test_mapping_with_fixed_table_enables_the_check_box_and_fills_the_table_name_field(self): flattened_mappings = [FixedValueMapping(Position.table_name, "nice table name"), EntityClassMapping(0)] @@ -69,6 +75,7 @@ def test_mapping_with_fixed_table_enables_the_check_box_and_fills_the_table_name self.assertTrue(editor._ui.fix_table_name_check_box.isChecked()) self.assertTrue(editor._ui.fix_table_name_line_edit.isEnabled()) self.assertEqual(editor._ui.fix_table_name_line_edit.text(), "nice table name") + editor.tear_down() def test_duplicate_specification(self): flattened_mappings = [FixedValueMapping(Position.table_name, "nice table name"), EntityClassMapping(0)] @@ -96,6 +103,11 @@ def test_duplicate_specification(self): show_duplicate.call_args.args[1].mapping_specifications()["my mappings"], mapping_specification ) self.assertEqual(show_duplicate.call_args.kwargs, {}) + with mock.patch( + "spinetoolbox.project_item.specification_editor_window.SpecificationEditorWindowBase.tear_down" + ) as tear_down_window: + tear_down_window.return_value = True + editor.tear_down() def test_forced_decrease_of_selected_dimension_by_entity_dimensions_is_stored_properly(self): mapping_dicts = [ @@ -143,6 +155,7 @@ def test_forced_decrease_of_selected_dimension_by_entity_dimensions_is_stored_pr {"map_type": "ParameterValue", "position": 5}, ] self.assertEqual(loaded_specification.mapping_specifications()["my mappings"].to_dict()["root"], expected_dicts) + editor.tear_down() if __name__ == "__main__":