scipion-pyworkflow 3.11.0__py3-none-any.whl → 3.11.2__py3-none-any.whl
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.
- pyworkflow/apps/__init__.py +29 -0
- pyworkflow/apps/pw_manager.py +37 -0
- pyworkflow/apps/pw_plot.py +51 -0
- pyworkflow/apps/pw_project.py +130 -0
- pyworkflow/apps/pw_protocol_list.py +143 -0
- pyworkflow/apps/pw_protocol_run.py +51 -0
- pyworkflow/apps/pw_run_tests.py +268 -0
- pyworkflow/apps/pw_schedule_run.py +322 -0
- pyworkflow/apps/pw_sleep.py +37 -0
- pyworkflow/apps/pw_sync_data.py +440 -0
- pyworkflow/apps/pw_viewer.py +78 -0
- pyworkflow/constants.py +1 -1
- pyworkflow/gui/__init__.py +36 -0
- pyworkflow/gui/browser.py +768 -0
- pyworkflow/gui/canvas.py +1190 -0
- pyworkflow/gui/dialog.py +981 -0
- pyworkflow/gui/form.py +2727 -0
- pyworkflow/gui/graph.py +247 -0
- pyworkflow/gui/graph_layout.py +271 -0
- pyworkflow/gui/gui.py +571 -0
- pyworkflow/gui/matplotlib_image.py +233 -0
- pyworkflow/gui/plotter.py +247 -0
- pyworkflow/gui/project/__init__.py +25 -0
- pyworkflow/gui/project/base.py +193 -0
- pyworkflow/gui/project/constants.py +139 -0
- pyworkflow/gui/project/labels.py +205 -0
- pyworkflow/gui/project/project.py +491 -0
- pyworkflow/gui/project/searchprotocol.py +240 -0
- pyworkflow/gui/project/searchrun.py +181 -0
- pyworkflow/gui/project/steps.py +171 -0
- pyworkflow/gui/project/utils.py +332 -0
- pyworkflow/gui/project/variables.py +179 -0
- pyworkflow/gui/project/viewdata.py +472 -0
- pyworkflow/gui/project/viewprojects.py +519 -0
- pyworkflow/gui/project/viewprotocols.py +2141 -0
- pyworkflow/gui/project/viewprotocols_extra.py +562 -0
- pyworkflow/gui/text.py +774 -0
- pyworkflow/gui/tooltip.py +185 -0
- pyworkflow/gui/tree.py +684 -0
- pyworkflow/gui/widgets.py +307 -0
- pyworkflow/mapper/__init__.py +26 -0
- pyworkflow/mapper/mapper.py +226 -0
- pyworkflow/mapper/sqlite.py +1583 -0
- pyworkflow/mapper/sqlite_db.py +145 -0
- pyworkflow/object.py +1 -0
- pyworkflow/plugin.py +4 -4
- pyworkflow/project/__init__.py +31 -0
- pyworkflow/project/config.py +454 -0
- pyworkflow/project/manager.py +180 -0
- pyworkflow/project/project.py +2095 -0
- pyworkflow/project/usage.py +165 -0
- pyworkflow/protocol/__init__.py +38 -0
- pyworkflow/protocol/bibtex.py +48 -0
- pyworkflow/protocol/constants.py +87 -0
- pyworkflow/protocol/executor.py +515 -0
- pyworkflow/protocol/hosts.py +318 -0
- pyworkflow/protocol/launch.py +277 -0
- pyworkflow/protocol/package.py +42 -0
- pyworkflow/protocol/params.py +781 -0
- pyworkflow/protocol/protocol.py +2712 -0
- pyworkflow/resources/protlabels.xcf +0 -0
- pyworkflow/resources/sprites.png +0 -0
- pyworkflow/resources/sprites.xcf +0 -0
- pyworkflow/template.py +1 -1
- pyworkflow/tests/__init__.py +29 -0
- pyworkflow/tests/test_utils.py +25 -0
- pyworkflow/tests/tests.py +342 -0
- pyworkflow/utils/__init__.py +38 -0
- pyworkflow/utils/dataset.py +414 -0
- pyworkflow/utils/echo.py +104 -0
- pyworkflow/utils/graph.py +169 -0
- pyworkflow/utils/log.py +293 -0
- pyworkflow/utils/path.py +528 -0
- pyworkflow/utils/process.py +154 -0
- pyworkflow/utils/profiler.py +92 -0
- pyworkflow/utils/progressbar.py +154 -0
- pyworkflow/utils/properties.py +618 -0
- pyworkflow/utils/reflection.py +129 -0
- pyworkflow/utils/utils.py +880 -0
- pyworkflow/utils/which.py +229 -0
- pyworkflow/webservices/__init__.py +8 -0
- pyworkflow/webservices/config.py +8 -0
- pyworkflow/webservices/notifier.py +152 -0
- pyworkflow/webservices/repository.py +59 -0
- pyworkflow/webservices/workflowhub.py +86 -0
- pyworkflowtests/tests/__init__.py +0 -0
- pyworkflowtests/tests/test_canvas.py +72 -0
- pyworkflowtests/tests/test_domain.py +45 -0
- pyworkflowtests/tests/test_logs.py +74 -0
- pyworkflowtests/tests/test_mappers.py +392 -0
- pyworkflowtests/tests/test_object.py +507 -0
- pyworkflowtests/tests/test_project.py +42 -0
- pyworkflowtests/tests/test_protocol_execution.py +146 -0
- pyworkflowtests/tests/test_protocol_export.py +78 -0
- pyworkflowtests/tests/test_protocol_output.py +158 -0
- pyworkflowtests/tests/test_streaming.py +47 -0
- pyworkflowtests/tests/test_utils.py +210 -0
- {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/METADATA +2 -2
- scipion_pyworkflow-3.11.2.dist-info/RECORD +162 -0
- scipion_pyworkflow-3.11.0.dist-info/RECORD +0 -71
- {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/WHEEL +0 -0
- {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/entry_points.txt +0 -0
- {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/licenses/LICENSE.txt +0 -0
- {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,145 @@
|
|
1
|
+
# **************************************************************************
|
2
|
+
# *
|
3
|
+
# * Authors: J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es)
|
4
|
+
# *
|
5
|
+
# * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
|
6
|
+
# *
|
7
|
+
# * This program is free software; you can redistribute it and/or modify
|
8
|
+
# * it under the terms of the GNU General Public License as published by
|
9
|
+
# * the Free Software Foundation; either version 3 of the License, or
|
10
|
+
# * (at your option) any later version.
|
11
|
+
# *
|
12
|
+
# * This program is distributed in the hope that it will be useful,
|
13
|
+
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# * GNU General Public License for more details.
|
16
|
+
# *
|
17
|
+
# * You should have received a copy of the GNU General Public License
|
18
|
+
# * along with this program; if not, write to the Free Software
|
19
|
+
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
20
|
+
# * 02111-1307 USA
|
21
|
+
# *
|
22
|
+
# * All comments concerning this program package may be sent to the
|
23
|
+
# * e-mail address 'scipion@cnb.csic.es'
|
24
|
+
# *
|
25
|
+
# **************************************************************************
|
26
|
+
|
27
|
+
|
28
|
+
"""
|
29
|
+
This module contains some sqlite basic tools to handle Databases.
|
30
|
+
"""
|
31
|
+
|
32
|
+
import logging
|
33
|
+
logger = logging.getLogger(__name__)
|
34
|
+
from sqlite3 import dbapi2 as sqlite
|
35
|
+
from sqlite3 import OperationalError as OperationalError
|
36
|
+
from pyworkflow.utils import STATUS, getExtraLogInfo, Config
|
37
|
+
|
38
|
+
|
39
|
+
class SqliteDb:
|
40
|
+
"""Class to handle a Sqlite database.
|
41
|
+
It will create connection, execute queries and commands.
|
42
|
+
"""
|
43
|
+
OPEN_CONNECTIONS = {} # Store all connections made
|
44
|
+
|
45
|
+
def __init__(self):
|
46
|
+
self._reuseConnections = False
|
47
|
+
|
48
|
+
def _createConnection(self, dbName, timeout):
|
49
|
+
"""Establish db connection"""
|
50
|
+
self._dbName = dbName
|
51
|
+
if self._reuseConnections and dbName in self.OPEN_CONNECTIONS:
|
52
|
+
self.connection = self.OPEN_CONNECTIONS[dbName]
|
53
|
+
else:
|
54
|
+
# self.closeConnection(dbName) # Close the connect if exists for this db
|
55
|
+
self.connection = sqlite.Connection(dbName, timeout, check_same_thread=False)
|
56
|
+
self.connection.row_factory = sqlite.Row
|
57
|
+
self.OPEN_CONNECTIONS[dbName] = self.connection
|
58
|
+
logger.debug("Connection open for %s" % dbName, extra=getExtraLogInfo(
|
59
|
+
"CONNECTIONS",
|
60
|
+
STATUS.START,
|
61
|
+
dbfilename=dbName))
|
62
|
+
|
63
|
+
self.cursor = self.connection.cursor()
|
64
|
+
# Define some shortcuts functions
|
65
|
+
if Config.debugSQLOn():
|
66
|
+
self.executeCommand = self._debugExecute
|
67
|
+
else:
|
68
|
+
self.executeCommand = self.cursor.execute
|
69
|
+
self.commit = self.connection.commit
|
70
|
+
|
71
|
+
@classmethod
|
72
|
+
def closeConnection(cls, dbName):
|
73
|
+
if dbName in cls.OPEN_CONNECTIONS:
|
74
|
+
connection = cls.OPEN_CONNECTIONS[dbName]
|
75
|
+
del cls.OPEN_CONNECTIONS[dbName]
|
76
|
+
connection.close()
|
77
|
+
logger.debug("Connection closed for %s" % dbName,
|
78
|
+
extra=getExtraLogInfo('CONNECTIONS', STATUS.STOP, dbfilename=dbName))
|
79
|
+
|
80
|
+
def getDbName(self):
|
81
|
+
return self._dbName
|
82
|
+
|
83
|
+
def close(self):
|
84
|
+
self.connection.close()
|
85
|
+
logger.debug("Connection closed for %s" % self._dbName,
|
86
|
+
extra=getExtraLogInfo(
|
87
|
+
"CONNECTIONS",
|
88
|
+
STATUS.STOP,
|
89
|
+
dbfilename=self._dbName))
|
90
|
+
if self._dbName in self.OPEN_CONNECTIONS:
|
91
|
+
del self.OPEN_CONNECTIONS[self._dbName]
|
92
|
+
|
93
|
+
def _debugExecute(self, *args):
|
94
|
+
try:
|
95
|
+
logger.debug("COMMAND: %s; %s" %(args[0] , self._dbName),
|
96
|
+
extra=getExtraLogInfo("QUERY", STATUS.EVENT, dbfilename=self._dbName)
|
97
|
+
)
|
98
|
+
logger.debug("ARGUMENTS: " + str(args[1:]))
|
99
|
+
return self.cursor.execute(*args)
|
100
|
+
except Exception as ex:
|
101
|
+
print(">>>> FAILED cursor.execute on db: '%s'" % self._dbName)
|
102
|
+
raise ex
|
103
|
+
|
104
|
+
def _iterResults(self):
|
105
|
+
row = self.cursor.fetchone()
|
106
|
+
while row is not None:
|
107
|
+
yield row
|
108
|
+
row = self.cursor.fetchone()
|
109
|
+
|
110
|
+
def _results(self, iterate=False):
|
111
|
+
""" Return the results to which cursor, point to.
|
112
|
+
If iterates=True, iterate yielding each result independently"""
|
113
|
+
if not iterate:
|
114
|
+
return self.cursor.fetchall()
|
115
|
+
else:
|
116
|
+
return self._iterResults()
|
117
|
+
|
118
|
+
def getTables(self, tablePattern=None):
|
119
|
+
""" Return the table names existing in the Database.
|
120
|
+
If tablePattern is not None, only tables matching
|
121
|
+
the pattern will be returned.
|
122
|
+
"""
|
123
|
+
self.executeCommand("SELECT name FROM sqlite_master "
|
124
|
+
"WHERE type='table' "
|
125
|
+
"AND name NOT LIKE 'sqlite_%';")
|
126
|
+
return [str(row['name']) for row in self._iterResults()]
|
127
|
+
|
128
|
+
def hasTable(self, tableName):
|
129
|
+
return tableName in self.getTables()
|
130
|
+
|
131
|
+
def getTableColumns(self, tableName):
|
132
|
+
self.executeCommand('PRAGMA table_info(%s)' % tableName)
|
133
|
+
return self.cursor.fetchall()
|
134
|
+
|
135
|
+
def getVersion(self):
|
136
|
+
""" Return the database 'version' that is used.
|
137
|
+
Internally it make use of the SQLite PRAGMA database.user_version;
|
138
|
+
"""
|
139
|
+
self.executeCommand('PRAGMA user_version')
|
140
|
+
return self.cursor.fetchone()[0]
|
141
|
+
|
142
|
+
def setVersion(self, version):
|
143
|
+
self.executeCommand('PRAGMA user_version=%d' % version)
|
144
|
+
self.commit()
|
145
|
+
|
pyworkflow/object.py
CHANGED
@@ -1344,6 +1344,7 @@ class Set(Object):
|
|
1344
1344
|
else:
|
1345
1345
|
self._idCount = max(self._idCount, item.getObjId())
|
1346
1346
|
self._insertItem(item)
|
1347
|
+
# FIXME: Incrementing always!! When updating this is wrong.
|
1347
1348
|
self._size.increment()
|
1348
1349
|
|
1349
1350
|
def _insertItem(self, item):
|
pyworkflow/plugin.py
CHANGED
@@ -410,11 +410,11 @@ class Domain:
|
|
410
410
|
viewerClassName,
|
411
411
|
doRaise=True)
|
412
412
|
viewers.append(prefViewer)
|
413
|
-
except Exception
|
414
|
-
logger.
|
413
|
+
except Exception:
|
414
|
+
logger.info("Couldn't load \"%s\" as preferred viewer for %s.\n"
|
415
415
|
"There might be a typo in your VIEWERS variable "
|
416
|
-
"or an error in the viewer's plugin installation"
|
417
|
-
% (prefViewerStr,
|
416
|
+
"or an error in the viewer's plugin installation or simply the plugin is not installed."
|
417
|
+
% (prefViewerStr, target))
|
418
418
|
|
419
419
|
cls._preferred_viewers[target] = viewers
|
420
420
|
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# **************************************************************************
|
3
|
+
# *
|
4
|
+
# * Authors: J.M. De la Rosa Trevin (delarosatrevin@scilifelab.se) [1]
|
5
|
+
# *
|
6
|
+
# * [1] SciLifeLab, Stockholm University
|
7
|
+
# *
|
8
|
+
# * This program is free software; you can redistribute it and/or modify
|
9
|
+
# * it under the terms of the GNU General Public License as published by
|
10
|
+
# * the Free Software Foundation; either version 3 of the License, or
|
11
|
+
# * (at your option) any later version.
|
12
|
+
# *
|
13
|
+
# * This program is distributed in the hope that it will be useful,
|
14
|
+
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
# * GNU General Public License for more details.
|
17
|
+
# *
|
18
|
+
# * You should have received a copy of the GNU General Public License
|
19
|
+
# * along with this program; if not, write to the Free Software
|
20
|
+
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
21
|
+
# * 02111-1307 USA
|
22
|
+
# *
|
23
|
+
# * All comments concerning this program package may be sent to the
|
24
|
+
# * e-mail address 'scipion@cnb.csic.es'
|
25
|
+
# *
|
26
|
+
# **************************************************************************
|
27
|
+
|
28
|
+
from .project import Project, MissingProjectDbException
|
29
|
+
from .manager import Manager, ProjectInfo
|
30
|
+
from .config import (ProjectSettings, MenuConfig,
|
31
|
+
NodeConfig, NodeConfigList, Label, LabelsList)
|
@@ -0,0 +1,454 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# **************************************************************************
|
4
|
+
# *
|
5
|
+
# * Authors: J.M. De la Rosa Trevin (delarosatrevin@scilifelab.se) [1]
|
6
|
+
# *
|
7
|
+
# * [1] SciLifeLab, Stockholm University
|
8
|
+
# *
|
9
|
+
# * This program is free software: you can redistribute it and/or modify
|
10
|
+
# * it under the terms of the GNU General Public License as published by
|
11
|
+
# * the Free Software Foundation, either version 3 of the License, or
|
12
|
+
# * (at your option) any later version.
|
13
|
+
# *
|
14
|
+
# * This program is distributed in the hope that it will be useful,
|
15
|
+
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
16
|
+
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
17
|
+
# * GNU General Public License for more details.
|
18
|
+
# *
|
19
|
+
# * You should have received a copy of the GNU General Public License
|
20
|
+
# * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
21
|
+
# *
|
22
|
+
# * All comments concerning this program package may be sent to the
|
23
|
+
# * e-mail address 'scipion@cnb.csic.es'
|
24
|
+
# *
|
25
|
+
# **************************************************************************
|
26
|
+
|
27
|
+
import logging
|
28
|
+
logger = logging.getLogger(__name__)
|
29
|
+
import json
|
30
|
+
import datetime as dt
|
31
|
+
|
32
|
+
import pyworkflow.object as pwobj
|
33
|
+
from pyworkflow.mapper import SqliteMapper
|
34
|
+
|
35
|
+
|
36
|
+
class ProjectSettings(pwobj.Object):
|
37
|
+
""" Store settings related to a project. """
|
38
|
+
|
39
|
+
COLOR_MODE_STATUS = 0
|
40
|
+
COLOR_MODE_LABELS = 1
|
41
|
+
COLOR_MODE_AGE = 2
|
42
|
+
COLOR_MODE_SIZE = 3
|
43
|
+
COLOR_MODES = (COLOR_MODE_STATUS, COLOR_MODE_LABELS, COLOR_MODE_AGE) # This has poor performance many cases, COLOR_MODE_SIZE)
|
44
|
+
|
45
|
+
def __init__(self, confs={}, **kwargs):
|
46
|
+
super().__init__(**kwargs)
|
47
|
+
# Store the current view selected by the user
|
48
|
+
self.currentProtocolsView = pwobj.String()
|
49
|
+
# Store the color mode: 0= Status, 1=Labels, ...
|
50
|
+
self.colorMode = pwobj.Integer(ProjectSettings.COLOR_MODE_STATUS)
|
51
|
+
# Store graph nodes positions and other info
|
52
|
+
self.nodeList = NodeConfigList()
|
53
|
+
self.labelsList = LabelsList() # Label list
|
54
|
+
self.mapper = None # This should be set when load, or write
|
55
|
+
self.runsView = pwobj.Integer(1) # by default the graph view
|
56
|
+
self.readOnly = pwobj.Boolean(False)
|
57
|
+
self.runSelection = pwobj.CsvList(int) # Store selected runs
|
58
|
+
self.dataSelection = pwobj.CsvList(int) # Store selected runs
|
59
|
+
# Some extra settings stored, now mainly used
|
60
|
+
# from the webtools
|
61
|
+
# Time when the project was created
|
62
|
+
self.creationTime = pwobj.String(dt.datetime.now())
|
63
|
+
# Number of days that this project is active
|
64
|
+
# if None, the project will not expire
|
65
|
+
# This is used in webtools where a limited time
|
66
|
+
# is allowed for each project
|
67
|
+
self.lifeTime = pwobj.Integer()
|
68
|
+
# Set a disk quota for the project (in Gb)
|
69
|
+
# if None, quota is unlimited
|
70
|
+
self.diskQuota = pwobj.Integer()
|
71
|
+
|
72
|
+
def commit(self):
|
73
|
+
""" Commit changes made. """
|
74
|
+
self.mapper.commit()
|
75
|
+
|
76
|
+
def getRunsView(self):
|
77
|
+
return self.runsView.get()
|
78
|
+
|
79
|
+
def setRunsView(self, value):
|
80
|
+
self.runsView.set(value)
|
81
|
+
|
82
|
+
def getReadOnly(self):
|
83
|
+
return self.readOnly.get()
|
84
|
+
|
85
|
+
def setReadOnly(self, value):
|
86
|
+
self.readOnly.set(value)
|
87
|
+
|
88
|
+
def getCreationTime(self):
|
89
|
+
return self.creationTime.datetime()
|
90
|
+
|
91
|
+
def setCreationTime(self, value):
|
92
|
+
self.creationTime.set(value)
|
93
|
+
|
94
|
+
def getLifeTime(self):
|
95
|
+
return self.lifeTime.get()
|
96
|
+
|
97
|
+
def setLifeTime(self, value):
|
98
|
+
self.lifeTime.set(value)
|
99
|
+
|
100
|
+
def getProtocolView(self):
|
101
|
+
return self.currentProtocolsView.get()
|
102
|
+
|
103
|
+
def setProtocolView(self, protocolView):
|
104
|
+
""" Set the new protocol Menu given its index.
|
105
|
+
The new ProtocolMenu will be returned.
|
106
|
+
"""
|
107
|
+
self.currentProtocolsView.set(protocolView)
|
108
|
+
|
109
|
+
def getColorMode(self):
|
110
|
+
return self.colorMode.get()
|
111
|
+
|
112
|
+
def setColorMode(self, colorMode):
|
113
|
+
""" Set the color mode to use when drawing the graph.
|
114
|
+
"""
|
115
|
+
# Skip LABELS color mode to avoid double iteration
|
116
|
+
if colorMode == self.COLOR_MODE_LABELS:
|
117
|
+
colorMode+=1
|
118
|
+
self.colorMode.set(colorMode)
|
119
|
+
|
120
|
+
def statusColorMode(self):
|
121
|
+
return self.getColorMode() == self.COLOR_MODE_STATUS
|
122
|
+
|
123
|
+
def labelsColorMode(self):
|
124
|
+
return self.getColorMode() == self.COLOR_MODE_LABELS
|
125
|
+
|
126
|
+
def ageColorMode(self):
|
127
|
+
return self.getColorMode() == self.COLOR_MODE_AGE
|
128
|
+
|
129
|
+
def sizeColorMode(self):
|
130
|
+
return self.getColorMode() == self.COLOR_MODE_SIZE
|
131
|
+
|
132
|
+
def write(self, dbPath=None):
|
133
|
+
self.setName('ProjectSettings')
|
134
|
+
if dbPath is not None:
|
135
|
+
self.mapper = SqliteMapper(dbPath, globals())
|
136
|
+
else:
|
137
|
+
if self.mapper is None:
|
138
|
+
raise Exception("Can't write ProjectSettings without "
|
139
|
+
"mapper or dbPath")
|
140
|
+
|
141
|
+
self.mapper.deleteAll()
|
142
|
+
self.mapper.insert(self)
|
143
|
+
self.mapper.commit()
|
144
|
+
|
145
|
+
def getNodes(self):
|
146
|
+
return self.nodeList
|
147
|
+
|
148
|
+
def cleanUpNodes(self, runsIds, toRemove=True):
|
149
|
+
""" This will clean up all the nodes that do not have a matching run.
|
150
|
+
This is because until now, the nodes here weren't removes when protocols were removed.
|
151
|
+
|
152
|
+
:param runsIds: iterable with protocol's objId to be removed.
|
153
|
+
:param toRemove: Passed is are to be removed. Otherwise, are the ones to keep
|
154
|
+
"""
|
155
|
+
|
156
|
+
try:
|
157
|
+
logger.info("Cleaning up unused graphical nodes.")
|
158
|
+
|
159
|
+
nodesToDelete = []
|
160
|
+
for node in self.getNodes():
|
161
|
+
nodeId = str(node.getId())
|
162
|
+
# if it is not the root node
|
163
|
+
if nodeId != '0':
|
164
|
+
|
165
|
+
if (nodeId in runsIds) == toRemove:
|
166
|
+
nodesToDelete.append(node.getId())
|
167
|
+
|
168
|
+
logger.info("Following graphical nodes %s unmatched. Deleting them" % nodesToDelete)
|
169
|
+
for key in nodesToDelete:
|
170
|
+
self.getNodes().removeNode(key)
|
171
|
+
|
172
|
+
except Exception as e:
|
173
|
+
logger.error("Couldn't clean up graphical nodes.", exc_info=e)
|
174
|
+
|
175
|
+
def getNodeById(self, nodeId):
|
176
|
+
return self.nodeList.getNode(nodeId)
|
177
|
+
|
178
|
+
def addNode(self, nodeId, **kwargs):
|
179
|
+
return self.nodeList.addNode(nodeId, **kwargs)
|
180
|
+
|
181
|
+
def removeNode(self, nodeId):
|
182
|
+
""" Removes a graphical node based on its id"""
|
183
|
+
self.nodeList.removeNode(nodeId)
|
184
|
+
|
185
|
+
def getLabels(self):
|
186
|
+
return self.labelsList
|
187
|
+
|
188
|
+
@classmethod
|
189
|
+
def load(cls, dbPath):
|
190
|
+
""" Load a ProjectSettings from dbPath. """
|
191
|
+
classDict = dict(globals())
|
192
|
+
classDict.update(pwobj.__dict__)
|
193
|
+
mapper = SqliteMapper(dbPath, classDict)
|
194
|
+
settingList = mapper.selectByClass('ProjectSettings')
|
195
|
+
n = len(settingList)
|
196
|
+
|
197
|
+
if n == 0:
|
198
|
+
raise Exception("Can't load ProjectSettings from %s" % dbPath)
|
199
|
+
elif n > 1:
|
200
|
+
raise Exception("Only one ProjectSettings is expected in db, "
|
201
|
+
"found %d in %s" % (n, dbPath))
|
202
|
+
|
203
|
+
settings = settingList[0]
|
204
|
+
settings.mapper = mapper
|
205
|
+
|
206
|
+
return settings
|
207
|
+
|
208
|
+
|
209
|
+
class MenuConfig(object):
|
210
|
+
"""Menu configuration in a tree fashion.
|
211
|
+
Each menu can contain submenus.
|
212
|
+
Leaf elements can contain actions"""
|
213
|
+
|
214
|
+
def __init__(self, text=None, value=None,
|
215
|
+
icon=None, tag=None, shortCut=None, openItem=False, visible=True):
|
216
|
+
"""Constructor for the Menu config item.
|
217
|
+
Arguments:
|
218
|
+
text: text to be displayed
|
219
|
+
value: internal value associated with the item.
|
220
|
+
icon: display an icon with the item
|
221
|
+
tag: put some tags to items
|
222
|
+
**args: pass other options to base class.
|
223
|
+
"""
|
224
|
+
self.text = text
|
225
|
+
self.value = value
|
226
|
+
self.icon = icon
|
227
|
+
self.tag = tag
|
228
|
+
self.shortCut = shortCut
|
229
|
+
self.openItem = openItem
|
230
|
+
self.visible = visible
|
231
|
+
self.childs = pwobj.List()
|
232
|
+
|
233
|
+
def addSubMenu(self, text, value=None, **args):
|
234
|
+
subMenu = type(self)(text, value, **args)
|
235
|
+
self.childs.append(subMenu)
|
236
|
+
return subMenu
|
237
|
+
|
238
|
+
def __iter__(self):
|
239
|
+
for v in self.childs:
|
240
|
+
yield v
|
241
|
+
|
242
|
+
def __len__(self):
|
243
|
+
return len(self.childs)
|
244
|
+
|
245
|
+
def isEmpty(self):
|
246
|
+
return len(self.childs) == 0
|
247
|
+
|
248
|
+
|
249
|
+
class NodeConfig(pwobj.Scalar):
|
250
|
+
""" Store Graph node information such as x, y. """
|
251
|
+
|
252
|
+
def __init__(self, nodeId=0, x=None, y=None, selected=False, expanded=True,
|
253
|
+
visible=True):
|
254
|
+
pwobj.Scalar.__init__(self)
|
255
|
+
# Special node id 0 for project node
|
256
|
+
self._values = {'id': nodeId,
|
257
|
+
'x': pwobj.Integer(x).get(0),
|
258
|
+
'y': pwobj.Integer(y).get(0),
|
259
|
+
'selected': selected,
|
260
|
+
'expanded': expanded,
|
261
|
+
'visible': pwobj.Boolean(visible).get(0),
|
262
|
+
'labels': []}
|
263
|
+
|
264
|
+
def _convertValue(self, value):
|
265
|
+
"""Value should be a str with comma separated values
|
266
|
+
or a list.
|
267
|
+
"""
|
268
|
+
self._values = json.loads(value)
|
269
|
+
|
270
|
+
def getObjValue(self):
|
271
|
+
self._objValue = json.dumps(self._values)
|
272
|
+
return self._objValue
|
273
|
+
|
274
|
+
def get(self):
|
275
|
+
return self.getObjValue()
|
276
|
+
|
277
|
+
def getId(self):
|
278
|
+
return self._values['id']
|
279
|
+
|
280
|
+
def setX(self, x):
|
281
|
+
self._values['x'] = x
|
282
|
+
|
283
|
+
def getX(self):
|
284
|
+
return self._values['x']
|
285
|
+
|
286
|
+
def setY(self, y):
|
287
|
+
self._values['y'] = y
|
288
|
+
|
289
|
+
def getY(self):
|
290
|
+
return self._values['y']
|
291
|
+
|
292
|
+
def setPosition(self, x, y):
|
293
|
+
self.setX(x)
|
294
|
+
self.setY(y)
|
295
|
+
|
296
|
+
def getPosition(self):
|
297
|
+
return self.getX(), self.getY()
|
298
|
+
|
299
|
+
def setSelected(self, selected):
|
300
|
+
self._values['selected'] = selected
|
301
|
+
|
302
|
+
def isSelected(self):
|
303
|
+
return self._values['selected']
|
304
|
+
|
305
|
+
def setExpanded(self, expanded):
|
306
|
+
self._values['expanded'] = expanded
|
307
|
+
|
308
|
+
def isExpanded(self):
|
309
|
+
return self._values['expanded']
|
310
|
+
|
311
|
+
def setVisible(self, visible):
|
312
|
+
self._values['visible'] = visible
|
313
|
+
|
314
|
+
def isVisible(self):
|
315
|
+
if self._values.get('visible') is None:
|
316
|
+
self._values['visible'] = True
|
317
|
+
return self._values['visible']
|
318
|
+
|
319
|
+
def setLabels(self, labels):
|
320
|
+
self._values['labels'] = labels
|
321
|
+
|
322
|
+
def getLabels(self):
|
323
|
+
return self._values.get('labels', None)
|
324
|
+
|
325
|
+
def __str__(self):
|
326
|
+
return 'NodeConfig: %s' % self._values
|
327
|
+
|
328
|
+
|
329
|
+
class NodeConfigList(pwobj.List):
|
330
|
+
""" Store all nodes information items and
|
331
|
+
also store a dictionary for quick access
|
332
|
+
to nodes query.
|
333
|
+
"""
|
334
|
+
|
335
|
+
def __init__(self):
|
336
|
+
self._nodesDict = {}
|
337
|
+
pwobj.List.__init__(self)
|
338
|
+
|
339
|
+
def getNode(self, nodeId):
|
340
|
+
return self._nodesDict.get(nodeId, None)
|
341
|
+
|
342
|
+
def addNode(self, nodeId, **kwargs):
|
343
|
+
node = NodeConfig(nodeId, **kwargs)
|
344
|
+
self._nodesDict[node.getId()] = node
|
345
|
+
self.append(node)
|
346
|
+
return node
|
347
|
+
|
348
|
+
def removeNode(self, nodeId):
|
349
|
+
""" Removes a node with the id = nodeId"""
|
350
|
+
nodeToRemove = self._nodesDict[nodeId]
|
351
|
+
self._nodesDict.pop(nodeId)
|
352
|
+
self.remove(nodeToRemove)
|
353
|
+
|
354
|
+
def updateDict(self):
|
355
|
+
self._nodesDict.clear()
|
356
|
+
for node in self:
|
357
|
+
self._nodesDict[node.getId()] = node
|
358
|
+
|
359
|
+
def clear(self):
|
360
|
+
pwobj.List.clear(self)
|
361
|
+
self._nodesDict.clear()
|
362
|
+
|
363
|
+
|
364
|
+
class Label(pwobj.Scalar):
|
365
|
+
""" Store Label information """
|
366
|
+
|
367
|
+
EMPTY_OLD_NAME = None
|
368
|
+
|
369
|
+
def __init__(self, name='', color=None):
|
370
|
+
pwobj.Scalar.__init__(self)
|
371
|
+
# Special node id 0 for project node
|
372
|
+
self._values = {'name': name,
|
373
|
+
'color': color}
|
374
|
+
self._oldName = self.EMPTY_OLD_NAME
|
375
|
+
|
376
|
+
def _convertValue(self, value):
|
377
|
+
"""Value should be a str with comma separated values
|
378
|
+
or a list.
|
379
|
+
"""
|
380
|
+
self._values = json.loads(value)
|
381
|
+
|
382
|
+
# Clean unused "id" field
|
383
|
+
if "id" in self._values:
|
384
|
+
self._values.pop("id")
|
385
|
+
|
386
|
+
def getObjValue(self):
|
387
|
+
self._objValue = json.dumps(self._values)
|
388
|
+
|
389
|
+
return self._objValue
|
390
|
+
|
391
|
+
def get(self):
|
392
|
+
return self.getObjValue()
|
393
|
+
|
394
|
+
|
395
|
+
def getName(self)->str:
|
396
|
+
return self._values['name']
|
397
|
+
|
398
|
+
def setName(self, newName):
|
399
|
+
# For recurrent edit,
|
400
|
+
# we keep the old name only the first time
|
401
|
+
if not self.hasOldName():
|
402
|
+
self._oldName = self._values['name']
|
403
|
+
|
404
|
+
self._values['name'] = newName
|
405
|
+
|
406
|
+
def hasOldName(self)->bool:
|
407
|
+
return self._oldName != self.EMPTY_OLD_NAME
|
408
|
+
|
409
|
+
def clearOldName(self):
|
410
|
+
self._oldName = self.EMPTY_OLD_NAME
|
411
|
+
|
412
|
+
def getOldName(self)->str:
|
413
|
+
return self._oldName
|
414
|
+
|
415
|
+
def setColor(self, color):
|
416
|
+
self._values['color'] = color
|
417
|
+
|
418
|
+
def getColor(self)->str:
|
419
|
+
return self._values.get('color', None)
|
420
|
+
|
421
|
+
def __str__(self):
|
422
|
+
return 'Label: %s' % self._values
|
423
|
+
|
424
|
+
def __eq__(self, other):
|
425
|
+
return self.getName() == other.getName()
|
426
|
+
|
427
|
+
|
428
|
+
class LabelsList(pwobj.List):
|
429
|
+
""" Store all labels information"""
|
430
|
+
|
431
|
+
def __init__(self):
|
432
|
+
self._labelsDict = {}
|
433
|
+
pwobj.List.__init__(self)
|
434
|
+
|
435
|
+
def getLabel(self, name):
|
436
|
+
return self._labelsDict.get(name, None)
|
437
|
+
|
438
|
+
def addLabel(self, label):
|
439
|
+
self._labelsDict[label.getName()] = label
|
440
|
+
self.append(label)
|
441
|
+
return label
|
442
|
+
|
443
|
+
def updateDict(self):
|
444
|
+
self._labelsDict.clear()
|
445
|
+
for label in self:
|
446
|
+
self._labelsDict[label.getName()] = label
|
447
|
+
|
448
|
+
def deleteLabel(self, label):
|
449
|
+
self._labelsDict.pop(label.getName())
|
450
|
+
self.remove(label)
|
451
|
+
|
452
|
+
def clear(self):
|
453
|
+
pwobj.List.clear(self)
|
454
|
+
self._labelDict.clear()
|