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,268 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# **************************************************************************
|
3
|
+
# *
|
4
|
+
# * Authors: J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es)
|
5
|
+
# *
|
6
|
+
# * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
|
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
|
+
"""
|
29
|
+
Run or show the selected tests. Tests can be selected by giving
|
30
|
+
the "case", or by giving the paths and file pattern to use for
|
31
|
+
searching them.
|
32
|
+
"""
|
33
|
+
from pyworkflow.utils import LoggingConfigurator
|
34
|
+
import argparse
|
35
|
+
from collections import OrderedDict
|
36
|
+
|
37
|
+
import pyworkflow.tests as pwtests
|
38
|
+
from pyworkflow import getTestsScript, SCIPION_TESTS_CMD, Config
|
39
|
+
|
40
|
+
from pyworkflow.tests import *
|
41
|
+
|
42
|
+
MODULE = 0
|
43
|
+
CLASS = 1
|
44
|
+
TEST = 2
|
45
|
+
|
46
|
+
|
47
|
+
class Tester:
|
48
|
+
def main(self, args=None):
|
49
|
+
|
50
|
+
# Trigger plugin's variable definition
|
51
|
+
Config.getDomain().getPlugins()
|
52
|
+
|
53
|
+
parser = argparse.ArgumentParser(prog=self.getTestsCommand(), description=__doc__)
|
54
|
+
g = parser.add_mutually_exclusive_group()
|
55
|
+
g.add_argument('--run', action='store_true', help='run the selected tests')
|
56
|
+
g.add_argument('--show', action='store_true', help='show available tests',
|
57
|
+
default=True)
|
58
|
+
|
59
|
+
add = parser.add_argument # shortcut
|
60
|
+
|
61
|
+
add('--pattern', default='test*.py',
|
62
|
+
help='pattern for the files that will be used in the tests')
|
63
|
+
add('--grep', default=None, nargs='+',
|
64
|
+
help='only show/run tests containing the provided words')
|
65
|
+
add('--skip', default=None, nargs='+',
|
66
|
+
help='skip tests that contains these words')
|
67
|
+
add('--log', default=None, nargs='?',
|
68
|
+
help="Generate logs files with the output of each test.")
|
69
|
+
add('--mode', default='classes', choices=['modules', 'classes', 'onlyclasses', 'all'],
|
70
|
+
help='how much detail to give in show mode')
|
71
|
+
add('tests', metavar='TEST', nargs='*',
|
72
|
+
help='test case from string identifier (module, class or callable)')
|
73
|
+
args = parser.parse_args(args)
|
74
|
+
|
75
|
+
if not args.run and not args.show and not args.tests:
|
76
|
+
sys.exit(parser.format_help())
|
77
|
+
|
78
|
+
# Logging stuff first
|
79
|
+
self.log = args.log
|
80
|
+
|
81
|
+
if self.log:
|
82
|
+
LoggingConfigurator.setupLogging(logFile=self.log)
|
83
|
+
else:
|
84
|
+
logging.basicConfig(level=Config.SCIPION_LOG_LEVEL, format=Config.SCIPION_LOG_FORMAT)
|
85
|
+
|
86
|
+
self.logger = logging.getLogger(__name__)
|
87
|
+
|
88
|
+
# This goes intentionally to the output. Is not a logging line._S
|
89
|
+
print("Scanning tests...")
|
90
|
+
|
91
|
+
|
92
|
+
testsDict = OrderedDict()
|
93
|
+
testLoader = unittest.defaultTestLoader
|
94
|
+
|
95
|
+
if args.tests:
|
96
|
+
print(pwutils.cyanStr("Loading test/s %s" % args.tests))
|
97
|
+
|
98
|
+
# In this case the tests are passed as argument.
|
99
|
+
# The full name of the test will be used to load it.
|
100
|
+
testsDict['tests'] = unittest.TestSuite()
|
101
|
+
for t in args.tests:
|
102
|
+
try:
|
103
|
+
testsDict['tests'].addTests(testLoader.loadTestsFromName(t))
|
104
|
+
except Exception as e:
|
105
|
+
self.logger.error('Cannot load test %s -- skipping' % t, exc_info=True)
|
106
|
+
else:
|
107
|
+
# In this other case, we will load the test available
|
108
|
+
# from pyworkflow and the other plugins
|
109
|
+
# self.paths = [('pyworkflow', os.path.dirname(os.path.dirname(pw.__file__)))]
|
110
|
+
self.paths = []
|
111
|
+
for name, plugin in pw.Config.getDomain().getPlugins().items():
|
112
|
+
self.paths.append((name, os.path.dirname(plugin.__path__[0])))
|
113
|
+
for k, p in self.paths:
|
114
|
+
testPath = os.path.join(p, k, 'tests')
|
115
|
+
if os.path.exists(testPath):
|
116
|
+
self.logger.debug("Discovering tests at %s" % testPath)
|
117
|
+
testsDict[k] = testLoader.discover(testPath,
|
118
|
+
pattern=args.pattern,
|
119
|
+
top_level_dir=p)
|
120
|
+
|
121
|
+
self.grep = [g.lower() for g in args.grep] if args.grep else []
|
122
|
+
self.skip = [s.lower() for s in args.skip] if args.skip else []
|
123
|
+
self.mode = args.mode
|
124
|
+
|
125
|
+
if args.tests:
|
126
|
+
self.runSingleTest(testsDict['tests'])
|
127
|
+
|
128
|
+
elif args.run:
|
129
|
+
for moduleName, tests in testsDict.items():
|
130
|
+
self.logger.info(pwutils.cyanStr(">>>> %s" % moduleName))
|
131
|
+
self.runTests(moduleName, tests)
|
132
|
+
|
133
|
+
elif args.grep:
|
134
|
+
pattern = args.grep[0]
|
135
|
+
for moduleName, tests in testsDict.items():
|
136
|
+
self.printTests(pattern, tests)
|
137
|
+
|
138
|
+
else:
|
139
|
+
for moduleName, tests in testsDict.items():
|
140
|
+
if self._match(moduleName):
|
141
|
+
print(pwutils.cyanStr(">>>> %s" % moduleName))
|
142
|
+
self.printTests(moduleName, tests)
|
143
|
+
|
144
|
+
def _match(self, itemName):
|
145
|
+
# Add a space to allow " tomo." as a filter for example to narrow search to tomo and leaving out xmipptomo, emntomo, ...
|
146
|
+
itemLower = " " + itemName.lower()
|
147
|
+
grep = (not self.grep or
|
148
|
+
all(g in itemLower for g in self.grep))
|
149
|
+
skip = (self.skip and
|
150
|
+
any(s in itemLower for s in self.skip))
|
151
|
+
|
152
|
+
return grep and not skip
|
153
|
+
|
154
|
+
def __iterTests(self, test):
|
155
|
+
""" Recursively iterate over a testsuite. """
|
156
|
+
self.logger.debug("__iterTests: %s, is-suite: %s" % (str(test.__class__),
|
157
|
+
isinstance(test, unittest.TestSuite)))
|
158
|
+
|
159
|
+
if isinstance(test, unittest.TestSuite):
|
160
|
+
for t in test:
|
161
|
+
self.__iterTests(t)
|
162
|
+
else:
|
163
|
+
yield test
|
164
|
+
|
165
|
+
def _visitTests(self, moduleName, tests, newItemCallback):
|
166
|
+
""" Show the list of tests available """
|
167
|
+
mode = self.mode
|
168
|
+
|
169
|
+
if mode not in ['modules', 'classes', 'onlyclasses', 'all']:
|
170
|
+
raise AssertionError('Unknown mode %s' % mode)
|
171
|
+
|
172
|
+
# First flatten the list of tests.
|
173
|
+
# testsFlat = list(iter(self.__iterTests(tests)))
|
174
|
+
|
175
|
+
testsFlat = []
|
176
|
+
toCheck = [t for t in tests]
|
177
|
+
|
178
|
+
while toCheck:
|
179
|
+
test = toCheck.pop()
|
180
|
+
if isinstance(test, unittest.TestSuite):
|
181
|
+
toCheck += [t for t in test]
|
182
|
+
elif test not in testsFlat:
|
183
|
+
testsFlat.append(test)
|
184
|
+
|
185
|
+
# Follow the flattened list of tests and show the module, class
|
186
|
+
# and name, in a nice way.
|
187
|
+
lastClass = None
|
188
|
+
lastModule = None
|
189
|
+
if testsFlat:
|
190
|
+
for t in testsFlat:
|
191
|
+
|
192
|
+
testModuleName, className, testName = t.id().rsplit('.', 2)
|
193
|
+
|
194
|
+
# If there is a failure loading the test, show it
|
195
|
+
errorStr = 'unittest.loader._FailedTest.'
|
196
|
+
if testModuleName.startswith(errorStr):
|
197
|
+
newName = t.id().replace(errorStr, '')
|
198
|
+
if self._match(newName):
|
199
|
+
self.logger.error(
|
200
|
+
pwutils.redStr('Error loading the test. Please, run the test for more information: ' + newName))
|
201
|
+
continue
|
202
|
+
|
203
|
+
if testModuleName != lastModule:
|
204
|
+
lastModule = testModuleName
|
205
|
+
if mode != 'onlyclasses':
|
206
|
+
newItemCallback(MODULE, "%s" % testModuleName)
|
207
|
+
|
208
|
+
if mode in ['classes', 'onlyclasses', 'all'] and className != lastClass:
|
209
|
+
lastClass = className
|
210
|
+
newItemCallback(CLASS, "%s.%s"
|
211
|
+
% (testModuleName, className))
|
212
|
+
|
213
|
+
if mode == 'all':
|
214
|
+
newItemCallback(TEST, "%s.%s.%s"
|
215
|
+
% (testModuleName, className, testName))
|
216
|
+
else:
|
217
|
+
if not self.grep:
|
218
|
+
self.logger.warning(pwutils.greenStr(' The plugin does not have any test'))
|
219
|
+
|
220
|
+
def _printNewItem(self, itemType, itemName):
|
221
|
+
if self._match(itemName):
|
222
|
+
spaces = (itemType * 2) * ' '
|
223
|
+
# Do not use intentionally the logger. This is not a logging output but a GUI output
|
224
|
+
print("%s %s %s" % (spaces, self.getTestsCommand(), itemName))
|
225
|
+
|
226
|
+
def getTestsCommand(self):
|
227
|
+
return os.environ.get(SCIPION_TESTS_CMD, getTestsScript())
|
228
|
+
|
229
|
+
def printTests(self, moduleName, tests):
|
230
|
+
self._visitTests(moduleName, tests, self._printNewItem)
|
231
|
+
|
232
|
+
def _runNewItem(self, itemType, itemName):
|
233
|
+
if self._match(itemName):
|
234
|
+
spaces = (itemType * 2) * ' '
|
235
|
+
script = getTestsScript()
|
236
|
+
cmd = "%s %s %s %s" % (pw.PYTHON, script, spaces, itemName)
|
237
|
+
run = ((itemType == MODULE and self.mode == 'modules') or
|
238
|
+
(itemType == CLASS and self.mode in ('classes', 'onlyclasses')) or
|
239
|
+
(itemType == TEST and self.mode == 'all'))
|
240
|
+
if run:
|
241
|
+
if self.log:
|
242
|
+
# logFile = join(self.testsDir, '%s.txt' % itemName)
|
243
|
+
cmdFull = cmd + " >> %s 2>&1" % self.log
|
244
|
+
else:
|
245
|
+
logFile = ''
|
246
|
+
cmdFull = cmd
|
247
|
+
|
248
|
+
self.logger.info(pwutils.greenStr(cmdFull))
|
249
|
+
t = pwutils.Timer()
|
250
|
+
t.tic()
|
251
|
+
self.testCount += 1
|
252
|
+
os.system(cmdFull)
|
253
|
+
|
254
|
+
def runTests(self, moduleName, tests):
|
255
|
+
|
256
|
+
self.testCount = 0
|
257
|
+
self._visitTests(moduleName, tests, self._runNewItem)
|
258
|
+
|
259
|
+
def runSingleTest(self, tests):
|
260
|
+
result = pwtests.GTestResult()
|
261
|
+
tests.run(result)
|
262
|
+
result.doReport()
|
263
|
+
resultPassed = result.numberTests - result.testFailed
|
264
|
+
sys.exit(1 if result.testFailed > 0 or resultPassed == 0 else 0)
|
265
|
+
|
266
|
+
|
267
|
+
if __name__ == '__main__':
|
268
|
+
Tester().main()
|
@@ -0,0 +1,322 @@
|
|
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
|
+
import logging
|
29
|
+
|
30
|
+
from pyworkflow.utils import LoggingConfigurator
|
31
|
+
|
32
|
+
import os
|
33
|
+
import sys
|
34
|
+
import time
|
35
|
+
import argparse
|
36
|
+
|
37
|
+
from pyworkflow.protocol import (getProtocolFromDb,
|
38
|
+
STATUS_FINISHED, STATUS_ABORTED, STATUS_FAILED,
|
39
|
+
STATUS_RUNNING, STATUS_SCHEDULED, STATUS_SAVED,
|
40
|
+
STATUS_LAUNCHED, Set, Protocol, MAX_SLEEP_TIME)
|
41
|
+
from pyworkflow.constants import PROTOCOL_UPDATED
|
42
|
+
|
43
|
+
stopStatuses = [STATUS_FINISHED, STATUS_ABORTED, STATUS_FAILED]
|
44
|
+
|
45
|
+
|
46
|
+
class RunScheduler:
|
47
|
+
""" Check that all dependencies are met before launching a run. """
|
48
|
+
|
49
|
+
def __init__(self, args, logger):
|
50
|
+
self._args = args
|
51
|
+
# Enter to the project directory and load protocol from db
|
52
|
+
self.protocol = self._loadProtocol()
|
53
|
+
self.project = self.protocol.getProject()
|
54
|
+
self.log = logger
|
55
|
+
self.protPid = os.getpid()
|
56
|
+
self.protocol.setPid(self.protPid)
|
57
|
+
self.protocol._store(self.protocol._pid)
|
58
|
+
self.prerequisites = list(map(int, self.protocol.getPrerequisites()))
|
59
|
+
# Keep track of the last time the protocol was checked and
|
60
|
+
# its modification date to avoid unnecessary db opening
|
61
|
+
self.updatedProtocols = dict()
|
62
|
+
self.initial_sleep = self._args.initial_sleep
|
63
|
+
|
64
|
+
def getSleepTime(self):
|
65
|
+
return self._args.sleepTime
|
66
|
+
|
67
|
+
def getInitialSleepTime(self):
|
68
|
+
return self.initial_sleep
|
69
|
+
|
70
|
+
def _loadProtocol(self) -> Protocol:
|
71
|
+
return getProtocolFromDb(self._args.projPath,
|
72
|
+
self._args.dbPath,
|
73
|
+
self._args.protId, chdir=True)
|
74
|
+
|
75
|
+
def _log(self, msg):
|
76
|
+
self.log.info(msg)
|
77
|
+
|
78
|
+
def _updateProtocol(self, protocol):
|
79
|
+
|
80
|
+
protId = protocol.getObjId()
|
81
|
+
|
82
|
+
if protId in self.updatedProtocols:
|
83
|
+
return self.updatedProtocols[protId]
|
84
|
+
|
85
|
+
protDb = protocol.getDbPath()
|
86
|
+
|
87
|
+
if os.path.exists(protDb):
|
88
|
+
updateResult = self.project._updateProtocol(protocol)
|
89
|
+
if updateResult == PROTOCOL_UPDATED:
|
90
|
+
self._log("Updated protocol: %s (%s)" % (protId, protocol))
|
91
|
+
else:
|
92
|
+
self._log("The protocol %s (%s) is up to date" % (protId, protocol))
|
93
|
+
self.updatedProtocols[protId] = protocol
|
94
|
+
|
95
|
+
return protocol
|
96
|
+
|
97
|
+
def _getProtocolFromPointer(self, pointer):
|
98
|
+
"""
|
99
|
+
The function return a protocol from an attribute
|
100
|
+
|
101
|
+
A) When the pointer points to a protocol
|
102
|
+
|
103
|
+
B) When the pointer points to another object (INDIRECTLY).
|
104
|
+
- The pointer has an _extended value (new parameters
|
105
|
+
configuration in the protocol)
|
106
|
+
|
107
|
+
C) When the pointer points to another object (DIRECTLY).
|
108
|
+
- The pointer has not an _extended value (old parameters
|
109
|
+
configuration in the protocol)
|
110
|
+
"""
|
111
|
+
output = pointer.get()
|
112
|
+
if isinstance(output, Protocol): # case A
|
113
|
+
protocol = output
|
114
|
+
else:
|
115
|
+
if pointer.hasExtended(): # case B
|
116
|
+
protocol = pointer.getObjValue()
|
117
|
+
else: # case C
|
118
|
+
protocol = self.project.getRunsGraph().getNode(str(output.getObjParentId())).run
|
119
|
+
return protocol
|
120
|
+
|
121
|
+
def _getSecondsToWait(self, inProt):
|
122
|
+
"""
|
123
|
+
Assigns a timeout penalty or reward depending on the status of the
|
124
|
+
input protocol (inProt)
|
125
|
+
If inProt status is stopped we assign a reward i.o.c a penalty
|
126
|
+
"""
|
127
|
+
protStatus = inProt.getStatus()
|
128
|
+
inStreaming = inProt.worksInStreaming()
|
129
|
+
meStreaming = self.protocol.worksInStreaming()
|
130
|
+
|
131
|
+
penaltyRewardValues = {
|
132
|
+
STATUS_LAUNCHED: 5 if inStreaming else 10,
|
133
|
+
STATUS_RUNNING: -2 if inStreaming else 3,
|
134
|
+
STATUS_SCHEDULED: int(self.getSleepTime()/2),
|
135
|
+
STATUS_SAVED: self.getSleepTime(),
|
136
|
+
}
|
137
|
+
|
138
|
+
secondToWait = penaltyRewardValues.get(protStatus, -3)
|
139
|
+
|
140
|
+
if not meStreaming:
|
141
|
+
secondToWait += 3 * self.getSleepTime()
|
142
|
+
|
143
|
+
return secondToWait
|
144
|
+
|
145
|
+
def _checkPrerequisites(self, prerequisites, project):
|
146
|
+
# Check if we need to wait for required protocols
|
147
|
+
wait = False
|
148
|
+
# For each protocol that is a prerequisite that has not finished,
|
149
|
+
# we'll penalize with 3 more seconds of waiting
|
150
|
+
penalize = 0
|
151
|
+
self._log("Checking prerequisites... %s" % prerequisites)
|
152
|
+
for protId in prerequisites:
|
153
|
+
# Check if prerequisites exist. In the case of metaprotocols, it
|
154
|
+
# may be necessary to load them from the project database.
|
155
|
+
node = project.getRunsGraph().getNode(str(protId))
|
156
|
+
|
157
|
+
if node is None:
|
158
|
+
self._log("Updating runs graph. Missing protocol ... %s" % protId)
|
159
|
+
project.getRunsGraph(refresh=True)
|
160
|
+
|
161
|
+
node = project.getRunsGraph().getNode(str(protId))
|
162
|
+
# Check if the protocol is within our workflow
|
163
|
+
if node is None:
|
164
|
+
self._log("Protocol can't wait for %s. Missing prerequisite " % protId)
|
165
|
+
break
|
166
|
+
|
167
|
+
prot = project.getRunsGraph().getNode(str(protId)).run
|
168
|
+
if prot is not None:
|
169
|
+
prot = self._updateProtocol(prot)
|
170
|
+
penalize += self._getSecondsToWait(prot)
|
171
|
+
if prot.getStatus() not in stopStatuses:
|
172
|
+
wait = True
|
173
|
+
self._log(" ...waiting for %s" % prot)
|
174
|
+
return wait, penalize
|
175
|
+
|
176
|
+
def _checkMissingInput(self):
|
177
|
+
"""
|
178
|
+
Check if there are missing inputs
|
179
|
+
In case of having them, check if protocol is in streaming
|
180
|
+
"""
|
181
|
+
inputMissing = False
|
182
|
+
# For each input that is not ready we'll penalize with 3 more
|
183
|
+
# seconds of waiting
|
184
|
+
penalize = 0
|
185
|
+
|
186
|
+
self._log("Checking input data...")
|
187
|
+
# Updating input protocols
|
188
|
+
for key, attr in self.protocol.iterInputAttributes():
|
189
|
+
self.log.debug("Turn for %s" % key)
|
190
|
+
inputProt = self._getProtocolFromPointer(attr)
|
191
|
+
inputProt = self._updateProtocol(inputProt)
|
192
|
+
penalize += self._getSecondsToWait(inputProt)
|
193
|
+
|
194
|
+
validation = self.protocol.validate()
|
195
|
+
if len(validation) > 0:
|
196
|
+
inputMissing = True
|
197
|
+
self._log("%s doesn't validate:\n\t- %s" % (self.protocol.getObjLabel(),
|
198
|
+
'\n\t- '.join(
|
199
|
+
validation)))
|
200
|
+
elif not self.protocol.worksInStreaming():
|
201
|
+
for key, attr in self.protocol.iterInputAttributes():
|
202
|
+
inSet = attr.get()
|
203
|
+
if isinstance(inSet, Set) and inSet.isStreamOpen():
|
204
|
+
inputMissing = True
|
205
|
+
self._log("Waiting for closing %s... (does not work in "
|
206
|
+
"streaming)" % inSet)
|
207
|
+
break
|
208
|
+
elif isinstance(inSet, Protocol) and not inSet.isFinished(): # Then is a pointer to a protocol
|
209
|
+
inputMissing = True
|
210
|
+
self._log("Waiting for protocol %s to finish... (does not work in "
|
211
|
+
"streaming)" % inSet)
|
212
|
+
break
|
213
|
+
|
214
|
+
else:
|
215
|
+
self._log("%s is not blocking this protocol. All ready." % inSet)
|
216
|
+
|
217
|
+
if not inputMissing:
|
218
|
+
inputProtocolIds = self.protocol.getProtocolsToUpdate()
|
219
|
+
for protId in inputProtocolIds:
|
220
|
+
protocol = self.project.getProtocol(protId)
|
221
|
+
self.log.debug("Turn from inputProtocolDict for %s" % protocol)
|
222
|
+
self._updateProtocol(protocol)
|
223
|
+
|
224
|
+
return inputMissing, penalize
|
225
|
+
|
226
|
+
def schedule(self):
|
227
|
+
self._log("Scheduling protocol %s, PID: %s,prerequisites: %s, works in streaming: %s." %
|
228
|
+
(self.protocol.getObjId(), self.protPid, self.prerequisites,
|
229
|
+
self.protocol.worksInStreaming()))
|
230
|
+
|
231
|
+
initialSleepTime = runScheduler.getInitialSleepTime()
|
232
|
+
self._log("Waiting %s seconds before start checking inputs " % initialSleepTime)
|
233
|
+
time.sleep(initialSleepTime)
|
234
|
+
|
235
|
+
while True:
|
236
|
+
# Clear the list of protocols updated in the previous loop
|
237
|
+
self.updatedProtocols.clear()
|
238
|
+
sleepTime = self.getSleepTime()
|
239
|
+
# FIXME: This does not cover all the cases:
|
240
|
+
# When the user registers new coordinates after clicking the
|
241
|
+
# "Analyze result" button, this action is registered in the project.sqlite
|
242
|
+
# and not in it's own run.db and never gets updated. It is not critical and
|
243
|
+
# will only affect a combination of json import with extended that will
|
244
|
+
# appear after clicking on the "Analyze result" button.
|
245
|
+
|
246
|
+
# Check if there are missing inputs
|
247
|
+
missing, penalize = self._checkMissingInput()
|
248
|
+
sleepTime += penalize
|
249
|
+
|
250
|
+
# Check the prerequisites
|
251
|
+
wait, penalize = self._checkPrerequisites(self.prerequisites,
|
252
|
+
self.project)
|
253
|
+
sleepTime += penalize
|
254
|
+
|
255
|
+
if not missing and not wait:
|
256
|
+
break
|
257
|
+
|
258
|
+
sleepTime = max(min(sleepTime, MAX_SLEEP_TIME), self.getSleepTime())
|
259
|
+
|
260
|
+
self._log("Still not ready, sleeping %s seconds...\n" % sleepTime)
|
261
|
+
time.sleep(sleepTime)
|
262
|
+
|
263
|
+
self._log("Launching the protocol >>>>")
|
264
|
+
self.project.launchProtocol(self.protocol, scheduled=True, force=True)
|
265
|
+
|
266
|
+
def parseArgs():
|
267
|
+
parser = argparse.ArgumentParser()
|
268
|
+
_addArg = parser.add_argument # short notation
|
269
|
+
|
270
|
+
_addArg("projPath", metavar='PROJECT_NAME',
|
271
|
+
help="Project database path.")
|
272
|
+
|
273
|
+
_addArg("dbPath", metavar='DATABASE_PATH',
|
274
|
+
help="Protocol database path.")
|
275
|
+
|
276
|
+
_addArg("protId", type=int, metavar='PROTOCOL_ID',
|
277
|
+
help="Protocol ID.")
|
278
|
+
|
279
|
+
_addArg("logPath", metavar='LOG_PATH', help='Path to the log file.')
|
280
|
+
|
281
|
+
_addArg("--initial_sleep", type=int, default=0,
|
282
|
+
dest='initial_sleep', metavar='SECONDS',
|
283
|
+
help="Initial sleeping time (in seconds)")
|
284
|
+
|
285
|
+
_addArg("--sleep_time", type=int, default=15,
|
286
|
+
dest='sleepTime', metavar='SECONDS',
|
287
|
+
help="Sleeping time (in seconds) between updates.")
|
288
|
+
|
289
|
+
_addArg("--wait_for", nargs='*', type=int, default=[],
|
290
|
+
dest='waitProtIds', metavar='PROTOCOL_ID',
|
291
|
+
help="List of protocol ids that should be not running "
|
292
|
+
"(i.e, finished, aborted or failed) before this "
|
293
|
+
"run will be executed.")
|
294
|
+
|
295
|
+
return parser.parse_args()
|
296
|
+
|
297
|
+
|
298
|
+
if __name__ == '__main__':
|
299
|
+
|
300
|
+
# Create a child process
|
301
|
+
# using os.fork() method
|
302
|
+
pid = os.fork()
|
303
|
+
|
304
|
+
# pid greater than 0 represents
|
305
|
+
# the parent process
|
306
|
+
if pid > 0:
|
307
|
+
sys.exit(0)
|
308
|
+
else:
|
309
|
+
try:
|
310
|
+
# Parse arguments
|
311
|
+
args = parseArgs()
|
312
|
+
|
313
|
+
# Configure logging
|
314
|
+
LoggingConfigurator.setUpProtocolSchedulingLog(args.logPath)
|
315
|
+
logger = logging.getLogger(__name__)
|
316
|
+
|
317
|
+
runScheduler = RunScheduler(args, logger)
|
318
|
+
runScheduler.schedule()
|
319
|
+
|
320
|
+
except Exception as ex:
|
321
|
+
print("Scheduling failed with these parameters: ", sys.argv)
|
322
|
+
raise ex from None
|
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# **************************************************************************
|
3
|
+
# *
|
4
|
+
# * Authors: J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es)
|
5
|
+
# *
|
6
|
+
# * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
|
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
|
+
This module is responsible for launching protocol executions.
|
29
|
+
"""
|
30
|
+
import os
|
31
|
+
import sys
|
32
|
+
|
33
|
+
if __name__ == '__main__':
|
34
|
+
seconds = sys.argv[1]
|
35
|
+
print("Sleeper, pid: ", os.getpid())
|
36
|
+
print("Sleeping %s seconds...." % seconds)
|
37
|
+
os.system("sleep %s" % seconds)
|