Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'entity_class_byname' field #481

Merged
merged 1 commit into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
parameter definition, paramater value and list value items.
``parsed_value`` replaces the ``value`` and ``type`` (``default_value`` and ``default_type`` for parameter definitions)
fields and accepts the value directly so manual conversion using ``to_database()`` is not needed anymore.
- Added a read-only field ``entity_class_byname`` to EntityClassItem (accessible from EntityItem as well)
which works analogously to ``entity_byname``.

### Changed

Expand Down
41 changes: 27 additions & 14 deletions spinedb_api/mapped_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
# 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 <http://www.gnu.org/licenses/>.
######################################################################################################################
from collections.abc import Iterator
import inspect
from operator import itemgetter
import re
from typing import ClassVar
from .db_mapping_base import MappedItemBase
from typing import ClassVar, Union
from .db_mapping_base import DatabaseMappingBase, MappedItemBase
from .exception import SpineDBAPIError
from .helpers import DisplayStatus, name_from_dimensions, name_from_elements
from .parameter_value import (
Expand All @@ -36,7 +37,12 @@ def item_factory(item_type):

_ENTITY_BYNAME_VALUE = (
"A tuple with the entity name as single element if the entity is 0-dimensional, "
"or the 0-dimensional element names if the entity is multi-dimensional."
"or the 0-dimensional element names if it is multi-dimensional."
)

_ENTITY_CLASS_BYNAME_VALUE = (
"A tuple with the class name as single element if the class is 0-dimensional, "
"or the 0-dimensional class names if it is multi-dimensional."
)


Expand Down Expand Up @@ -64,6 +70,7 @@ class EntityClassItem(MappedItemBase):
"value": "The dimension names for a multi-dimensional class.",
"optional": True,
},
"entity_class_byname": {"type": tuple, "value": _ENTITY_CLASS_BYNAME_VALUE},
"description": {"type": str, "value": "The class description.", "optional": True},
"display_icon": {
"type": int,
Expand Down Expand Up @@ -109,6 +116,8 @@ def __getitem__(self, key):
if key in ("superclass_id", "superclass_name"):
mapped_table = self._db_map.mapped_table("superclass_subclass")
return mapped_table.find_item({"subclass_id": self["id"]}, fetch=True).get(key)
if key == "entity_class_byname":
return tuple(_byname_iter(self, "dimension_id_list", self._db_map))
return super().__getitem__(key)

def merge(self, other):
Expand Down Expand Up @@ -144,6 +153,7 @@ class EntityItem(MappedItemBase):
_references = {"class_id": "entity_class", "element_id_list": "entity"}
_external_fields = {
"entity_class_name": ("class_id", "name"),
"entity_class_byname": ("class_id", "entity_class_byname"),
"dimension_id_list": ("class_id", "dimension_id_list"),
"dimension_name_list": ("class_id", "dimension_name_list"),
"superclass_id": ("class_id", "superclass_id"),
Expand Down Expand Up @@ -180,19 +190,9 @@ def unique_values_for_item(cls, item, skip_keys=()):
if None not in sc_value:
yield (key, sc_value)

def _byname_iter(self, entity):
element_id_list = entity["element_id_list"]
if not element_id_list:
yield entity["name"]
else:
find_by_id = self._db_map.mapped_table("entity").find_item_by_id
for el_id in element_id_list:
element = find_by_id(el_id)
yield from self._byname_iter(element)

def __getitem__(self, key):
if key == "entity_byname":
return tuple(self._byname_iter(self))
return tuple(_byname_iter(self, "element_id_list", self._db_map))
return super().__getitem__(key)

def resolve_internal_fields(self, skip_keys=()):
Expand Down Expand Up @@ -1149,3 +1149,16 @@ def commit(self, _commit_id):
x for x in tuple(locals().values()) if inspect.isclass(x) and issubclass(x, MappedItemBase) and x != MappedItemBase
)
ITEM_CLASS_BY_TYPE = {klass.item_type: klass for klass in ITEM_CLASSES}


def _byname_iter(
item: Union[EntityClassItem, EntityItem], id_list_name: str, db_map: DatabaseMappingBase
) -> Iterator[str]:
id_list = item[id_list_name]
if not id_list:
yield item["name"]
else:
find_by_id = db_map.mapped_table(item.item_type).find_item_by_id
for id_ in id_list:
element = find_by_id(id_)
yield from _byname_iter(element, id_list_name, db_map)
13 changes: 13 additions & 0 deletions tests/test_DatabaseMapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -2039,6 +2039,19 @@ def test_update_list_value_with_parsed_value(self):
value_item.update(parsed_value=2.3)
self.assertEqual(value_item["parsed_value"], 2.3)

def test_entity_class_bynames(self):
with DatabaseMapping("sqlite://", create=True) as db_map:
item = self._assert_success(db_map.add_entity_class_item(name="Object"))
self.assertEqual(item["entity_class_byname"], ("Object",))
self._assert_success(db_map.add_entity_class_item(name="Subject"))
item = self._assert_success(db_map.add_entity_class_item(dimension_name_list=("Subject", "Object")))
self.assertEqual(item["entity_class_byname"], ("Subject", "Object"))
self._assert_success(db_map.add_entity_class_item(dimension_name_list=("Object", "Subject")))
item = self._assert_success(
db_map.add_entity_class_item(dimension_name_list=("Subject__Object", "Object__Subject"))
)
self.assertEqual(item["entity_class_byname"], ("Subject", "Object", "Object", "Subject"))


class TestDatabaseMappingLegacy(unittest.TestCase):
"""'Backward compatibility' tests, i.e. pre-entity tests converted to work with the entity structure."""
Expand Down