Skip to content
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
23 changes: 13 additions & 10 deletions spinetoolbox/fetch_parent.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def __init__(
] = {}
self._obsolete = False
self._fetched: dict[DatabaseMapping, bool] = {}
self._busy = False
self._busy_db_maps: set[DatabaseMapping] = set()
self._position: dict[DatabaseMapping, int] = {}
self._timer = QTimer()
self._timer.setSingleShot(True)
Expand Down Expand Up @@ -86,7 +86,7 @@ def reset(self) -> None:
self._timer.stop()
self._changes_by_db_map.clear()
self._fetched.clear()
self._busy = False
self._busy_db_maps.clear()
self._position.clear()

def position(self, db_map: DatabaseMapping) -> int:
Expand All @@ -112,7 +112,7 @@ def _apply_pending_changes(self) -> None:
items = [item]
last_handler = handler
last_handler({db_map: items})
QTimer.singleShot(0, lambda: self.set_busy(False))
QTimer.singleShot(0, lambda db_map=db_map: self.set_busy(db_map, False))

def bind_item(self, item: PublicItem, db_map: DatabaseMapping) -> None:
# NOTE: If `item` is in the process of calling callbacks in another thread,
Expand Down Expand Up @@ -197,7 +197,7 @@ def set_obsolete(self, obsolete: bool) -> None:
obsolete: whether parent has become obsolete
"""
if obsolete:
self.set_busy(False)
self._busy_db_maps.clear()
self._obsolete = obsolete

def is_fetched(self, db_map: DatabaseMapping) -> bool:
Expand All @@ -211,20 +211,23 @@ def set_fetched(self, db_map: DatabaseMapping, fetched: bool) -> None:
fetched: whether parent has been fetched completely
"""
if fetched:
self.set_busy(False)
self.set_busy(db_map, False)
self._fetched[db_map] = fetched

@property
def is_busy(self) -> bool:
return self._busy
def is_busy(self, db_map: DatabaseMapping) -> bool:
return db_map in self._busy_db_maps

def set_busy(self, busy: bool) -> None:
def set_busy(self, db_map: DatabaseMapping, busy: bool) -> None:
"""Sets the busy status.

Args:
db_map: Database mapping where busy status has changed.
busy: whether parent is busy fetching
"""
self._busy = busy
if busy:
self._busy_db_maps.add(db_map)
else:
self._busy_db_maps.discard(db_map)

def handle_items_added(self, db_map_data: DBMapMixedItems) -> None:
"""
Expand Down
20 changes: 16 additions & 4 deletions spinetoolbox/spine_db_editor/mvcmodels/multi_db_tree_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@
######################################################################################################################

"""Base classes to represent items from multiple databases in a tree."""
from __future__ import annotations
from collections.abc import Callable
from typing import ClassVar
from PySide6.QtCore import Qt
from spinedb_api import DatabaseMapping
from spinedb_api.helpers import ItemType
from spinedb_api.temp_id import TempId
from ...fetch_parent import FetchIndex, FlexibleFetchParent
from ...helpers import bisect_chunks, order_key, rows_to_row_count_tuples
Expand All @@ -23,9 +27,9 @@
class MultiDBTreeItem(TreeItem):
"""A tree item that may belong in multiple databases."""

item_type = None
item_type: ClassVar[ItemType] = None
"""Item type identifier string. Should be set to a meaningful value by subclasses."""
visual_key = ["name"]
visual_key: ClassVar[list[str]] = ["name"]

def __init__(self, model: MinimalTreeModel, db_map_ids: dict[DatabaseMapping, TempId] | None = None):
"""
Expand Down Expand Up @@ -295,8 +299,16 @@ def _insert_children_sorted(self, new_children):
self.insert_children(pos, chunk)

@property
def _children_sort_key(self):
return lambda item: (len(item.display_id[1]), order_key(item.display_id[0].casefold()), item.display_id[1:])
def _children_sort_key(self) -> Callable[[MultiDBTreeItem], tuple]:
def sort_key(item):
display_id = item.display_id
return (
len(display_id[1]),
order_key(display_id[0].casefold()),
tuple(value if value is not None else "" for value in display_id[1:]),
)

return sort_key

@property
def fetch_item_type(self):
Expand Down
6 changes: 3 additions & 3 deletions spinetoolbox/spine_db_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ def fetch_more(self, parent):
parent (FetchParent): fetch parent
"""
self.register_fetch_parent(parent)
if not parent.is_busy:
parent.set_busy(True)
if not parent.is_busy(self._db_map):
parent.set_busy(self._db_map, True)
self._do_fetch_more(parent)

def _do_fetch_more(self, parent): # pylint: disable=method-hidden
Expand Down Expand Up @@ -202,7 +202,7 @@ def close_db_map(self) -> None:
if not parent.is_obsolete:
parent.set_obsolete(True)
while any(
parent.is_busy and not parent.is_fetched(self._db_map)
parent.is_busy(self._db_map) and not parent.is_fetched(self._db_map)
for parents in self._parents_by_type.values()
for parent in parents
):
Expand Down
27 changes: 27 additions & 0 deletions tests/spine_db_editor/mvcmodels/test_entity_tree_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# this program. If not, see <http://www.gnu.org/licenses/>.
######################################################################################################################
from PySide6.QtWidgets import QApplication
from spinedb_api import DatabaseMapping
from spinetoolbox.spine_db_editor.mvcmodels.entity_tree_models import EntityTreeModel


Expand Down Expand Up @@ -61,3 +62,29 @@ def test_entity_items_advertise_they_have_children(self, parent_object, app_sett
QApplication.processEvents()
assert [relationship.display_data for relationship in relationship_a_b.children] == ["٭ ǀ ٭ ǀ ٭ ǀ ٭"]
assert all(not relationship.has_children() for relationship in relationship_a_b.children)

def test_same_class_in_two_databases_but_one_has_superclass(
self, parent_object, app_settings, db_mngr, logger, tmp_path
):
url = "sqlite:///" + str(tmp_path / "db.sqlite")
with DatabaseMapping(url, create=True) as db_map:
db_map.add_entity_class(name="A")
db_map.commit_session("Add entity class.")
url_with_superclass = "sqlite:///" + str(tmp_path / "db_with_superclass.sqlite")
with DatabaseMapping(url_with_superclass, create=True) as db_map:
db_map.add_entity_class(name="A")
db_map.add_entity_class(name="Superclass")
db_map.add_superclass_subclass(superclass_name="Superclass", subclass_name="A")
db_map.commit_session("Add entity class and superclass.")
model = EntityTreeModel(
parent_object,
app_settings,
db_mngr,
db_mngr.get_db_map(url, logger),
db_mngr.get_db_map(url_with_superclass, logger),
)
model.build_tree()
model.root_item.fetch_more()
while len(model.root_item.children) != 3:
QApplication.processEvents()
assert [child.display_data for child in model.root_item.children] == ["A", "A (Superclass)", "Superclass"]