From 7a5fcdb2a0f55b31afcbd10694fa99e49afdb126 Mon Sep 17 00:00:00 2001 From: Antti Soininen Date: Fri, 20 Feb 2026 09:25:25 +0200 Subject: [PATCH 1/2] Add export mapping for entity class and entity descriptions The new description mappings were added to mapping settings where they make sense, i.e. the ones where we export only entity classes and/or entities. Having them part of e.g. parameter value mappings doesn't make sense since the description would then be replicated to every value row. Re spine-tools/Spine-Toolbox#1892 --- spinedb_api/export_mapping/export_mapping.py | 46 ++- spinedb_api/export_mapping/settings.py | 200 ++++++------ tests/conftest.py | 19 ++ tests/export_mapping/test_export_mapping.py | 4 +- tests/export_mapping/test_settings.py | 300 +++++++++++------- tests/spine_io/exporters/test_csv_writer.py | 12 +- tests/spine_io/exporters/test_excel_writer.py | 18 +- tests/spine_io/exporters/test_gdx_writer.py | 23 +- tests/spine_io/exporters/test_sql_writer.py | 20 +- tests/spine_io/exporters/test_writer.py | 13 +- tests/test_scenario_recipes.py | 8 +- 11 files changed, 401 insertions(+), 262 deletions(-) create mode 100644 tests/conftest.py diff --git a/spinedb_api/export_mapping/export_mapping.py b/spinedb_api/export_mapping/export_mapping.py index f5643381..1451e1e4 100644 --- a/spinedb_api/export_mapping/export_mapping.py +++ b/spinedb_api/export_mapping/export_mapping.py @@ -1228,30 +1228,54 @@ def is_buddy(parent): return isinstance(parent, ScenarioAlternativeMapping) -class _DescriptionMappingBase(ExportMapping): - """Maps descriptions.""" - - MAP_TYPE = "Description" - name_field = "description" - id_field = "description" - - -class AlternativeDescriptionMapping(_DescriptionMappingBase): +class AlternativeDescriptionMapping(ExportMapping): """Maps alternative descriptions. Cannot be used as the topmost mapping; must have :class:`AlternativeMapping` as parent. """ MAP_TYPE = "AlternativeDescription" + name_field = "description" + id_field = "description" -class ScenarioDescriptionMapping(_DescriptionMappingBase): +class ScenarioDescriptionMapping(ExportMapping): """Maps scenario descriptions. Cannot be used as the topmost mapping; must have :class:`ScenarioMapping` as parent. """ MAP_TYPE = "ScenarioDescription" + name_field = "description" + id_field = "description" + + +class EntityClassDescriptionMapping(ExportMapping): + """Maps entity class descriptions. + + Cannot be used as the topmost mapping; must have :class:`EntityClassMapping` as parent. + """ + + MAP_TYPE = "EntityClassDescription" + name_field = "entity_class_description" + id_field = "entity_class_description" + + def build_query_columns(self, db_map, columns): + columns.append(db_map.wide_entity_class_sq.c.description.label("entity_class_description")) + + +class EntityDescriptionMapping(ExportMapping): + """Maps entity descriptions. + + Cannot be used as the topmost mapping; must have :class:`EntityMapping` as parent. + """ + + MAP_TYPE = "EntityDescription" + name_field = "entity_description" + id_field = "entity_description" + + def build_query_columns(self, db_map, columns): + columns.append(db_map.wide_entity_sq.c.description.label("entity_description")) class MetadataNameMapping(ExportMapping): @@ -1484,9 +1508,11 @@ def from_dict(serialized): ExpandedParameterValueMapping, FixedValueMapping, IndexNameMapping, + EntityClassDescriptionMapping, EntityClassMapping, EntityGroupMapping, EntityGroupEntityMapping, + EntityDescriptionMapping, EntityMapping, EntityMetadataNameMapping, EntityMetadataValueMapping, diff --git a/spinedb_api/export_mapping/settings.py b/spinedb_api/export_mapping/settings.py index ea8bdecb..18705ee2 100644 --- a/spinedb_api/export_mapping/settings.py +++ b/spinedb_api/export_mapping/settings.py @@ -18,7 +18,9 @@ DefaultValueIndexNameMapping, DimensionMapping, ElementMapping, + EntityClassDescriptionMapping, EntityClassMapping, + EntityDescriptionMapping, EntityGroupEntityMapping, EntityGroupMapping, EntityMapping, @@ -71,97 +73,103 @@ def entity_group_export( def entity_export( - entity_class_position=Position.hidden, - entity_position=Position.hidden, - dimension_positions=None, - element_positions=None, -): + entity_class_position: Position | int = Position.hidden, + entity_class_description_position: Position | int = Position.hidden, + entity_position: Position | int = Position.hidden, + entity_description_position: Position | int = Position.hidden, + dimension_positions: list[Position | int] | None = None, + element_positions: list[Position | int] | None = None, +) -> EntityClassMapping: """ Sets up export items for exporting entities without parameters. Args: - entity_class_position (int or Position): position of entity classes in a table - entity_position (int or Position): position of entities in a table - dimension_positions (Iterable, optional): positions of dimension in a table - element_positions (Iterable, optional): positions of element in a table + entity_class_position: position of entity classes in a table + entity_class_description_position: position of entity class description. + entity_position: position of entities in a table + entity_description_position: position of entity description. + dimension_positions: positions of dimension in a table + element_positions: positions of element in a table Returns: - ExportMapping: root mapping + root mapping """ if dimension_positions is None: dimension_positions = [] if element_positions is None: element_positions = [] entity_class = EntityClassMapping(entity_class_position) - dimension = _generate_dimensions(entity_class, DimensionMapping, dimension_positions) - entity = EntityMapping(entity_position) - dimension.child = entity - _generate_dimensions(entity, ElementMapping, element_positions) + entity_class_description = entity_class.child = EntityClassDescriptionMapping(entity_class_description_position) + entity_class_description.set_ignorable(True) + dimension = _generate_dimensions(entity_class_description, DimensionMapping, dimension_positions) + entity = dimension.child = EntityMapping(entity_position) + entity_description = entity.child = EntityDescriptionMapping(entity_description_position) + entity_description.set_ignorable(True) + _generate_dimensions(entity_description, ElementMapping, element_positions) return entity_class def entity_parameter_default_value_export( - entity_class_position=Position.hidden, - definition_position=Position.hidden, - value_type_position=Position.hidden, - value_position=Position.hidden, - index_name_positions=None, - index_positions=None, -): + entity_class_position: Position | int = Position.hidden, + definition_position: Position | int = Position.hidden, + value_type_position: Position | int = Position.hidden, + value_position: Position | int = Position.hidden, + index_name_positions: list[Position | int] | None = None, + index_positions: list[Position | int] | None = None, +) -> EntityClassMapping: """ Sets up export mappings for exporting entity classes and default parameter values. Args: - entity_class_position (int or Position): position of relationship classes - definition_position (int or Position): position of parameter definitions - value_type_position (int or Position): position of parameter value types - value_position (int or Position): position of parameter values - index_name_positions (list of int, optional): positions of index names - index_positions (list of int, optional): positions of parameter indexes + entity_class_position: position of relationship classes + definition_position: position of parameter definitions + value_type_position: position of parameter value types + value_position: position of parameter values + index_name_positions: positions of index names + index_positions: positions of parameter indexes Returns: - ExportMapping: root mapping + root mapping """ entity_class = EntityClassMapping(entity_class_position) - definition = ParameterDefinitionMapping(definition_position) + definition = entity_class.child = ParameterDefinitionMapping(definition_position) _generate_default_value_mappings( definition, value_type_position, value_position, index_name_positions, index_positions ) - entity_class.child = definition return entity_class def entity_parameter_value_export( - entity_class_position=Position.hidden, - definition_position=Position.hidden, - value_list_position=Position.hidden, - entity_position=Position.hidden, - dimension_positions=None, - element_positions=None, - alternative_position=Position.hidden, - value_type_position=Position.hidden, - value_position=Position.hidden, - index_name_positions=None, - index_positions=None, -): + entity_class_position: Position | int = Position.hidden, + definition_position: Position | int = Position.hidden, + value_list_position: Position | int = Position.hidden, + entity_position: Position | int = Position.hidden, + dimension_positions: list[Position | int] | None = None, + element_positions: list[Position | int] | None = None, + alternative_position: Position | int = Position.hidden, + value_type_position: Position | int = Position.hidden, + value_position: Position | int = Position.hidden, + index_name_positions: list[Position | int] | None = None, + index_positions: list[Position | int] | None = None, +) -> EntityClassMapping: """ Sets up export mappings for exporting entities and parameter values. Args: - entity_class_position (int or Position): position of entity classes - definition_position (int or Position): position of parameter definitions - value_list_position (int or Position): position of parameter value lists - entity_position (int or Position): position of entities - dimension_positions (list of int, optional): positions of dimensions - element_positions (list of int, optional): positions of elements - alternative_position (int or Position): positions of alternatives - value_type_position (int or Position): position of parameter value types - value_position (int or Position): position of parameter values - index_name_positions (list of int, optional): positions of index names - index_positions (list of int, optional): positions of parameter indexes + entity_class_position: position of entity classes + definition_position: position of parameter definitions + value_list_position: position of parameter value lists + entity_position: position of entities + dimension_positions: positions of dimensions + element_positions: positions of elements + alternative_position: positions of alternatives + value_type_position: position of parameter value types + value_position: position of parameter values + index_name_positions: positions of index names + index_positions: positions of parameter indexes Returns: - ExportMapping: root mapping + root mapping """ if dimension_positions is None: dimension_positions = [] @@ -169,13 +177,10 @@ def entity_parameter_value_export( element_positions = [] entity_class = EntityClassMapping(entity_class_position) dimension = _generate_dimensions(entity_class, DimensionMapping, dimension_positions) - value_list = ParameterValueListMapping(value_list_position) + definition = dimension.child = ParameterDefinitionMapping(definition_position) + value_list = definition.child = ParameterValueListMapping(value_list_position) value_list.set_ignorable(True) - definition = ParameterDefinitionMapping(definition_position) - dimension.child = definition - relationship = EntityMapping(entity_position) - definition.child = value_list - value_list.child = relationship + relationship = value_list.child = EntityMapping(entity_position) element = _generate_dimensions(relationship, ElementMapping, element_positions) _generate_parameter_value_mappings( element, @@ -228,40 +233,39 @@ def entity_dimension_parameter_default_value_export( def entity_dimension_parameter_value_export( - entity_class_position=Position.hidden, - definition_position=Position.hidden, - value_list_position=Position.hidden, - entity_position=Position.hidden, - dimension_positions=None, - element_positions=None, - alternative_position=Position.hidden, - value_type_position=Position.hidden, - value_position=Position.hidden, - index_name_positions=None, - index_positions=None, - highlight_position=0, -): + entity_class_position: Position | int = Position.hidden, + definition_position: Position | int = Position.hidden, + value_list_position: Position | int = Position.hidden, + entity_position: Position | int = Position.hidden, + dimension_positions: list[Position | int] | None = None, + element_positions: list[Position | int] | None = None, + alternative_position: Position | int = Position.hidden, + value_type_position: Position | int = Position.hidden, + value_position: Position | int = Position.hidden, + index_name_positions: list[Position | int] | None = None, + index_positions: list[Position | int] | None = None, + highlight_position: int | None = 0, +) -> EntityClassMapping: """ Sets up export mappings for exporting entities and element parameter values. Args: - entity_class_position (int or Position): position of entity classes - definition_position (int or Position): position of parameter definitions - value_list_position (int or Position): position of parameter value lists - entity_position (int or Position): position of relationships - dimension_positions (list of int, optional): positions of object classes - element_positions (list of int, optional): positions of objects - alternative_position (int or Position): positions of alternatives - value_type_position (int or Position): position of parameter value types - value_position (int or Position): position of parameter values - index_name_positions (list of int, optional): positions of index names - index_positions (list of int, optional): positions of parameter indexes - highlight_position (int): selected dimension position + entity_class_position: position of entity classes + definition_position: position of parameter definitions + value_list_position: position of parameter value lists + entity_position: position of relationships + dimension_positions: positions of object classes + element_positions: positions of objects + alternative_position: positions of alternatives + value_type_position: position of parameter value types + value_position: position of parameter values + index_name_positions: positions of index names + index_positions: positions of parameter indexes + highlight_position: selected dimension position Returns: - ExportMapping: root mapping + root mapping """ - # TODO fix dimension highlighting if dimension_positions is None: dimension_positions = [] if element_positions is None: @@ -287,20 +291,32 @@ def entity_dimension_parameter_value_export( return entity_class -def set_entity_dimensions(entity_mapping, dimensions): +def set_entity_dimensions(entity_mapping: EntityClassMapping, dimensions: int) -> None: """ Modifies given entity mapping's dimensions. Args: - entity_mapping (ExportMapping): an entity mapping - dimensions (int): number of dimensions + entity_mapping: an entity mapping + dimensions: number of dimensions """ mapping_list = entity_mapping.flatten() + dimension_parent_class = ( + EntityClassDescriptionMapping + if any(isinstance(mapping, EntityDescriptionMapping) for mapping in mapping_list) + else EntityClassMapping + ) mapping_list = _change_amount_of_consecutive_mappings( - mapping_list, EntityClassMapping, DimensionMapping, dimensions + mapping_list, dimension_parent_class, DimensionMapping, dimensions ) if any(isinstance(m, EntityMapping) for m in mapping_list): - mapping_list = _change_amount_of_consecutive_mappings(mapping_list, EntityMapping, ElementMapping, dimensions) + element_parent_class = ( + EntityDescriptionMapping + if any(isinstance(mapping, EntityDescriptionMapping) for mapping in mapping_list) + else EntityMapping + ) + mapping_list = _change_amount_of_consecutive_mappings( + mapping_list, element_parent_class, ElementMapping, dimensions + ) unflatten(mapping_list) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..782cbd59 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,19 @@ +###################################################################################################################### +# Copyright (C) 2017-2022 Spine project consortium +# Copyright Spine Database API contributors +# This file is part of Spine Database API. +# Spine Database API is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser +# General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +# Public License for more details. You should have received a copy of the GNU Lesser General Public License along with +# this program. If not, see . +###################################################################################################################### +import pytest +from spinedb_api import DatabaseMapping + + +@pytest.fixture +def db_map(): + with DatabaseMapping("sqlite://", create=True) as db_map: + yield db_map diff --git a/tests/export_mapping/test_export_mapping.py b/tests/export_mapping/test_export_mapping.py index daf7153b..68931bbe 100644 --- a/tests/export_mapping/test_export_mapping.py +++ b/tests/export_mapping/test_export_mapping.py @@ -621,7 +621,9 @@ def test_object_relationship_name_as_table_name(self): import_relationship_classes(db_map, (("rc", ("oc1", "oc2")),)) import_relationships(db_map, (("rc", ("o1", "O")), ("rc", ("o2", "O")))) db_map.commit_session("Add test data.") - mappings = entity_export(0, Position.table_name, [1, 2], [Position.table_name, 3]) + mappings = entity_export( + 0, Position.hidden, Position.table_name, Position.hidden, [1, 2], [Position.table_name, 3] + ) tables = {} for title, title_key in titles(mappings, db_map): tables[title] = list(rows(mappings, db_map, {}, title_key)) diff --git a/tests/export_mapping/test_settings.py b/tests/export_mapping/test_settings.py index b3476dae..eb493873 100644 --- a/tests/export_mapping/test_settings.py +++ b/tests/export_mapping/test_settings.py @@ -15,22 +15,21 @@ import numpy from spinedb_api import ( DatabaseMapping, + Map, TimeSeriesFixedResolution, import_object_classes, - import_object_parameter_values, import_object_parameters, - import_objects, import_relationship_classes, - import_relationship_parameter_values, import_relationship_parameters, - import_relationships, ) from spinedb_api.export_mapping import rows from spinedb_api.export_mapping.export_mapping import ( DefaultValueIndexNameMapping, DimensionMapping, ElementMapping, + EntityClassDescriptionMapping, EntityClassMapping, + EntityDescriptionMapping, EntityMapping, EntityMetadataNameMapping, EntityMetadataValueMapping, @@ -64,50 +63,105 @@ from tests.mock_helpers import AssertSuccessTestCase -class TestEntityParameterExport(AssertSuccessTestCase): - def test_export_with_parameter_values(self): - with DatabaseMapping("sqlite://", create=True) as db_map: - self._assert_imports(import_object_classes(db_map, ("oc1", "oc2"))) - self._assert_imports(import_objects(db_map, (("oc1", "o1"), ("oc2", "o2"), ("oc2", "o3")))) - self._assert_imports(import_relationship_classes(db_map, (("rc", ("oc1", "oc2")),))) - self._assert_imports(import_relationship_parameters(db_map, (("rc", "p"),))) - self._assert_imports(import_relationships(db_map, (("rc", ("o1", "o2")), ("rc", ("o1", "o3"))))) - self._assert_imports( - import_relationship_parameter_values( - db_map, - ( - ( - "rc", - ("o1", "o2"), - "p", - TimeSeriesFixedResolution("2022-06-22T11:00", "1h", [-1.1, -2.2], False, False), - ), - ( - "rc", - ("o1", "o3"), - "p", - TimeSeriesFixedResolution("2022-06-22T11:00", "1h", [-3.3, -4.4], False, False), - ), - ), - ) - ) - db_map.commit_session("Add test data.") - root_mapping = entity_parameter_value_export( - element_positions=[-1, -2], - value_position=-3, - index_name_positions=[Position.hidden], - index_positions=[0], - ) - expected = [ - [None, "o1", "o1"], - [None, "o2", "o3"], - [numpy.datetime64("2022-06-22T11:00:00"), -1.1, -3.3], - [numpy.datetime64("2022-06-22T12:00:00"), -2.2, -4.4], - ] - self.assertEqual(list(rows(root_mapping, db_map, {})), expected) +class TestEntityExport: + def test_export_class_description(self, db_map): + db_map.add_entity_class(name="cat", description="A feline creature.") + db_map.commit_session("Add test cat.") + root_mapping = entity_export(entity_class_position=0, entity_class_description_position=1) + entity_mapping = next(iter(mapping for mapping in root_mapping.flatten() if isinstance(mapping, EntityMapping))) + entity_mapping.set_ignorable(True) + expected = [["cat", "A feline creature."]] + assert list(rows(root_mapping, db_map, {})) == expected + + def test_export_entity_description(self, db_map): + db_map.add_entity_class(name="cat") + db_map.add_entity( + entity_class_name="cat", name="Garfield", description="A ball of fur that'll empty the fridge." + ) + db_map.commit_session("Add test cat.") + root_mapping = entity_export(entity_class_position=0, entity_position=1, entity_description_position=2) + expected = [["cat", "Garfield", "A ball of fur that'll empty the fridge."]] + assert list(rows(root_mapping, db_map, {})) == expected + + def test_export_dimensions_and_elements(self, db_map): + db_map.add_entity_class(name="cat") + db_map.add_entity(entity_class_name="cat", name="Garfield") + db_map.add_entity_class(name="mouse") + db_map.add_entity(entity_class_name="mouse", name="Mickey") + db_map.add_entity_class(dimension_name_list=("mouse", "cat")) + db_map.add_entity(entity_class_name="mouse__cat", entity_byname=("Mickey", "Garfield")) + db_map.commit_session("Add test cat.") + root_mapping = entity_export( + entity_class_position=0, dimension_positions=[1, 2], entity_position=3, element_positions=[4, 5] + ) + expected = [["mouse__cat", "mouse", "cat", "Mickey__Garfield", "Mickey", "Garfield"]] + assert list(rows(root_mapping, db_map, {})) == expected -class TestEntityClassDimensionParameterDefaultValueExport(AssertSuccessTestCase): +class TestEntityParameterDefaultValueExport: + def test_export_indexed_default_value(self, db_map): + db_map.add_entity_class(name="cat") + db_map.add_parameter_definition( + entity_class_name="cat", name="laziness", parsed_value=Map(["Mon", "Wed"], [2.3, 3.2], index_name="weekday") + ) + db_map.commit_session("Add test cat.") + root_mapping = entity_parameter_default_value_export( + entity_class_position=0, + definition_position=1, + value_type_position=2, + value_position=3, + index_name_positions=[4], + index_positions=[5], + ) + expected = [ + ["cat", "laziness", "1d_map", 2.3, "weekday", numpy.str_("Mon")], + ["cat", "laziness", "1d_map", 3.2, "weekday", numpy.str_("Wed")], + ] + assert list(rows(root_mapping, db_map, {})) == expected + + +class TestEntityParameterValueExport: + def test_export_with_indexed_parameter_values(self, db_map): + db_map.add_entity_class(name="oc1") + db_map.add_entity_class(name="oc2") + db_map.add_entity(entity_class_name="oc1", name="o1") + db_map.add_entity(entity_class_name="oc2", name="o2") + db_map.add_entity(entity_class_name="oc2", name="o3") + db_map.add_entity_class(name="rc", dimension_name_list=("oc1", "oc2")) + db_map.add_parameter_definition(entity_class_name="rc", name="p") + db_map.add_entity(entity_class_name="rc", entity_byname=("o1", "o2")) + db_map.add_entity(entity_class_name="rc", entity_byname=("o1", "o3")) + db_map.add_parameter_value( + entity_class_name="rc", + entity_byname=("o1", "o2"), + parameter_definition_name="p", + alternative_name="Base", + parsed_value=TimeSeriesFixedResolution("2022-06-22T11:00", "1h", [-1.1, -2.2], False, False), + ) + db_map.add_parameter_value( + entity_class_name="rc", + entity_byname=("o1", "o3"), + parameter_definition_name="p", + alternative_name="Base", + parsed_value=TimeSeriesFixedResolution("2022-06-22T11:00", "1h", [-3.3, -4.4], False, False), + ) + db_map.commit_session("Add test data.") + root_mapping = entity_parameter_value_export( + element_positions=[-1, -2], + value_position=-3, + index_name_positions=[Position.hidden], + index_positions=[0], + ) + expected = [ + [None, "o1", "o1"], + [None, "o2", "o3"], + [numpy.datetime64("2022-06-22T11:00:00"), -1.1, -3.3], + [numpy.datetime64("2022-06-22T12:00:00"), -2.2, -4.4], + ] + assert list(rows(root_mapping, db_map, {})) == expected + + +class TestEntityDimensionParameterDefaultValueExport(AssertSuccessTestCase): def test_export_with_two_dimensions(self): with DatabaseMapping("sqlite://", create=True) as db_map: self._assert_imports(import_object_classes(db_map, ("oc1", "oc2"))) @@ -133,49 +187,75 @@ def test_export_with_two_dimensions(self): self.assertEqual(list(rows(root_mapping, db_map, {})), expected) -class TestEntityElementParameterExport(AssertSuccessTestCase): - def test_export_with_two_dimensions(self): - with DatabaseMapping("sqlite://", create=True) as db_map: - self._assert_imports(import_object_classes(db_map, ("oc1", "oc2"))) - self._assert_imports(import_object_parameters(db_map, (("oc1", "p11"), ("oc1", "p12"), ("oc2", "p21")))) - self._assert_imports(import_objects(db_map, (("oc1", "o11"), ("oc1", "o12"), ("oc2", "o21")))) - self._assert_imports( - import_object_parameter_values( - db_map, - ( - ("oc1", "o11", "p11", 2.3), - ("oc1", "o12", "p11", -2.3), - ("oc1", "o12", "p12", -5.0), - ("oc2", "o21", "p21", "shouldn't show"), - ), - ) - ) - self._assert_imports(import_relationship_classes(db_map, (("rc", ("oc1", "oc2")),))) - self._assert_imports(import_relationship_parameters(db_map, (("rc", "rc_p"),))) - self._assert_imports(import_relationships(db_map, (("rc", ("o11", "o21")), ("rc", ("o12", "o21"))))) - self._assert_imports( - import_relationship_parameter_values(db_map, (("rc", ("o11", "o21"), "rc_p", "dummy"),)) - ) - db_map.commit_session("Add test data.") - root_mapping = entity_dimension_parameter_value_export( - entity_class_position=0, - definition_position=1, - value_list_position=Position.hidden, - entity_position=2, - dimension_positions=[3, 4], - element_positions=[5, 6], - alternative_position=7, - value_type_position=8, - value_position=9, - highlight_position=0, - ) - set_entity_dimensions(root_mapping, 2) - expected = [ - ["rc", "p11", "o11__o21", "oc1", "oc2", "o11", "o21", "Base", "float", 2.3], - ["rc", "p11", "o12__o21", "oc1", "oc2", "o12", "o21", "Base", "float", -2.3], - ["rc", "p12", "o12__o21", "oc1", "oc2", "o12", "o21", "Base", "float", -5.0], - ] - self.assertEqual(list(rows(root_mapping, db_map, {})), expected) +class TestEntityDimensionParameterExport: + def test_export_with_two_dimensions(self, db_map): + db_map.add_entity_class(name="oc1") + db_map.add_entity_class(name="oc2") + db_map.add_parameter_definition(entity_class_name="oc1", name="p11") + db_map.add_parameter_definition(entity_class_name="oc1", name="p12") + db_map.add_parameter_definition(entity_class_name="oc2", name="p21") + db_map.add_entity(entity_class_name="oc1", name="o11") + db_map.add_entity(entity_class_name="oc1", name="o12") + db_map.add_entity(entity_class_name="oc2", name="o21") + db_map.add_parameter_value( + entity_class_name="oc1", + entity_byname=("o11",), + parameter_definition_name="p11", + alternative_name="Base", + parsed_value=2.3, + ) + db_map.add_parameter_value( + entity_class_name="oc1", + entity_byname=("o12",), + parameter_definition_name="p11", + alternative_name="Base", + parsed_value=-2.3, + ) + db_map.add_parameter_value( + entity_class_name="oc1", + entity_byname=("o12",), + parameter_definition_name="p12", + alternative_name="Base", + parsed_value=-5.0, + ) + db_map.add_parameter_value( + entity_class_name="oc2", + entity_byname=("o21",), + parameter_definition_name="p21", + alternative_name="Base", + parsed_value="shouldn't show", + ) + db_map.add_entity_class(name="rc", dimension_name_list=("oc1", "oc2")) + db_map.add_parameter_definition(entity_class_name="rc", name="rc_p") + db_map.add_entity(entity_class_name="rc", entity_byname=("o11", "o21")) + db_map.add_entity(entity_class_name="rc", entity_byname=("o12", "o21")) + db_map.add_parameter_value( + entity_class_name="rc", + entity_byname=("o11", "o21"), + parameter_definition_name="rc_p", + alternative_name="Base", + parsed_value="dummy", + ) + db_map.commit_session("Add test data.") + root_mapping = entity_dimension_parameter_value_export( + entity_class_position=0, + definition_position=1, + value_list_position=Position.hidden, + entity_position=2, + dimension_positions=[3, 4], + element_positions=[5, 6], + alternative_position=7, + value_type_position=8, + value_position=9, + highlight_position=0, + ) + set_entity_dimensions(root_mapping, 2) + expected = [ + ["rc", "p11", "o11__o21", "oc1", "oc2", "o11", "o21", "Base", "float", 2.3], + ["rc", "p11", "o12__o21", "oc1", "oc2", "o12", "o21", "Base", "float", -2.3], + ["rc", "p12", "o12__o21", "oc1", "oc2", "o12", "o21", "Base", "float", -5.0], + ] + assert list(rows(root_mapping, db_map, {})) == expected class TestMetadataExport: @@ -280,74 +360,82 @@ def test_metadata_is_exported(self): class TestSetEntityDimensions(unittest.TestCase): def test_change_dimensions_from_zero_to_one(self): - mapping = entity_export(0, 1) - self.assertEqual(mapping.count_mappings(), 2) - set_entity_dimensions(mapping, 1) + mapping = entity_export(0, Position.hidden, 1) self.assertEqual(mapping.count_mappings(), 4) + set_entity_dimensions(mapping, 1) + self.assertEqual(mapping.count_mappings(), 6) flattened = mapping.flatten() classes = [type(mapping) for mapping in flattened] self.assertEqual( classes, [ EntityClassMapping, + EntityClassDescriptionMapping, DimensionMapping, EntityMapping, + EntityDescriptionMapping, ElementMapping, ], ) positions = [mapping.position for mapping in flattened] - self.assertEqual(positions, [0, Position.hidden, 1, Position.hidden]) + self.assertEqual(positions, [0, Position.hidden, Position.hidden, 1, Position.hidden, Position.hidden]) def test_change_dimension_from_one_to_zero(self): - mapping = entity_export(0, 1, [2], [3]) - self.assertEqual(mapping.count_mappings(), 4) + mapping = entity_export(0, Position.hidden, 1, Position.hidden, [2], [3]) + self.assertEqual(mapping.count_mappings(), 6) set_entity_dimensions(mapping, 0) - self.assertEqual(mapping.count_mappings(), 2) + self.assertEqual(mapping.count_mappings(), 4) flattened = mapping.flatten() classes = [type(mapping) for mapping in flattened] - self.assertEqual(classes, [EntityClassMapping, EntityMapping]) + self.assertEqual( + classes, [EntityClassMapping, EntityClassDescriptionMapping, EntityMapping, EntityDescriptionMapping] + ) positions = [mapping.position for mapping in flattened] - self.assertEqual(positions, [0, 1]) + self.assertEqual(positions, [0, Position.hidden, 1, Position.hidden]) def test_increase_dimensions(self): - mapping = entity_export(0, 1, [2], [3]) - self.assertEqual(mapping.count_mappings(), 4) - set_entity_dimensions(mapping, 2) + mapping = entity_export(0, Position.hidden, 1, Position.hidden, [2], [3]) self.assertEqual(mapping.count_mappings(), 6) + set_entity_dimensions(mapping, 2) + self.assertEqual(mapping.count_mappings(), 8) flattened = mapping.flatten() classes = [type(mapping) for mapping in flattened] self.assertEqual( classes, [ EntityClassMapping, + EntityClassDescriptionMapping, DimensionMapping, DimensionMapping, EntityMapping, + EntityDescriptionMapping, ElementMapping, ElementMapping, ], ) positions = [mapping.position for mapping in flattened] - self.assertEqual(positions, [0, 2, Position.hidden, 1, 3, Position.hidden]) + self.assertEqual(positions, [0, Position.hidden, 2, Position.hidden, 1, Position.hidden, 3, Position.hidden]) def test_decrease_dimensions(self): - mapping = entity_export(0, 1, [2, 3], [4, 5]) - self.assertEqual(mapping.count_mappings(), 6) + mapping = entity_export(0, Position.hidden, 1, Position.hidden, [2, 3], [4, 5]) + self.assertEqual(mapping.count_mappings(), 8) set_entity_dimensions(mapping, 1) - self.assertEqual(mapping.count_mappings(), 4) + self.assertEqual(mapping.count_mappings(), 6) flattened = mapping.flatten() classes = [type(mapping) for mapping in flattened] self.assertEqual( classes, [ EntityClassMapping, + EntityClassDescriptionMapping, DimensionMapping, EntityMapping, + EntityDescriptionMapping, ElementMapping, ], ) positions = [mapping.position for mapping in flattened] - self.assertEqual(positions, [0, 2, 1, 4]) + self.assertEqual(positions, [0, Position.hidden, 2, 1, Position.hidden, 4]) class TestSetEntityElements: diff --git a/tests/spine_io/exporters/test_csv_writer.py b/tests/spine_io/exporters/test_csv_writer.py index a7d5b78a..076e3f98 100644 --- a/tests/spine_io/exporters/test_csv_writer.py +++ b/tests/spine_io/exporters/test_csv_writer.py @@ -9,7 +9,7 @@ # Public License for more details. You should have received a copy of the GNU Lesser General Public License along with # this program. If not, see . ###################################################################################################################### -""" Unit tests for csv writer. """ +"""Unit tests for csv writer.""" from pathlib import Path from tempfile import TemporaryDirectory import unittest @@ -25,7 +25,7 @@ class TestCsvWriter(AssertSuccessTestCase): def test_write_empty_database(self): with TemporaryDirectory() as temp_dir: with DatabaseMapping("sqlite://", create=True) as db_map: - root_mapping = entity_export(0, 1) + root_mapping = entity_export(0, Position.hidden, 1) out_path = Path(temp_dir, "out.csv") writer = CsvWriter(out_path.parent, out_path.name) write(db_map, writer, root_mapping) @@ -39,7 +39,7 @@ def test_write_single_object_class_and_object(self): self._assert_imports(import_object_classes(db_map, ("oc",))) self._assert_imports(import_objects(db_map, (("oc", "o1"),))) db_map.commit_session("Add test data.") - root_mapping = entity_export(0, 1) + root_mapping = entity_export(0, Position.hidden, 1) out_path = Path(temp_dir, "out.csv") writer = CsvWriter(out_path.parent, out_path.name) write(db_map, writer, root_mapping) @@ -53,7 +53,7 @@ def test_tables_are_written_to_separate_files(self): self._assert_imports(import_object_classes(db_map, ("oc1", "oc2"))) self._assert_imports(import_objects(db_map, (("oc1", "o1"), ("oc2", "o2")))) db_map.commit_session("Add test data.") - root_mapping = entity_export(Position.table_name, 0) + root_mapping = entity_export(Position.table_name, Position.hidden, 0) out_path = Path(temp_dir, "out.csv") writer = CsvWriter(out_path.parent, out_path.name) write(db_map, writer, root_mapping) @@ -77,8 +77,8 @@ def test_append_to_table(self): self._assert_imports(import_object_classes(db_map, ("oc",))) self._assert_imports(import_objects(db_map, (("oc", "o1"),))) db_map.commit_session("Add test data.") - root_mapping1 = entity_export(0, 1) - root_mapping2 = entity_export(0, 1) + root_mapping1 = entity_export(0, Position.hidden, 1) + root_mapping2 = entity_export(0, Position.hidden, 1) out_path = Path(temp_dir, "out.csv") writer = CsvWriter(out_path.parent, out_path.name) write(db_map, writer, root_mapping1, root_mapping2) diff --git a/tests/spine_io/exporters/test_excel_writer.py b/tests/spine_io/exporters/test_excel_writer.py index a4d56cfb..3790603e 100644 --- a/tests/spine_io/exporters/test_excel_writer.py +++ b/tests/spine_io/exporters/test_excel_writer.py @@ -9,7 +9,7 @@ # Public License for more details. You should have received a copy of the GNU Lesser General Public License along with # this program. If not, see . ###################################################################################################################### -""" Unit tests for Excel writer. """ +"""Unit tests for Excel writer.""" from itertools import zip_longest import os.path from tempfile import TemporaryDirectory @@ -27,7 +27,7 @@ class TestExcelWriter(AssertSuccessTestCase): def test_write_empty_database(self): with TemporaryDirectory() as temp_dir: with DatabaseMapping("sqlite://", create=True) as db_map: - root_mapping = entity_export(0, 1) + root_mapping = entity_export(0, Position.hidden, 1) path = os.path.join(temp_dir, "test.xlsx") writer = ExcelWriter(path) write(db_map, writer, root_mapping) @@ -43,7 +43,7 @@ def test_write_single_object_class_and_object(self): self._assert_imports(import_object_classes(db_map, ("oc",))) self._assert_imports(import_objects(db_map, (("oc", "o1"),))) db_map.commit_session("Add test data.") - root_mapping = entity_export(0, 1) + root_mapping = entity_export(0, Position.hidden, 1) path = os.path.join(temp_dir, "test.xlsx") writer = ExcelWriter(path) write(db_map, writer, root_mapping) @@ -59,7 +59,7 @@ def test_write_to_existing_sheet(self): self._assert_imports(import_object_classes(db_map, ("Sheet1",))) self._assert_imports(import_objects(db_map, (("Sheet1", "o1"), ("Sheet1", "o2")))) db_map.commit_session("Add test data.") - root_mapping = entity_export(Position.table_name, 0) + root_mapping = entity_export(Position.table_name, Position.hidden, 0) path = os.path.join(temp_dir, "test.xlsx") writer = ExcelWriter(path) write(db_map, writer, root_mapping) @@ -75,7 +75,7 @@ def test_write_to_named_sheets(self): self._assert_imports(import_object_classes(db_map, ("oc1", ("oc2")))) self._assert_imports(import_objects(db_map, (("oc1", "o11"), ("oc1", "o12"), ("oc2", "o21")))) db_map.commit_session("Add test data.") - root_mapping = entity_export(Position.table_name, 1) + root_mapping = entity_export(Position.table_name, Position.hidden, 1) path = os.path.join(temp_dir, "test.xlsx") writer = ExcelWriter(path) write(db_map, writer, root_mapping) @@ -93,8 +93,8 @@ def test_append_to_anonymous_table(self): self._assert_imports(import_object_classes(db_map, ("oc",))) self._assert_imports(import_objects(db_map, (("oc", "o1"),))) db_map.commit_session("Add test data.") - root_mapping1 = entity_export(0, 1) - root_mapping2 = entity_export(0, 1) + root_mapping1 = entity_export(0, Position.hidden, 1) + root_mapping2 = entity_export(0, Position.hidden, 1) path = os.path.join(temp_dir, "test.xlsx") writer = ExcelWriter(path) write(db_map, writer, root_mapping1, root_mapping2) @@ -110,8 +110,8 @@ def test_append_to_named_table(self): self._assert_imports(import_object_classes(db_map, ("oc",))) self._assert_imports(import_objects(db_map, (("oc", "o1"),))) db_map.commit_session("Add test data.") - root_mapping1 = entity_export(Position.table_name, 0) - root_mapping2 = entity_export(Position.table_name, 0) + root_mapping1 = entity_export(Position.table_name, Position.hidden, 0) + root_mapping2 = entity_export(Position.table_name, Position.hidden, 0) path = os.path.join(temp_dir, "test.xlsx") writer = ExcelWriter(path) write(db_map, writer, root_mapping1, root_mapping2) diff --git a/tests/spine_io/exporters/test_gdx_writer.py b/tests/spine_io/exporters/test_gdx_writer.py index f71823fc..ab51a187 100644 --- a/tests/spine_io/exporters/test_gdx_writer.py +++ b/tests/spine_io/exporters/test_gdx_writer.py @@ -9,7 +9,7 @@ # Public License for more details. You should have received a copy of the GNU Lesser General Public License along with # this program. If not, see . ###################################################################################################################### -""" Unit tests for gdx writer. """ +"""Unit tests for gdx writer.""" import math from pathlib import Path import sys @@ -56,8 +56,8 @@ def test_write_single_object_class_and_object(self): self._assert_imports(import_object_classes(db_map, ("oc",))) self._assert_imports(import_objects(db_map, (("oc", "o1"),))) db_map.commit_session("Add test data.") - root_mapping = entity_export(Position.table_name, 0) - root_mapping.child.header = "*" + root_mapping = entity_export(Position.table_name, Position.hidden, 0) + root_mapping.child.child.header = "*" with TemporaryDirectory() as temp_dir: file_path = Path(temp_dir, "test_write_single_object_class_and_object.gdx") writer = GdxWriter(str(file_path), self._gams_dir) @@ -77,7 +77,12 @@ def test_write_2D_relationship(self): self._assert_imports(import_relationships(db_map, (("rel", ("o1", "o2")),))) db_map.commit_session("Add test data.") root_mapping = entity_export( - Position.table_name, Position.hidden, [Position.header, Position.header], [0, 1] + Position.table_name, + Position.hidden, + Position.hidden, + Position.hidden, + [Position.header, Position.header], + [0, 1], ) with TemporaryDirectory() as temp_dir: file_path = Path(temp_dir, "test_write_2D_relationship.gdx") @@ -179,7 +184,7 @@ def test_two_tables(self): self._assert_imports(import_objects(db_map, (("oc1", "o"), ("oc2", "p")))) self._assert_imports(db_map.commit_session("Add test data.")) root_mapping = entity_export(entity_class_position=Position.table_name, entity_position=0) - root_mapping.child.header = "*" + root_mapping.child.child.header = "*" with TemporaryDirectory() as temp_dir: file_path = Path(temp_dir, "test_two_tables.gdx") writer = GdxWriter(str(file_path), self._gams_dir) @@ -203,12 +208,12 @@ def test_append_to_table(self): [FixedValueMapping(Position.table_name, value="set_X")] + entity_export(entity_position=0).flatten() ) root_mapping1.child.filter_re = "oc1" - root_mapping1.child.child.header = "*" + root_mapping1.child.child.child.header = "*" root_mapping2 = unflatten( [FixedValueMapping(Position.table_name, value="set_X")] + entity_export(entity_position=0).flatten() ) root_mapping2.child.filter_re = "oc2" - root_mapping2.child.child.header = "*" + root_mapping2.child.child.child.header = "*" with TemporaryDirectory() as temp_dir: file_path = Path(temp_dir, "test_two_tables.gdx") writer = GdxWriter(str(file_path), self._gams_dir) @@ -317,7 +322,3 @@ def test_special_value_conversions(self): self.assertEqual(gams_parameter[("o1", "infinity")], math.inf) self.assertEqual(gams_parameter[("o1", "negative_infinity")], -math.inf) self.assertTrue(math.isnan(gams_parameter[("o1", "nan")])) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/spine_io/exporters/test_sql_writer.py b/tests/spine_io/exporters/test_sql_writer.py index fae62276..d728edf5 100644 --- a/tests/spine_io/exporters/test_sql_writer.py +++ b/tests/spine_io/exporters/test_sql_writer.py @@ -9,7 +9,7 @@ # Public License for more details. You should have received a copy of the GNU Lesser General Public License along with # this program. If not, see . ###################################################################################################################### -""" Unit tests for SQL writer. """ +"""Unit tests for SQL writer.""" from pathlib import Path from tempfile import TemporaryDirectory import unittest @@ -194,12 +194,12 @@ def test_append_to_table(self): self._assert_imports(import_object_classes(db_map, ("oc",))) self._assert_imports(import_objects(db_map, (("oc", "o1"), ("oc", "q1")))) db_map.commit_session("Add test data.") - root_mapping1 = entity_export(Position.table_name, 0) - root_mapping1.child.header = "objects" - root_mapping1.child.filter_re = "o1" - root_mapping2 = entity_export(Position.table_name, 0) - root_mapping2.child.header = "objects" - root_mapping2.child.filter_re = "q1" + root_mapping1 = entity_export(Position.table_name, Position.hidden, 0) + root_mapping1.child.child.header = "objects" + root_mapping1.child.child.filter_re = "o1" + root_mapping2 = entity_export(Position.table_name, Position.hidden, 0) + root_mapping2.child.child.header = "objects" + root_mapping2.child.child.filter_re = "q1" out_path = Path(temp_dir, "out.sqlite") writer = SqlWriter(str(out_path), overwrite_existing=True) write(db_map, writer, root_mapping1) @@ -236,7 +236,7 @@ def test_appending_to_table_in_existing_database(self): metadata.create_all(out_engine) out_connection.execute(object_table.insert(), {"objects": "initial_object"}) out_engine.dispose() - root_mapping = entity_export(Position.table_name, 0) + root_mapping = entity_export(Position.table_name, Position.hidden, 0) root_mapping.child.header = "objects" writer = SqlWriter(str(out_path), overwrite_existing=False) write(db_map, writer, root_mapping) @@ -257,7 +257,3 @@ def test_appending_to_table_in_existing_database(self): self.assertEqual(row, expected) session.close() engine.dispose() - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/spine_io/exporters/test_writer.py b/tests/spine_io/exporters/test_writer.py index 7e738d37..fd9ce5de 100644 --- a/tests/spine_io/exporters/test_writer.py +++ b/tests/spine_io/exporters/test_writer.py @@ -9,10 +9,11 @@ # Public License for more details. You should have received a copy of the GNU Lesser General Public License along with # this program. If not, see . ###################################################################################################################### -""" Unit tests for ``writer`` module. """ +"""Unit tests for ``writer`` module.""" import unittest from spinedb_api import DatabaseMapping, import_object_classes, import_objects from spinedb_api.export_mapping.settings import entity_export +from spinedb_api.mapping import Position from spinedb_api.spine_io.exporters.writer import Writer, write from tests.mock_helpers import AssertSuccessTestCase @@ -57,7 +58,7 @@ def test_max_rows(self): ) db_map.commit_session("Add test data.") writer = _TableWriter() - root_mapping = entity_export(0, 1) + root_mapping = entity_export(0, Position.hidden, 1) write(db_map, writer, root_mapping, max_rows=2) self.assertEqual(writer.tables, {None: [["class1", "obj1"], ["class1", "obj2"]]}) @@ -79,11 +80,7 @@ def test_max_rows_with_filter(self): ) db_map.commit_session("Add test data.") writer = _TableWriter() - root_mapping = entity_export(0, 1) - root_mapping.child.filter_re = "obj6" + root_mapping = entity_export(0, Position.hidden, 1) + root_mapping.child.child.filter_re = "obj6" write(db_map, writer, root_mapping, max_rows=1) self.assertEqual(writer.tables, {None: [["class2", "obj6"]]}) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_scenario_recipes.py b/tests/test_scenario_recipes.py index 970d3294..b8a86f56 100644 --- a/tests/test_scenario_recipes.py +++ b/tests/test_scenario_recipes.py @@ -10,16 +10,10 @@ # this program. If not, see . ###################################################################################################################### import pytest -from spinedb_api import DatabaseMapping, SpineDBAPIError +from spinedb_api import SpineDBAPIError import spinedb_api.scenario_recipes as recipes -@pytest.fixture -def db_map(): - with DatabaseMapping("sqlite://", create=True) as db_map: - yield db_map - - class TestDuplicateScenario: def test_duplicate_scenario_without_alternatives(self, db_map): original = db_map.add_scenario(name="Scenario", description="Original scenario") From 7c292aa1dee0474dc2aa6281f6f76ca1c0cb6d99 Mon Sep 17 00:00:00 2001 From: Antti Soininen Date: Mon, 23 Feb 2026 08:55:40 +0200 Subject: [PATCH 2/2] Add export mapping for parameter descriptions Re spine-tools/Spine-Toolbox#1892 --- spinedb_api/export_mapping/export_mapping.py | 21 +++++- spinedb_api/export_mapping/settings.py | 43 +++++++------ tests/export_mapping/test_export_mapping.py | 10 +-- tests/export_mapping/test_settings.py | 67 +++++++++++--------- 4 files changed, 86 insertions(+), 55 deletions(-) diff --git a/spinedb_api/export_mapping/export_mapping.py b/spinedb_api/export_mapping/export_mapping.py index 1451e1e4..416610d9 100644 --- a/spinedb_api/export_mapping/export_mapping.py +++ b/spinedb_api/export_mapping/export_mapping.py @@ -1278,6 +1278,20 @@ def build_query_columns(self, db_map, columns): columns.append(db_map.wide_entity_sq.c.description.label("entity_description")) +class ParameterDefinitionDescriptionMapping(ExportMapping): + """Maps parameter definition descriptions. + + Cannot be used as the topmost mapping; must have :class:`ParameterDefinitionMapping` as parent. + """ + + MAP_TYPE = "ParameterDefinitionDescription" + name_field = "parameter_definition_description" + id_field = "parameter_definition_description" + + def build_query_columns(self, db_map, columns): + columns.append(db_map.parameter_definition_sq.c.description.label("parameter_definition_description")) + + class MetadataNameMapping(ExportMapping): """Maps metadata names.""" @@ -1486,15 +1500,15 @@ def find_my_buddy(mapping, buddies): return None -def from_dict(serialized): +def from_dict(serialized: list[dict]) -> ExportMapping: """ Deserializes mappings. Args: - serialized (list): serialized mappings + serialized: serialized mappings Returns: - ExportMapping: root mapping + root mapping """ mappings = { klass.MAP_TYPE: klass @@ -1521,6 +1535,7 @@ def from_dict(serialized): ParameterDefaultValueIndexMapping, ParameterDefaultValueMapping, ParameterDefaultValueTypeMapping, + ParameterDefinitionDescriptionMapping, ParameterDefinitionMapping, ParameterValueIndexMapping, ParameterValueListMapping, diff --git a/spinedb_api/export_mapping/settings.py b/spinedb_api/export_mapping/settings.py index 18705ee2..eed32bea 100644 --- a/spinedb_api/export_mapping/settings.py +++ b/spinedb_api/export_mapping/settings.py @@ -35,6 +35,7 @@ ParameterDefaultValueIndexMapping, ParameterDefaultValueMapping, ParameterDefaultValueTypeMapping, + ParameterDefinitionDescriptionMapping, ParameterDefinitionMapping, ParameterValueIndexMapping, ParameterValueListMapping, @@ -112,6 +113,7 @@ def entity_export( def entity_parameter_default_value_export( entity_class_position: Position | int = Position.hidden, definition_position: Position | int = Position.hidden, + definition_description_position: Position | int = Position.hidden, value_type_position: Position | int = Position.hidden, value_position: Position | int = Position.hidden, index_name_positions: list[Position | int] | None = None, @@ -123,6 +125,7 @@ def entity_parameter_default_value_export( Args: entity_class_position: position of relationship classes definition_position: position of parameter definitions + definition_description_position: position of parameter definition descriptions value_type_position: position of parameter value types value_position: position of parameter values index_name_positions: positions of index names @@ -133,8 +136,10 @@ def entity_parameter_default_value_export( """ entity_class = EntityClassMapping(entity_class_position) definition = entity_class.child = ParameterDefinitionMapping(definition_position) + description = definition.child = ParameterDefinitionDescriptionMapping(definition_description_position) + description.set_ignorable(True) _generate_default_value_mappings( - definition, value_type_position, value_position, index_name_positions, index_positions + description, value_type_position, value_position, index_name_positions, index_positions ) return entity_class @@ -194,30 +199,30 @@ def entity_parameter_value_export( def entity_dimension_parameter_default_value_export( - entity_class_position=Position.hidden, - definition_position=Position.hidden, - dimension_positions=None, - value_type_position=Position.hidden, - value_position=Position.hidden, - index_name_positions=None, - index_positions=None, - highlight_position=0, -): + entity_class_position: Position | int = Position.hidden, + definition_position: Position | int = Position.hidden, + dimension_positions: list[Position | int] | None = None, + value_type_position: Position | int = Position.hidden, + value_position: Position | int = Position.hidden, + index_name_positions: list[Position | int] | None = None, + index_positions: list[Position | int] | None = None, + highlight_position: int | None = 0, +) -> EntityClassMapping: """ Sets up export mappings for exporting entity classes but with default dimension parameter values. Args: - entity_class_position (int or Position): position of entity classes - definition_position (int or Position): position of parameter definitions - dimension_positions (list of int, optional): positions of dimensions - value_type_position (int or Position): position of parameter value types - value_position (int or Position): position of parameter values - index_name_positions (list of int, optional): positions of index names - index_positions (list of int, optional): positions of parameter indexes - highlight_position (int): selected dimension + entity_class_position: position of entity classes + definition_position: position of parameter definitions + dimension_positions: positions of dimensions + value_type_position: position of parameter value types + value_position: position of parameter values + index_name_positions: positions of index names + index_positions: positions of parameter indexes + highlight_position: selected dimension Returns: - ExportMapping: root mapping + root mapping """ root_mapping = unflatten( [ diff --git a/tests/export_mapping/test_export_mapping.py b/tests/export_mapping/test_export_mapping.py index 68931bbe..ce35f7db 100644 --- a/tests/export_mapping/test_export_mapping.py +++ b/tests/export_mapping/test_export_mapping.py @@ -1300,7 +1300,7 @@ def test_default_value_index_names_with_nested_map(self): ) db_map.commit_session("Add test data.") mapping = entity_parameter_default_value_export( - 0, 1, Position.hidden, 4, [Position.header, Position.header], [2, 3] + 0, 1, Position.hidden, Position.hidden, 4, [Position.header, Position.header], [2, 3] ) expected = [["", "", "idx1", "idx2", ""], ["oc", "p", "A", "b", 2.3]] self.assertEqual(list(rows(mapping, db_map, {})), expected) @@ -1318,7 +1318,7 @@ def test_parameter_default_value_type(self): import_object_classes(db_map, ("oc1", "oc2", "oc3")) import_object_parameters(db_map, (("oc1", "p11", 3.14), ("oc2", "p21", 14.3), ("oc2", "p22", -1.0))) db_map.commit_session("Add test data.") - root_mapping = entity_parameter_default_value_export(0, 1, 2, 3, None, None) + root_mapping = entity_parameter_default_value_export(0, 1, Position.hidden, 2, 3, None, None) expected = [ ["oc1", "p11", "float", 3.14], ["oc2", "p21", "float", 14.3], @@ -1344,7 +1344,9 @@ def test_default_map_value_with_more_dimensions_than_index_mappings(self): import_object_classes(db_map, ("oc",)) import_object_parameters(db_map, (("oc", "p", Map(["A"], [Map(["b"], [2.3])])),)) db_map.commit_session("Add test data.") - mapping = entity_parameter_default_value_export(0, 1, Position.hidden, 3, [Position.hidden], [2]) + mapping = entity_parameter_default_value_export( + 0, 1, Position.hidden, Position.hidden, 3, [Position.hidden], [2] + ) expected = [["oc", "p", "A", "map"]] self.assertEqual(list(rows(mapping, db_map, {})), expected) @@ -1366,7 +1368,7 @@ def test_default_map_value_with_single_value_mapping(self): import_object_classes(db_map, ("oc",)) import_object_parameters(db_map, (("oc", "p", Map(["A"], [2.3])),)) db_map.commit_session("Add test data.") - mapping = entity_parameter_default_value_export(0, 1, Position.hidden, 2, None, None) + mapping = entity_parameter_default_value_export(0, 1, Position.hidden, Position.hidden, 2, None, None) expected = [["oc", "p", "map"]] self.assertEqual(list(rows(mapping, db_map, {})), expected) diff --git a/tests/export_mapping/test_settings.py b/tests/export_mapping/test_settings.py index eb493873..dd85cc40 100644 --- a/tests/export_mapping/test_settings.py +++ b/tests/export_mapping/test_settings.py @@ -17,10 +17,6 @@ DatabaseMapping, Map, TimeSeriesFixedResolution, - import_object_classes, - import_object_parameters, - import_relationship_classes, - import_relationship_parameters, ) from spinedb_api.export_mapping import rows from spinedb_api.export_mapping.export_mapping import ( @@ -60,7 +56,6 @@ set_parameter_default_value_dimensions, set_parameter_dimensions, ) -from tests.mock_helpers import AssertSuccessTestCase class TestEntityExport: @@ -119,6 +114,22 @@ def test_export_indexed_default_value(self, db_map): ] assert list(rows(root_mapping, db_map, {})) == expected + def test_export_parameter_description(self, db_map): + db_map.add_entity_class(name="cat") + db_map.add_parameter_definition( + entity_class_name="cat", name="weight", description="Measure of cat's mass.", parsed_value=2.3 + ) + db_map.commit_session("Add test cat.") + root_mapping = entity_parameter_default_value_export( + entity_class_position=0, + definition_position=1, + definition_description_position=2, + ) + expected = [ + ["cat", "weight", "Measure of cat's mass."], + ] + assert list(rows(root_mapping, db_map, {})) == expected + class TestEntityParameterValueExport: def test_export_with_indexed_parameter_values(self, db_map): @@ -161,30 +172,28 @@ def test_export_with_indexed_parameter_values(self, db_map): assert list(rows(root_mapping, db_map, {})) == expected -class TestEntityDimensionParameterDefaultValueExport(AssertSuccessTestCase): - def test_export_with_two_dimensions(self): - with DatabaseMapping("sqlite://", create=True) as db_map: - self._assert_imports(import_object_classes(db_map, ("oc1", "oc2"))) - self._assert_imports( - import_object_parameters( - db_map, (("oc1", "p11", 2.3), ("oc1", "p12", 5.0), ("oc2", "p21", "shouldn't show")) - ) - ) - self._assert_imports(import_relationship_classes(db_map, (("rc", ("oc1", "oc2")),))) - self._assert_imports(import_relationship_parameters(db_map, (("rc", "rc_p", "dummy"),))) - db_map.commit_session("Add test data.") - root_mapping = entity_dimension_parameter_default_value_export( - entity_class_position=0, - definition_position=1, - dimension_positions=[2, 3], - value_position=4, - value_type_position=5, - index_name_positions=None, - index_positions=None, - highlight_position=0, - ) - expected = [["rc", "p11", "oc1", "oc2", 2.3, "float"], ["rc", "p12", "oc1", "oc2", 5.0, "float"]] - self.assertEqual(list(rows(root_mapping, db_map, {})), expected) +class TestEntityDimensionParameterDefaultValueExport: + def test_export_with_two_dimensions(self, db_map): + db_map.add_entity_class(name="oc1") + db_map.add_entity_class(name="oc2") + db_map.add_parameter_definition(entity_class_name="oc1", name="p11", parsed_value=2.3) + db_map.add_parameter_definition(entity_class_name="oc1", name="p12", parsed_value=5.0) + db_map.add_parameter_definition(entity_class_name="oc2", name="p21", parsed_value="shouldn't show") + db_map.add_entity_class(name="rc", dimension_name_list=("oc1", "oc2")) + db_map.add_parameter_definition(entity_class_name="rc", name="rc_p", parsed_value="dummy") + db_map.commit_session("Add test data.") + root_mapping = entity_dimension_parameter_default_value_export( + entity_class_position=0, + definition_position=1, + dimension_positions=[2, 3], + value_position=4, + value_type_position=5, + index_name_positions=None, + index_positions=None, + highlight_position=0, + ) + expected = [["rc", "p11", "oc1", "oc2", 2.3, "float"], ["rc", "p12", "oc1", "oc2", 5.0, "float"]] + assert list(rows(root_mapping, db_map, {})) == expected class TestEntityDimensionParameterExport: