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.
Files changed (34) hide show
  1. pyworkflow/apps/pw_project.py +21 -4
  2. pyworkflow/apps/pw_run_tests.py +2 -1
  3. pyworkflow/apps/pw_sync_data.py +4 -3
  4. pyworkflow/constants.py +1 -1
  5. pyworkflow/gui/browser.py +8 -0
  6. pyworkflow/gui/dialog.py +3 -1
  7. pyworkflow/gui/form.py +1 -0
  8. pyworkflow/gui/gui.py +5 -0
  9. pyworkflow/gui/project/base.py +2 -1
  10. pyworkflow/gui/project/searchprotocol.py +5 -3
  11. pyworkflow/gui/project/viewprojects.py +10 -1
  12. pyworkflow/gui/project/viewprotocols.py +42 -17
  13. pyworkflow/gui/text.py +4 -1
  14. pyworkflow/mapper/mapper.py +4 -0
  15. pyworkflow/mapper/sqlite.py +7 -5
  16. pyworkflow/object.py +1 -0
  17. pyworkflow/plugin.py +4 -4
  18. pyworkflow/protocol/executor.py +55 -23
  19. pyworkflow/protocol/hosts.py +2 -1
  20. pyworkflow/protocol/protocol.py +5 -0
  21. pyworkflow/resources/protlabels.xcf +0 -0
  22. pyworkflow/resources/sprites.png +0 -0
  23. pyworkflow/resources/sprites.xcf +0 -0
  24. pyworkflow/template.py +1 -1
  25. pyworkflow/tests/tests.py +2 -1
  26. pyworkflow/utils/process.py +2 -1
  27. pyworkflow/utils/properties.py +1 -0
  28. pyworkflow/webservices/workflowhub.py +37 -25
  29. {scipion_pyworkflow-3.11.1.dist-info → scipion_pyworkflow-3.11.2.dist-info}/METADATA +1 -1
  30. {scipion_pyworkflow-3.11.1.dist-info → scipion_pyworkflow-3.11.2.dist-info}/RECORD +34 -33
  31. {scipion_pyworkflow-3.11.1.dist-info → scipion_pyworkflow-3.11.2.dist-info}/WHEEL +0 -0
  32. {scipion_pyworkflow-3.11.1.dist-info → scipion_pyworkflow-3.11.2.dist-info}/entry_points.txt +0 -0
  33. {scipion_pyworkflow-3.11.1.dist-info → scipion_pyworkflow-3.11.2.dist-info}/licenses/LICENSE.txt +0 -0
  34. {scipion_pyworkflow-3.11.1.dist-info → scipion_pyworkflow-3.11.2.dist-info}/top_level.txt +0 -0
@@ -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
- projWindow = ProjectWindow(projPath)
93
- projWindow.show()
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):
@@ -166,7 +166,8 @@ class Tester:
166
166
  """ Show the list of tests available """
167
167
  mode = self.mode
168
168
 
169
- assert mode in ['modules', 'classes', 'onlyclasses', 'all'], 'Unknown mode %s' % mode
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)))
@@ -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
- assert md5 == md5Remote, \
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
- assert t_manifest > last and time.time() - t_manifest < 60*60*24*7
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.1'
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:
@@ -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
- version = "%s - %s (core)" % (os.environ.get('SCIPION_VERSION', ""), pw.LAST_VERSION)
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
- Popen([pw.PYTHON, script, projName])
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 actionList:
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
- if item.nodeInfo.isExpanded():
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(False)
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 == 'entry':
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()
@@ -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)
@@ -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 '%s' (id=%s) in the database, stored as '%s'"
215
- ", is being mapped to %s object. " %
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 childs"""
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 as e:
414
- logger.error("Couldn't load \"%s\" as preferred viewer for %s.\n"
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, className), exc_info=e)
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
 
@@ -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, context=self.protocol.getSubmitDict())
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 there
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(0.5)
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." , exc_info=e)
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("Step on node %s is requesting GPUs but there isn't any available. Review configuration of threads/GPUs. Returning an empty list." % nodeId)
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.debug("Running step %s on node %s" % (step, node))
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)
@@ -164,7 +164,8 @@ class HostConfig(Object):
164
164
  hosts = OrderedDict()
165
165
 
166
166
  try:
167
- assert cp.read(hostsConf) != [], 'Missing file %s' % hostsConf
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)
@@ -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
Binary file
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
- raise Exception("Alias %s not recognized." % alias)
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
- assert name in cls._datasetDict, "Dataset: %s dataset doesn't exist." % name
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
@@ -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
- assert hostConfig is not None, 'hostConfig needed to launch MPI processes.'
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')
@@ -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
- return make_request(self.url, asJson=False)
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
- req = Request(url)
22
- req.add_header("accept", "application/json")
23
-
24
- with urlopen(req, timeout=1) as response:
25
- if asJson:
26
- data = json.load(response)
27
- else:
28
- html_response = response.read()
29
- encoding = response.headers.get_content_charset('utf-8')
30
- data = html_response.decode(encoding)
31
- return data
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 path.endswith(".json.template"):
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
- workflow_id = workflow["id"]
57
- name = workflow["name"]
58
- description = workflow['description']
59
- version = workflow["versions"][-1]
60
- version_id = version["id"]
61
- template_url = get_workflow_file_url(workflow_id, version_id)
62
-
63
- new_template = WHTemplate("Workflow hub", name, description, template_url)
64
- if template_id is None or new_template.getObjId() == template_id:
65
- template_list.append(new_template)
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.1
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=YGPhGk8-RijDCXrYi8bir3cM263wl-PcCz064moLdX0,7800
3
+ pyworkflow/constants.py,sha256=CAAE2iM024hLj2jTKfItkX1galaOrognBEp2gQil6_U,7800
4
4
  pyworkflow/exceptions.py,sha256=3VFxuNJHcIWxRnLPR0vYg0RFAQMmxPBJZLZSi87VI8E,507
5
- pyworkflow/object.py,sha256=01nUr0Ud-EDVCULtElfC4patgxUYuUIbrI5qmCsC7BY,55149
6
- pyworkflow/plugin.py,sha256=VVUYkLvH_sBcqsrR3_WzTkogCUi9Zi-MkIgp5GGxRz8,30574
7
- pyworkflow/template.py,sha256=uScWMZCR3U6ishDlx7QGDDr8B_aLpKXoh8zOhqAirCY,10463
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=1P6aGFxc-nDcWrTQ6SbtZSHZQkKHFHkGlnPkDLH0spk,3579
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=0rUeBXIMzq5sWC0BmNBA02zkQrqAkwkX0WJzIpvUdOs,10587
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=rwAShzTuVlCDaLKMLeuY1cPSLyWSP9-1mNTgIgCX40A,17371
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=gFBvVYNw2tQW3-n8LqWazuzkKOvI-5FadR0h2aBVeHY,26716
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=PQQfS_bYevqBAAGOq1B7wGd9adabUk3KVTlyjiuddV4,35351
25
- pyworkflow/gui/form.py,sha256=bF8-y4LeXfV3n3B0Li81zwnzKq-4nyKXd55kOu0agsU,107449
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=c_KcuOZS7_Euu4Qu21KUJipdhzxI5a0XZjrqzzvk-u4,20619
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=cmAhCm_gCJMi5UDl9br8FoexHeDQjby8dplw9ZiUIgM,28576
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=N64UXy5ep7RFweyBWTDro9UESgKRRAIvlRuotWIaO_w,7871
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=NchaO_JFQMGzZkYFyn5_D_BMS3NSbnLTuao6E5UTOfc,9338
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=ZcWzfFLVeCKzVsFboxquzBsOagEoPW0CpVUQ8ZLpvQE,22516
47
- pyworkflow/gui/project/viewprotocols.py,sha256=6iq91LHtbHUtq0iQo6UsSuCe69X2mLWLHvjMbE3E64E,85391
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=YGVlBK3btHL-jLID6v2jLy7Mb9Wu47lxfZjHdzz0hMg,7726
51
- pyworkflow/mapper/sqlite.py,sha256=Y8SGO2n71hfBiuES-oykTEpL08vtNpiQsyRc2qFPUIg,65460
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=pnUJr2sNu-Iqnf3-M3YWc4YTNmsUdyf7-v1Kg97rZBY,19108
72
- pyworkflow/protocol/hosts.py,sha256=fbuQjHsOL5zWeDuoLqOXZmVc1lfjS2Pb1uomXiC2ysc,10697
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=KLqlm6KW4ITJ6TCvc972dUa3fZoTxtkr18CZAIHYG9I,103104
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=mGbZqKApcnbITOBlH206FKTLcS7kVkrnwG1v9apiifQ,53903
96
- pyworkflow/resources/sprites.xcf,sha256=XImVzBax2VMQNNi1jnH78EhZfE9bb5cpVAUwUsYZFJ0,93331
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=KgBh_eNHrg3VcYHu-zGye7ot5DzGKFyH8ZI-xABjs98,11757
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=6l4Mf6C3nyYMeLBQxbrGaAWWmJvf6zQuTLQzPYQVxPU,5790
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=EaTb7VTofsXuKZuOA4cK6ZmwfqmTSOwzd__TZwaL8Cc,23092
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=hA4RETMXmxUF-l605INS1TCT2nWnUwOIjrYKzRUZVLQ,2179
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.1.dist-info/licenses/LICENSE.txt,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
157
- scipion_pyworkflow-3.11.1.dist-info/METADATA,sha256=fj1k-LO55-QDXIxh7iGFe3sr_fxiphLt0EkSxd_o_lQ,4440
158
- scipion_pyworkflow-3.11.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
159
- scipion_pyworkflow-3.11.1.dist-info/entry_points.txt,sha256=-P6GVocWl_NS8wS7lB-bTKf-tKANbw4n7DlXXh_VrWk,54
160
- scipion_pyworkflow-3.11.1.dist-info/top_level.txt,sha256=PzyJteyenJwLjAeSFP7oYrTN_U71GABQwET8oLZkh9k,27
161
- scipion_pyworkflow-3.11.1.dist-info/RECORD,,
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,,