processview 1.5.0__tar.gz → 1.5.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. {processview-1.5.0 → processview-1.5.2}/PKG-INFO +3 -2
  2. processview-1.5.2/example/show_processmanagerwindow.py +118 -0
  3. {processview-1.5.0 → processview-1.5.2}/processview/core/sorting.py +1 -1
  4. processview-1.5.2/processview/gui/__init__.py +4 -0
  5. processview-1.5.2/processview/gui/processmanager/ObservationTable.py +137 -0
  6. processview-1.5.2/processview/gui/processmanager/ProcessManager.py +83 -0
  7. processview-1.5.2/processview/gui/processmanager/ProcessManagerWidget.py +83 -0
  8. processview-1.5.2/processview/gui/processmanager/ProcessManagerWindow.py +17 -0
  9. processview-1.5.2/processview/gui/processmanager/_DatasetProcessModel.py +215 -0
  10. processview-1.5.2/processview/gui/processmanager/_FilterWidget.py +102 -0
  11. processview-1.5.2/processview/gui/processmanager/_OptionsWidget.py +36 -0
  12. processview-1.5.2/processview/gui/processmanager/_OrderingWidget.py +38 -0
  13. processview-1.5.2/processview/gui/processmanager/__init__.py +2 -0
  14. {processview-1.5.0 → processview-1.5.2}/processview/gui/processmanager.py +20 -19
  15. processview-1.5.2/processview/gui/utils/qitem_model_resetter.py +17 -0
  16. processview-1.5.2/processview/resources/__init__.py +0 -0
  17. processview-1.5.2/processview/resources/gui/__init__.py +0 -0
  18. {processview-1.5.0 → processview-1.5.2}/processview/version.py +1 -1
  19. {processview-1.5.0 → processview-1.5.2}/processview.egg-info/PKG-INFO +3 -2
  20. {processview-1.5.0 → processview-1.5.2}/processview.egg-info/SOURCES.txt +13 -1
  21. processview-1.5.0/processview/gui/icons.py +0 -403
  22. processview-1.5.0/processview/resources/__init__.py +0 -277
  23. {processview-1.5.0 → processview-1.5.2}/LICENSE +0 -0
  24. {processview-1.5.0 → processview-1.5.2}/README.md +0 -0
  25. {processview-1.5.0 → processview-1.5.2}/processview/__init__.py +0 -0
  26. {processview-1.5.0 → processview-1.5.2}/processview/core/__init__.py +0 -0
  27. {processview-1.5.0 → processview-1.5.2}/processview/core/dataset.py +0 -0
  28. {processview-1.5.0 → processview-1.5.2}/processview/core/helpers.py +0 -0
  29. {processview-1.5.0 → processview-1.5.2}/processview/core/manager/__init__.py +0 -0
  30. {processview-1.5.0 → processview-1.5.2}/processview/core/manager/manager.py +0 -0
  31. {processview-1.5.0 → processview-1.5.2}/processview/core/manager/test/__init__.py +0 -0
  32. {processview-1.5.0 → processview-1.5.2}/processview/core/manager/test/test_manager.py +0 -0
  33. {processview-1.5.0 → processview-1.5.2}/processview/core/setup.py +0 -0
  34. {processview-1.5.0 → processview-1.5.2}/processview/core/superviseprocess.py +0 -0
  35. {processview-1.5.0 → processview-1.5.2}/processview/core/test/__init__.py +0 -0
  36. {processview-1.5.0 → processview-1.5.2}/processview/gui/DropDownWidget.py +0 -0
  37. {processview-1.5.0 → processview-1.5.2}/processview/gui/messagebox.py +0 -0
  38. {processview-1.5.0 → processview-1.5.2}/processview/gui/test/__init__.py +0 -0
  39. {processview-1.5.0 → processview-1.5.2}/processview/gui/test/test_process_manager.py +0 -0
  40. {processview-1.5.0/processview/gui → processview-1.5.2/processview/gui/utils}/__init__.py +0 -0
  41. {processview-1.5.0 → processview-1.5.2}/processview/resources/gui/icons/advancement.png +0 -0
  42. {processview-1.5.0 → processview-1.5.2}/processview/resources/gui/icons/advancement.svg +0 -0
  43. {processview-1.5.0 → processview-1.5.2}/processview/resources/gui/icons/magnifying_glass.png +0 -0
  44. {processview-1.5.0 → processview-1.5.2}/processview/resources/gui/icons/magnifying_glass.svg +0 -0
  45. {processview-1.5.0 → processview-1.5.2}/processview/test/__init__.py +0 -0
  46. {processview-1.5.0 → processview-1.5.2}/processview/utils/__init__.py +0 -0
  47. {processview-1.5.0 → processview-1.5.2}/processview/utils/singleton.py +0 -0
  48. {processview-1.5.0 → processview-1.5.2}/processview.egg-info/dependency_links.txt +0 -0
  49. {processview-1.5.0 → processview-1.5.2}/processview.egg-info/requires.txt +0 -0
  50. {processview-1.5.0 → processview-1.5.2}/processview.egg-info/top_level.txt +0 -0
  51. {processview-1.5.0 → processview-1.5.2}/processview.egg-info/zip-safe +0 -0
  52. {processview-1.5.0 → processview-1.5.2}/setup.cfg +0 -0
  53. {processview-1.5.0 → processview-1.5.2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: processview
3
- Version: 1.5.0
3
+ Version: 1.5.2
4
4
  Summary: Simple helper to provide user feedback about dataset processing
5
5
  Author: data analysis unit
6
6
  Author-email: henri.payno@esrf.fr
@@ -35,3 +35,4 @@ Provides-Extra: full
35
35
  Requires-Dist: Sphinx; extra == "full"
36
36
  Requires-Dist: pytest; extra == "full"
37
37
  Requires-Dist: PyQt5; extra == "full"
38
+ Dynamic: license-file
@@ -0,0 +1,118 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ import numpy
5
+ import threading
6
+ from silx.gui import qt
7
+ from processview.gui.processmanager import ProcessManagerWindow
8
+ from processview.core.superviseprocess import SuperviseProcess
9
+ from processview.core.dataset import Dataset
10
+ from processview.core.dataset import DatasetIdentifier
11
+ from processview.core.manager import DatasetState
12
+ from processview.gui.processmanager import ProcessManager
13
+ import datetime
14
+
15
+
16
+ class _DummyDataset(Dataset):
17
+ def __init__(self, name):
18
+ super().__init__()
19
+ self.__name = name
20
+
21
+ def __str__(self) -> str:
22
+ return self.__name
23
+
24
+ def get_identifier(self) -> DatasetIdentifier:
25
+ return _DummyIdentifier(
26
+ self,
27
+ metadata={
28
+ "name": self.__name,
29
+ "creation_time": datetime.datetime.now(),
30
+ "modification_time": datetime.datetime.now(),
31
+ },
32
+ )
33
+
34
+
35
+ class _DummyIdentifier(DatasetIdentifier):
36
+ def to_str(self):
37
+ return str(self)
38
+
39
+ def __str__(self):
40
+ return self.name()
41
+
42
+ def __eq__(self, other):
43
+ return self.name() == other.name()
44
+
45
+ def __hash__(self):
46
+ return hash(self.name())
47
+
48
+
49
+ app = qt.QApplication([])
50
+
51
+ window = ProcessManagerWindow(parent=None)
52
+ window.show()
53
+
54
+ p1 = SuperviseProcess(name="process1")
55
+ p2 = SuperviseProcess(name="process2")
56
+ p3 = SuperviseProcess(name="process3")
57
+
58
+
59
+ manager = ProcessManager()
60
+
61
+
62
+ class RecursiveThread(threading.Thread):
63
+ def __init__(self, execute_each: int):
64
+ self.running = True
65
+ self.execute_each = execute_each
66
+ super().__init__()
67
+
68
+ def run(self):
69
+ """Method implementing thread loop that updates the plot"""
70
+ while self.running:
71
+ time.sleep(self.execute_each)
72
+ self.process()
73
+
74
+ def process(self):
75
+ raise NotImplementedError()
76
+
77
+ def stop(self):
78
+ """Stop the update thread"""
79
+ self.running = False
80
+ self.join(2)
81
+
82
+
83
+ class CreateNewDataset(RecursiveThread):
84
+ """Thread creating a new dataset each n seconds"""
85
+
86
+ def process(self):
87
+ dataset = _DummyDataset(f"scan {numpy.random.randint(0, 999999)}")
88
+ manager.notify_dataset_state(
89
+ dataset=dataset, state=DatasetState.PENDING, process=p1
90
+ )
91
+
92
+
93
+ class UpdateDataset(RecursiveThread):
94
+ """Thread that will update randomly one of the existing dataset"""
95
+
96
+ def process(self):
97
+ datasets = manager.get_datasets()
98
+ if len(datasets) == 0:
99
+ return
100
+ dataset_to_update = numpy.random.choice(datasets)
101
+ state = numpy.random.choice(DatasetState)
102
+ process = numpy.random.choice(manager.get_processes())
103
+ manager.notify_dataset_state(
104
+ dataset=dataset_to_update,
105
+ state=state,
106
+ process=process,
107
+ )
108
+
109
+
110
+ create_new_dataset_thread = CreateNewDataset(execute_each=3)
111
+ create_new_dataset_thread.start()
112
+ update_dataset_thread = UpdateDataset(execute_each=1)
113
+ update_dataset_thread.start()
114
+
115
+ app.exec_()
116
+
117
+ create_new_dataset_thread.stop()
118
+ update_dataset_thread.stop()
@@ -1,4 +1,4 @@
1
- from silx.utils.enum import Enum as _Enum
1
+ from enum import Enum as _Enum
2
2
 
3
3
 
4
4
  class SortType(_Enum):
@@ -0,0 +1,4 @@
1
+ from silx import resources as _silx_resources # noqa
2
+
3
+ # Add processview resources to silx resource management
4
+ _silx_resources.register_resource_directory("processview", "processview.resources")
@@ -0,0 +1,137 @@
1
+ from __future__ import annotations
2
+
3
+ from silx.gui import qt
4
+ from processview.gui.messagebox import MessageBox
5
+ from processview.core.manager import ProcessManager as _ProcessManager
6
+ from processview.core.superviseprocess import SuperviseProcess
7
+ from processview.core.dataset import DatasetIdentifier
8
+
9
+ from .ProcessManager import ProcessManager
10
+
11
+
12
+ class ObservationTable(qt.QTableView):
13
+ """
14
+ Redefinition of QTableView for datasets and processes
15
+ """
16
+
17
+ def __init__(self, parent):
18
+ qt.QTableView.__init__(self, parent)
19
+ self.verticalHeader().setSectionsClickable(True)
20
+
21
+ # QMenu for the dataset name
22
+ self.dataset_menu = qt.QMenu()
23
+ self._copyAction = qt.QAction("copy")
24
+ self.dataset_menu.addAction(self._copyAction)
25
+ self._removeAction = qt.QAction("remove")
26
+ self.dataset_menu.addAction(self._removeAction)
27
+
28
+ # QMenu for cell from on dataset and one process
29
+ self.menu_dataset_vs_process = qt.QMenu()
30
+ self._reprocessAction = qt.QAction("reprocess")
31
+ self.menu_dataset_vs_process.addAction(self._reprocessAction)
32
+ self._cancelAction = qt.QAction("cancel")
33
+ self.menu_dataset_vs_process.addAction(self._cancelAction)
34
+ self._infoAction = qt.QAction("info")
35
+ self.menu_dataset_vs_process.addAction(self._infoAction)
36
+ self.menu_dataset_vs_process.addAction(self._removeAction)
37
+
38
+ self._target = (None, None)
39
+ # register target of the last menu (process, DatasetIdentifier)
40
+
41
+ # connect signal / slot
42
+ self._copyAction.triggered.connect(self._requestDatasetIdCopy)
43
+ self._removeAction.triggered.connect(self._requestRemoveDataset)
44
+ self._reprocessAction.triggered.connect(self._requestReprocessing)
45
+ self._infoAction.triggered.connect(self._requestInfo)
46
+ self._cancelAction.triggered.connect(self._requestCancelProcessing)
47
+
48
+ def _processAt(self, x_pos):
49
+ column = self.columnAt(x_pos)
50
+ if column >= 1:
51
+ processes = self.model()._processes
52
+ process_idx = column - 1
53
+ if process_idx < len(processes):
54
+ return processes[list(processes.keys())[process_idx]]
55
+
56
+ def _datasetAt(self, y_pos):
57
+ row = self.rowAt(y_pos)
58
+ if row >= 0:
59
+ datasets = self.model()._sorted_datasets
60
+ if row < len(datasets):
61
+ return datasets[list(datasets.keys())[row]]
62
+
63
+ def contextMenuEvent(self, event):
64
+ row = self.columnAt(event.pos().x())
65
+ dataset = self._datasetAt(event.pos().y())
66
+ if row == 0:
67
+ # handle column column
68
+ self._target = (None, dataset)
69
+ self.dataset_menu.exec_(event.globalPos())
70
+ else:
71
+ # handle processes column
72
+ process = self._processAt(event.pos().x())
73
+ if (
74
+ process is not None
75
+ and dataset is not None
76
+ and _ProcessManager().met(process=process, dataset=dataset)
77
+ ):
78
+ self._target = (process, dataset)
79
+ self.menu_dataset_vs_process.exec_(event.globalPos())
80
+ else:
81
+ self._target = (None, None)
82
+ super().contextMenuEvent(event)
83
+
84
+ def _requestReprocessing(self, *args, **kwargs):
85
+ process, dataset = self._target
86
+
87
+ if process is not None and dataset is not None:
88
+ assert isinstance(process, SuperviseProcess)
89
+ assert isinstance(dataset, DatasetIdentifier)
90
+ process.reprocess(dataset.recreate_dataset())
91
+
92
+ def _requestCancelProcessing(self, *args, **kwargs):
93
+ process, dataset = self._target
94
+ if process is not None and dataset is not None:
95
+ assert isinstance(process, SuperviseProcess)
96
+ assert isinstance(dataset, DatasetIdentifier)
97
+ process.cancel(dataset.recreate_dataset())
98
+
99
+ def _requestDatasetIdCopy(self, *args, **kwargs):
100
+ _, dataset = self._target
101
+ if dataset is not None:
102
+ clipboard = qt.QGuiApplication.clipboard()
103
+ clipboard.setText(dataset.to_str())
104
+
105
+ def _requestRemoveDataset(self, *args, **kwargs):
106
+ def get_dataset_at(row: int):
107
+ datasets = self.model()._sorted_datasets
108
+ return datasets.get(
109
+ list(datasets.keys())[row],
110
+ None,
111
+ )
112
+
113
+ datasets = [get_dataset_at(index.row()) for index in self.selectedIndexes()]
114
+ [ProcessManager().remove_dataset(dataset) for dataset in datasets]
115
+ self.model().remove_datasets(datasets)
116
+
117
+ def _requestInfo(self, *args, **kwargs):
118
+ process, dataset = self._target
119
+ if process is not None and dataset is not None:
120
+ infos = ProcessManager().get_dataset_details(
121
+ dataset=dataset, process=process
122
+ )
123
+ if infos in (None, ""):
124
+ infos = "No extra information provided"
125
+
126
+ msg = MessageBox(self)
127
+ msg.setInfos(infos=infos)
128
+ extra_info = "{} processing {}".format(process.name, dataset)
129
+ msg.setWindowTitle(extra_info)
130
+ msg.setWindowModality(qt.Qt.NonModal)
131
+ msg.show()
132
+
133
+ def sizeHintForColumn(self, column):
134
+ if column == 0:
135
+ return 350
136
+ else:
137
+ return super().sizeHintForColumn(column)
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ from silx.gui import qt
4
+ from processview.core.manager import ProcessManager as _ProcessManager
5
+ from processview.core.superviseprocess import SuperviseProcess
6
+ from processview.utils import docstring
7
+ from processview.core.manager import DatasetState
8
+
9
+
10
+ class ProcessManager(qt.QObject):
11
+ sigUpdated = qt.Signal()
12
+ """Signal emitted when the state of some process / dataset is updated
13
+ """
14
+
15
+ sigNewProcessRegistered = qt.Signal()
16
+ """Signal emitted when a new process is registered"""
17
+
18
+ sigProcessUnregistered = qt.Signal()
19
+ """Signal emitted when a process is unregistered"""
20
+
21
+ sigProcessRenamed = qt.Signal(SuperviseProcess)
22
+ """Emit when the process is renamed"""
23
+
24
+ def __init__(self):
25
+ qt.QObject.__init__(self)
26
+ self.manager = _ProcessManager()
27
+
28
+ # monkey patch manager updated function
29
+ # TODO: add / remove callback would be simpler
30
+ self.manager.add_update_callback(self.updated)
31
+ self.manager.add_new_process_callback(self.processAdded)
32
+ self.manager.add_process_removed_callback(self.processRemoved)
33
+ self.manager.add_process_name_changed_callback(self.processRenamed)
34
+
35
+ def updated(self):
36
+ self.sigUpdated.emit()
37
+
38
+ def processAdded(self):
39
+ self.sigNewProcessRegistered.emit()
40
+
41
+ def processRemoved(self):
42
+ self.sigProcessUnregistered.emit()
43
+
44
+ def processRenamed(self, process):
45
+ self.sigProcessRenamed.emit(process)
46
+
47
+ def destroyed(self, object_):
48
+ self.manager.remove_update_callback(self.updated)
49
+ qt.QObject.destroyed(object_)
50
+
51
+ # expose some of the original ProcessManager API
52
+ @docstring(_ProcessManager)
53
+ def notify_dataset_state(self, dataset, process, state, details=None) -> None:
54
+ self.manager.notify_dataset_state(
55
+ dataset=dataset, process=process, state=state, details=details
56
+ )
57
+
58
+ @docstring(_ProcessManager)
59
+ def get_dataset_state(self, dataset, process) -> None | DatasetState:
60
+ return self.manager.get_dataset_state(dataset_id=dataset, process=process)
61
+
62
+ @docstring(_ProcessManager)
63
+ def get_dataset_details(self, dataset, process) -> None | DatasetState:
64
+ return self.manager.get_dataset_details(dataset_id=dataset, process=process)
65
+
66
+ @docstring(_ProcessManager)
67
+ def get_dataset_stream(self, dataset, time_stamp=False) -> tuple:
68
+ return self.manager.get_dataset_stream(dataset=dataset, time_stamp=time_stamp)
69
+
70
+ @docstring(_ProcessManager)
71
+ def get_process_history(self, process, time_stamp=False) -> tuple:
72
+ return self.manager.get_process_history(process=process, time_stamp=time_stamp)
73
+
74
+ @docstring(_ProcessManager)
75
+ def get_processes(self):
76
+ return self.manager.get_processes()
77
+
78
+ @docstring(_ProcessManager)
79
+ def get_datasets(self):
80
+ return self.manager.get_datasets()
81
+
82
+ def remove_dataset(self, *args, **kwargs):
83
+ return self.manager.remove_dataset(*args, **kwargs)
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ from silx.gui import qt
4
+ from processview.core.superviseprocess import SuperviseProcess
5
+
6
+ from .ProcessManager import ProcessManager
7
+ from ._OptionsWidget import OptionsWidget
8
+ from ..DropDownWidget import DropDownWidget
9
+ from .ObservationTable import ObservationTable
10
+ from ._DatasetProcessModel import DatasetProcessModel
11
+
12
+
13
+ class ProcessManagerWidget(qt.QWidget):
14
+ """
15
+ Main widget to display dataset vs process metadata
16
+ """
17
+
18
+ def __init__(self, parent):
19
+ qt.QWidget.__init__(self, parent)
20
+ self.setLayout(qt.QVBoxLayout())
21
+
22
+ self._manager = ProcessManager()
23
+
24
+ self._optionsWidget = OptionsWidget(parent=self)
25
+ self._dropDownOptionsWidget = DropDownWidget(parent=self)
26
+ self._dropDownOptionsWidget.setWidget(self._optionsWidget)
27
+
28
+ self._dropDownOptionsWidget.setSizePolicy(
29
+ qt.QSizePolicy.Minimum, qt.QSizePolicy.Minimum
30
+ )
31
+ self.setContentsMargins(0, 0, 0, 0)
32
+ self.layout().setContentsMargins(0, 0, 0, 0)
33
+ self.layout().setSpacing(2)
34
+ self.layout().addWidget(self._dropDownOptionsWidget)
35
+
36
+ self.observationTable = ObservationTable(self)
37
+
38
+ self.layout().addWidget(self.observationTable)
39
+ self.observationTable.setModel(
40
+ DatasetProcessModel(parent=self.observationTable, header=tuple())
41
+ )
42
+ self.observationTable.resizeColumnsToContents()
43
+ self.observationTable.setSortingEnabled(True)
44
+ self.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Expanding)
45
+
46
+ # connect signal / slot
47
+ self._manager.sigUpdated.connect(self._updateDatasetStates)
48
+ self._manager.sigNewProcessRegistered.connect(self._updateProcesses)
49
+ self._optionsWidget.filterWidget.sigDatasetPatternEditingFinished.connect(
50
+ self._filterUpdated
51
+ )
52
+ self._optionsWidget.filterWidget.sigProcessPatternEditingFinished.connect(
53
+ self._filterUpdated
54
+ )
55
+ self._manager.sigProcessUnregistered.connect(self._updateProcesses)
56
+ self._optionsWidget._orderingWidget.sigSortTypeChanged.connect(
57
+ self.observationTable.model()._setSorting
58
+ )
59
+ self._manager.sigProcessRenamed.connect(self._renameProcess)
60
+ # update to fit existing processes / datasets
61
+ self._updateProcesses()
62
+ self._updateDatasetStates()
63
+
64
+ def _updateDatasetStates(self):
65
+ self.observationTable.model().setDatasets(self._manager.get_datasets())
66
+
67
+ def _updateProcesses(self):
68
+ self.observationTable.model().setProcesses(self._manager.get_processes())
69
+
70
+ def _renameProcess(self, process: SuperviseProcess):
71
+ self.observationTable.model().headerDataChanged.emit(
72
+ qt.Qt.Horizontal, 0, len(self._manager.get_processes())
73
+ )
74
+
75
+ def _filterUpdated(self):
76
+ self.observationTable.model().process_patterns = (
77
+ self._dropDownOptionsWidget.getProcessPatterns()
78
+ )
79
+ self._updateProcesses()
80
+ self.observationTable.model().dataset_patterns = (
81
+ self._dropDownOptionsWidget.getDatasetPatterns()
82
+ )
83
+ self._updateDatasetStates()
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ from silx.gui import qt
4
+ from .ProcessManagerWidget import ProcessManagerWidget
5
+
6
+
7
+ class ProcessManagerWindow(qt.QMainWindow):
8
+ """
9
+ Main window of the process manager
10
+ """
11
+
12
+ def __init__(self, parent):
13
+ qt.QMainWindow.__init__(self, parent)
14
+ self.setWindowFlags(qt.Qt.Widget)
15
+
16
+ self._centralWidget = ProcessManagerWidget(parent=self)
17
+ self.setCentralWidget(self._centralWidget)
@@ -0,0 +1,215 @@
1
+ from __future__ import annotations
2
+
3
+ import fnmatch
4
+ import datetime
5
+
6
+ from silx.gui import qt
7
+ from processview.core.sorting import SortType
8
+ from processview.core.manager import DatasetState
9
+ from processview.core.dataset import DatasetIdentifier
10
+ from processview.gui.utils.qitem_model_resetter import qitem_model_resetter
11
+
12
+ from .ProcessManager import ProcessManager
13
+
14
+ from collections import OrderedDict
15
+
16
+
17
+ _DATASET_STATE_BACKGROUND = {
18
+ DatasetState.ON_GOING: qt.QColor("#839684"), # light blue
19
+ DatasetState.SUCCEED: qt.QColor("#068c0c"), # green
20
+ DatasetState.FAILED: qt.QColor("#f52718"), # red
21
+ DatasetState.PENDING: qt.QColor("#609ab3"), # blue gray
22
+ DatasetState.SKIPPED: qt.QColor("#f08e0e"), # light orange
23
+ DatasetState.WAIT_USER_VALIDATION: qt.QColor("#cb34c1"), # pink
24
+ DatasetState.CANCELLED: qt.QColor("#a4a8a2"), # light black
25
+ }
26
+
27
+
28
+ class DatasetProcessModel(qt.QAbstractTableModel):
29
+ def __init__(self, parent, header, *args):
30
+ qt.QAbstractTableModel.__init__(self, parent, *args)
31
+ self.header = header
32
+ self._processes = OrderedDict()
33
+ # processes with order as key and Process as value
34
+ self._sorted_datasets = OrderedDict()
35
+ self._unsorted_datasets = OrderedDict()
36
+ self._processPatterns = tuple()
37
+ # is there some process name pattern to follow ?
38
+ self._datasetPatterns = tuple()
39
+ # is there some dataset id pattern to follow ?
40
+ self._sort_type = SortType.FIRST_APPEARANCE
41
+
42
+ def _match_dataset_patterns(self, dataset):
43
+ if len(self._datasetPatterns) == 0:
44
+ return True
45
+ for pattern in self._datasetPatterns:
46
+ if fnmatch.fnmatch(str(dataset), pattern):
47
+ return True
48
+ return False
49
+
50
+ def _match_process_patterns(self, process):
51
+ if len(self.process_patterns) == 0:
52
+ return True
53
+ for pattern in self._processPatterns:
54
+ if process.name is None or pattern is None:
55
+ continue
56
+ elif fnmatch.fnmatch(process.name, pattern):
57
+ return True
58
+ return False
59
+
60
+ @property
61
+ def process_patterns(self):
62
+ return self._processPatterns
63
+
64
+ @process_patterns.setter
65
+ def process_patterns(self, patterns):
66
+ self._processPatterns = patterns
67
+
68
+ @property
69
+ def dataset_patterns(self):
70
+ return self._datasetPatterns
71
+
72
+ @dataset_patterns.setter
73
+ def dataset_patterns(self, patterns):
74
+ self._datasetPatterns = patterns
75
+
76
+ def remove_datasets(self, datasets):
77
+ remaining_datasets = list(self._unsorted_datasets)
78
+ for dataset in datasets:
79
+ if not isinstance(dataset, DatasetIdentifier):
80
+ raise ValueError(
81
+ f"dataset is expected to be a dataset identifier. Get {type(dataset)} instead"
82
+ )
83
+ try:
84
+ remaining_datasets.remove(dataset)
85
+ except Exception:
86
+ pass
87
+
88
+ with qitem_model_resetter(self):
89
+ self.setDatasets(remaining_datasets)
90
+
91
+ def clear(self):
92
+ with qitem_model_resetter(self):
93
+ self._processes = OrderedDict()
94
+
95
+ def rowCount(self, parent=None):
96
+ return len(self._unsorted_datasets)
97
+
98
+ def columnCount(self, parent=None):
99
+ return len(self._processes) + 1
100
+
101
+ def setProcesses(self, processes):
102
+ assert isinstance(processes, (tuple, list))
103
+ with qitem_model_resetter(self):
104
+ self._processes = {}
105
+ processes = list(filter(self._match_process_patterns, processes))
106
+ for i_process, process in enumerate(processes):
107
+ self._processes[i_process] = process
108
+
109
+ @staticmethod
110
+ def sort_datasets(datasets: list, sort_type):
111
+ if sort_type is SortType.FIRST_APPEARANCE:
112
+ pass
113
+ elif sort_type is SortType.LAST_APPEARANCE:
114
+ datasets.reverse()
115
+ elif sort_type in (SortType.ALPHABETICAL, SortType.REVERSE_ALPHABETICAL):
116
+ datasets = sorted(
117
+ datasets,
118
+ key=lambda x: x.name() or "",
119
+ reverse=sort_type is SortType.REVERSE_ALPHABETICAL,
120
+ )
121
+ elif sort_type in (SortType.CREATION_TIME, SortType.REVERSE_CREATION_TIME):
122
+ datasets = sorted(
123
+ datasets,
124
+ key=lambda x: x.creation_time() or datetime.datetime.fromtimestamp(0),
125
+ reverse=sort_type is SortType.REVERSE_CREATION_TIME,
126
+ )
127
+ elif sort_type in (
128
+ SortType.MODIFICATION_TIME,
129
+ SortType.REVERSE_MODIFICATION_TIME,
130
+ ):
131
+ datasets = sorted(
132
+ datasets,
133
+ key=lambda x: x.modification_time()
134
+ or datetime.datetime.fromtimestamp(0),
135
+ reverse=sort_type is SortType.REVERSE_MODIFICATION_TIME,
136
+ )
137
+ else:
138
+ raise ValueError("sort type not handled")
139
+ return datasets
140
+
141
+ def setDatasets(self, datasets):
142
+ assert isinstance(datasets, (tuple, list))
143
+ with qitem_model_resetter(self):
144
+ self._unsorted_datasets = datasets
145
+ # filter datasets
146
+ datasets = list(filter(self._match_dataset_patterns, datasets))
147
+ # sort datasets
148
+ datasets = self.sort_datasets(datasets=datasets, sort_type=self._sort_type)
149
+
150
+ self._sorted_datasets.clear()
151
+ for i_dataset, dataset in enumerate(datasets):
152
+ self._sorted_datasets[i_dataset] = dataset
153
+
154
+ def headerData(self, col, orientation, role):
155
+ if orientation == qt.Qt.Horizontal and role == qt.Qt.DisplayRole:
156
+ if col == 0:
157
+ return
158
+ else:
159
+ process_idx = col - 1
160
+ if process_idx < len(self._processes):
161
+ return self._processes[process_idx].name
162
+ return None
163
+
164
+ def data(self, index, role):
165
+ if index.isValid() is False:
166
+ return None
167
+
168
+ if role not in (qt.Qt.DisplayRole, qt.Qt.ToolTipRole, qt.Qt.BackgroundRole):
169
+ return None
170
+
171
+ if index.column() == 0:
172
+ # if dataset name
173
+ if role == qt.Qt.DisplayRole:
174
+ return self._sorted_datasets[index.row()].short_description() or str(
175
+ self._sorted_datasets[index.row()]
176
+ )
177
+ elif role == qt.Qt.ToolTipRole:
178
+ return self._sorted_datasets[index.row()].long_description()
179
+ elif role == qt.Qt.BackgroundRole:
180
+ return qt.QColor("#dedede")
181
+ else:
182
+ return
183
+
184
+ dataset_short_name = self._sorted_datasets[index.row()]
185
+ process_name = self._processes[index.column() - 1]
186
+ dataset_process_state = ProcessManager().get_dataset_state(
187
+ dataset=dataset_short_name, process=process_name
188
+ )
189
+ dataset_process_details = ProcessManager().get_dataset_details(
190
+ dataset=dataset_short_name, process=process_name
191
+ )
192
+ if role == qt.Qt.BackgroundRole:
193
+ if dataset_process_state is None:
194
+ # if "unmet"
195
+ return qt.QColor("#ffffff")
196
+ else:
197
+ return _DATASET_STATE_BACKGROUND[dataset_process_state]
198
+ elif role == qt.Qt.DisplayRole:
199
+ if dataset_process_state is None:
200
+ return ""
201
+ else:
202
+ return dataset_process_state.value
203
+ if role == qt.Qt.ToolTipRole:
204
+ if dataset_process_details is None:
205
+ return ""
206
+ else:
207
+ return dataset_process_details
208
+
209
+ def _setSorting(self, sort_type: SortType):
210
+ sort_type = SortType(sort_type)
211
+ changed = self._sort_type is not sort_type
212
+ if changed:
213
+ self._sort_type = sort_type
214
+ self.setDatasets(datasets=self._unsorted_datasets)
215
+ self.layoutChanged.emit()