scipion-pyworkflow 3.11.1__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/pw_project.py +21 -4
- pyworkflow/apps/pw_run_tests.py +2 -1
- pyworkflow/apps/pw_sync_data.py +4 -3
- pyworkflow/constants.py +1 -1
- pyworkflow/gui/browser.py +8 -0
- pyworkflow/gui/dialog.py +3 -1
- pyworkflow/gui/form.py +1 -0
- pyworkflow/gui/gui.py +5 -0
- pyworkflow/gui/project/base.py +2 -1
- pyworkflow/gui/project/searchprotocol.py +5 -3
- pyworkflow/gui/project/viewprojects.py +10 -1
- pyworkflow/gui/project/viewprotocols.py +42 -17
- pyworkflow/gui/text.py +4 -1
- pyworkflow/mapper/mapper.py +4 -0
- pyworkflow/mapper/sqlite.py +7 -5
- pyworkflow/object.py +1 -0
- pyworkflow/plugin.py +4 -4
- pyworkflow/protocol/executor.py +55 -23
- pyworkflow/protocol/hosts.py +2 -1
- pyworkflow/protocol/protocol.py +5 -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/tests.py +2 -1
- pyworkflow/utils/process.py +2 -1
- pyworkflow/utils/properties.py +1 -0
- pyworkflow/webservices/workflowhub.py +37 -25
- {scipion_pyworkflow-3.11.1.dist-info → scipion_pyworkflow-3.11.2.dist-info}/METADATA +1 -1
- {scipion_pyworkflow-3.11.1.dist-info → scipion_pyworkflow-3.11.2.dist-info}/RECORD +34 -33
- {scipion_pyworkflow-3.11.1.dist-info → scipion_pyworkflow-3.11.2.dist-info}/WHEEL +0 -0
- {scipion_pyworkflow-3.11.1.dist-info → scipion_pyworkflow-3.11.2.dist-info}/entry_points.txt +0 -0
- {scipion_pyworkflow-3.11.1.dist-info → scipion_pyworkflow-3.11.2.dist-info}/licenses/LICENSE.txt +0 -0
- {scipion_pyworkflow-3.11.1.dist-info → scipion_pyworkflow-3.11.2.dist-info}/top_level.txt +0 -0
pyworkflow/apps/pw_project.py
CHANGED
@@ -30,8 +30,9 @@ Launch main project window
|
|
30
30
|
|
31
31
|
import sys
|
32
32
|
import os
|
33
|
+
from subprocess import Popen
|
33
34
|
|
34
|
-
from pyworkflow import Config
|
35
|
+
from pyworkflow import Config, PYTHON
|
35
36
|
from pyworkflow.project import Manager
|
36
37
|
from pyworkflow.gui.project import ProjectWindow
|
37
38
|
import pyworkflow.utils as pwutils
|
@@ -89,12 +90,28 @@ def openProject(projectName):
|
|
89
90
|
projPath = manager.getProjectPath(projName)
|
90
91
|
|
91
92
|
if os.path.exists(projPath):
|
92
|
-
|
93
|
-
|
93
|
+
|
94
|
+
# This opens the project in the same process as the launcher. This is good for directly debugging code
|
95
|
+
# but does not allow -O or not execution (usage of __debug__ flag).
|
96
|
+
# All we can do is to go straight to loading the project if debug is active or running optimized.
|
97
|
+
if Config.debugOn() or not __debug__:
|
98
|
+
|
99
|
+
|
100
|
+
# This may or may not be run Optimized (-O). It depends on the call to scipion last (launcher)
|
101
|
+
print("Launching project in debug or optimized...")
|
102
|
+
projWindow = ProjectWindow(projPath)
|
103
|
+
projWindow.show()
|
104
|
+
else:
|
105
|
+
|
106
|
+
# Run this same script optimized: Defined in scipion module under scipion-app: Circular definition. To fix! Bad design.
|
107
|
+
print("Launching project optimized...")
|
108
|
+
Popen([PYTHON, "-O", "-m","scipion", "project", projectName])
|
109
|
+
|
110
|
+
|
94
111
|
else:
|
95
112
|
print("Can't open project %s. It does not exist" % projPath)
|
96
113
|
|
97
|
-
#Show the list of projects
|
114
|
+
# Show the list of projects
|
98
115
|
showProjectList(manager)
|
99
116
|
|
100
117
|
def showProjectList(manager):
|
pyworkflow/apps/pw_run_tests.py
CHANGED
@@ -166,7 +166,8 @@ class Tester:
|
|
166
166
|
""" Show the list of tests available """
|
167
167
|
mode = self.mode
|
168
168
|
|
169
|
-
|
169
|
+
if mode not in ['modules', 'classes', 'onlyclasses', 'all']:
|
170
|
+
raise AssertionError('Unknown mode %s' % mode)
|
170
171
|
|
171
172
|
# First flatten the list of tests.
|
172
173
|
# testsFlat = list(iter(self.__iterTests(tests)))
|
pyworkflow/apps/pw_sync_data.py
CHANGED
@@ -278,8 +278,8 @@ def download(dataset, destination=None, url=None, verbose=False):
|
|
278
278
|
urlopen('%s/%s/%s' % (url, dataset, fname)))
|
279
279
|
|
280
280
|
md5 = md5sum(fpath)
|
281
|
-
|
282
|
-
"Bad md5. Expected: %s Computed: %s" % (md5Remote, md5)
|
281
|
+
if md5 != md5Remote:
|
282
|
+
raise AssertionError("Bad md5. Expected: %s Computed: %s" % (md5Remote, md5))
|
283
283
|
|
284
284
|
done += inc
|
285
285
|
if verbose:
|
@@ -315,7 +315,8 @@ def update(dataset, workingCopy=None, url=None, verbose=False):
|
|
315
315
|
try:
|
316
316
|
last = max(os.stat(join(datasetFolder, x)).st_mtime for x in md5sRemote)
|
317
317
|
t_manifest = os.stat(join(datasetFolder, 'MANIFEST')).st_mtime
|
318
|
-
|
318
|
+
if not (t_manifest > last and time.time() - t_manifest < 60*60*24*7):
|
319
|
+
raise AssertionError("Manifest times seems to be wrong.")
|
319
320
|
except (OSError, IOError, AssertionError) as e:
|
320
321
|
logger.info("Regenerating local MANIFEST...")
|
321
322
|
createMANIFEST(datasetFolder)
|
pyworkflow/constants.py
CHANGED
@@ -43,7 +43,7 @@ VERSION_1 = '1.0.0'
|
|
43
43
|
VERSION_1_1 = '1.1.0'
|
44
44
|
VERSION_1_2 = '1.2.0'
|
45
45
|
VERSION_2_0 = '2.0.0'
|
46
|
-
VERSION_3_0 = '3.11.
|
46
|
+
VERSION_3_0 = '3.11.2'
|
47
47
|
|
48
48
|
# For a new release, define a new constant and assign it to LAST_VERSION
|
49
49
|
# The existing one has to be added to OLD_VERSIONS list.
|
pyworkflow/gui/browser.py
CHANGED
@@ -72,6 +72,10 @@ class ObjectBrowser(tk.Frame):
|
|
72
72
|
p.grid(row=0, column=0, sticky='news')
|
73
73
|
|
74
74
|
leftPanel = tk.Frame(p)
|
75
|
+
|
76
|
+
# Optional, widget to get the focus
|
77
|
+
self.initial_focus=None
|
78
|
+
|
75
79
|
self._fillLeftPanel(leftPanel)
|
76
80
|
p.add(leftPanel, padx=5, pady=5)
|
77
81
|
p.paneconfig(leftPanel, minsize=300)
|
@@ -88,6 +92,7 @@ class ObjectBrowser(tk.Frame):
|
|
88
92
|
def _fillLeftPanel(self, frame):
|
89
93
|
gui.configureWeigths(frame)
|
90
94
|
self.tree = BoundTree(frame, self.treeProvider, style=LIST_TREEVIEW)
|
95
|
+
self.initial_focus=self.tree
|
91
96
|
self.tree.grid(row=0, column=0, sticky='news')
|
92
97
|
self.itemConfig = self.tree.itemConfig
|
93
98
|
self.getImage = self.tree.getImage
|
@@ -489,6 +494,7 @@ class FileBrowser(ObjectBrowser):
|
|
489
494
|
pathLabel.grid(row=0, column=0, padx=0, pady=3)
|
490
495
|
pathEntry = tk.Entry(pathFrame, bg='white', width=65,
|
491
496
|
textvariable=self.pathVar, font=gui.getDefaultFont())
|
497
|
+
self.initial_focus=pathEntry
|
492
498
|
pathEntry.grid(row=0, column=1, sticky='new', pady=3)
|
493
499
|
pathEntry.bind("<Return>", self._onEnterPath)
|
494
500
|
pathEntry.bind("<KP_Enter>", self._onEnterPath)
|
@@ -711,6 +717,8 @@ class BrowserWindow(gui.Window):
|
|
711
717
|
browser.grid(row=row, column=column, sticky='news')
|
712
718
|
self.itemConfig = browser.tree.itemConfig
|
713
719
|
|
720
|
+
if browser.initial_focus is not None:
|
721
|
+
self.initial_focus = browser.initial_focus
|
714
722
|
|
715
723
|
STANDARD_IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg']
|
716
724
|
|
pyworkflow/gui/dialog.py
CHANGED
@@ -644,7 +644,7 @@ class ListDialog(Dialog):
|
|
644
644
|
label = tk.Label(bodyFrame, text=self.message, compound=tk.LEFT,
|
645
645
|
image=self.getImage(Icon.LIGHTBULB))
|
646
646
|
label.grid(row=2, column=0, sticky='nw', padx=5, pady=5)
|
647
|
-
self.initial_focus = self.tree
|
647
|
+
# CAncel this, now focus is set to the search box. self.initial_focus = self.tree
|
648
648
|
|
649
649
|
def _createTree(self, parent):
|
650
650
|
self.tree = BoundTree(parent, self.provider, selectmode=self._selectmode, style=LIST_TREEVIEW)
|
@@ -677,6 +677,7 @@ class ListDialog(Dialog):
|
|
677
677
|
self.entry.bind('<KeyRelease>', self._onSearch)
|
678
678
|
self.entry.focus_set()
|
679
679
|
self.entry.grid(row=0, column=1, sticky='news')
|
680
|
+
self.initial_focus=self.entry
|
680
681
|
self.searchBoxframe.grid(row=0, column=0, sticky='news', padx=5,
|
681
682
|
pady=(10, 5))
|
682
683
|
|
@@ -910,6 +911,7 @@ class SearchBaseWindow(Window):
|
|
910
911
|
entry.bind(TK.ENTER, self._onSearchClick)
|
911
912
|
entry.focus_set()
|
912
913
|
entry.grid(row=0, column=1, sticky='nw')
|
914
|
+
self.initial_focus=entry
|
913
915
|
btn = widgets.IconButton(frame, "Search",
|
914
916
|
imagePath=Icon.ACTION_SEARCH,
|
915
917
|
command=self._onSearchClick)
|
pyworkflow/gui/form.py
CHANGED
@@ -1937,6 +1937,7 @@ class FormWindow(Window):
|
|
1937
1937
|
self.waitForVar.set(', '.join(self.protocol.getPrerequisites()))
|
1938
1938
|
entryWf = tk.Entry(runFrame, font=self.font, width=25,
|
1939
1939
|
textvariable=self.waitForVar)
|
1940
|
+
self.initial_focus = entryWf
|
1940
1941
|
entryWf.grid(row=r, column=c + 1, padx=(0, 5), pady=5, sticky='ew')
|
1941
1942
|
|
1942
1943
|
self.waitForVar.trace('w', self._setWaitFor)
|
pyworkflow/gui/gui.py
CHANGED
@@ -371,6 +371,8 @@ class Window:
|
|
371
371
|
self.master = masterWindow
|
372
372
|
setCommonFonts(self)
|
373
373
|
|
374
|
+
self.initial_focus = None
|
375
|
+
|
374
376
|
if kwargs.get('enableQueue', False):
|
375
377
|
self.queue = queue.Queue(maxsize=0)
|
376
378
|
else:
|
@@ -451,6 +453,9 @@ class Window:
|
|
451
453
|
if self.queue is not None:
|
452
454
|
self._queueTimer = self.root.after(1000, self.__processQueue)
|
453
455
|
|
456
|
+
if self.initial_focus is not None:
|
457
|
+
self.initial_focus.focus_set()
|
458
|
+
|
454
459
|
if modal:
|
455
460
|
self.root.wait_window(self.root)
|
456
461
|
else:
|
pyworkflow/gui/project/base.py
CHANGED
@@ -92,7 +92,8 @@ class ProjectBaseWindow(Window):
|
|
92
92
|
logoLabel = tk.Label(header, image=logoImg,
|
93
93
|
borderwidth=0, anchor='nw', bg=pw.Config.SCIPION_BG_COLOR)
|
94
94
|
logoLabel.grid(row=0, column=0, sticky='nw', padx=(5, 0), pady=5)
|
95
|
-
|
95
|
+
|
96
|
+
version = "%s - %s (core%s)" % (os.environ.get('SCIPION_VERSION', ""), pw.LAST_VERSION, ' optimized'if not __debug__ else '')
|
96
97
|
|
97
98
|
versionLabel = tk.Label(header, text=version,
|
98
99
|
bg=pw.Config.SCIPION_BG_COLOR)
|
@@ -24,6 +24,8 @@
|
|
24
24
|
# **************************************************************************
|
25
25
|
""" This module contains the provider and dialog to search for a protocol"""
|
26
26
|
import tkinter as tk
|
27
|
+
import logging
|
28
|
+
logger = logging.getLogger(__name__)
|
27
29
|
from pyworkflow import Config
|
28
30
|
import pyworkflow.gui as pwgui
|
29
31
|
import pyworkflow.object as pwobj
|
@@ -35,12 +37,9 @@ from pyworkflow.project.usage import getNextProtocolSuggestions
|
|
35
37
|
from pyworkflow.utils import Icon
|
36
38
|
|
37
39
|
UPDATED = "updated"
|
38
|
-
|
39
40
|
NEW = "new"
|
40
|
-
|
41
41
|
BETA = "beta"
|
42
42
|
|
43
|
-
|
44
43
|
class ProtocolTreeProvider(pwgui.tree.ObjectTreeProvider):
|
45
44
|
"""Create the tree elements for a Protocol run"""
|
46
45
|
|
@@ -81,6 +80,7 @@ class SearchProtocolWindow(SearchBaseWindow):
|
|
81
80
|
title="Add a protocol" + posStr)
|
82
81
|
|
83
82
|
self.root.bind("<FocusIn>", self._onWindowFocusIn)
|
83
|
+
self.root.focus()
|
84
84
|
|
85
85
|
def _onWindowFocusIn(self, event):
|
86
86
|
"""
|
@@ -92,6 +92,8 @@ class SearchProtocolWindow(SearchBaseWindow):
|
|
92
92
|
self.selectedProtocol = self.selectionGetter()
|
93
93
|
if self._isSuggestionActive():
|
94
94
|
self._onSearchClick()
|
95
|
+
self.initial_focus.focus_set()
|
96
|
+
|
95
97
|
def _isSuggestionActive(self):
|
96
98
|
"""
|
97
99
|
:return: Returns true if current mode is suggestion mode.
|
@@ -220,7 +220,16 @@ class ProjectsView(tk.Frame):
|
|
220
220
|
def openProject(self, projName):
|
221
221
|
from subprocess import Popen
|
222
222
|
script = pw.join(pw.APPS, 'pw_project.py')
|
223
|
-
|
223
|
+
args=[pw.PYTHON, "-O", script, projName]
|
224
|
+
|
225
|
+
# Launcher is the ona calling this process. Since launcher does not deal
|
226
|
+
# with __debug__ we should use the variable in the config
|
227
|
+
if pw.Config.debugOn():
|
228
|
+
# If not optimizing code, remove -O
|
229
|
+
logger.warning(f"Launching project {projName} in debug mode.")
|
230
|
+
del args[1]
|
231
|
+
|
232
|
+
Popen(args)
|
224
233
|
|
225
234
|
def deleteProject(self, projInfo):
|
226
235
|
|
@@ -85,10 +85,23 @@ class ProtocolsView(tk.Frame):
|
|
85
85
|
SIZE_1GB: "orange",
|
86
86
|
SIZE_1TB: "red"}
|
87
87
|
|
88
|
+
_actionList = [
|
89
|
+
ACTION_NEW, ACTION_EDIT, ACTION_RENAME, ACTION_DUPLICATE, ACTION_COPY, ACTION_PASTE, ACTION_DELETE,
|
90
|
+
ACTION_BROWSE,
|
91
|
+
ACTION_STOP, ACTION_STOP_WORKFLOW, ACTION_CONTINUE, ACTION_CONTINUE_WORKFLOW, ACTION_RESTART_WORKFLOW,
|
92
|
+
ACTION_RESET_WORKFLOW,
|
93
|
+
ACTION_RESULTS,
|
94
|
+
ACTION_EXPORT, ACTION_EXPORT_UPLOAD,
|
95
|
+
ACTION_COLLAPSE, ACTION_EXPAND,
|
96
|
+
ACTION_LABELS, ACTION_SEARCH,
|
97
|
+
ACTION_SELECT_FROM, ACTION_SELECT_TO,
|
98
|
+
ACTION_STEPS, ACTION_DB
|
99
|
+
]
|
88
100
|
_protocolViews = None
|
89
101
|
|
90
102
|
def __init__(self, parent, window, **args):
|
91
103
|
tk.Frame.__init__(self, parent, **args)
|
104
|
+
|
92
105
|
# Load global configuration
|
93
106
|
self.window = window
|
94
107
|
self.project = window.project
|
@@ -110,6 +123,8 @@ class ProtocolsView(tk.Frame):
|
|
110
123
|
self.root.bind("<Control-t>", self._toggleColorScheme)
|
111
124
|
self.root.bind("<Control-D>", self._toggleDebug)
|
112
125
|
self.root.bind("<Control-l>", self._locateProtocol)
|
126
|
+
# Bind to root "focus in"
|
127
|
+
self.root.bind("<FocusIn>", self._onWindowFocusIn)
|
113
128
|
|
114
129
|
if Config.debugOn():
|
115
130
|
self.root.bind("<Control-i>", self._inspectProtocols)
|
@@ -295,6 +310,11 @@ class ProtocolsView(tk.Frame):
|
|
295
310
|
|
296
311
|
return p
|
297
312
|
|
313
|
+
def _onWindowFocusIn(self, event):
|
314
|
+
""" Refresh on windows get focus """
|
315
|
+
if event.widget == self.root:
|
316
|
+
self.runsGraphCanvas.focus_set()
|
317
|
+
|
298
318
|
def _viewObject(self, objId):
|
299
319
|
""" Call appropriate viewer for objId. """
|
300
320
|
proj = self.project
|
@@ -442,17 +462,6 @@ class ProtocolsView(tk.Frame):
|
|
442
462
|
""" Prepare the buttons that will be available for protocol actions. """
|
443
463
|
|
444
464
|
self.actionButtons = {}
|
445
|
-
actionList = [
|
446
|
-
ACTION_NEW, ACTION_EDIT, ACTION_RENAME, ACTION_DUPLICATE, ACTION_COPY, ACTION_PASTE, ACTION_DELETE,
|
447
|
-
ACTION_BROWSE,
|
448
|
-
ACTION_STOP, ACTION_STOP_WORKFLOW, ACTION_CONTINUE, ACTION_CONTINUE_WORKFLOW, ACTION_RESTART_WORKFLOW, ACTION_RESET_WORKFLOW,
|
449
|
-
ACTION_RESULTS,
|
450
|
-
ACTION_EXPORT, ACTION_EXPORT_UPLOAD,
|
451
|
-
ACTION_COLLAPSE, ACTION_EXPAND,
|
452
|
-
ACTION_LABELS, ACTION_SEARCH,
|
453
|
-
ACTION_SELECT_FROM, ACTION_SELECT_TO,
|
454
|
-
ACTION_STEPS, ACTION_DB
|
455
|
-
]
|
456
465
|
|
457
466
|
def addButton(action, text, toolbar):
|
458
467
|
|
@@ -464,17 +473,16 @@ class ProtocolsView(tk.Frame):
|
|
464
473
|
callback = lambda e: self._runActionClicked(action, event=e)
|
465
474
|
btn.bind(TK.LEFT_CLICK, callback)
|
466
475
|
|
467
|
-
# Shortcuts
|
476
|
+
# Shortcuts, these are bind later!!
|
468
477
|
shortCut = ActionShortCuts.get(action, None)
|
469
478
|
if shortCut:
|
470
479
|
text += " (%s)" % shortCut
|
471
|
-
self.root.bind(shortCut, callback)
|
472
480
|
|
473
481
|
ToolTip(btn, text, 500)
|
474
482
|
|
475
483
|
return btn
|
476
484
|
|
477
|
-
for action in
|
485
|
+
for action in self._actionList:
|
478
486
|
self.actionButtons[action] = addButton(action, action,
|
479
487
|
self.runsToolbar)
|
480
488
|
|
@@ -745,6 +753,17 @@ class ProtocolsView(tk.Frame):
|
|
745
753
|
|
746
754
|
self.updateRunsGraph()
|
747
755
|
|
756
|
+
# Shortcuts for actions
|
757
|
+
for action in self._actionList:
|
758
|
+
|
759
|
+
# prevent "late binding closure" by passing action as a default value for the lambda param.
|
760
|
+
callback = lambda e, my_action=action: self._runActionClicked(my_action, event=e)
|
761
|
+
|
762
|
+
# Shortcuts:
|
763
|
+
shortCut = ActionShortCuts.get(action, None)
|
764
|
+
if shortCut:
|
765
|
+
self.runsGraphCanvas.bind(shortCut, callback)
|
766
|
+
|
748
767
|
def updateRunsGraph(self, refresh=False, checkPids=False, position=None):
|
749
768
|
|
750
769
|
self.runsGraph = self.project.getRunsGraph(refresh=refresh,
|
@@ -1215,9 +1234,15 @@ class ProtocolsView(tk.Frame):
|
|
1215
1234
|
self._selectItemProtocol(prot)
|
1216
1235
|
|
1217
1236
|
def _runItemDoubleClick(self, item=None, e=None):
|
1218
|
-
|
1237
|
+
|
1238
|
+
if self.runsView == VIEW_LIST:
|
1219
1239
|
self._runActionClicked(ACTION_EDIT)
|
1220
1240
|
|
1241
|
+
elif item.nodeInfo.isExpanded():
|
1242
|
+
self._runActionClicked(ACTION_EDIT)
|
1243
|
+
else:
|
1244
|
+
self._runActionClicked(ACTION_EXPAND)
|
1245
|
+
|
1221
1246
|
def _runItemMiddleClick(self, e=None):
|
1222
1247
|
self._runActionClicked(ACTION_SELECT_TO)
|
1223
1248
|
|
@@ -1612,7 +1637,7 @@ class ProtocolsView(tk.Frame):
|
|
1612
1637
|
|
1613
1638
|
self.project.loadProtocols(jsonStr=self.clipboard_get())
|
1614
1639
|
self.info("Clipboard content pasted successfully.")
|
1615
|
-
self.updateRunsGraph(
|
1640
|
+
self.updateRunsGraph(True)
|
1616
1641
|
except Exception as e:
|
1617
1642
|
self.info("Paste failed, maybe clipboard content is not valid content? See GUI log for details.")
|
1618
1643
|
logger.error("Clipboard content couldn't be pasted." , exc_info=e)
|
@@ -1988,7 +2013,7 @@ class ProtocolsView(tk.Frame):
|
|
1988
2013
|
if event is not None:
|
1989
2014
|
# log Search box events are reaching here
|
1990
2015
|
# Since this method is bound to the window events
|
1991
|
-
if event.widget.widgetName
|
2016
|
+
if event.widget.widgetName in ['text', 'entry']:
|
1992
2017
|
return
|
1993
2018
|
|
1994
2019
|
# Following actions do not need a select run
|
pyworkflow/gui/text.py
CHANGED
@@ -170,6 +170,7 @@ class Text(tk.Text, Scrollable):
|
|
170
170
|
# Associate with right click
|
171
171
|
self.bind("<Button-1>", self.onClick)
|
172
172
|
self.bind("<Button-3>", self.onRightClick)
|
173
|
+
self.bind("Control-c", self.copyToClipboard)
|
173
174
|
|
174
175
|
def getDefaults(self):
|
175
176
|
"""This should be implemented in subclasses to provide defaults"""
|
@@ -234,6 +235,7 @@ class Text(tk.Text, Scrollable):
|
|
234
235
|
def copyToClipboard(self, e=None):
|
235
236
|
self.clipboard_clear()
|
236
237
|
self.clipboard_append(self.selection)
|
238
|
+
return "break"
|
237
239
|
|
238
240
|
def openFile(self):
|
239
241
|
# What happens when you right-click and select "Open path"
|
@@ -508,7 +510,7 @@ class TextFileViewer(tk.Frame):
|
|
508
510
|
self.maxSize = maxSize
|
509
511
|
self.width = width
|
510
512
|
self.height = height
|
511
|
-
|
513
|
+
self.searchEntry = None
|
512
514
|
self.createWidgets(fileList)
|
513
515
|
self.master = master
|
514
516
|
self.addBinding()
|
@@ -755,6 +757,7 @@ def openTextFileEditor(filename, tkParent=None):
|
|
755
757
|
def showTextFileViewer(title, filelist, parent=None, main=False):
|
756
758
|
w = gui.Window(title, parent, minsize=(600, 400))
|
757
759
|
viewer = TextFileViewer(w.root, filelist, maxSize=-1, font=w.font)
|
760
|
+
w.initial_focus=viewer.searchEntry
|
758
761
|
viewer.grid(row=0, column=0, sticky='news')
|
759
762
|
gui.configureWeigths(w.root)
|
760
763
|
w.show()
|
pyworkflow/mapper/mapper.py
CHANGED
@@ -84,6 +84,10 @@ class Mapper:
|
|
84
84
|
|
85
85
|
try:
|
86
86
|
instance = self.dictClasses[className]()
|
87
|
+
|
88
|
+
if __debug__:
|
89
|
+
logger.debug("Object instantiated of class %s", className)
|
90
|
+
|
87
91
|
except Exception as e:
|
88
92
|
clazz = self.dictClasses._default
|
89
93
|
logger.error('Class %s could not be created. Replacing it with %s ' % (className, clazz.__name__), exc_info=e)
|
pyworkflow/mapper/sqlite.py
CHANGED
@@ -187,6 +187,10 @@ class SqliteMapper(Mapper):
|
|
187
187
|
"""Build the object which id is objId"""
|
188
188
|
if objId in self.objDict:
|
189
189
|
obj = self.objDict[objId]
|
190
|
+
|
191
|
+
if __debug__:
|
192
|
+
# If not optimized code
|
193
|
+
logger.debug("Object with id %s already loaded: %s", objId, obj)
|
190
194
|
else:
|
191
195
|
objRow = self.db.selectObjectById(objId)
|
192
196
|
if objRow is None:
|
@@ -211,10 +215,8 @@ class SqliteMapper(Mapper):
|
|
211
215
|
rowName = self._getStrValue(objRow['name'])
|
212
216
|
|
213
217
|
if not hasattr(obj, ID_ATTRIBUTE):
|
214
|
-
raise Exception("Entry '
|
215
|
-
", is being mapped to
|
216
|
-
(rowName, rowId,
|
217
|
-
objRow['classname'], type(obj)))
|
218
|
+
raise Exception(f"Entry '{rowName}' (id={rowId}) in the database, stored as '{objRow['classname']}'"
|
219
|
+
f", is being mapped to {type(obj)} object.")
|
218
220
|
|
219
221
|
obj._objId = rowId
|
220
222
|
|
@@ -880,7 +882,7 @@ class SqliteFlatMapper(Mapper):
|
|
880
882
|
self.db.deleteAll()
|
881
883
|
|
882
884
|
def delete(self, obj):
|
883
|
-
"""Delete an object and all its
|
885
|
+
"""Delete an object and all its children"""
|
884
886
|
self.db.deleteObject(obj.getObjId())
|
885
887
|
|
886
888
|
def updateTo(self, obj, level=1):
|
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
|
|
pyworkflow/protocol/executor.py
CHANGED
@@ -31,6 +31,7 @@ using different threads and the last one with MPI processes.
|
|
31
31
|
"""
|
32
32
|
|
33
33
|
import logging
|
34
|
+
|
34
35
|
logger = logging.getLogger(__name__)
|
35
36
|
import time
|
36
37
|
import datetime
|
@@ -47,6 +48,7 @@ from .launch import _submit, UNKNOWN_JOBID, _checkJobStatus
|
|
47
48
|
|
48
49
|
class StepExecutor:
|
49
50
|
""" Run a list of Protocol steps. """
|
51
|
+
|
50
52
|
def __init__(self, hostConfig, **kwargs):
|
51
53
|
self.hostConfig = hostConfig
|
52
54
|
self.gpuList = kwargs.get(cts.GPU_LIST, None)
|
@@ -56,11 +58,22 @@ class StepExecutor:
|
|
56
58
|
""" Return the GPU list assigned to current thread. """
|
57
59
|
return self.gpuList
|
58
60
|
|
61
|
+
def getGpuListStr(self, sep=" "):
|
62
|
+
""" Returns the GPU list assigned to the current thread as string
|
63
|
+
:param sep: default ot space. Pass "," or other string to use a separator
|
64
|
+
"""
|
65
|
+
return sep.join(map(str, self.getGpuList()))
|
66
|
+
|
67
|
+
def setCudaVisibleDevices(self):
|
68
|
+
""" sets CUDA_VISIBLE DEVICES in the environment to the available GPUs in turn"""
|
69
|
+
# https://developer.nvidia.com/blog/cuda-pro-tip-control-gpu-visibility-cuda_visible_devices/
|
70
|
+
os.environ["CUDA_VISIBLE_DEVICES"] = self.getGpuListStr(",")
|
71
|
+
|
59
72
|
def setProtocol(self, protocol):
|
60
73
|
""" Set protocol to append active jobs to its jobIds. """
|
61
74
|
self.protocol = protocol
|
62
75
|
|
63
|
-
def runJob(self, log, programName, params,
|
76
|
+
def runJob(self, log, programName, params,
|
64
77
|
numberOfMpi=1, numberOfThreads=1,
|
65
78
|
env=None, cwd=None, executable=None):
|
66
79
|
""" This function is a wrapper around runJob,
|
@@ -69,7 +82,8 @@ class StepExecutor:
|
|
69
82
|
process.runJob(log, programName, params,
|
70
83
|
numberOfMpi, numberOfThreads,
|
71
84
|
self.hostConfig,
|
72
|
-
env=env, cwd=cwd, gpuList=self._getGPUListForCommand(programName, params), executable=executable,
|
85
|
+
env=env, cwd=cwd, gpuList=self._getGPUListForCommand(programName, params), executable=executable,
|
86
|
+
context=self.protocol.getSubmitDict())
|
73
87
|
|
74
88
|
def _getGPUListForCommand(self, program, params):
|
75
89
|
""" Returns the list of GPUs if the program or the params have the GPU placeholder %(GPU)s """
|
@@ -86,13 +100,14 @@ class StepExecutor:
|
|
86
100
|
|
87
101
|
for s in steps:
|
88
102
|
if (s.getStatus() == cts.STATUS_NEW and
|
89
|
-
all(steps[i-1].isFinished() for i in s._prerequisites)):
|
103
|
+
all(steps[i - 1].isFinished() for i in s._prerequisites)):
|
90
104
|
|
91
105
|
if self._isStepRunnable(s):
|
92
106
|
rs.append(s)
|
93
107
|
if len(rs) == n:
|
94
108
|
break
|
95
109
|
return rs
|
110
|
+
|
96
111
|
def _isStepRunnable(self, step):
|
97
112
|
""" Should be implemented by inherited classes to test extra conditions """
|
98
113
|
return True
|
@@ -102,9 +117,9 @@ class StepExecutor:
|
|
102
117
|
that can be done and thus enable other steps to be executed.
|
103
118
|
"""
|
104
119
|
return any(s.isRunning() or s.isWaiting() for s in steps)
|
105
|
-
|
106
|
-
def runSteps(self, steps,
|
107
|
-
stepStartedCallback,
|
120
|
+
|
121
|
+
def runSteps(self, steps,
|
122
|
+
stepStartedCallback,
|
108
123
|
stepFinishedCallback,
|
109
124
|
stepsCheckCallback,
|
110
125
|
stepsCheckSecs=3):
|
@@ -128,15 +143,15 @@ class StepExecutor:
|
|
128
143
|
stepStartedCallback(step)
|
129
144
|
step.run()
|
130
145
|
doContinue = stepFinishedCallback(step)
|
131
|
-
|
146
|
+
|
132
147
|
if not doContinue:
|
133
148
|
break
|
134
149
|
|
135
150
|
elif self._arePending(steps):
|
136
|
-
# We have not found any runnable step, but still
|
151
|
+
# We have not found any runnable step, but still
|
137
152
|
# there are some running or waiting for dependencies
|
138
153
|
# So, let's wait a bit to check if something changes
|
139
|
-
time.sleep(
|
154
|
+
time.sleep(3)
|
140
155
|
else:
|
141
156
|
# No steps to run, neither running or waiting
|
142
157
|
# So, we are done, either failed or finished :)
|
@@ -152,6 +167,7 @@ class StepExecutor:
|
|
152
167
|
|
153
168
|
class StepThread(threading.Thread):
|
154
169
|
""" Thread to run Steps in parallel. """
|
170
|
+
|
155
171
|
def __init__(self, step, lock):
|
156
172
|
threading.Thread.__init__(self)
|
157
173
|
self.thId = step.getObjId()
|
@@ -167,7 +183,7 @@ class StepThread(threading.Thread):
|
|
167
183
|
self.step._run() # not self.step.run() , to avoid race conditions
|
168
184
|
except Exception as e:
|
169
185
|
error = str(e)
|
170
|
-
logger.error("Couldn't run the code in a thread."
|
186
|
+
logger.error("Couldn't run the code in a thread.", exc_info=e)
|
171
187
|
finally:
|
172
188
|
with self.lock:
|
173
189
|
if error is None:
|
@@ -178,6 +194,7 @@ class StepThread(threading.Thread):
|
|
178
194
|
|
179
195
|
class ThreadStepExecutor(StepExecutor):
|
180
196
|
""" Run steps in parallel using threads. """
|
197
|
+
|
181
198
|
def __init__(self, hostConfig, nThreads, **kwargs):
|
182
199
|
StepExecutor.__init__(self, hostConfig, **kwargs)
|
183
200
|
self.numberOfProcs = nThreads
|
@@ -194,7 +211,7 @@ class ThreadStepExecutor(StepExecutor):
|
|
194
211
|
nThreads = self.numberOfProcs
|
195
212
|
|
196
213
|
# Nodes: each concurrent steps
|
197
|
-
nodes = range(1, nThreads+1)
|
214
|
+
nodes = range(1, nThreads + 1)
|
198
215
|
|
199
216
|
# Number of GPUs
|
200
217
|
nGpu = len(self.gpuList)
|
@@ -215,8 +232,8 @@ class ThreadStepExecutor(StepExecutor):
|
|
215
232
|
# Node 0 : GPU 0 1
|
216
233
|
# Node 1 : GPU 2
|
217
234
|
|
218
|
-
extraGpu = 1 if spare>0 else 0
|
219
|
-
toPos = fromPos + step +extraGpu
|
235
|
+
extraGpu = 1 if spare > 0 else 0
|
236
|
+
toPos = fromPos + step + extraGpu
|
220
237
|
gpusForNode = list(self.gpuList[fromPos:toPos])
|
221
238
|
|
222
239
|
newGpusForNode = self.cleanVoidGPUs(gpusForNode)
|
@@ -227,7 +244,7 @@ class ThreadStepExecutor(StepExecutor):
|
|
227
244
|
self.gpuDict[-node] = newGpusForNode
|
228
245
|
|
229
246
|
fromPos = toPos
|
230
|
-
spare-=1
|
247
|
+
spare -= 1
|
231
248
|
|
232
249
|
else:
|
233
250
|
# Expand gpuList repeating until reach nThreads items
|
@@ -244,10 +261,10 @@ class ThreadStepExecutor(StepExecutor):
|
|
244
261
|
else:
|
245
262
|
logger.info("GPU slot for gpu %s." % gpu)
|
246
263
|
# Any negative number in the key means a free gpu slot. can't be 0!
|
247
|
-
self.gpuDict[-index-1] = [gpu]
|
264
|
+
self.gpuDict[-index - 1] = [gpu]
|
248
265
|
|
249
266
|
def cleanVoidGPUs(self, gpuList):
|
250
|
-
newGPUList=[]
|
267
|
+
newGPUList = []
|
251
268
|
for gpuid in gpuList:
|
252
269
|
if gpuid == cts.VOID_GPU:
|
253
270
|
logger.info("Void GPU detected in %s" % gpuList)
|
@@ -280,10 +297,12 @@ class ThreadStepExecutor(StepExecutor):
|
|
280
297
|
|
281
298
|
gpus = self.getFreeGpuSlot(nodeId)
|
282
299
|
if gpus is None:
|
283
|
-
logger.warning(
|
300
|
+
logger.warning(
|
301
|
+
"Step on node %s is requesting GPUs but there isn't any available. Review configuration of threads/GPUs. Returning an empty list." % nodeId)
|
284
302
|
return []
|
285
303
|
else:
|
286
304
|
return gpus
|
305
|
+
|
287
306
|
def getFreeGpuSlot(self, stepId=None):
|
288
307
|
""" Returns a free gpu slot available or None. If node is passed it also reserves it for that node
|
289
308
|
|
@@ -303,6 +322,7 @@ class ThreadStepExecutor(StepExecutor):
|
|
303
322
|
return gpus
|
304
323
|
|
305
324
|
return None
|
325
|
+
|
306
326
|
def freeGpusSlot(self, node):
|
307
327
|
gpus = self.gpuDict.get(node, None)
|
308
328
|
|
@@ -323,8 +343,8 @@ class ThreadStepExecutor(StepExecutor):
|
|
323
343
|
|
324
344
|
return True
|
325
345
|
|
326
|
-
def runSteps(self, steps,
|
327
|
-
stepStartedCallback,
|
346
|
+
def runSteps(self, steps,
|
347
|
+
stepStartedCallback,
|
328
348
|
stepFinishedCallback,
|
329
349
|
stepsCheckCallback,
|
330
350
|
stepsCheckSecs=5):
|
@@ -345,7 +365,7 @@ class ThreadStepExecutor(StepExecutor):
|
|
345
365
|
sharedLock = threading.Lock()
|
346
366
|
|
347
367
|
runningSteps = {} # currently running step in each node ({node: step})
|
348
|
-
freeNodes = list(range(1, self.numberOfProcs+1)) # available nodes to send jobs
|
368
|
+
freeNodes = list(range(1, self.numberOfProcs + 1)) # available nodes to send jobs
|
349
369
|
logger.info("Execution threads: %s" % freeNodes)
|
350
370
|
logger.info("Running steps using %s threads. 1 thread is used for this main process." % self.numberOfProcs)
|
351
371
|
|
@@ -382,17 +402,21 @@ class ThreadStepExecutor(StepExecutor):
|
|
382
402
|
stepStartedCallback(step)
|
383
403
|
node = freeNodes.pop(0) # take an available node
|
384
404
|
runningSteps[node] = step
|
385
|
-
logger.
|
405
|
+
logger.info("Running step %s on node %s" % (step, node))
|
386
406
|
t = StepThread(step, sharedLock)
|
387
407
|
# won't keep process up if main thread ends
|
388
408
|
t.daemon = True
|
389
409
|
t.start()
|
410
|
+
|
390
411
|
anyPending = self._arePending(steps)
|
391
412
|
|
392
413
|
if not anyLaunched:
|
414
|
+
logger.debug("Nothing launched in this loop")
|
393
415
|
if anyPending: # nothing running
|
416
|
+
logger.debug("There are steps pending. Waiting 3 secs")
|
394
417
|
time.sleep(3)
|
395
418
|
else:
|
419
|
+
logger.info("Nothing pending. Breaking the loop.")
|
396
420
|
break # yeah, we are done, either failed or finished :)
|
397
421
|
|
398
422
|
now = datetime.datetime.now()
|
@@ -407,6 +431,15 @@ class ThreadStepExecutor(StepExecutor):
|
|
407
431
|
if t is not threading.current_thread():
|
408
432
|
t.join()
|
409
433
|
|
434
|
+
def _arePending(self, steps):
|
435
|
+
""" Return True if there are pending steps (either running, waiting or new (not yet executed)
|
436
|
+
"""
|
437
|
+
for s in steps:
|
438
|
+
if s.isRunning() or s.isWaiting() or s.isNew():
|
439
|
+
return True
|
440
|
+
|
441
|
+
return False
|
442
|
+
|
410
443
|
|
411
444
|
class QueueStepExecutor(ThreadStepExecutor):
|
412
445
|
def __init__(self, hostConfig, submitDict, nThreads, **kwargs):
|
@@ -429,7 +462,7 @@ class QueueStepExecutor(ThreadStepExecutor):
|
|
429
462
|
for i in range(len(gpuList)):
|
430
463
|
self.gpuDict[threadId][i] = i
|
431
464
|
|
432
|
-
logger.debug("Updated gpus ids rebase starting from 0: %s per thread" %self.gpuDict)
|
465
|
+
logger.debug("Updated gpus ids rebase starting from 0: %s per thread" % self.gpuDict)
|
433
466
|
|
434
467
|
def getThreadJobId(self, stepId):
|
435
468
|
""" Returns the job id extension assigned to each thread/step """
|
@@ -457,7 +490,6 @@ class QueueStepExecutor(ThreadStepExecutor):
|
|
457
490
|
gpuList=self._getGPUListForCommand(programName, params),
|
458
491
|
context=submitDict)
|
459
492
|
|
460
|
-
|
461
493
|
jobid = _submit(self.hostConfig, submitDict, cwd, env)
|
462
494
|
self.protocol.appendJobId(jobid) # append active jobs
|
463
495
|
self.protocol._store(self.protocol._jobId)
|
pyworkflow/protocol/hosts.py
CHANGED
@@ -164,7 +164,8 @@ class HostConfig(Object):
|
|
164
164
|
hosts = OrderedDict()
|
165
165
|
|
166
166
|
try:
|
167
|
-
|
167
|
+
if not cp.read(hostsConf):
|
168
|
+
raise AssertionError('Missing file %s' % hostsConf)
|
168
169
|
|
169
170
|
for hostName in cp.sections():
|
170
171
|
host = HostConfig(label=hostName, hostName=hostName)
|
pyworkflow/protocol/protocol.py
CHANGED
@@ -1795,6 +1795,11 @@ class Protocol(Step):
|
|
1795
1795
|
self._stepsExecutor = executor
|
1796
1796
|
self._stepsExecutor.setProtocol(self) # executor needs the protocol to store the jobs Ids submitted to a queue
|
1797
1797
|
|
1798
|
+
def getExecutor(self):
|
1799
|
+
"""Return the executor associated. This must be used only during protocol execution (steps code).
|
1800
|
+
In "design/GUI time" is not set."""
|
1801
|
+
return self._stepsExecutor
|
1802
|
+
|
1798
1803
|
def getFiles(self):
|
1799
1804
|
resultFiles = set()
|
1800
1805
|
for paramName, _ in self.getDefinition().iterPointerParams():
|
Binary file
|
pyworkflow/resources/sprites.png
CHANGED
Binary file
|
pyworkflow/resources/sprites.xcf
CHANGED
Binary file
|
pyworkflow/template.py
CHANGED
@@ -101,7 +101,7 @@ class Template:
|
|
101
101
|
field.setValue(oldValue)
|
102
102
|
raise Exception("%s is not compatible with %s(%s) parameter." % (newValue, field.getTitle(), alias))
|
103
103
|
if not paramsSetted:
|
104
|
-
|
104
|
+
logger.warning('Argument "%s" not available in the template.' % alias)
|
105
105
|
return paramsSetted
|
106
106
|
|
107
107
|
class LocalTemplate(Template):
|
pyworkflow/tests/tests.py
CHANGED
@@ -61,7 +61,8 @@ class DataSet:
|
|
61
61
|
"""
|
62
62
|
This method is called every time the dataset want to be retrieved
|
63
63
|
"""
|
64
|
-
|
64
|
+
if name not in cls._datasetDict:
|
65
|
+
raise AssertionError("Dataset: %s dataset doesn't exist." % name)
|
65
66
|
|
66
67
|
ds = cls._datasetDict[name]
|
67
68
|
folder = ds.folder
|
pyworkflow/utils/process.py
CHANGED
@@ -96,7 +96,8 @@ def buildRunCommand(programname, params, numberOfMpi, hostConfig=None,
|
|
96
96
|
if numberOfMpi <= 1:
|
97
97
|
return '%s %s %s' % (prepend, programname, params)
|
98
98
|
else:
|
99
|
-
|
99
|
+
if hostConfig is None:
|
100
|
+
raise AssertionError('hostConfig needed to launch MPI processes.')
|
100
101
|
|
101
102
|
if programname.startswith('xmipp') and not programname.startswith('xmipp_mpi'):
|
102
103
|
programname = programname.replace('xmipp', 'xmipp_mpi')
|
pyworkflow/utils/properties.py
CHANGED
@@ -519,6 +519,7 @@ class Icon:
|
|
519
519
|
ACTION_FILAMENT_PICKING = SpriteImage(48, 176, 'filament.png')
|
520
520
|
ACTION_GRID = SpriteImage(64, 160, 'grid.png')
|
521
521
|
ACTION_CONTRAST = SpriteImage(80, 160, 'contrast.png')
|
522
|
+
ACTION_INTERPOLATE = SpriteImage(80, 144, 'interpolate.png')
|
522
523
|
|
523
524
|
|
524
525
|
# Host template
|
@@ -1,6 +1,8 @@
|
|
1
1
|
import json
|
2
2
|
from urllib.request import Request, urlopen
|
3
3
|
from pyworkflow.template import Template
|
4
|
+
import logging
|
5
|
+
logger = logging.getLogger(__name__)
|
4
6
|
|
5
7
|
|
6
8
|
class WHTemplate(Template):
|
@@ -12,24 +14,29 @@ class WHTemplate(Template):
|
|
12
14
|
def loadContent(self):
|
13
15
|
""" Download the file pointer by url and read the content"""
|
14
16
|
|
15
|
-
|
17
|
+
data = make_request(self.url, asJson=False)
|
16
18
|
|
19
|
+
# Clean all text up to the first [
|
20
|
+
data = data[data.index("["):]
|
21
|
+
return data
|
17
22
|
|
18
23
|
def make_request(url, asJson=True):
|
19
24
|
""" Makes a request to the url and returns the json as a dictionary"""
|
20
25
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
try:
|
27
|
+
req = Request(url)
|
28
|
+
req.add_header("accept", "application/json")
|
29
|
+
|
30
|
+
with urlopen(req, timeout=1) as response:
|
31
|
+
if asJson:
|
32
|
+
data = json.load(response)
|
33
|
+
else:
|
34
|
+
html_response = response.read()
|
35
|
+
encoding = response.headers.get_content_charset('utf-8')
|
36
|
+
data = html_response.decode(encoding)
|
37
|
+
return data
|
38
|
+
except Exception as e:
|
39
|
+
logger.error(f"Couldn't get data from workflow hub at {url}: {str(e)}")
|
33
40
|
|
34
41
|
def get_workflow_file_url(workflow_id, version):
|
35
42
|
root_url = "https://workflowhub.eu/workflows/%s/git/%s/" % (workflow_id, version)
|
@@ -39,9 +46,12 @@ def get_workflow_file_url(workflow_id, version):
|
|
39
46
|
|
40
47
|
for file in result["tree"]:
|
41
48
|
path = (file["path"])
|
42
|
-
if
|
49
|
+
if ".json" in path:
|
43
50
|
return root_url + 'raw/' + path
|
44
51
|
|
52
|
+
# Haven't found the workflow file
|
53
|
+
raise Exception(f"Couldn't find any path element at {url} containing .json in it")
|
54
|
+
|
45
55
|
|
46
56
|
def get_wh_templates(template_id=None, organization="Scipion%20CNB"):
|
47
57
|
""" Returns a list of scipion templates available in workflow hub"""
|
@@ -53,17 +63,19 @@ def get_wh_templates(template_id=None, organization="Scipion%20CNB"):
|
|
53
63
|
template_list = []
|
54
64
|
|
55
65
|
for workflow in response:
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
66
|
+
try:
|
67
|
+
workflow_id = workflow["id"]
|
68
|
+
name = workflow["name"]
|
69
|
+
description = workflow['description']
|
70
|
+
version = workflow["versions"][-1]
|
71
|
+
version_id = version["id"]
|
72
|
+
template_url = get_workflow_file_url(workflow_id, version_id)
|
73
|
+
|
74
|
+
new_template = WHTemplate("WH", name, description, template_url)
|
75
|
+
if template_id is None or new_template.getObjId() == template_id:
|
76
|
+
template_list.append(new_template)
|
77
|
+
except Exception as e:
|
78
|
+
logger.warning(f"Couldn't retrieve a data for {name} workflow hub template. ERROR: {e}" )
|
67
79
|
return template_list
|
68
80
|
|
69
81
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: scipion-pyworkflow
|
3
|
-
Version: 3.11.
|
3
|
+
Version: 3.11.2
|
4
4
|
Summary: Workflow platform used in scientific applications, initially developed within the Scipion framework for image processing in Electron Microscopy but generic by design to be applied to any domain.
|
5
5
|
Author-email: Pablo Conesa <pconesa@cnb.csic.es>, Yunior Fonseca <cfonseca@cnb.csic.es>, "J.M. De la Rosa Trevin" <josemiguel.delarosatrevin@stjude.org>, Roberto Marabini <roberto@cnb.csic.es>, Grigory Sharov <sharov.grigory@gmail.com>, Josue Gomez Blanco <josue.gomez-blanco@mcgill.ca>
|
6
6
|
License: GNU General Public License v3 (GPLv3)
|
@@ -1,54 +1,54 @@
|
|
1
1
|
pyworkflow/__init__.py,sha256=Wr-MVKMQJy_Cy-rpPPB0-pyv8-8tx7GPPaLSNBrV1AI,1246
|
2
2
|
pyworkflow/config.py,sha256=tvGMn6OlOrIUzZKCQwWFOA2oBSn85FTgPwNzA86oFn0,26291
|
3
|
-
pyworkflow/constants.py,sha256=
|
3
|
+
pyworkflow/constants.py,sha256=CAAE2iM024hLj2jTKfItkX1galaOrognBEp2gQil6_U,7800
|
4
4
|
pyworkflow/exceptions.py,sha256=3VFxuNJHcIWxRnLPR0vYg0RFAQMmxPBJZLZSi87VI8E,507
|
5
|
-
pyworkflow/object.py,sha256=
|
6
|
-
pyworkflow/plugin.py,sha256=
|
7
|
-
pyworkflow/template.py,sha256=
|
5
|
+
pyworkflow/object.py,sha256=bMb6vohVw7O12xDXJOtjXhLqVVZviO8q_i83EYMCAt4,55217
|
6
|
+
pyworkflow/plugin.py,sha256=guiGN2K38cMzRmhdaNcU4YNoY9MC89ywzlz4jnCFs7w,30592
|
7
|
+
pyworkflow/template.py,sha256=KaF8VJjHHSHTiY5gmdbfMVoLlf_sav9SX3bf-khPRRk,10482
|
8
8
|
pyworkflow/viewer.py,sha256=YCRoZaF0NYoBnSGhgBLQjCXuGKNWbKP6N1GWuALU_cQ,12053
|
9
9
|
pyworkflow/wizard.py,sha256=nXuk_qMUVlQNa6nB6GCt0CoFQ_P2dnJbGWdwtpDG2tQ,2633
|
10
10
|
pyworkflow/apps/__init__.py,sha256=doupoWHiPmpEwa7o7ro7suxYYjj7h-912kc-fxwnhfE,1187
|
11
11
|
pyworkflow/apps/pw_manager.py,sha256=LqvNLqREsMN-O4QlxHr5mvG7XlKSYVvzgV1pGICV9rU,1356
|
12
12
|
pyworkflow/apps/pw_plot.py,sha256=69L9_qbdaYuC8QbvE9MNOu4P2MYIna0II29PBu0HQdg,1829
|
13
|
-
pyworkflow/apps/pw_project.py,sha256=
|
13
|
+
pyworkflow/apps/pw_project.py,sha256=L7nfLC9Z1slA63f7Y-2XuOj9YPR68gzHenp6oSmJEvA,4415
|
14
14
|
pyworkflow/apps/pw_protocol_list.py,sha256=bRC0W_M5Yx0KQ651S4HxB4AOqE4_P8hacc8qrS_3vPo,5193
|
15
15
|
pyworkflow/apps/pw_protocol_run.py,sha256=Uboywq_pwIBAqa_YIsHtBg5iO9b134HEVwrhwkLMoNY,1916
|
16
|
-
pyworkflow/apps/pw_run_tests.py,sha256=
|
16
|
+
pyworkflow/apps/pw_run_tests.py,sha256=0BNl_hmEJ5q-lqG3hDshwzYaBQjADLfaIZ_zrHqlD6E,10621
|
17
17
|
pyworkflow/apps/pw_schedule_run.py,sha256=N3os7_wA-QIGnQGmnVoRWmM385idB0cH-h_sCxVy_14,12471
|
18
18
|
pyworkflow/apps/pw_sleep.py,sha256=KhXjsmm04K_JMM13CMDI25sYEay7C2aJ_mTQlBH9n-I,1443
|
19
|
-
pyworkflow/apps/pw_sync_data.py,sha256=
|
19
|
+
pyworkflow/apps/pw_sync_data.py,sha256=rNoS_GnamoPI1jljvRI3E8qJ7FZCvuw2wmCauMaT24w,17460
|
20
20
|
pyworkflow/apps/pw_viewer.py,sha256=dlvIPzBa_citucezBd0Cl0uCND4eXWYgXF-lZpXWzBc,2595
|
21
21
|
pyworkflow/gui/__init__.py,sha256=D7PVYky81yTcqvkGEtw1CIxRkICpsn4TO5z5lJg84yM,1373
|
22
|
-
pyworkflow/gui/browser.py,sha256=
|
22
|
+
pyworkflow/gui/browser.py,sha256=Nq6itmAnVCbKXA-6WYpj36Py9ggRlhUTbZi4S9jhLCc,26969
|
23
23
|
pyworkflow/gui/canvas.py,sha256=M_pD7QbqnYq9MY145WN6BYzeIAo0cIkbxLrZKNE80yg,42106
|
24
|
-
pyworkflow/gui/dialog.py,sha256=
|
25
|
-
pyworkflow/gui/form.py,sha256=
|
24
|
+
pyworkflow/gui/dialog.py,sha256=ZX0k_y-bhUUo84YLwbP1ab6qwkSaNMRbDbjVAqOc3to,35473
|
25
|
+
pyworkflow/gui/form.py,sha256=g701xBE6wfpBevlZ9lppFkcCYZTlYRtVp3h7JxamO6M,107486
|
26
26
|
pyworkflow/gui/graph.py,sha256=HSZ5PfFKkhv3tzl0o9UtSU_FfDevKR2M3L9HgU9_Gkc,8747
|
27
27
|
pyworkflow/gui/graph_layout.py,sha256=R9uTc_Ei4oWAeCkZ3d78JjC1QKV7HPJaDLyhvSfK7NE,9520
|
28
|
-
pyworkflow/gui/gui.py,sha256=
|
28
|
+
pyworkflow/gui/gui.py,sha256=b29zlbl74cu4Rg6elBZO6alZg37v7i4oyuv9Sq4YNKI,20741
|
29
29
|
pyworkflow/gui/matplotlib_image.py,sha256=vGD5LKp-AZvu_kXa7Mf_jtOQfZ-QcRUoTMnicNrMqUk,7821
|
30
30
|
pyworkflow/gui/plotter.py,sha256=E9ld43B-AzYhV1PIEPBy2TQWyzoia4y5lD5rqJOQlZY,8464
|
31
|
-
pyworkflow/gui/text.py,sha256=
|
31
|
+
pyworkflow/gui/text.py,sha256=xQp-sZbqmWh1uK4uVS-Wk3FSWxJ9_awFpfHWsqCaCuo,28722
|
32
32
|
pyworkflow/gui/tooltip.py,sha256=OceYHesQQ7vSH7FRJmbvDBXjVG1IKkX1A8vocb64ljE,8531
|
33
33
|
pyworkflow/gui/tree.py,sha256=TglyZ-snZHp6hTIRRp4o5Fvzf8Z7hogpFaZ4DVgMGvo,23084
|
34
34
|
pyworkflow/gui/widgets.py,sha256=mIG1AAFvfT_py_yjjgoYeikgIRz-IbD3VOfKAcknqY0,11115
|
35
35
|
pyworkflow/gui/project/__init__.py,sha256=rZmTvqocYhsD2PH1owu7Dkgs7dMrGx7VbKOCpaqvLzM,1118
|
36
|
-
pyworkflow/gui/project/base.py,sha256=
|
36
|
+
pyworkflow/gui/project/base.py,sha256=Sfc6lT-kgOj2GQXYNg4SDVJaulxQB8Wro5PL8Y4BT4A,7912
|
37
37
|
pyworkflow/gui/project/constants.py,sha256=rORDaNCdAFRFcBmjRt2PJ1cXpSmYgDLfbvrbqZh1Bb0,4943
|
38
38
|
pyworkflow/gui/project/labels.py,sha256=7m4heNcN3zXe0BHuFJyQrPD5hC8LiHnlu1sizRc8l_Y,7680
|
39
39
|
pyworkflow/gui/project/project.py,sha256=2LdwlbPrCejKr_cdFALm_1kOx-Agk9KKIIS4mHOpTrk,18801
|
40
|
-
pyworkflow/gui/project/searchprotocol.py,sha256=
|
40
|
+
pyworkflow/gui/project/searchprotocol.py,sha256=Mkam8PwHOeJMjILBugwAaj_jNp-uDQ_mikN6OMrhFLg,9453
|
41
41
|
pyworkflow/gui/project/searchrun.py,sha256=xCGxs7L9PMk48ei9Gi3HI2PAOYEM-zXR5vRfAQRLHKI,7269
|
42
42
|
pyworkflow/gui/project/steps.py,sha256=fq0WhPBoByFs8Lr-qmGZ-aFC4lx3XCF2zM2DOBd_KAc,6053
|
43
43
|
pyworkflow/gui/project/utils.py,sha256=H9oFPzz9lAjAI4PRYSYyYniTBn98Y6YSs0bE0qXpMEM,11923
|
44
44
|
pyworkflow/gui/project/variables.py,sha256=UczW6yQqzssj3eETEKaae5GSpsWr04ItPrr5o2WBnu4,7496
|
45
45
|
pyworkflow/gui/project/viewdata.py,sha256=DUpoF6HURNZagdOtI9M8QNMYcqSFRtXGFA5Ftmrw2P8,18172
|
46
|
-
pyworkflow/gui/project/viewprojects.py,sha256=
|
47
|
-
pyworkflow/gui/project/viewprotocols.py,sha256=
|
46
|
+
pyworkflow/gui/project/viewprojects.py,sha256=zLVSaoGerElTab3wT70VmhQKI8lAI42Egh46xEVSmXA,22868
|
47
|
+
pyworkflow/gui/project/viewprotocols.py,sha256=UkDRh_sXYTe8X0-8MaqWYlHEcxjgaHl1aqC2SaqLsXs,86201
|
48
48
|
pyworkflow/gui/project/viewprotocols_extra.py,sha256=_wHn4fa1Qy9zDhyHYJoluIEZtS6SxvF0s9fFmVA1mgc,21262
|
49
49
|
pyworkflow/mapper/__init__.py,sha256=HM7tMMd1r2BZ8hbPGryUQ80E4evY46zIVjiZ3edg_Mg,1186
|
50
|
-
pyworkflow/mapper/mapper.py,sha256=
|
51
|
-
pyworkflow/mapper/sqlite.py,sha256=
|
50
|
+
pyworkflow/mapper/mapper.py,sha256=tUP5L-B5m33bBjhp34fWgaIGSnuVSEjpYa8hf1tBLjQ,7829
|
51
|
+
pyworkflow/mapper/sqlite.py,sha256=3DaJbZ6RJNkajVs3tfshM1SiBi4DITzyK7d3Oh7A_m0,65543
|
52
52
|
pyworkflow/mapper/sqlite_db.py,sha256=HYSXe_fRX1NBKDs6l2zefdO4PMEajnoDMXPjmoFs8Kc,5602
|
53
53
|
pyworkflow/project/__init__.py,sha256=05l9tvr3FfBNL3XNZSbFCs6yO-1HbFlmFu204w7yyKk,1369
|
54
54
|
pyworkflow/project/config.py,sha256=F9H1vLJJ9vPyT6zeSqHX69o-wbaN8hWueOuDn53evjc,13692
|
@@ -68,12 +68,12 @@ pyworkflow/project/scripts/stop.py,sha256=vCeCxkwPCoUkLbna5HCxKWJ1hrsI4U19Sg9JD4
|
|
68
68
|
pyworkflow/protocol/__init__.py,sha256=bAdIpvUW4GAYdIuv92DZ44-OEkZ7lTtnp1S9T5cwtVs,1413
|
69
69
|
pyworkflow/protocol/bibtex.py,sha256=mCUk1Hp5Vp_i2lozDM1BQNOw10e_RSu86oXvrR63sOA,2122
|
70
70
|
pyworkflow/protocol/constants.py,sha256=gyYtGFjfZbyAi2nDK6YpIRq6baZIJc_EdPD3oP2VdNM,3391
|
71
|
-
pyworkflow/protocol/executor.py,sha256=
|
72
|
-
pyworkflow/protocol/hosts.py,sha256=
|
71
|
+
pyworkflow/protocol/executor.py,sha256=_UCV9nxez7QVDMJUx-CiO-UpVENSAmsdATALIz1CrU8,20189
|
72
|
+
pyworkflow/protocol/hosts.py,sha256=j6vC1ALRVwo8bSxLM9cFfnfWW5qGrzgrrAFJobvVnX0,10729
|
73
73
|
pyworkflow/protocol/launch.py,sha256=Gls0WlLAkLtOxj4IWyKrDbmqaXXHsGd8oqBAcT61zEI,11731
|
74
74
|
pyworkflow/protocol/package.py,sha256=L6x3HHKtbrhDQRJHD07SG3DQKNMGaRQ0ROoLEY3SuRQ,1444
|
75
75
|
pyworkflow/protocol/params.py,sha256=kPXMPtPJrsSMJQQKIHIInPhity__lWYIVc1m3PXIywE,27947
|
76
|
-
pyworkflow/protocol/protocol.py,sha256=
|
76
|
+
pyworkflow/protocol/protocol.py,sha256=tJodpBU0PxKBOltQgVYyjzN00VqTuAsfr107Efry8BY,103318
|
77
77
|
pyworkflow/resources/Imagej.png,sha256=nU2nWI1wxZB_xlOKsZzdUjj-qiCTjO6GwEKYgZ5Risg,14480
|
78
78
|
pyworkflow/resources/chimera.png,sha256=AKCuwMqmZo0Cg2sddMUjBWUhmAq-nPsAVCBpVrYNeiQ,815
|
79
79
|
pyworkflow/resources/fa-exclamation-triangle_alert.png,sha256=31_XvRu0CkJ2dvHSpcBAR43378lIJTWwiag_A7SuUQc,585
|
@@ -83,6 +83,7 @@ pyworkflow/resources/fa-times-circle_alert.png,sha256=pgTdYSR5s5tIt6i9ut2YTTMf0u
|
|
83
83
|
pyworkflow/resources/file_vol.png,sha256=b_M_W5iXEnG5jEcGvlFAyOm8Nn_ngZcrJnDBq9_361g,961
|
84
84
|
pyworkflow/resources/loading.gif,sha256=6Jktjiva7Z5W6tqxiQZJZI9zahpwmme_D5G-K9EwI1o,16824
|
85
85
|
pyworkflow/resources/no-image128.png,sha256=AnPz8ehji-QTJiSSv0kwYh1QR1TMTLGEKRZg-dz0UKA,13217
|
86
|
+
pyworkflow/resources/protlabels.xcf,sha256=djXjiVydmUoxtdrLxkgasycQtlBVY4Kp--kfYA8Wwlk,32512
|
86
87
|
pyworkflow/resources/scipion_bn.png,sha256=0yVccw1PlBJaoJpIEsChGZH2dXEgphaqHnquaP2i-1c,113916
|
87
88
|
pyworkflow/resources/scipion_icon.png,sha256=moMOOoaLmcWdPdOn_PUBoAQv79WlfxsKwx8nPmrLbzU,2674
|
88
89
|
pyworkflow/resources/scipion_icon.svg,sha256=6CGXzBW6tlbwY0tywHcWxsKeEAhynKmzhvYxnIxN9qQ,126140
|
@@ -92,8 +93,8 @@ pyworkflow/resources/scipion_icon_prot.png,sha256=WOp8WTw54JPwlhXUzFDP75T1nEbIQy
|
|
92
93
|
pyworkflow/resources/scipion_logo.png,sha256=NYxY7AxZmp7kb1So2uCDpkzoYRxKl-Fb-zmf59nBkBc,1999
|
93
94
|
pyworkflow/resources/scipion_logo_normal.png,sha256=nrsdm4tI9r1WThlY7KoNtVRl48fZd8oSaGOSvMJdoKk,35151
|
94
95
|
pyworkflow/resources/scipion_logo_small.png,sha256=lUUaMbJmCCewirp4SPc4LJrenMGSxz4243yuDYUaP2Q,7811
|
95
|
-
pyworkflow/resources/sprites.png,sha256=
|
96
|
-
pyworkflow/resources/sprites.xcf,sha256=
|
96
|
+
pyworkflow/resources/sprites.png,sha256=NG6lQivvgHAMvpEzX3NU3NOwgE8XPvDer8pW4JRwXSE,48542
|
97
|
+
pyworkflow/resources/sprites.xcf,sha256=9Y4P7d7C-tJuAmTYazIVwqtiMXrWClUFvtL4ecqT_OE,97411
|
97
98
|
pyworkflow/resources/wait.gif,sha256=H9UcWI4UbL9cpxrX_gZAtkmLhXHDAHFYirTRtZrn4q0,4035
|
98
99
|
pyworkflow/resources/showj/arrowDown.png,sha256=qpePLXb2UKAWBL7EaHZChEpAFNwFm1mBiEfckNUwpus,268
|
99
100
|
pyworkflow/resources/showj/arrowUp.png,sha256=1-rxZDyFUHVZ1SIO9at1_OhkvLnaJDlMx90r137RjGI,276
|
@@ -118,17 +119,17 @@ pyworkflow/resources/showj/volumeOff.png,sha256=LKMWEt8_xwA4tDBruP30Y22QqNebHtH3
|
|
118
119
|
pyworkflow/resources/showj/volumeOn.png,sha256=SsZsmeTdIa0pfMcm0Kq8KzsTDG6A3nhi7jdWWk_7Qfk,614
|
119
120
|
pyworkflow/tests/__init__.py,sha256=tCnNEVIyRLERuNkAd3stcKo23TxHUILonZM9UFEbUD0,1259
|
120
121
|
pyworkflow/tests/test_utils.py,sha256=HmPVZ6UPu89u-x_aQmF5yyA4RwK55x4bhEhvWeNy1lY,493
|
121
|
-
pyworkflow/tests/tests.py,sha256=
|
122
|
+
pyworkflow/tests/tests.py,sha256=mnuaWUBCKWcau0j2YhxRqYnxYgY8aw81xxti8BqbUfQ,11791
|
122
123
|
pyworkflow/utils/__init__.py,sha256=UEd4SxmVd9zv0JElz1J0Sva7klJw3HEKSzwf5a0c-Xc,1429
|
123
124
|
pyworkflow/utils/dataset.py,sha256=141u2xb-FTe8nF6OVJBJtTNHWz7eDWd24veBWX7eoTI,14943
|
124
125
|
pyworkflow/utils/echo.py,sha256=ZXJRrmxUaTT4Xxf7_pQwg7Th341iFafTs66VEKNOZmE,3442
|
125
126
|
pyworkflow/utils/graph.py,sha256=z3Hcj0I38du97DQEqNT5gk--SCDTRPlKotaCszoZfX8,4981
|
126
127
|
pyworkflow/utils/log.py,sha256=y7qLB1NKeHF43QhyWZq-32_5DtSJYR1uzrWjRxoSvWk,11096
|
127
128
|
pyworkflow/utils/path.py,sha256=hDisc13HhfB6CxpBcI1JBd5er_S6yVTKw1MFSw1AR3U,16803
|
128
|
-
pyworkflow/utils/process.py,sha256=
|
129
|
+
pyworkflow/utils/process.py,sha256=a2yixJxEQeZXI9dg2TKrfgaVmcDQocTQ2GUZ9d4rfQU,5816
|
129
130
|
pyworkflow/utils/profiler.py,sha256=BC0KkAgfYqf-CV40zLcRxo5Td79f5jw1gzvaDH8iqt8,2218
|
130
131
|
pyworkflow/utils/progressbar.py,sha256=VntEF_FTdQHjMKawfR1R4IoNgYNTEMmnLUIDvUXurxk,5903
|
131
|
-
pyworkflow/utils/properties.py,sha256=
|
132
|
+
pyworkflow/utils/properties.py,sha256=3hpTFoWEmH6bwSYIuwBFgbFY-OlATO8q19KBV0fjoiA,23157
|
132
133
|
pyworkflow/utils/reflection.py,sha256=48cvIDO73JinBsFn3XMiVR56AcivfdJoiMXGM7ZwUDY,4429
|
133
134
|
pyworkflow/utils/utils.py,sha256=6h1PthMyGc804PBqbNnVqzzr8upkJMZtaLKalIAELNc,26330
|
134
135
|
pyworkflow/utils/which.py,sha256=KuyKYE4GnkwMpBJoKgOMnx-hNZjHf6OTyqxEsdHIftI,8627
|
@@ -136,7 +137,7 @@ pyworkflow/webservices/__init__.py,sha256=CfkvvoFQp2t2Tt5p7uOF_tpkZVWPHOl9TLlAtB
|
|
136
137
|
pyworkflow/webservices/config.py,sha256=7XSkwUiVT71UuARNBQUcePARz8UevwlmxEdoKa_ZVlg,399
|
137
138
|
pyworkflow/webservices/notifier.py,sha256=ntjmIxi2rO0Oc7RE6meuJZ6y5HG4NcrShRqfSUxM8eA,5601
|
138
139
|
pyworkflow/webservices/repository.py,sha256=Hw2ZhvuJzKbN5ivTuN2gTNeJT49Q3-PuM9BdwayTYfU,2365
|
139
|
-
pyworkflow/webservices/workflowhub.py,sha256=
|
140
|
+
pyworkflow/webservices/workflowhub.py,sha256=HBk0aEKklakhg-CPx9GkvexVs2LMnf82IwP--Xh_xCA,2768
|
140
141
|
pyworkflowtests/__init__.py,sha256=RoXNgyShL7moVEXaimTDdfY1fU26dgGKtdjO4JfBQOk,1686
|
141
142
|
pyworkflowtests/bibtex.py,sha256=1f9PjkRO937PB2b-ax-eKLwjU4YY11M5px3dk3xWQzw,2102
|
142
143
|
pyworkflowtests/objects.py,sha256=uaD9THeybiCkUDbb_cpqEwqCpVG3RLyMsIjOKOX7oNQ,26688
|
@@ -153,9 +154,9 @@ pyworkflowtests/tests/test_protocol_export.py,sha256=z18nKPkOnrYLMU8KqcnVsF6-ylQ
|
|
153
154
|
pyworkflowtests/tests/test_protocol_output.py,sha256=8gnIFMRNmwPnIBRCG29WHJB6mqK4FLGn1jiXHtTD6pY,5980
|
154
155
|
pyworkflowtests/tests/test_streaming.py,sha256=vOH-bKCM-fVUSsejqNnCX5TPXhdUayk9ZtJHsNVcfCY,1615
|
155
156
|
pyworkflowtests/tests/test_utils.py,sha256=_pTYGCuXC7YNMdCBzUYNfSBCR3etrHsxHfIhsQi4VPc,7465
|
156
|
-
scipion_pyworkflow-3.11.
|
157
|
-
scipion_pyworkflow-3.11.
|
158
|
-
scipion_pyworkflow-3.11.
|
159
|
-
scipion_pyworkflow-3.11.
|
160
|
-
scipion_pyworkflow-3.11.
|
161
|
-
scipion_pyworkflow-3.11.
|
157
|
+
scipion_pyworkflow-3.11.2.dist-info/licenses/LICENSE.txt,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
|
158
|
+
scipion_pyworkflow-3.11.2.dist-info/METADATA,sha256=ySnL_EiwGsdzu6T-r66WwCR1wntg80_xpvFeqchrrhA,4440
|
159
|
+
scipion_pyworkflow-3.11.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
160
|
+
scipion_pyworkflow-3.11.2.dist-info/entry_points.txt,sha256=-P6GVocWl_NS8wS7lB-bTKf-tKANbw4n7DlXXh_VrWk,54
|
161
|
+
scipion_pyworkflow-3.11.2.dist-info/top_level.txt,sha256=PzyJteyenJwLjAeSFP7oYrTN_U71GABQwET8oLZkh9k,27
|
162
|
+
scipion_pyworkflow-3.11.2.dist-info/RECORD,,
|
File without changes
|
{scipion_pyworkflow-3.11.1.dist-info → scipion_pyworkflow-3.11.2.dist-info}/entry_points.txt
RENAMED
File without changes
|
{scipion_pyworkflow-3.11.1.dist-info → scipion_pyworkflow-3.11.2.dist-info}/licenses/LICENSE.txt
RENAMED
File without changes
|
File without changes
|