Skip to content

Commit

Permalink
Merge branch 'master' into bundling_edits
Browse files Browse the repository at this point in the history
  • Loading branch information
ptsavol committed Jun 13, 2024
2 parents d6e1cf9 + 8bcb6a1 commit 7f0566f
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 38 deletions.
10 changes: 7 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)

- Entity group -column in *Add entities* -dialog. If filled, the created entity will be added to the specified group.
If the group doesn't yet exist, it will be created.
- [Bundled App] Embedded Python now includes pip, jill, and ipykernel
- Native kernel (i.e. python3 for Python) can now be used in the Detached Console or in Tool execution.
- [Bundled App] **Embedded Python** now includes pip.

### Changed

Expand All @@ -22,8 +23,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
### Removed

### Fixed

- Plugin manager widget now correctly shows the plugin names
- [Bundled App] Fixed execution in Jupyter Console and opening Detached Consoles by adding jupyter-client and
qtconsole packages to the bundle.
- [Bundled App] Fixed creating new kernel specs in Settings->Tools by adding ipykernel package to the
**embedded Python**.
- [Bundled App] Fixed 'Install Julia' button in Settings->Tools by adding the jill package to the **embedded Python**.

### Security

Expand Down
8 changes: 6 additions & 2 deletions spinetoolbox/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1829,7 +1829,11 @@ def add_action(self, text, slot, enabled=True, tooltip=None, icon=None):

def order_key(name):
"""Splits the given string into a list of its substrings and digits
example: "David_1946_Gilmour" -> ["David_", 1946, "_Gilmour"]
If given a string that starts with a digit, a 'big' string (in comparisons) will be added to the start
of the order key that makes sure that every key starts with a str and alternates with int after that.
"""
return [int(text) if text.isdigit() else text for text in re.split(r"(\d+)", name) if text]
key_list = [int(text) if text.isdigit() else text for text in re.split(r"(\d+)", name) if text]
if len(key_list) and isinstance(key_list[0], int):
key_list.insert(0, "\U0010FFFF")
return key_list
11 changes: 5 additions & 6 deletions spinetoolbox/kernel_fetcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
import json
from PySide6.QtCore import Signal, Slot, QThread
from PySide6.QtGui import QIcon
from jupyter_client.kernelspec import find_kernel_specs
from spine_engine.utils.helpers import resolve_conda_executable
from spine_engine.utils.helpers import resolve_conda_executable, custom_find_kernel_specs
from spine_engine.execution_managers.conda_kernel_spec_manager import CondaKernelSpecManager


Expand Down Expand Up @@ -50,9 +49,9 @@ def stop_thread(self):

def get_all_regular_kernels(self):
"""Finds all kernel specs as quickly as possible."""
for kernel_name, resource_dir in find_kernel_specs().items(): # Find regular Kernels
if not os.path.exists(resource_dir):
continue
for kernel_name, resource_dir in custom_find_kernel_specs().items(): # Find regular Kernels
# if not os.path.exists(resource_dir):
# continue
icon = self.get_icon(resource_dir)
self.kernel_found.emit(kernel_name, resource_dir, False, icon, {})
if not self.keep_going:
Expand All @@ -79,7 +78,7 @@ def run(self):
self.get_all_conda_kernels()
return
# To find just a subset of kernels, we need to open kernel.json file and check the language
for kernel_name, resource_dir in find_kernel_specs().items():
for kernel_name, resource_dir in custom_find_kernel_specs().items():
d = self.get_kernel_deats(resource_dir)
icon = self.get_icon(resource_dir)
if d["language"].lower().strip() == "python": # Regular Python kernel found
Expand Down
23 changes: 13 additions & 10 deletions spinetoolbox/spine_db_editor/mvcmodels/single_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,15 @@ def __init__(self, parent, db_map, entity_class_id, committed, lazy=False):
def __lt__(self, other):
if self.entity_class_name == other.entity_class_name:
return self.db_map.codename < other.db_map.codename
keys = []
for model in (self, other):
dim = model.dimension_id_list
keys = {}
for side, model in {"left": self, "right": other}.items():
dim = len(model.dimension_id_list)
class_name = model.entity_class_name
keys.append((dim, class_name))
return keys[0] < keys[1]
keys[side] = (
dim,
class_name,
)
return keys["left"] < keys["right"]

@property
def item_type(self):
Expand Down Expand Up @@ -392,10 +395,10 @@ def item_type(self):

def _sort_key(self, element):
item = self.db_item_from_id(element)
byname = order_key("".join(item.get("entity_byname", ())))
param_name = order_key(item.get("parameter_name", ""))
byname = order_key("_".join(item.get("entity_byname", ())))
parameter_name = order_key(item.get("parameter_name", ""))
alt_name = order_key(item.get("alternative_name", ""))
return byname, param_name, alt_name
return byname, parameter_name, alt_name

def _do_update_items_in_db(self, db_map_data):
self.db_mngr.update_parameter_values(db_map_data)
Expand All @@ -410,8 +413,8 @@ def item_type(self):

def _sort_key(self, element):
item = self.db_item_from_id(element)
byname = order_key("".join(item.get("entity_byname", ())))
alt_name = order_key("".join(item.get("alternative_name", "")))
byname = order_key("_".join(item.get("entity_byname", ())))
alt_name = order_key(item.get("alternative_name", ""))
return byname, alt_name

@property
Expand Down
11 changes: 7 additions & 4 deletions spinetoolbox/widgets/kernel_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
from PySide6.QtWidgets import QDialog, QMessageBox, QDialogButtonBox, QWidget
from PySide6.QtCore import Slot, Qt, QTimer
from PySide6.QtGui import QGuiApplication, QIcon
from jupyter_client.kernelspec import find_kernel_specs
from spine_engine.utils.helpers import resolve_current_python_interpreter, resolve_default_julia_executable
from spine_engine.utils.helpers import (
resolve_current_python_interpreter,
resolve_default_julia_executable,
custom_find_kernel_specs,
)
from spinetoolbox.execution_managers import QProcessExecutionManager
from spinetoolbox.helpers import (
busy_effect,
Expand Down Expand Up @@ -56,7 +59,7 @@ def __init__(self, parent, python_or_julia):
self._rebuild_ijulia_process = None
self._install_julia_kernel_process = None
self._ready_to_install_kernel = False
self.kernel_names_before = find_kernel_specs().keys()
self.kernel_names_before = custom_find_kernel_specs().keys()
self._new_kernel_name = ""
self.setAttribute(Qt.WA_DeleteOnClose)
self._cursors = {w: w.cursor() for w in self.findChildren(QWidget)}
Expand Down Expand Up @@ -93,7 +96,7 @@ def new_kernel_name(self):

def _solve_new_kernel_name(self):
"""Finds out the new kernel name after a new kernel has been created."""
kernel_names_after = find_kernel_specs().keys()
kernel_names_after = custom_find_kernel_specs().keys()
try:
self._new_kernel_name = list(set(kernel_names_after) - set(self.kernel_names_before))[0]
except IndexError:
Expand Down
57 changes: 45 additions & 12 deletions tests/spine_db_editor/widgets/test_custom_qtableview.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,11 +410,11 @@ def test_undoing_purge(self):
["object_class", f"object_{object_n}", f"parameter_{parameter_n}", "Base", "a_value", self.db_codename]
for object_n, parameter_n in itertools.product(range(self._n_entities), range(self._n_parameters))
]
entity_names = (f"object_{n} ǀ object_{1 - n}" for n in range(self._n_ND_entities))
nd_entity_names = [f"object_{i} ǀ object_{j}" for i, j in itertools.permutations(range(self._n_ND_entities), 2)]
expected.extend(
[
["multi_d_class", entity_name, f"parameter_{parameter_n}", "Base", "a_value", self.db_codename]
for entity_name, parameter_n in itertools.product(entity_names, range(self._n_ND_parameters))
for entity_name, parameter_n in itertools.product(nd_entity_names, range(self._n_ND_parameters))
]
)
expected.append([None, None, None, None, None, self.db_codename])
Expand Down Expand Up @@ -442,11 +442,11 @@ def test_rolling_back_purge(self):
["object_class", f"object_{object_n}", f"parameter_{parameter_n}", "Base", "a_value", self.db_codename]
for object_n, parameter_n in itertools.product(range(self._n_entities), range(self._n_parameters))
]
entity_names = (f"object_{n} ǀ object_{1-n}" for n in range(self._n_ND_entities))
nd_entity_names = [f"object_{i} ǀ object_{j}" for i, j in itertools.permutations(range(self._n_ND_entities), 2)]
expected.extend(
[
["multi_d_class", entity_name, f"parameter_{parameter_n}", "Base", "a_value", self.db_codename]
for entity_name, parameter_n in itertools.product(entity_names, range(self._n_ND_parameters))
for entity_name, parameter_n in itertools.product(nd_entity_names, range(self._n_ND_parameters))
]
)
QApplication.processEvents()
Expand All @@ -456,25 +456,58 @@ def test_rolling_back_purge(self):
self.assertEqual(model.index(row, column).data(), expected[row][column])

def test_sorting(self):
"""Test that the parameter value table sorts in an expected order."""
url = "sqlite:///" + os.path.join(self._temp_dir.name, "test_database.sqlite")
db_map = DatabaseMapping(url)
parameter_definition_data = (
("object_class", f"0parameter_"),
("object_class", f"1parameter_"),
)
import_functions.import_object_parameters(db_map, parameter_definition_data)
parameter_value_data = (
("object_class", f"object_0", f"0parameter_", "a_value"),
("object_class", f"object_0", f"1parameter_", "a_value"),
("object_class", f"object_1", f"0parameter_", "a_value"),
("object_class", f"object_1", f"1parameter_", "a_value"),
)
import_functions.import_object_parameter_values(db_map, parameter_value_data)
db_map.commit_session("Add test data.")
db_map.close()
table_view = self._db_editor.ui.tableView_parameter_value
model = table_view.model()
self.assertEqual(model.rowCount(), self._CHUNK_SIZE + 1)
while model.rowCount() != self._whole_model_rowcount():
while model.rowCount() != self._whole_model_rowcount() + 4:
model.fetchMore(QModelIndex())
QApplication.processEvents()
expected = [
["object_class", f"object_{object_n}", f"parameter_{parameter_n}", "Base", "a_value", self.db_codename]
for object_n, parameter_n in itertools.product(range(self._n_entities), range(self._n_parameters))
]
entity_names = (f"object_{n} ǀ object_{1 - n}" for n in range(self._n_ND_entities))
expected = []
for object_n in range(self._n_entities):
for parameter_n in range(self._n_parameters):
expected.append(
[
"object_class",
f"object_{object_n}",
f"parameter_{parameter_n}",
"Base",
"a_value",
self.db_codename,
]
)
if object_n < 2:
expected.extend(
[
["object_class", f"object_{object_n}", f"0parameter_", "Base", "a_value", self.db_codename],
["object_class", f"object_{object_n}", f"1parameter_", "Base", "a_value", self.db_codename],
]
)
nd_entity_names = [f"object_{i} ǀ object_{j}" for i, j in itertools.permutations(range(self._n_ND_entities), 2)]
expected.extend(
[
["multi_d_class", entity_name, f"parameter_{parameter_n}", "Base", "a_value", self.db_codename]
for entity_name, parameter_n in itertools.product(entity_names, range(self._n_ND_parameters))
for entity_name, parameter_n in itertools.product(nd_entity_names, range(self._n_ND_parameters))
]
)
expected.append([None, None, None, None, None, self.db_codename])
self.assertEqual(model.rowCount(), self._whole_model_rowcount())
self.assertEqual(model.rowCount(), self._whole_model_rowcount() + 4)
for row, column in itertools.product(range(model.rowCount()), range(model.columnCount())):
self.assertEqual(model.index(row, column).data(), expected[row][column])

Expand Down
50 changes: 50 additions & 0 deletions tests/test_SpineDBManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from pathlib import Path
from tempfile import TemporaryDirectory
import unittest
from unittest import mock
from unittest.mock import MagicMock
from PySide6.QtCore import Qt, QSettings
from PySide6.QtWidgets import QApplication
Expand Down Expand Up @@ -556,5 +557,54 @@ def test_updating_indexed_value_changes_the_unparsed_value_in_database(self):
self.assertEqual(update_value, Map(["a"], ["c"]))


class TestRemoveScenarioAlternative(unittest.TestCase):
@classmethod
def setUpClass(cls):
if not QApplication.instance():
QApplication()

def setUp(self):
mock_settings = MagicMock()
mock_settings.value.side_effect = lambda *args, **kwargs: 0
self._db_mngr = SpineDBManager(mock_settings, None)
self._logger = MagicMock()
self._db_map = self._db_mngr.get_db_map("sqlite://", self._logger, codename="database", create=True)

def tearDown(self):
self._db_mngr.close_all_sessions()
while not self._db_map.closed:
QApplication.processEvents()
self._db_mngr.clean_up()

def _assert_success(self, result):
item, error = result
self.assertIsNone(error)
return item

def test_removing_scenario_alternative_and_committing(self):
"""Test that removing non-rank 1 scenario alternative works."""
scenario = self._assert_success(self._db_map.add_scenario_item(name="scenario"))
base = self._db_map.get_alternative_item(name="Base")
next_level = self._assert_success(self._db_map.add_alternative_item(name="Next level"))
scen_alt_1 = self._assert_success(
self._db_map.add_scenario_alternative_item(scenario_id=scenario["id"], alternative_id=base["id"], rank=1)
)
scen_alt_2 = self._assert_success(
self._db_map.add_scenario_alternative_item(
scenario_id=scenario["id"], alternative_id=next_level["id"], rank=2
)
)
self._db_mngr.commit_session("Added data.", self._db_map)
db_map_scen_alt_data = {
self._db_map: [{"alternative_id_list": [scen_alt_2["alternative_id"]], "id": scenario["id"]}]
}
db_map_typed_data_to_rm = {self._db_map: {"scenario": set()}}
self._db_mngr.error_msg = MagicMock()
self._db_mngr.set_scenario_alternatives(db_map_scen_alt_data)
self._db_mngr.remove_items(db_map_typed_data_to_rm)
self._db_mngr.commit_session("Remove scenario alternative", self._db_map)
self._db_mngr.error_msg.emit.assert_not_called()


if __name__ == "__main__":
unittest.main()
3 changes: 2 additions & 1 deletion tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,8 @@ def test_merge_dicts_when_source_overwrites_data_in_target(self):
def test_order_key(self):
self.assertEqual(["Humphrey_Bogart"], order_key("Humphrey_Bogart"))
self.assertEqual(["Wes_", 1969, "_Anderson"], order_key("Wes_1969_Anderson"))
self.assertEqual([1899, "_Alfred-", 1980, "Hitchcock"], order_key("1899_Alfred-1980Hitchcock"))
self.assertEqual(["\U0010ffff", 1899, "_Alfred-", 1980, "Hitchcock"], order_key("1899_Alfred-1980Hitchcock"))
self.assertEqual([], order_key(""))


class TestHTMLTagFilter(unittest.TestCase):
Expand Down

0 comments on commit 7f0566f

Please sign in to comment.