diff --git a/beat/editor/backend/__init__.py b/beat/editor/backend/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..057f30dbface14b6fa5548be312381ebcb9fa628 --- /dev/null +++ b/beat/editor/backend/__init__.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# vim: set fileencoding=utf-8 : + +############################################################################### +# # +# Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ # +# Contact: beat.support@idiap.ch # +# # +# This file is part of the beat.cmdline module of the BEAT platform. # +# # +# Commercial License Usage # +# Licensees holding valid commercial BEAT licenses may use this file in # +# accordance with the terms contained in a written agreement between you # +# and Idiap. For further information contact tto@idiap.ch # +# # +# Alternatively, this file may be used under the terms of the GNU Affero # +# Public License version 3 as published by the Free Software and appearing # +# in the file LICENSE.AGPL included in the packaging of this file. # +# The BEAT platform 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. # +# # +# You should have received a copy of the GNU Affero Public License along # +# with the BEAT platform. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### diff --git a/beat/editor/backend/assetmodel.py b/beat/editor/backend/assetmodel.py new file mode 100644 index 0000000000000000000000000000000000000000..185fb57c2c492f5e2368f8a48fc9b403289f67ca --- /dev/null +++ b/beat/editor/backend/assetmodel.py @@ -0,0 +1,183 @@ +# vim: set fileencoding=utf-8 : +############################################################################### +# # +# Copyright (c) 2019 Idiap Research Institute, http://www.idiap.ch/ # +# Contact: beat.support@idiap.ch # +# # +# This file is part of the beat.editor module of the BEAT platform. # +# # +# Commercial License Usage # +# Licensees holding valid commercial BEAT licenses may use this file in # +# accordance with the terms contained in a written agreement between you # +# and Idiap. For further information contact tto@idiap.ch # +# # +# Alternatively, this file may be used under the terms of the GNU Affero # +# Public License version 3 as published by the Free Software and appearing # +# in the file LICENSE.AGPL included in the packaging of this file. # +# The BEAT platform 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. # +# # +# You should have received a copy of the GNU Affero Public License along # +# with the BEAT platform. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +import os + +from PyQt5.QtCore import pyqtSignal +from PyQt5.QtCore import pyqtSlot +from PyQt5.QtCore import pyqtProperty +from PyQt5.QtCore import Q_ENUMS +from PyQt5.QtCore import QStringListModel + + +class AssetType: + """All possible assets available on the BEAT platform""" + + Unknown = 0 + Algorithm = 1 + Database = 2 + Dataformat = 3 + Experiment = 4 + Library = 5 + Plotter = 6 + Toolchain = 7 + + @classmethod + def path(cls, type_): + path = "unkown" + if type_ == cls.Algorithm: + path = "algorithms" + elif type_ == cls.Database: + path = "databases" + elif type_ == cls.Dataformat: + path = "dataformats" + elif type_ == cls.Experiment: + path = "experiments" + elif type_ == cls.Library: + path = "libraries" + elif type_ == cls.Plotter: + path = "plotters" + elif type_ == cls.Toolchain: + path = "toolchains" + + return path + + +class AssetModel(QStringListModel, AssetType): + """The asset model present a list of available asset from a given type""" + + Q_ENUMS(AssetType) + + assetTypeChanged = pyqtSignal(AssetType) + prefixPathChanged = pyqtSignal(str) + + def __init__(self, parent=None): + """Constructor""" + + super(QStringListModel, self).__init__(parent) + + self.__prefix_path = AssetType.Unknown + self.__asset_type = None + + @pyqtSlot() + def reload(self): + """Loads the content regarding the asset property from the prefix""" + + if not self.__prefix_path or self.__asset_type == AssetType.Unknown: + return + + asset_folder = os.path.join( + self.__prefix_path, AssetType.path(self.__asset_type) + ) + asset_users = os.scandir(asset_folder) + + latest_assets_list = [] + for asset_user in asset_users: + for asset_folder in os.scandir(asset_user): + if self.asset_type == AssetType.Experiment: + for root, dirs, files in os.walk(asset_folder, topdown=False): + if dirs: + continue + anchor = "experiments/" + position = root.index(anchor) + len(anchor) + experiment_path = root[position:] + for json_file in [ + file for file in files if file.endswith("json") + ]: + latest_assets_list.append( + "{experiment_path}/{name}".format( + experiment_path=experiment_path, + name=json_file.split(".")[0], + ) + ) + else: + asset_items = os.scandir(asset_folder) + json_files = sorted( + [ + item.name + for item in asset_items + if item.is_file() and item.name.endswith("json") + ] + ) + if json_files: + latest_assets_list.append( + "{user}/{name}/{version}".format( + user=asset_user.name, + name=asset_folder.name, + version=json_files[-1].split(".")[0], + ) + ) + + self.setStringList(sorted(latest_assets_list)) + + def assetType(self): + """Returns the asset type of this model + + :return: Asset type of this model + """ + + return self.__asset_type + + def setAssetType(self, type_): + """Set the asset type of this model + + :param AssetType type_: Asset type this model should show + """ + + if self.__asset_type == type_: + return + + self.__asset_type = type_ + self.reload() + self.assetTypeChanged.emit(type_) + + asset_type = pyqtProperty( + AssetType, fget=assetType, fset=setAssetType, notify=assetTypeChanged + ) + + def prefixPath(self): + """Returns the prefix path use by this model + + :return: the prefix path used + """ + + return self.__prefix_path + + def setPrefixPath(self, path): + """Set the prefix path use by this model + + :param str path: Path to prefix + """ + + if self.__prefix_path == path: + return + + self.__prefix_path = path + self.reload() + self.prefixPathChanged.emit(path) + + prefix_path = pyqtProperty( + str, fget=prefixPath, fset=setPrefixPath, notify=prefixPathChanged + ) diff --git a/beat/editor/test/conftest.py b/beat/editor/test/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..635f9d04f21022fc30bd3ec6a87a6d3a29598971 --- /dev/null +++ b/beat/editor/test/conftest.py @@ -0,0 +1,63 @@ +# vim: set fileencoding=utf-8 : +############################################################################### +# # +# Copyright (c) 2019 Idiap Research Institute, http://www.idiap.ch/ # +# Contact: beat.support@idiap.ch # +# # +# This file is part of the beat.editor module of the BEAT platform. # +# # +# Commercial License Usage # +# Licensees holding valid commercial BEAT licenses may use this file in # +# accordance with the terms contained in a written agreement between you # +# and Idiap. For further information contact tto@idiap.ch # +# # +# Alternatively, this file may be used under the terms of the GNU Affero # +# Public License version 3 as published by the Free Software and appearing # +# in the file LICENSE.AGPL included in the packaging of this file. # +# The BEAT platform 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. # +# # +# You should have received a copy of the GNU Affero Public License along # +# with the BEAT platform. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +import os +import sys +import tempfile +import pytest +import pkg_resources +import subprocess +import shutil +import importlib + + +if sys.platform == "darwin": + prefix_folder = tempfile.mkdtemp(prefix=__name__, suffix=".prefix", dir="/tmp") +else: + prefix_folder = tempfile.mkdtemp(prefix=__name__, suffix=".prefix") + + +prefix = os.path.join(prefix_folder, "prefix") + + +@pytest.fixture(scope="module") +def test_prefix(): + prefixes = [ + pkg_resources.resource_filename("beat.backend.python.test", "prefix"), + pkg_resources.resource_filename("beat.core.test", "prefix"), + ] + + for path in prefixes: + subprocess.check_call(["rsync", "-arz", path, prefix_folder]) + + yield prefix + + shutil.rmtree(prefix_folder) + + # Getting the prefix from beat.core triggers its setup_package but not the + # corresponding tear down, therefore trigger it manually + beat_core_test = importlib.import_module("beat.core.test") + teardown_package = getattr(beat_core_test, "teardown_package") + teardown_package() diff --git a/beat/editor/test/test_models.py b/beat/editor/test/test_models.py new file mode 100644 index 0000000000000000000000000000000000000000..44c4a420b1f5081632a9e1583a0632c4028747ab --- /dev/null +++ b/beat/editor/test/test_models.py @@ -0,0 +1,65 @@ +# vim: set fileencoding=utf-8 : +############################################################################### +# # +# Copyright (c) 2019 Idiap Research Institute, http://www.idiap.ch/ # +# Contact: beat.support@idiap.ch # +# # +# This file is part of the beat.editor module of the BEAT platform. # +# # +# Commercial License Usage # +# Licensees holding valid commercial BEAT licenses may use this file in # +# accordance with the terms contained in a written agreement between you # +# and Idiap. For further information contact tto@idiap.ch # +# # +# Alternatively, this file may be used under the terms of the GNU Affero # +# Public License version 3 as published by the Free Software and appearing # +# in the file LICENSE.AGPL included in the packaging of this file. # +# The BEAT platform 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. # +# # +# You should have received a copy of the GNU Affero Public License along # +# with the BEAT platform. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +from ..backend.assetmodel import AssetModel + + +class TestAssetModel: + """Test that the mock editor works correctly""" + + def test_model_load(self, qtbot, test_prefix): + model = AssetModel() + asset_type = AssetModel.Dataformat + + with qtbot.waitSignals( + [model.assetTypeChanged, model.prefixPathChanged] + ) as blocker: + model.asset_type = asset_type + model.prefix_path = test_prefix + + assert blocker.all_signals_and_args[0].args[0] == asset_type + assert blocker.all_signals_and_args[1].args[0] == test_prefix + + asset_list = model.stringList() + for item in asset_list: + assert len(item.split("/")) == 3 + + def test_experiment_model_load(self, qtbot, test_prefix): + model = AssetModel() + asset_type = AssetModel.Experiment + + with qtbot.waitSignals( + [model.assetTypeChanged, model.prefixPathChanged] + ) as blocker: + model.asset_type = asset_type + model.prefix_path = test_prefix + + assert blocker.all_signals_and_args[0].args[0] == asset_type + assert blocker.all_signals_and_args[1].args[0] == test_prefix + + asset_list = model.stringList() + + for item in asset_list: + assert len(item.split("/")) == 5