diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d7c010bf..e54b18ffa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
## [Unreleased]
### Added
+- Support for version 11 Spine Toolbox projects.
- Executable Tool Specifications can be used to run any (shell) command. This enhancement
duplicates the functionality of Gimlet project items and makes them obsolete.
- There is now an option to select if new scenarios or tools are automatically used
@@ -30,6 +31,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
- "Make Julia Kernel" and "Make Python Kernel" buttons in Settings->Tools page. Clicking them creates a new
Julia or Python kernel based on selected Julia/Python executable on the same page if the kernel does not exist.
If the kernel already exists, it is selected automatically.
+- ``project.json`` now has an experimental option ["project"]["settings"]["enable_execute_all"] which disables the
+ Execute Project button when set to ``false``. The option is currently not settable in the UI.
### Changed
- The console settings of Python tools as well as the command and shell settings of executable tools
@@ -54,6 +57,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
Duplicating a previously duplicated item now has the number `xx` incremented instead of having a new number appended.
- "Open kernel spec editor" buttons in Settings->Tools page have been changed "Make Julia kernel" and
"Make Python Kernel" buttons
+-
### Deprecated
diff --git a/bin/build_ui.py b/bin/build_ui.py
index 0083dde79..17a6d5481 100755
--- a/bin/build_ui.py
+++ b/bin/build_ui.py
@@ -23,9 +23,9 @@ def fix_resources_imports(path):
lines = list()
with open(path, 'r') as in_file:
for line in in_file:
- if line == "from . import resources_icons_rc\n":
+ if line == "from . import resources_icons_rc\n":
lines.append("from spinetoolbox import resources_icons_rc\n")
- elif line == "from . import resources_logos_rc\n":
+ elif line == "from . import resources_logos_rc\n":
lines.append("from spinetoolbox import resources_logos_rc\n")
else:
lines.append(line)
diff --git a/execution_tests/import_file_packs/.spinetoolbox/project.json b/execution_tests/import_file_packs/.spinetoolbox/project.json
index ef8f1c031..750a58242 100644
--- a/execution_tests/import_file_packs/.spinetoolbox/project.json
+++ b/execution_tests/import_file_packs/.spinetoolbox/project.json
@@ -1,6 +1,6 @@
{
"project": {
- "version": 10,
+ "version": 11,
"description": "",
"specifications": {
"Tool": [
@@ -42,7 +42,10 @@
]
}
],
- "jumps": []
+ "jumps": [],
+ "settings": {
+ "enable_execute_all": true
+ }
},
"items": {
"Create file pack": {
diff --git a/execution_tests/loop_condition_with_cmd_line_args/.spinetoolbox/project.json b/execution_tests/loop_condition_with_cmd_line_args/.spinetoolbox/project.json
index 734748375..34e59cefb 100644
--- a/execution_tests/loop_condition_with_cmd_line_args/.spinetoolbox/project.json
+++ b/execution_tests/loop_condition_with_cmd_line_args/.spinetoolbox/project.json
@@ -1,6 +1,6 @@
{
"project": {
- "version": 10,
+ "version": 11,
"description": "",
"specifications": {
"Tool": [
@@ -106,7 +106,10 @@
}
]
}
- ]
+ ],
+ "settings": {
+ "enable_execute_all": true
+ }
},
"items": {
"Write data": {
diff --git a/execution_tests/merger_write_order/.spinetoolbox/project.json b/execution_tests/merger_write_order/.spinetoolbox/project.json
index 09f8b4654..c33bab31c 100644
--- a/execution_tests/merger_write_order/.spinetoolbox/project.json
+++ b/execution_tests/merger_write_order/.spinetoolbox/project.json
@@ -1,6 +1,6 @@
{
"project": {
- "version": 10,
+ "version": 11,
"description": "",
"specifications": {},
"connections": [
@@ -52,7 +52,10 @@
}
}
],
- "jumps": []
+ "jumps": [],
+ "settings": {
+ "enable_execute_all": true
+ }
},
"items": {
"First source": {
diff --git a/execution_tests/modify_connection_filter_by_script/.spinetoolbox/project.json b/execution_tests/modify_connection_filter_by_script/.spinetoolbox/project.json
index 4a79daffd..456025258 100644
--- a/execution_tests/modify_connection_filter_by_script/.spinetoolbox/project.json
+++ b/execution_tests/modify_connection_filter_by_script/.spinetoolbox/project.json
@@ -1,6 +1,6 @@
{
"project": {
- "version": 10,
+ "version": 11,
"description": "",
"specifications": {
"Exporter": [
@@ -34,7 +34,10 @@
}
}
],
- "jumps": []
+ "jumps": [],
+ "settings": {
+ "enable_execute_all": true
+ }
},
"items": {
"Data": {
diff --git a/execution_tests/parallel_importer/.spinetoolbox/project.json b/execution_tests/parallel_importer/.spinetoolbox/project.json
index b0febb087..f9dd0bc3e 100644
--- a/execution_tests/parallel_importer/.spinetoolbox/project.json
+++ b/execution_tests/parallel_importer/.spinetoolbox/project.json
@@ -1,6 +1,6 @@
{
"project": {
- "version": 10,
+ "version": 11,
"description": "",
"specifications": {
"Tool": [
@@ -53,7 +53,10 @@
]
}
],
- "jumps": []
+ "jumps": [],
+ "settings": {
+ "enable_execute_all": true
+ }
},
"items": {
"Source": {
diff --git a/execution_tests/parallel_importer_with_datapackage/.spinetoolbox/project.json b/execution_tests/parallel_importer_with_datapackage/.spinetoolbox/project.json
index 457e36ab8..9c40af724 100644
--- a/execution_tests/parallel_importer_with_datapackage/.spinetoolbox/project.json
+++ b/execution_tests/parallel_importer_with_datapackage/.spinetoolbox/project.json
@@ -1,6 +1,6 @@
{
"project": {
- "version": 10,
+ "version": 11,
"description": "",
"specifications": {
"Tool": [
@@ -56,7 +56,10 @@
]
}
],
- "jumps": []
+ "jumps": [],
+ "settings": {
+ "enable_execute_all": true
+ }
},
"items": {
"Source": {
diff --git a/execution_tests/scenario_filters/.spinetoolbox/project.json b/execution_tests/scenario_filters/.spinetoolbox/project.json
index 4c9845ea7..3b24211ba 100644
--- a/execution_tests/scenario_filters/.spinetoolbox/project.json
+++ b/execution_tests/scenario_filters/.spinetoolbox/project.json
@@ -1,6 +1,6 @@
{
"project": {
- "version": 10,
+ "version": 11,
"description": "Test project to test scenario filtering in a Tool project item.",
"specifications": {
"Importer": [
@@ -58,7 +58,10 @@
}
}
],
- "jumps": []
+ "jumps": [],
+ "settings": {
+ "enable_execute_all": true
+ }
},
"items": {
"Data store": {
diff --git a/spinetoolbox/config.py b/spinetoolbox/config.py
index 039a33f55..b55e95ee1 100644
--- a/spinetoolbox/config.py
+++ b/spinetoolbox/config.py
@@ -18,7 +18,7 @@
from pathlib import Path
# NOTE: All required Python package versions are in setup.cfg
-LATEST_PROJECT_VERSION = 10
+LATEST_PROJECT_VERSION = 11
# For the Add/Update SpineOpt wizard
REQUIRED_SPINE_OPT_VERSION = "0.6.9"
diff --git a/spinetoolbox/project.py b/spinetoolbox/project.py
index 3a1a9201a..701515d02 100644
--- a/spinetoolbox/project.py
+++ b/spinetoolbox/project.py
@@ -35,6 +35,7 @@
)
from spine_engine.utils.serialization import deserialize_path, serialize_path
from spine_engine.server.util.zip_handler import ZipHandler
+from .project_settings import ProjectSettings
from .server.engine_client import EngineClient
from .metaobject import MetaObject
from .helpers import (
@@ -107,13 +108,14 @@ class SpineToolboxProject(MetaObject):
specification_saved = Signal(str, str)
"""Emitted after a specification has been saved."""
- def __init__(self, toolbox, p_dir, plugin_specs, settings, logger):
+ def __init__(self, toolbox, p_dir, plugin_specs, app_settings, settings, logger):
"""
Args:
toolbox (ToolboxUI): toolbox of this project
p_dir (str): Project directory
plugin_specs (Iterable of ProjectItemSpecification): specifications available as plugins
- settings (QSettings): Toolbox settings
+ app_settings (QSettings): Toolbox settings
+ settings (ProjectSettings): project settings
logger (LoggerInterface): a logger instance
"""
_, name = os.path.split(p_dir)
@@ -124,6 +126,7 @@ def __init__(self, toolbox, p_dir, plugin_specs, settings, logger):
self._connections = list()
self._jumps = list()
self._logger = logger
+ self._app_settings = app_settings
self._settings = settings
self._engine_workers = []
self._execution_in_progress = False
@@ -148,6 +151,10 @@ def toolbox(self):
def all_item_names(self):
return list(self._project_items)
+ @property
+ def settings(self):
+ return self._settings
+
def _create_project_structure(self, directory):
"""Makes the given directory a Spine Toolbox project directory.
Creates directories and files that are common to all projects.
@@ -193,6 +200,7 @@ def save(self):
project_dict = {
"version": LATEST_PROJECT_VERSION,
"description": self.description,
+ "settings": self._settings.to_dict(),
"specifications": serialized_spec_paths,
"connections": [connection.to_dict() for connection in self._connections],
"jumps": [jump.to_dict() for jump in self._jumps],
@@ -288,6 +296,7 @@ def load(self, spec_factories, item_factories):
self._merge_local_data_to_project_info(local_data_dict, project_info)
# Parse project info
self.set_description(project_info["project"]["description"])
+ self._settings = ProjectSettings.from_dict(project_info["project"]["settings"])
spec_paths_per_type = project_info["project"]["specifications"]
deserialized_paths = [
deserialize_path(path, self.project_dir) for paths in spec_paths_per_type.values() for path in paths
@@ -296,7 +305,7 @@ def load(self, spec_factories, item_factories):
specification_local_data = load_specification_local_data(self.config_dir)
for path in deserialized_paths:
spec = load_specification_from_file(
- path, specification_local_data, spec_factories, self._settings, self._logger
+ path, specification_local_data, spec_factories, self._app_settings, self._logger
)
if spec is not None:
self.add_specification(spec, save_to_disk=False)
@@ -973,7 +982,7 @@ def _execute_dags(self, dags, execution_permits_list):
if not self.job_id:
self.project_execution_finished.emit()
return
- settings = make_settings_dict_for_engine(self._settings)
+ settings = make_settings_dict_for_engine(self._app_settings)
darker_fg_color = QColor(FG_COLOR).darker().name()
darker = lambda x: f'{x}'
for k, (dag, execution_permits) in enumerate(zip(dags, execution_permits_list)):
@@ -1401,8 +1410,8 @@ def _update_ranks(self, dag):
item.set_rank(ranks[item_name])
@property
- def settings(self):
- return self._settings
+ def app_settings(self):
+ return self._app_settings
@busy_effect
def prepare_remote_execution(self):
@@ -1412,7 +1421,7 @@ def prepare_remote_execution(self):
str: Job Id if server is ready for remote execution, empty string if something went wrong or "1" if
local execution is enabled.
"""
- if not self._settings.value("engineSettings/remoteExecutionEnabled", defaultValue="false") == "true":
+ if not self._app_settings.value("engineSettings/remoteExecutionEnabled", defaultValue="false") == "true":
return "1" # Something that isn't False
host, port, sec_model, sec_folder = self._toolbox.engine_server_settings()
if not host:
@@ -1462,7 +1471,7 @@ def prepare_remote_execution(self):
def finalize_remote_execution(self):
"""Sends a request to server to remove the project directory and removes the project ZIP file from client."""
- if not self._settings.value("engineSettings/remoteExecutionEnabled", defaultValue="false") == "true":
+ if not self._app_settings.value("engineSettings/remoteExecutionEnabled", defaultValue="false") == "true":
return
host, port, sec_model, sec_folder = self._toolbox.engine_server_settings()
try:
diff --git a/spinetoolbox/project_settings.py b/spinetoolbox/project_settings.py
new file mode 100644
index 000000000..ff5d9553f
--- /dev/null
+++ b/spinetoolbox/project_settings.py
@@ -0,0 +1,40 @@
+######################################################################################################################
+# Copyright (C) 2017-2022 Spine project consortium
+# This file is part of Spine Toolbox.
+# Spine Toolbox 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 .
+######################################################################################################################
+"""Contains project-specific settings."""
+
+import dataclasses
+
+
+@dataclasses.dataclass
+class ProjectSettings:
+ """Spine Toolbox project settings."""
+
+ enable_execute_all: bool = True
+
+ def to_dict(self):
+ """Serializes the settings into a dictionary.
+
+ Returns:
+ dict: serialized settings
+ """
+ return dataclasses.asdict(self)
+
+ @staticmethod
+ def from_dict(settings_dict):
+ """Deserializes settings from dictionary.
+
+ Args:
+ settings_dict (dict): serialized settings
+
+ Returns:
+ ProjectSettings: deserialized settings
+ """
+ return ProjectSettings(**settings_dict)
diff --git a/spinetoolbox/project_upgrader.py b/spinetoolbox/project_upgrader.py
index 8352b5c66..199f8da33 100644
--- a/spinetoolbox/project_upgrader.py
+++ b/spinetoolbox/project_upgrader.py
@@ -22,6 +22,7 @@
from spine_engine.utils.serialization import serialize_path, deserialize_path
from .config import LATEST_PROJECT_VERSION, PROJECT_FILENAME
from .helpers import home_dir
+from .project_settings import ProjectSettings
class ProjectUpgrader:
@@ -100,6 +101,8 @@ def upgrade_to_latest(self, v, project_dict, project_dir):
project_dict = self.upgrade_v8_to_v9(project_dict)
elif v == 9:
project_dict = self.upgrade_v9_to_v10(project_dict)
+ elif v == 10:
+ project_dict = self.upgrade_v10_to_v11(project_dict)
v += 1
self._toolbox.msg_success.emit(f"Project upgraded to version {v}")
return project_dict
@@ -502,6 +505,24 @@ def upgrade_v9_to_v10(old):
new["items"].pop(name)
return new
+ @staticmethod
+ def upgrade_v10_to_v11(old):
+ """Upgrades version 10 project dictionary to version 11.
+
+ Changes:
+ 1. Add ["project"]["settings"] key
+
+ Args:
+ old (dict): Version 10 project dictionary
+
+ Returns:
+ dict: Version 11 project dictionary
+ """
+ new = copy.deepcopy(old)
+ new["project"]["version"] = 11
+ new["project"]["settings"] = ProjectSettings().to_dict()
+ return new
+
@staticmethod
def make_unique_importer_specification_name(importer_name, label, k):
return f"{importer_name} - {os.path.basename(label['path'])} - {k}"
@@ -543,13 +564,23 @@ def get_project_directory(self):
return answer # New project directory
def is_valid(self, v, p):
- """Checks given project dict if it is valid for given version."""
+ """Checks given project dict if it is valid for given version.
+
+ Args:
+ v (int): project version to validate against
+ p (dict): project dictionary
+
+ Returns:
+ bool: True if project is valid, False otherwise
+ """
if v == 1:
return self.is_valid_v1(p)
if 2 <= v <= 8:
return self.is_valid_v2_to_v8(p, v)
if 9 <= v <= 10:
return self.is_valid_v9_to_v10(p)
+ if v == 11:
+ return self.is_valid_v11(p)
raise NotImplementedError(f"No validity check available for version {v}")
def is_valid_v1(self, p):
@@ -564,10 +595,10 @@ def is_valid_v1(self, p):
Returns:
bool: True if project is a valid version 1 project, False if it is not
"""
- if "project" not in p.keys():
+ if "project" not in p:
self._toolbox.msg_error.emit("Invalid project.json file. Key 'project' not found.")
return False
- if "objects" not in p.keys():
+ if "objects" not in p:
self._toolbox.msg_error.emit("Invalid project.json file. Key 'objects' not found.")
return False
required_project_keys = ["version", "name", "description", "tool_specifications", "connections"]
@@ -611,10 +642,10 @@ def is_valid_v2_to_v8(self, p, v):
Returns:
bool: True if project is a valid version 2 to version 8 project, False if it is not
"""
- if "project" not in p.keys():
+ if "project" not in p:
self._toolbox.msg_error.emit("Invalid project.json file. Key 'project' not found.")
return False
- if "items" not in p.keys():
+ if "items" not in p:
self._toolbox.msg_error.emit("Invalid project.json file. Key 'items' not found.")
return False
required_project_keys = ["version", "name", "description", "specifications", "connections"]
@@ -657,10 +688,10 @@ def is_valid_v9_to_v10(self, p):
Returns:
bool: True if project is a valid version 9 and 10 project, False otherwise
"""
- if "project" not in p.keys():
+ if "project" not in p:
self._toolbox.msg_error.emit("Invalid project.json file. Key 'project' not found.")
return False
- if "items" not in p.keys():
+ if "items" not in p:
self._toolbox.msg_error.emit("Invalid project.json file. Key 'items' not found.")
return False
required_project_keys = ["version", "description", "specifications", "connections"]
@@ -678,6 +709,29 @@ def is_valid_v9_to_v10(self, p):
return False
return True
+ def is_valid_v11(self, p):
+ """Checks that the given project JSON dictionary contains
+ a valid version 11 Spine Toolbox project. Valid meaning, that
+ it contains all required keys and values are of the correct
+ type.
+
+ Args:
+ p (dict): Project information JSON
+
+ Returns:
+ bool: True if project is a valid version 11 project, False otherwise
+ """
+ if "project" not in p:
+ self._toolbox.msg_error.emit("Invalid project.json file. Key 'project' not found.")
+ return False
+ if "settings" not in p["project"]:
+ self._toolbox.msg_error.emit("Invalid project.json file. Key 'items' not found in 'project'.")
+ return False
+ if not isinstance(p["project"]["settings"], dict):
+ self._toolbox.msg_error.emit("Invalid project.json file. 'settings' must be a dict.")
+ return False
+ return True
+
def backup_project_file(self, project_dir, v):
"""Makes a backup copy of project.json file."""
src = os.path.join(project_dir, ".spinetoolbox", PROJECT_FILENAME)
diff --git a/spinetoolbox/resources_icons_rc.py b/spinetoolbox/resources_icons_rc.py
index 89c91f904..d6e0ccd42 100644
--- a/spinetoolbox/resources_icons_rc.py
+++ b/spinetoolbox/resources_icons_rc.py
@@ -10,7 +10,7 @@
# this program. If not, see .
######################################################################################################################
# Created by: object code
-# Created by: The Resource Compiler for Qt version 6.4.3
+# Created by: The Resource Compiler for Qt version 6.5.2
# WARNING! All changes made in this file will be lost!
from PySide6 import QtCore
@@ -32065,177 +32065,177 @@
\x00\x00\x004\x00\x02\x00\x00\x00\x01\x00\x00\x00\x05\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00T\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x01\x88\x04\xacr\xc1\
+\x00\x00\x01\x83\xb1\xb2\x06\x95\
\x00\x00\x00x\x00\x00\x00\x00\x00\x01\x00\x00\x09\xf5\
-\x00\x00\x01\x89\x05\xd7\x05\xe0\
+\x00\x00\x01\x89\xd8\xc60\x1f\
\x00\x00\x00\xb2\x00\x00\x00\x00\x00\x01\x00\x008\x89\
-\x00\x00\x01\x88\xc2\xdeI`\
+\x00\x00\x01\x89\xd8\xc60/\
\x00\x00\x00\x9e\x00\x01\x00\x00\x00\x01\x00\x00\x10N\
-\x00\x00\x01\x89\x05\xd73\xa6\
+\x00\x00\x01\x89\xd8\xc60\x1f\
\x00\x00\x02R\x00\x00\x00\x00\x00\x01\x00\x06A\x9c\
-\x00\x00\x01\x88\x04\xacr\xc7\
+\x00\x00\x01\x83\xb1\xb2\x06\x99\
\x00\x00\x01\xea\x00\x00\x00\x00\x00\x01\x00\x062\x92\
-\x00\x00\x01\x88\x04\xacr\xe0\
+\x00\x00\x01\x83\xb1\xb2\x06\xae\
\x00\x00\x04X\x00\x00\x00\x00\x00\x01\x00\x06\xa7<\
-\x00\x00\x01\x88\x04\xacr\xc6\
+\x00\x00\x01\x83\xb1\xb2\x06\x98\
\x00\x00\x04\xa0\x00\x00\x00\x00\x00\x01\x00\x06\xbc&\
-\x00\x00\x01\x88\x04\xacr\xdb\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x03D\x00\x00\x00\x00\x00\x01\x00\x06\x88\x1a\
-\x00\x00\x01\x88\x04\xacr\xe1\
+\x00\x00\x01\x83\xb1\xb2\x06\xae\
\x00\x00\x04\x80\x00\x00\x00\x00\x00\x01\x00\x06\xa9\x10\
-\x00\x00\x01\x88\x04\xacr\xc5\
+\x00\x00\x01\x83\xb1\xb2\x06\x98\
\x00\x00\x03\xf4\x00\x00\x00\x00\x00\x01\x00\x06\x98\x16\
-\x00\x00\x01\x88\x04\xacr\xdb\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x02\x80\x00\x00\x00\x00\x00\x01\x00\x06E\xb2\
-\x00\x00\x01\x88\x04\xacr\xc4\
+\x00\x00\x01\x83\xb1\xb2\x06\x97\
\x00\x00\x02h\x00\x00\x00\x00\x00\x01\x00\x06B\xab\
-\x00\x00\x01\x88\x04\xacr\xe0\
+\x00\x00\x01\x83\xb1\xb2\x06\xae\
\x00\x00\x03\xde\x00\x00\x00\x00\x00\x01\x00\x06\x95\xa7\
-\x00\x00\x01\x88\x04\xacr\xcb\
+\x00\x00\x01\x83\xb1\xb2\x06\x9d\
\x00\x00\x03^\x00\x00\x00\x00\x00\x01\x00\x06\x8a3\
-\x00\x00\x01\x88\x04\xacr\xc6\
+\x00\x00\x01\x83\xb1\xb2\x06\x98\
\x00\x00\x02\xc4\x00\x00\x00\x00\x00\x01\x00\x06Q\xfb\
-\x00\x00\x01\x88\x04\xacr\xc2\
+\x00\x00\x01\x83\xb1\xb2\x06\x96\
\x00\x00\x02.\x00\x00\x00\x00\x00\x01\x00\x06?\xec\
-\x00\x00\x01\x88\x04\xacr\xc7\
+\x00\x00\x01\x83\xb1\xb2\x06\x9a\
\x00\x00\x03(\x00\x00\x00\x00\x00\x01\x00\x06b\xb3\
-\x00\x00\x01\x88\x04\xacr\xc4\
+\x00\x00\x01\x83\xb1\xb2\x06\x97\
\x00\x00\x01\xbe\x00\x00\x00\x00\x00\x01\x00\x060\xaf\
-\x00\x00\x01\x88\x04\xacr\xdb\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x01\xa6\x00\x00\x00\x00\x00\x01\x00\x06/\x9c\
-\x00\x00\x01\x88\x04\xacr\xe0\
+\x00\x00\x01\x83\xb1\xb2\x06\xae\
\x00\x00\x04\x0c\x00\x00\x00\x00\x00\x01\x00\x06\x9e\x7f\
-\x00\x00\x01\x88\x04\xacr\xdf\
+\x00\x00\x01\x83\xb1\xb2\x06\xae\
\x00\x00\x03\x98\x00\x02\x00\x00\x00\x09\x00\x00\x00O\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x03~\x00\x02\x00\x00\x00-\x00\x00\x00\x22\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x02\x0a\x00\x00\x00\x00\x00\x01\x00\x065G\
-\x00\x00\x01\x88\x04\xacr\xe2\
+\x00\x00\x01\x83\xb1\xb2\x06\xae\
\x00\x00\x04<\x00\x00\x00\x00\x00\x01\x00\x06\xa1\x98\
-\x00\x00\x01\x88\x04\xacr\xe1\
+\x00\x00\x01\x83\xb1\xb2\x06\xae\
\x00\x00\x03\xc2\x00\x00\x00\x00\x00\x01\x00\x06\x94o\
-\x00\x00\x01\x88\x04\xacr\xc5\
+\x00\x00\x01\x83\xb1\xb2\x06\x98\
\x00\x00\x02\xda\x00\x00\x00\x00\x00\x01\x00\x06T\x05\
-\x00\x00\x01\x88\x04\xacr\xc4\
+\x00\x00\x01\x83\xb1\xb2\x06\x97\
\x00\x00\x02\x9e\x00\x00\x00\x00\x00\x01\x00\x06G\x93\
-\x00\x00\x01\x88\x04\xacr\xe1\
+\x00\x00\x01\x83\xb1\xb2\x06\xae\
\x00\x00\x03\x02\x00\x00\x00\x00\x00\x01\x00\x06`G\
-\x00\x00\x01\x88\x04\xacr\xc3\
+\x00\x00\x01\x83\xb1\xb2\x06\x97\
\x00\x00\x02R\x00\x00\x00\x00\x00\x01\x00\x07\x05\xa0\
-\x00\x00\x01\x88\x04\xacr\xd2\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x0b\x18\x00\x00\x00\x00\x00\x01\x00\x07\xad\x12\
-\x00\x00\x01\x88\x04\xacr\xd8\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x06\x18\x00\x00\x00\x00\x00\x01\x00\x06\xebN\
-\x00\x00\x01\x88\x04\xacr\xda\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x08\xba\x00\x00\x00\x00\x00\x01\x00\x07A\xae\
-\x00\x00\x01\x88\x04\xacr\xcd\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x09\x16\x00\x00\x00\x00\x00\x01\x00\x07Vm\
-\x00\x00\x01\x88\x04\xacr\xce\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x06\xba\x00\x00\x00\x00\x00\x01\x00\x07\x03D\
-\x00\x00\x01\x88\x04\xacr\xd9\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x08>\x00\x00\x00\x00\x00\x01\x00\x07;\xea\
-\x00\x00\x01\x88\x04\xacr\xd7\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x08\xf2\x00\x00\x00\x00\x00\x01\x00\x07T\xab\
-\x00\x00\x01\x88\x04\xacr\xd2\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x0a2\x00\x00\x00\x00\x00\x01\x00\x07\x94\xae\
-\x00\x00\x01\x88\x04\xacr\xd2\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x07F\x00\x00\x00\x00\x00\x01\x00\x07\x17\xb0\
-\x00\x00\x01\x88\x04\xacr\xd9\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x06\xda\x00\x00\x00\x00\x00\x01\x00\x07\x08\xe0\
-\x00\x00\x01\x88\x04\xacr\xd6\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x07\x22\xda\
-\x00\x00\x01\x88\x04\xacr\xcf\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x08`\x00\x00\x00\x00\x00\x01\x00\x07=\xf7\
-\x00\x00\x01\x88\x04\xacr\xd6\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x07\x98\x00\x00\x00\x00\x00\x01\x00\x07\x1d\xab\
-\x00\x00\x01\x88\x04\xacr\xd8\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x08\x94\x00\x00\x00\x00\x00\x01\x00\x07?S\
-\x00\x00\x01\x88\x04\xacr\xd7\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x06>\x00\x00\x00\x00\x00\x01\x00\x06\xedW\
-\x00\x00\x01\x88\x04\xacr\xcc\
+\x00\x00\x01\x83\xb1\xb2\x06\x9e\
\x00\x00\x09d\x00\x00\x00\x00\x00\x01\x00\x07r\xb2\
-\x00\x00\x01\x88\x04\xacr\xcf\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x07^\x00\x00\x00\x00\x00\x01\x00\x07\x19\xa3\
-\x00\x00\x01\x88\x04\xacr\xd1\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x0a\x86\x00\x00\x00\x00\x00\x01\x00\x07\x97y\
-\x00\x00\x01\x88\x04\xacr\xd4\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x06\x02\x00\x00\x00\x00\x00\x01\x00\x06\xe8\xdf\
-\x00\x00\x01\x88\x04\xacr\xda\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x06\x98\x00\x00\x00\x00\x00\x01\x00\x06\xf5g\
-\x00\x00\x01\x88\x04\xacr\xcf\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x07\xfa\x00\x00\x00\x00\x00\x01\x00\x07!x\
-\x00\x00\x01\x88\x04\xacr\xd0\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x06\xfe\x00\x00\x00\x00\x00\x01\x00\x07\x13\x93\
-\x00\x00\x01\x88\x04\xacr\xcc\
+\x00\x00\x01\x83\xb1\xb2\x06\x9f\
\x00\x00\x0a\xd0\x00\x00\x00\x00\x00\x01\x00\x07\x9b;\
-\x00\x00\x01\x88\x04\xacr\xd3\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x0aV\x00\x00\x00\x00\x00\x01\x00\x07\x96t\
-\x00\x00\x01\x88\x04\xacr\xd6\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x05\xe6\x00\x00\x00\x00\x00\x01\x00\x06\xe5\xba\
-\x00\x00\x01\x88\x04\xacr\xd3\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x07\x80\x00\x00\x00\x00\x00\x01\x00\x07\x1a\xd0\
-\x00\x00\x01\x88\x04\xacr\xcc\
+\x00\x00\x01\x83\xb1\xb2\x06\x9f\
\x00\x00\x09N\x00\x00\x00\x00\x00\x01\x00\x07p\xff\
-\x00\x00\x01\x88\x04\xacr\xcd\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x0a\xf8\x00\x00\x00\x00\x00\x01\x00\x07\x9d\x96\
-\x00\x00\x01\x88\x04\xacr\xce\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x09\xb8\x00\x00\x00\x00\x00\x01\x00\x07\x82E\
-\x00\x00\x01\x88\x04\xacr\xd7\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x08\xda\x00\x00\x00\x00\x00\x01\x00\x07S\x8c\
-\x00\x00\x01\x88\x04\xacr\xd8\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x098\x00\x00\x00\x00\x00\x01\x00\x07d\xdb\
-\x00\x00\x01\x88\x04\xacr\xda\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x06\x84\x00\x00\x00\x00\x00\x01\x00\x06\xf0\xcd\
-\x00\x00\x01\x88\x04\xacr\xcd\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x09\xe4\x00\x00\x00\x00\x00\x01\x00\x07\x85\x07\
-\x00\x00\x01\x88\x04\xacr\xd5\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x09\x88\x00\x00\x00\x00\x00\x01\x00\x07\x80\xaf\
-\x00\x00\x01\x88\x04\xacr\xd3\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x070\x00\x00\x00\x00\x00\x01\x00\x07\x15a\
-\x00\x00\x01\x88\x04\xacr\xd0\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x09\xfc\x00\x00\x00\x00\x00\x01\x00\x07\x87Q\
-\x00\x00\x01\x88\x04\xacr\xcd\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x06n\x00\x00\x00\x00\x00\x01\x00\x06\xef#\
-\x00\x00\x01\x88\x04\xacr\xd7\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x0a\x14\x00\x00\x00\x00\x00\x01\x00\x07\x88\xb7\
-\x00\x00\x01\x88\x04\xacr\xce\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x07\xcc\x00\x01\x00\x00\x00\x01\x00\x07\x1f\x00\
-\x00\x00\x01\x88\x04\xacr\xd8\
+\x00\x00\x01\x84\x9aC*K\
\x00\x00\x07\xe6\x00\x00\x00\x00\x00\x01\x00\x07\x1f\xda\
-\x00\x00\x01\x88\x04\xacr\xd1\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x0a\xaa\x00\x00\x00\x00\x00\x01\x00\x07\x99\x5c\
-\x00\x00\x01\x88\x04\xacr\xd1\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x04\xd8\x00\x00\x00\x00\x00\x01\x00\x07\x06\xaf\
-\x00\x00\x01\x88\x04\xacr\xd1\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x05@\x00\x00\x00\x00\x00\x01\x00\x071X\
-\x00\x00\x01\x88\x04\xacr\xcf\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x05l\x00\x00\x00\x00\x00\x01\x00\x07f/\
-\x00\x00\x01\x88\x04\xacr\xd0\
+\x00\x00\x01\x83\xb1\xb2\x06\xa0\
\x00\x00\x05\x98\x00\x00\x00\x00\x00\x01\x00\x06\xe1\xa9\
-\x00\x00\x01\x88\x04\xacr\xdd\
+\x00\x00\x01\x83\xb1\xb2\x06\xae\
\x00\x00\x04\xf6\x00\x00\x00\x00\x00\x01\x00\x06\xc90\
-\x00\x00\x01\x88\x04\xacr\xde\
+\x00\x00\x01\x83\xb1\xb2\x06\xae\
\x00\x00\x02\x80\x00\x00\x00\x00\x00\x01\x00\x06\xc5\x1e\
-\x00\x00\x01\x88\x04\xacr\xdd\
+\x00\x00\x01\x83\xb1\xb2\x06\xae\
\x00\x00\x04\xb6\x00\x00\x00\x00\x00\x01\x00\x06\xc3\x0b\
-\x00\x00\x01\x88\x04\xacr\xdb\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x05\xca\x00\x00\x00\x00\x00\x01\x00\x06\xe3{\
-\x00\x00\x01\x88\x04\xacr\xdc\
+\x00\x00\x01\x83\xb1\xb2\x06\xa8\
\x00\x00\x05\x10\x00\x00\x00\x00\x00\x01\x00\x06\xcb\xba\
-\x00\x00\x01\x88\x04\xacr\xde\
+\x00\x00\x01\x83\xb1\xb2\x06\xae\
\x00\x00\x04\xd8\x00\x00\x00\x00\x00\x01\x00\x06\xc6\xff\
-\x00\x00\x01\x88\x04\xacr\xdd\
+\x00\x00\x01\x83\xb1\xb2\x06\xae\
\x00\x00\x05@\x00\x00\x00\x00\x00\x01\x00\x06\xcem\
-\x00\x00\x01\x89\x02*\x10\xe9\
+\x00\x00\x01\x89\xd8\xc60/\
\x00\x00\x05l\x00\x00\x00\x00\x00\x01\x00\x06\xd8'\
-\x00\x00\x01\x89\x02*\x10\xed\
+\x00\x00\x01\x89\xd8\xc60/\
\x00\x00\x01&\x00\x01\x00\x00\x00\x01\x00\x03\x04\x82\
-\x00\x00\x01\x88\x04\xacr\xc8\
+\x00\x00\x01\x83\xb1\xb2\x06\x9a\
\x00\x00\x00\xe6\x00\x01\x00\x00\x00\x01\x00\x02\xbaN\
-\x00\x00\x01\x88\x04\xacr\xc8\
+\x00\x00\x01\x83\xb1\xb2\x06\x9b\
\x00\x00\x01d\x00\x00\x00\x00\x00\x01\x00\x03\x10\xa8\
-\x00\x00\x01\x88\x04\xacr\xcb\
+\x00\x00\x01\x83\xb1\xb2\x06\x9d\
"
def qInitResources():
diff --git a/spinetoolbox/ui/mainwindow.py b/spinetoolbox/ui/mainwindow.py
index e366d3f87..58c31c411 100644
--- a/spinetoolbox/ui/mainwindow.py
+++ b/spinetoolbox/ui/mainwindow.py
@@ -13,7 +13,7 @@
################################################################################
## Form generated from reading UI file 'mainwindow.ui'
##
-## Created by: Qt User Interface Compiler version 6.4.3
+## Created by: Qt User Interface Compiler version 6.5.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -599,14 +599,23 @@ def retranslateUi(self, MainWindow):
self.actionSet_description.setToolTip(QCoreApplication.translate("MainWindow", u"Modify or set project description", None))
#endif // QT_CONFIG(tooltip)
self.actionExecute_project.setText(QCoreApplication.translate("MainWindow", u"Project", None))
+#if QT_CONFIG(tooltip)
+ self.actionExecute_project.setToolTip(QCoreApplication.translate("MainWindow", u"Execute all items in project.", None))
+#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(shortcut)
self.actionExecute_project.setShortcut(QCoreApplication.translate("MainWindow", u"F5", None))
#endif // QT_CONFIG(shortcut)
self.actionExecute_selection.setText(QCoreApplication.translate("MainWindow", u"Selection", None))
+#if QT_CONFIG(tooltip)
+ self.actionExecute_selection.setToolTip(QCoreApplication.translate("MainWindow", u"Execute selected items.", None))
+#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(shortcut)
self.actionExecute_selection.setShortcut(QCoreApplication.translate("MainWindow", u"F6", None))
#endif // QT_CONFIG(shortcut)
self.actionStop_execution.setText(QCoreApplication.translate("MainWindow", u"Stop", None))
+#if QT_CONFIG(tooltip)
+ self.actionStop_execution.setToolTip(QCoreApplication.translate("MainWindow", u"Stop execution.", None))
+#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(shortcut)
self.actionStop_execution.setShortcut(QCoreApplication.translate("MainWindow", u"F7", None))
#endif // QT_CONFIG(shortcut)
diff --git a/spinetoolbox/ui/mainwindow.ui b/spinetoolbox/ui/mainwindow.ui
index 3f60d75bf..080bf2be8 100644
--- a/spinetoolbox/ui/mainwindow.ui
+++ b/spinetoolbox/ui/mainwindow.ui
@@ -894,6 +894,9 @@
Project
+
+ Execute all items in project.
+
F5
@@ -906,6 +909,9 @@
Selection
+
+ Execute selected items.
+
F6
@@ -918,6 +924,9 @@
Stop
+
+ Stop execution.
+
F7
diff --git a/spinetoolbox/ui_main.py b/spinetoolbox/ui_main.py
index be9ef09dd..704430827 100644
--- a/spinetoolbox/ui_main.py
+++ b/spinetoolbox/ui_main.py
@@ -60,6 +60,7 @@
from .mvcmodels.project_item_model import ProjectItemModel
from .mvcmodels.project_item_specification_models import ProjectItemSpecificationModel, FilteredSpecificationModel
from .mvcmodels.filter_execution_model import FilterExecutionModel
+from .project_settings import ProjectSettings
from .widgets.set_description_dialog import SetDescriptionDialog
from .widgets.multi_tab_spec_editor import MultiTabSpecEditor
from .widgets.about_widget import AboutWidget
@@ -184,6 +185,7 @@ def __init__(self):
self.ui.actionExecute_project, self.ui.actionExecute_selection, self.ui.actionStop_execution, self
)
self.addToolBar(Qt.TopToolBarArea, self.main_toolbar)
+ self._original_execute_project_action_tooltip = self.ui.actionExecute_project.toolTip()
self.setStatusBar(None)
# Additional consoles for item execution
self._item_consoles = {} # Mapping of ProjectItem to console
@@ -365,7 +367,14 @@ def _update_qsettings(self):
def _update_execute_enabled(self):
first_index = next(self.project_item_model.leaf_indexes(), None)
- self.ui.actionExecute_project.setEnabled(first_index is not None and not self.execution_in_progress)
+ enabled_by_project = self._project.settings.enable_execute_all if self._project is not None else False
+ self.ui.actionExecute_project.setEnabled(
+ enabled_by_project and first_index is not None and not self.execution_in_progress
+ )
+ if not enabled_by_project:
+ self.ui.actionExecute_project.setToolTip("Executing entire project disabled by project settings.")
+ else:
+ self.ui.actionExecute_project.setToolTip(self._original_execute_project_action_tooltip)
def _update_execute_selected_enabled(self):
has_selection = bool(self._selected_item_names)
@@ -513,7 +522,12 @@ def create_project(self, proj_dir):
return
self.undo_stack.clear()
self._project = SpineToolboxProject(
- self, proj_dir, self._plugin_manager.plugin_specs, settings=self._qsettings, logger=self
+ self,
+ proj_dir,
+ self._plugin_manager.plugin_specs,
+ app_settings=self._qsettings,
+ settings=ProjectSettings(),
+ logger=self,
)
self.project_item_model.connect_to_project(self._project)
self.specification_model.connect_to_project(self._project)
@@ -575,7 +589,12 @@ def restore_project(self, project_dir, ask_confirmation=True):
# Create project
self.undo_stack.clear()
self._project = SpineToolboxProject(
- self, project_dir, self._plugin_manager.plugin_specs, settings=self._qsettings, logger=self
+ self,
+ project_dir,
+ self._plugin_manager.plugin_specs,
+ app_settings=self._qsettings,
+ settings=ProjectSettings(),
+ logger=self,
)
self.project_item_model.connect_to_project(self._project)
self.specification_model.connect_to_project(self._project)
diff --git a/tests/test_ProjectUpgrader.py b/tests/test_ProjectUpgrader.py
index 9e6146692..be566c6aa 100644
--- a/tests/test_ProjectUpgrader.py
+++ b/tests/test_ProjectUpgrader.py
@@ -22,6 +22,8 @@
from pathlib import Path
from tempfile import TemporaryDirectory
from PySide6.QtWidgets import QApplication
+
+from spinetoolbox.project_settings import ProjectSettings
from spinetoolbox.project_upgrader import ProjectUpgrader
from spinetoolbox.resources_icons_rc import qInitResources
from spinetoolbox.config import LATEST_PROJECT_VERSION
@@ -172,17 +174,17 @@ def test_upgrade_v2_to_v3(self):
with open(spec_file_path, "w", encoding="utf-8") as tmp_spec_file:
tmp_spec_file.write("hello")
# Upgrade to version 3
- proj_v3 = pu.upgrade(proj_v2, project_dir)
- mock_backup.assert_called_once()
- mock_force_save.assert_called_once()
- self.assertTrue(pu.is_valid(3, proj_v3))
- # Check that items were transferred successfully by checking that item names are found in new
- # 'items' dict and that they contain a dict
- v2_items = proj_v2["items"]
- v3_items = proj_v3["items"]
- for name in v2_items.keys():
- self.assertTrue(name in v3_items.keys())
- self.assertIsInstance(v3_items[name], dict)
+ proj_v3 = pu.upgrade(proj_v2, project_dir)
+ mock_backup.assert_called_once()
+ mock_force_save.assert_called_once()
+ self.assertTrue(pu.is_valid(3, proj_v3))
+ # Check that items were transferred successfully by checking that item names are found in new
+ # 'items' dict and that they contain a dict
+ v2_items = proj_v2["items"]
+ v3_items = proj_v3["items"]
+ for name in v2_items.keys():
+ self.assertTrue(name in v3_items.keys())
+ self.assertIsInstance(v3_items[name], dict)
def test_upgrade_v3_to_v4(self):
pu = ProjectUpgrader(self.toolbox)
@@ -201,18 +203,18 @@ def test_upgrade_v3_to_v4(self):
spec_file_path = os.path.join(project_dir, "tool_specs", "preprocessing_tool.json")
with open(spec_file_path, "w", encoding="utf-8") as tmp_spec_file:
tmp_spec_file.write("hello")
- # Upgrade to version 4
- proj_v4 = pu.upgrade(proj_v3, project_dir)
- mock_backup.assert_called_once()
- mock_force_save.assert_called_once()
- self.assertTrue(pu.is_valid(4, proj_v4))
- # Check that items were transferred successfully by checking that item names are found in new
- # 'items' dict and that they contain a dict
- v3_items = proj_v3["items"]
- v4_items = proj_v4["items"]
- for name in v3_items.keys():
- self.assertTrue(name in v4_items.keys())
- self.assertIsInstance(v4_items[name], dict)
+ # Upgrade to version 4
+ proj_v4 = pu.upgrade(proj_v3, project_dir)
+ mock_backup.assert_called_once()
+ mock_force_save.assert_called_once()
+ self.assertTrue(pu.is_valid(4, proj_v4))
+ # Check that items were transferred successfully by checking that item names are found in new
+ # 'items' dict and that they contain a dict
+ v3_items = proj_v3["items"]
+ v4_items = proj_v4["items"]
+ for name in v3_items.keys():
+ self.assertTrue(name in v4_items.keys())
+ self.assertIsInstance(v4_items[name], dict)
def test_upgrade_v4_to_v5(self):
pu = ProjectUpgrader(self.toolbox)
@@ -231,27 +233,27 @@ def test_upgrade_v4_to_v5(self):
spec_file_path = os.path.join(project_dir, "tool_specs", "preprocessing_tool.json")
with open(spec_file_path, "w", encoding="utf-8") as tmp_spec_file:
tmp_spec_file.write("hello")
- # Upgrade to version 5
- proj_v5 = pu.upgrade(proj_v4, project_dir)
- mock_backup.assert_called_once()
- mock_force_save.assert_called_once()
- self.assertTrue(pu.is_valid(5, proj_v5))
- # Check that items were transferred successfully by checking that item names are found in new
- # 'items' dict and that they contain a dict. Combiners should be gone in v5
- v4_items = proj_v4["items"]
- # Make a list of Combiner names
- combiners = list()
- for name, d in v4_items.items():
- if d["type"] == "Combiner":
- combiners.append(name)
- v5_items = proj_v5["items"]
- for name in v4_items.keys():
- if name in combiners:
- # v5 should not have Combiners anymore
- self.assertFalse(name in v5_items.keys())
- else:
- self.assertTrue(name in v5_items.keys())
- self.assertIsInstance(v5_items[name], dict)
+ # Upgrade to version 5
+ proj_v5 = pu.upgrade(proj_v4, project_dir)
+ mock_backup.assert_called_once()
+ mock_force_save.assert_called_once()
+ self.assertTrue(pu.is_valid(5, proj_v5))
+ # Check that items were transferred successfully by checking that item names are found in new
+ # 'items' dict and that they contain a dict. Combiners should be gone in v5
+ v4_items = proj_v4["items"]
+ # Make a list of Combiner names
+ combiners = list()
+ for name, d in v4_items.items():
+ if d["type"] == "Combiner":
+ combiners.append(name)
+ v5_items = proj_v5["items"]
+ for name in v4_items.keys():
+ if name in combiners:
+ # v5 should not have Combiners anymore
+ self.assertFalse(name in v5_items.keys())
+ else:
+ self.assertTrue(name in v5_items.keys())
+ self.assertIsInstance(v5_items[name], dict)
def test_upgrade_v9_to_v10(self):
pu = ProjectUpgrader(self.toolbox)
@@ -270,28 +272,52 @@ def test_upgrade_v9_to_v10(self):
spec_file_path = os.path.join(project_dir, "tool_specs", "preprocessing_tool.json")
with open(spec_file_path, "w", encoding="utf-8") as tmp_spec_file:
tmp_spec_file.write("hello")
- # Upgrade to version 10
- proj_v10 = pu.upgrade(proj_v9, project_dir)
- mock_backup.assert_called_once()
- mock_force_save.assert_called_once()
- self.assertTrue(pu.is_valid(10, proj_v10))
- v10_items = proj_v10["items"]
- # Make a list of Gimlet and GdxExporter names in v9
- names = list()
- for name, d in proj_v9["items"].items():
- if d["type"] in ["Gimlet", "GdxExporter"]:
- names.append(name)
- self.assertEqual(4, len(names)) # Old should have 3 Gimlets, 1 GdxExporter
- # Check that connections have been removed
- for conn in proj_v10["project"]["connections"]:
- for name in names:
- self.assertTrue(name not in conn["from"] and name not in conn["to"])
- # Check that gimlet and GdxExporter dicts are gone from items
- for item_name in v10_items.keys():
- self.assertTrue(item_name not in names)
- # Check number of connections
- self.assertEqual(8, len(proj_v9["project"]["connections"]))
- self.assertEqual(1, len(proj_v10["project"]["connections"]))
+ # Upgrade to version 10
+ proj_v10 = pu.upgrade(proj_v9, project_dir)
+ mock_backup.assert_called_once()
+ mock_force_save.assert_called_once()
+ self.assertTrue(pu.is_valid(10, proj_v10))
+ v10_items = proj_v10["items"]
+ # Make a list of Gimlet and GdxExporter names in v9
+ names = list()
+ for name, d in proj_v9["items"].items():
+ if d["type"] in ["Gimlet", "GdxExporter"]:
+ names.append(name)
+ self.assertEqual(4, len(names)) # Old should have 3 Gimlets, 1 GdxExporter
+ # Check that connections have been removed
+ for conn in proj_v10["project"]["connections"]:
+ for name in names:
+ self.assertTrue(name not in conn["from"] and name not in conn["to"])
+ # Check that gimlet and GdxExporter dicts are gone from items
+ for item_name in v10_items.keys():
+ self.assertTrue(item_name not in names)
+ # Check number of connections
+ self.assertEqual(8, len(proj_v9["project"]["connections"]))
+ self.assertEqual(1, len(proj_v10["project"]["connections"]))
+
+ def test_upgrade_v10_to_v11(self):
+ pu = ProjectUpgrader(self.toolbox)
+ proj_v10 = make_v10_project_dict()
+ self.assertTrue(pu.is_valid(10, proj_v10))
+ with TemporaryDirectory() as project_dir:
+ with mock.patch(
+ "spinetoolbox.project_upgrader.ProjectUpgrader.backup_project_file"
+ ) as mock_backup, mock.patch(
+ "spinetoolbox.project_upgrader.ProjectUpgrader.force_save"
+ ) as mock_force_save, mock.patch(
+ 'spinetoolbox.project_upgrader.LATEST_PROJECT_VERSION', 11
+ ):
+ os.mkdir(os.path.join(project_dir, "tool_specs")) # Make /tool_specs dir
+ proj_v11 = pu.upgrade(proj_v10, project_dir)
+ mock_backup.assert_called_once()
+ mock_force_save.assert_called_once()
+ self.assertTrue(pu.is_valid(11, proj_v11))
+ self.assertEqual(proj_v11["project"]["version"], 11)
+ self.assertIn("settings", proj_v11["project"])
+ try:
+ ProjectSettings.from_dict(proj_v11["project"]["settings"])
+ except:
+ self.fail("project settings cannot be deserialized")
def test_upgrade_v1_to_latest(self):
pu = ProjectUpgrader(self.toolbox)
@@ -306,21 +332,22 @@ def test_upgrade_v1_to_latest(self):
spec_file_path = os.path.join(project_dir, "tool_specs", "preprocessing_tool.json")
with open(spec_file_path, "w", encoding="utf-8") as tmp_spec_file:
tmp_spec_file.write("hello")
- # Upgrade to latest version
- proj_latest = pu.upgrade(proj_v1, project_dir)
- mock_backup.assert_called_once()
- mock_force_save.assert_called_once()
- self.assertTrue(pu.is_valid(LATEST_PROJECT_VERSION, proj_latest))
- # Check that items were transferred successfully by checking that item names are found in new
- # 'items' dict and that they contain a dict. Combiners should be gone in v5
- v1_items = proj_v1["objects"]
- latest_items = proj_latest["items"]
- # v1 project items were categorized under a dict which were inside an 'objects' dict
- for item_category in v1_items.keys():
- for name in v1_items[item_category]:
- self.assertTrue(name in latest_items.keys())
- self.assertIsInstance(latest_items[name], dict)
- self.assertTrue(latest_items[name]["type"] == item_category[:-1])
+ # Upgrade to latest version
+ proj_latest = pu.upgrade(proj_v1, project_dir)
+ mock_backup.assert_called_once()
+ mock_force_save.assert_called_once()
+ self.assertTrue(pu.is_valid(LATEST_PROJECT_VERSION, proj_latest))
+ self.assertEqual(proj_latest["project"]["version"], LATEST_PROJECT_VERSION)
+ # Check that items were transferred successfully by checking that item names are found in new
+ # 'items' dict and that they contain a dict. Combiners should be gone in v5
+ v1_items = proj_v1["objects"]
+ latest_items = proj_latest["items"]
+ # v1 project items were categorized under a dict which were inside an 'objects' dict
+ for item_category in v1_items.keys():
+ for name in v1_items[item_category]:
+ self.assertTrue(name in latest_items.keys())
+ self.assertIsInstance(latest_items[name], dict)
+ self.assertTrue(latest_items[name]["type"] == item_category[:-1])
def test_upgrade_with_too_recent_project_version(self):
"""Tests that projects with too recent versions are not opened."""
@@ -358,6 +385,10 @@ def make_v10_project_dict():
return _get_project_dict(10)
+def make_v11_project_dict():
+ return _get_project_dict(11)
+
+
def _get_project_dict(v):
"""Returns a project dict read from a file according to given version."""
project_json_versions_dir = os.path.join(str(Path(__file__).parent), "test_resources", "project_json_versions")
diff --git a/tests/test_SpineToolboxProject.py b/tests/test_SpineToolboxProject.py
index f7c8705fa..28391f6a3 100644
--- a/tests/test_SpineToolboxProject.py
+++ b/tests/test_SpineToolboxProject.py
@@ -543,7 +543,14 @@ def test_save_when_storing_item_local_data(self):
project_dict,
{
"items": {"test item": {"type": "Tester", "a": {"c": 2}}},
- "project": {"connections": [], "description": "", "jumps": [], "specifications": {}, "version": 10},
+ "project": {
+ "connections": [],
+ "description": "",
+ "jumps": [],
+ "settings": {"enable_execute_all": True},
+ "specifications": {},
+ "version": 11,
+ },
},
)
with Path(project.config_dir, PROJECT_LOCAL_DATA_DIR_NAME, PROJECT_LOCAL_DATA_FILENAME).open() as fp:
diff --git a/tests/test_resources/Project Directory/.spinetoolbox/project.json b/tests/test_resources/Project Directory/.spinetoolbox/project.json
index 59ed29ed8..41e1ba08e 100644
--- a/tests/test_resources/Project Directory/.spinetoolbox/project.json
+++ b/tests/test_resources/Project Directory/.spinetoolbox/project.json
@@ -1,6 +1,6 @@
{
"project": {
- "version": 10,
+ "version": 11,
"description": "Project for unit tests.",
"specifications": {
"Tool": [],
@@ -38,7 +38,10 @@
"left"
]
}
- ]
+ ],
+ "settings": {
+ "enable_execute_all": true
+ }
},
"items": {
"a": {
diff --git a/tests/test_resources/project_json_versions/proj_v11.json b/tests/test_resources/project_json_versions/proj_v11.json
new file mode 100644
index 000000000..29c5ca97d
--- /dev/null
+++ b/tests/test_resources/project_json_versions/proj_v11.json
@@ -0,0 +1,293 @@
+{
+ "project": {
+ "version": 11,
+ "description": "Import and Export",
+ "settings": {"enable_execute_all": true},
+ "specifications": {
+ "Importer": [
+ {
+ "type": "path",
+ "relative": true,
+ "path": "Importer 1 - units.xlsx.json"
+ }
+ ],
+ "Exporter": [
+ {
+ "type": "path",
+ "relative": true,
+ "path": ".spinetoolbox/specifications/Exporter/pekka.json"
+ },
+ {
+ "type": "path",
+ "relative": true,
+ "path": ".spinetoolbox/specifications/Exporter/gdx_export_mapping.json"
+ }
+ ],
+ "Tool": [
+ {
+ "type": "path",
+ "relative": true,
+ "path": ".spinetoolbox/specifications/Tool/testeri.json"
+ }
+ ]
+ },
+ "connections": [
+ {
+ "name": "from Raw data to Importer 1",
+ "from": [
+ "Raw data",
+ "right"
+ ],
+ "to": [
+ "Importer 1",
+ "left"
+ ]
+ },
+ {
+ "name": "from Importer 1 to DS1",
+ "from": [
+ "Importer 1",
+ "right"
+ ],
+ "to": [
+ "DS1",
+ "left"
+ ],
+ "options": {
+ "purge_before_writing": true,
+ "purge_settings": null
+ }
+ },
+ {
+ "name": "from DS2 to Merger 1",
+ "from": [
+ "DS2",
+ "right"
+ ],
+ "to": [
+ "Merger 1",
+ "left"
+ ]
+ },
+ {
+ "name": "from Merger 1 to Output Db",
+ "from": [
+ "Merger 1",
+ "right"
+ ],
+ "to": [
+ "Output Db",
+ "left"
+ ],
+ "options": {
+ "purge_before_writing": true,
+ "purge_settings": {
+ "object_class": true,
+ "relationship_class": true,
+ "parameter_value_list": true,
+ "list_value": true,
+ "parameter_definition": true,
+ "object": true,
+ "relationship": true,
+ "entity_group": true,
+ "parameter_value": true,
+ "alternative": true,
+ "scenario": true,
+ "scenario_alternative": true,
+ "feature": true,
+ "tool": true,
+ "tool_feature": true,
+ "tool_feature_method": true,
+ "metadata": true,
+ "entity_metadata": true,
+ "parameter_value_metadata": true
+ }
+ }
+ },
+ {
+ "name": "from GDX file to GDX Exporter",
+ "from": [
+ "GDX file",
+ "right"
+ ],
+ "to": [
+ "GDX Exporter",
+ "left"
+ ]
+ },
+ {
+ "name": "from DS1 to Exporter 1",
+ "from": [
+ "DS1",
+ "right"
+ ],
+ "to": [
+ "Exporter 1",
+ "left"
+ ]
+ },
+ {
+ "name": "from DS1 to Merger 1",
+ "from": [
+ "DS1",
+ "right"
+ ],
+ "to": [
+ "Merger 1",
+ "left"
+ ]
+ }
+ ],
+ "jumps": []
+ },
+ "items": {
+ "Importer 1": {
+ "type": "Importer",
+ "description": "",
+ "x": 37.00929801385436,
+ "y": -50.831770740600405,
+ "specification": "Importer 1 - units.xlsx",
+ "cancel_on_error": true,
+ "on_conflict": "replace",
+ "file_selection": [
+ [
+ "/a.csv",
+ false
+ ],
+ [
+ "/c.ini",
+ false
+ ],
+ [
+ "/d.txt",
+ false
+ ],
+ [
+ "/units.xlsx",
+ true
+ ],
+ [
+ "/data.txt",
+ false
+ ]
+ ]
+ },
+ "DS1": {
+ "type": "Data Store",
+ "description": "",
+ "x": 161.72875313326617,
+ "y": -141.85480886591952,
+ "url": {
+ "dialect": "sqlite",
+ "host": "",
+ "port": "",
+ "database": {
+ "type": "path",
+ "relative": true,
+ "path": ".spinetoolbox/items/ds1/DS1.sqlite"
+ }
+ }
+ },
+ "Raw data": {
+ "type": "Data Connection",
+ "description": "",
+ "x": -92.35946011852081,
+ "y": -139.9349488970861,
+ "file_references": [
+ {
+ "type": "path",
+ "relative": true,
+ "path": "data.txt"
+ }
+ ],
+ "db_references": []
+ },
+ "Exporter 1": {
+ "type": "Exporter",
+ "description": "",
+ "x": 327.28798970484826,
+ "y": -34.157876811488,
+ "output_time_stamps": true,
+ "cancel_on_error": true,
+ "output_labels": [
+ {
+ "in_label": "db_url@DS1",
+ "out_label": "output_file"
+ }
+ ],
+ "specification": "SpineOptToTable"
+ },
+ "Tool 1": {
+ "type": "Tool",
+ "description": "",
+ "x": 8.781957645503311,
+ "y": -261.72457943295257,
+ "specification": "testeri",
+ "execute_in_work": true,
+ "cmd_line_args": []
+ },
+ "Merger 1": {
+ "type": "Merger",
+ "description": "",
+ "x": 318.01113350038577,
+ "y": -194.14196047822415,
+ "cancel_on_error": false
+ },
+ "DS2": {
+ "type": "Data Store",
+ "description": "",
+ "x": 164.00244309606893,
+ "y": -266.32829810435675,
+ "url": {
+ "dialect": "sqlite",
+ "host": "",
+ "port": "",
+ "database": {
+ "type": "path",
+ "relative": true,
+ "path": ".spinetoolbox/items/ds2/DS2.sqlite"
+ }
+ }
+ },
+ "Output Db": {
+ "type": "Data Store",
+ "description": "",
+ "x": 448.8029423394426,
+ "y": -215.88120679697633,
+ "url": {
+ "dialect": "sqlite",
+ "host": "",
+ "port": "",
+ "database": {
+ "type": "path",
+ "relative": true,
+ "path": ".spinetoolbox/items/output_db/Output Db.sqlite"
+ }
+ }
+ },
+ "GDX Exporter": {
+ "type": "Exporter",
+ "description": "",
+ "x": 180.76090893795083,
+ "y": 36.76493063144763,
+ "output_time_stamps": false,
+ "cancel_on_error": true,
+ "output_labels": [],
+ "specification": "gdx export mapping"
+ },
+ "GDX file": {
+ "type": "Data Connection",
+ "description": "",
+ "x": -69.44486897051219,
+ "y": 35.74368255835186,
+ "file_references": [
+ {
+ "type": "path",
+ "relative": false,
+ "path": "C:/Users/ttepsa/OneDrive - Teknologian Tutkimuskeskus VTT/Documents/SpineToolboxProjects/Gdx Export Test/.spinetoolbox/items/gdx_exporter/file.gdx"
+ }
+ ],
+ "db_references": []
+ }
+ }
+}