scipion-pyworkflow 3.11.0__py3-none-any.whl → 3.11.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. pyworkflow/apps/__init__.py +29 -0
  2. pyworkflow/apps/pw_manager.py +37 -0
  3. pyworkflow/apps/pw_plot.py +51 -0
  4. pyworkflow/apps/pw_project.py +130 -0
  5. pyworkflow/apps/pw_protocol_list.py +143 -0
  6. pyworkflow/apps/pw_protocol_run.py +51 -0
  7. pyworkflow/apps/pw_run_tests.py +268 -0
  8. pyworkflow/apps/pw_schedule_run.py +322 -0
  9. pyworkflow/apps/pw_sleep.py +37 -0
  10. pyworkflow/apps/pw_sync_data.py +440 -0
  11. pyworkflow/apps/pw_viewer.py +78 -0
  12. pyworkflow/constants.py +1 -1
  13. pyworkflow/gui/__init__.py +36 -0
  14. pyworkflow/gui/browser.py +768 -0
  15. pyworkflow/gui/canvas.py +1190 -0
  16. pyworkflow/gui/dialog.py +981 -0
  17. pyworkflow/gui/form.py +2727 -0
  18. pyworkflow/gui/graph.py +247 -0
  19. pyworkflow/gui/graph_layout.py +271 -0
  20. pyworkflow/gui/gui.py +571 -0
  21. pyworkflow/gui/matplotlib_image.py +233 -0
  22. pyworkflow/gui/plotter.py +247 -0
  23. pyworkflow/gui/project/__init__.py +25 -0
  24. pyworkflow/gui/project/base.py +193 -0
  25. pyworkflow/gui/project/constants.py +139 -0
  26. pyworkflow/gui/project/labels.py +205 -0
  27. pyworkflow/gui/project/project.py +491 -0
  28. pyworkflow/gui/project/searchprotocol.py +240 -0
  29. pyworkflow/gui/project/searchrun.py +181 -0
  30. pyworkflow/gui/project/steps.py +171 -0
  31. pyworkflow/gui/project/utils.py +332 -0
  32. pyworkflow/gui/project/variables.py +179 -0
  33. pyworkflow/gui/project/viewdata.py +472 -0
  34. pyworkflow/gui/project/viewprojects.py +519 -0
  35. pyworkflow/gui/project/viewprotocols.py +2141 -0
  36. pyworkflow/gui/project/viewprotocols_extra.py +562 -0
  37. pyworkflow/gui/text.py +774 -0
  38. pyworkflow/gui/tooltip.py +185 -0
  39. pyworkflow/gui/tree.py +684 -0
  40. pyworkflow/gui/widgets.py +307 -0
  41. pyworkflow/mapper/__init__.py +26 -0
  42. pyworkflow/mapper/mapper.py +226 -0
  43. pyworkflow/mapper/sqlite.py +1583 -0
  44. pyworkflow/mapper/sqlite_db.py +145 -0
  45. pyworkflow/object.py +1 -0
  46. pyworkflow/plugin.py +4 -4
  47. pyworkflow/project/__init__.py +31 -0
  48. pyworkflow/project/config.py +454 -0
  49. pyworkflow/project/manager.py +180 -0
  50. pyworkflow/project/project.py +2095 -0
  51. pyworkflow/project/usage.py +165 -0
  52. pyworkflow/protocol/__init__.py +38 -0
  53. pyworkflow/protocol/bibtex.py +48 -0
  54. pyworkflow/protocol/constants.py +87 -0
  55. pyworkflow/protocol/executor.py +515 -0
  56. pyworkflow/protocol/hosts.py +318 -0
  57. pyworkflow/protocol/launch.py +277 -0
  58. pyworkflow/protocol/package.py +42 -0
  59. pyworkflow/protocol/params.py +781 -0
  60. pyworkflow/protocol/protocol.py +2712 -0
  61. pyworkflow/resources/protlabels.xcf +0 -0
  62. pyworkflow/resources/sprites.png +0 -0
  63. pyworkflow/resources/sprites.xcf +0 -0
  64. pyworkflow/template.py +1 -1
  65. pyworkflow/tests/__init__.py +29 -0
  66. pyworkflow/tests/test_utils.py +25 -0
  67. pyworkflow/tests/tests.py +342 -0
  68. pyworkflow/utils/__init__.py +38 -0
  69. pyworkflow/utils/dataset.py +414 -0
  70. pyworkflow/utils/echo.py +104 -0
  71. pyworkflow/utils/graph.py +169 -0
  72. pyworkflow/utils/log.py +293 -0
  73. pyworkflow/utils/path.py +528 -0
  74. pyworkflow/utils/process.py +154 -0
  75. pyworkflow/utils/profiler.py +92 -0
  76. pyworkflow/utils/progressbar.py +154 -0
  77. pyworkflow/utils/properties.py +618 -0
  78. pyworkflow/utils/reflection.py +129 -0
  79. pyworkflow/utils/utils.py +880 -0
  80. pyworkflow/utils/which.py +229 -0
  81. pyworkflow/webservices/__init__.py +8 -0
  82. pyworkflow/webservices/config.py +8 -0
  83. pyworkflow/webservices/notifier.py +152 -0
  84. pyworkflow/webservices/repository.py +59 -0
  85. pyworkflow/webservices/workflowhub.py +86 -0
  86. pyworkflowtests/tests/__init__.py +0 -0
  87. pyworkflowtests/tests/test_canvas.py +72 -0
  88. pyworkflowtests/tests/test_domain.py +45 -0
  89. pyworkflowtests/tests/test_logs.py +74 -0
  90. pyworkflowtests/tests/test_mappers.py +392 -0
  91. pyworkflowtests/tests/test_object.py +507 -0
  92. pyworkflowtests/tests/test_project.py +42 -0
  93. pyworkflowtests/tests/test_protocol_execution.py +146 -0
  94. pyworkflowtests/tests/test_protocol_export.py +78 -0
  95. pyworkflowtests/tests/test_protocol_output.py +158 -0
  96. pyworkflowtests/tests/test_streaming.py +47 -0
  97. pyworkflowtests/tests/test_utils.py +210 -0
  98. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/METADATA +2 -2
  99. scipion_pyworkflow-3.11.2.dist-info/RECORD +162 -0
  100. scipion_pyworkflow-3.11.0.dist-info/RECORD +0 -71
  101. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/WHEEL +0 -0
  102. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/entry_points.txt +0 -0
  103. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/licenses/LICENSE.txt +0 -0
  104. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,562 @@
1
+ # -*- coding: utf-8 -*-
2
+ # **************************************************************************
3
+ # *
4
+ # * Authors: Pablo Conesa [1]
5
+ # *
6
+ # * [1] Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
7
+ # *
8
+ # * This program is free software: you can redistribute it and/or modify
9
+ # * it under the terms of the GNU General Public License as published by
10
+ # * the Free Software Foundation, either version 3 of the License, or
11
+ # * (at your option) any later version.
12
+ # *
13
+ # * This program is distributed in the hope that it will be useful,
14
+ # * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # * GNU General Public License for more details.
17
+ # *
18
+ # * You should have received a copy of the GNU General Public License
19
+ # * along with this program. If not, see <https://www.gnu.org/licenses/>.
20
+ # *
21
+ # * All comments concerning this program package may be sent to the
22
+ # * e-mail address 'scipion@cnb.csic.es'
23
+ # *
24
+ # **************************************************************************
25
+ """ This modules hosts most of the accessory code that is used in view protocols"""
26
+ import json
27
+ import os
28
+ from configparser import ConfigParser
29
+
30
+ import logging
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ from pyworkflow import Config
35
+ import pyworkflow.gui as pwgui
36
+ import pyworkflow.object as pwobj
37
+ import pyworkflow.utils as pwutils
38
+ from pyworkflow.gui.project.utils import isAFinalProtocol
39
+ from pyworkflow.project import MenuConfig
40
+ from pyworkflow.utils import Message, Icon
41
+ from pyworkflow.viewer import DESKTOP_TKINTER
42
+
43
+
44
+
45
+ class RunIOTreeProvider(pwgui.tree.TreeProvider):
46
+ """Create the tree elements from a Protocol Run input/output children"""
47
+
48
+ def __init__(self, parent, protocol, mapper, loggerCallback):
49
+ """
50
+
51
+ :param parent:
52
+ :param protocol:
53
+ :param mapper:
54
+ :param loggerCallback: method to call to log events in the gui.
55
+ """
56
+
57
+ self.parent = parent
58
+ self.protocol = protocol
59
+ self.mapper = mapper
60
+ self._loggerCallback = loggerCallback
61
+
62
+ @staticmethod
63
+ def getColumns():
64
+ return [('Attribute', 200), ('Info', 100)]
65
+
66
+ def getObjects(self):
67
+ objs = []
68
+ if self.protocol is not None:
69
+ # Store a dict with input parents (input, PointerList)
70
+ self.inputParentDict = pwobj.OrderedDict()
71
+ inputs = []
72
+ inputObj = pwobj.String(Message.LABEL_INPUT)
73
+ inputObj._icon = Icon.ACTION_IN
74
+ self.inputParentDict['_input'] = inputObj
75
+ inputParents = [inputObj]
76
+
77
+ for key, attr in self.protocol.iterInputAttributes():
78
+ attr._parentKey = key
79
+ # Repeated keys means there are inside a pointerList
80
+ # since the same key is yielded for all items inside
81
+ # so update the parent dict with a new object
82
+ if key in self.inputParentDict:
83
+ if self.inputParentDict[key] == inputObj:
84
+ parentObj = pwobj.String(key)
85
+ parentObj._icon = Icon.ACTION_IN
86
+ parentObj._parentKey = '_input'
87
+ inputParents.append(parentObj)
88
+ self.inputParentDict[key] = parentObj
89
+ else:
90
+ self.inputParentDict[key] = inputObj
91
+ inputs.append(attr)
92
+
93
+ outputs = [attr for _, attr in
94
+ self.protocol.iterOutputAttributes()]
95
+ self.outputStr = pwobj.String(Message.LABEL_OUTPUT)
96
+ objs = inputParents + inputs + [self.outputStr] + outputs
97
+ return objs
98
+
99
+ def _visualizeObject(self, ViewerClass, obj):
100
+ viewer = ViewerClass(project=self.protocol.getProject(),
101
+ protocol=self.protocol,
102
+ parent=self.parent.window)
103
+ viewer.visualize(obj, windows=self.parent.window)
104
+
105
+ def _editObject(self, obj):
106
+ """Open the Edit GUI Form given an instance"""
107
+ pwgui.dialog.EditObjectDialog(self.parent, Message.TITLE_EDIT_OBJECT,
108
+ obj, self.mapper)
109
+
110
+ def _deleteObject(self, obj):
111
+ """ Remove unnecessary output, specially for Coordinates. """
112
+ prot = self.protocol
113
+ try:
114
+ objLabel = self.getObjectLabel(obj, prot)
115
+ if self.parent.window.askYesNo("Delete object",
116
+ "Are you sure to delete *%s* object?"
117
+ % objLabel):
118
+ prot.getProject().deleteProtocolOutput(prot, obj)
119
+ self.parent._fillSummary()
120
+ logger.info("Object *%s* successfully deleted upon user request."
121
+ % objLabel)
122
+ except Exception as ex:
123
+ self.parent.window.showError(str(ex))
124
+
125
+ @staticmethod
126
+ def getObjectPreview(obj):
127
+ desc = "<name>: " + obj.getName()
128
+ return None, desc
129
+
130
+ def getObjectActions(self, obj):
131
+ if isinstance(obj, pwobj.Pointer):
132
+ obj = obj.get()
133
+ isPointer = True
134
+ else:
135
+ isPointer = False
136
+ actions = []
137
+
138
+ # If viewers not loaded yet (firstime)
139
+ domain = Config.getDomain()
140
+
141
+ if not domain.viewersLoaded():
142
+ self._loggerCallback("Discovering viewers for the first time across all the plugins.")
143
+
144
+
145
+ viewers = Config.getDomain().findViewers(obj, DESKTOP_TKINTER)
146
+
147
+ def viewerCallback(viewer):
148
+ return lambda: self._visualizeObject(viewer, obj)
149
+
150
+ for v in viewers:
151
+ actions.append((v.getName(),
152
+ viewerCallback(v),
153
+ Icon.ACTION_VISUALIZE))
154
+ # EDIT
155
+ actions.append((Message.LABEL_EDIT,
156
+ lambda: self._editObject(obj),
157
+ Icon.ACTION_EDIT))
158
+ # DELETE
159
+ # Special case to allow delete outputCoordinates
160
+ # since we can end up with several outputs and
161
+ # we may want to clean up
162
+ if self.protocol.allowsDelete(obj) and not isPointer:
163
+ actions.append((Message.LABEL_DELETE,
164
+ lambda: self._deleteObject(obj),
165
+ Icon.ACTION_DELETE))
166
+ return actions
167
+
168
+ @staticmethod
169
+ def getObjectLabel(obj, parent):
170
+ """ We will try to show in the list the string representation
171
+ that is more readable for the user to pick the desired object.
172
+ """
173
+ label = 'None'
174
+ if obj is not None:
175
+ label = obj.getObjLabel()
176
+ if not len(label.strip()):
177
+ parentLabel = parent.getObjLabel() if parent else 'None'
178
+ label = "%s -> %s" % (parentLabel, obj.getLastName())
179
+ return label
180
+
181
+ def getObjectInfo(self, obj):
182
+
183
+ def stringToInfo():
184
+ """ String objects converted to info dictionary for the tree"""
185
+
186
+ value = obj.get()
187
+ infoStr = {'key': value, 'text': value, 'values': '', 'open': True}
188
+ if hasattr(obj, '_parentKey'):
189
+ infoStr['parent'] = self.inputParentDict[obj._parentKey]
190
+ return infoStr
191
+
192
+ def labelToValue(label, key, name):
193
+ """ To tolerate str(labelObj) in case xmippLib is missing, but
194
+ still being able to open a project."""
195
+ try:
196
+ value = str(label)
197
+ except Exception as e:
198
+ logger.info("Can not convert object %s - %s to string." % (key, name))
199
+ value = str(e)
200
+
201
+ return value
202
+
203
+ def pointerToInfo():
204
+ """ Converts a Pointer into an info dictionary for the tree"""
205
+
206
+ namePtr = obj.getLastName()
207
+ # Remove ugly item notations inside lists
208
+ namePtr = namePtr.replace('__item__000', '')
209
+ # Consider Pointer as inputs
210
+ imagePtr = getattr(obj, '_icon', '')
211
+ parentPtr = self.inputParentDict[obj._parentKey]
212
+
213
+ suffix = ''
214
+ if obj.hasExtended():
215
+ # getExtended method remove old attributes conventions.
216
+ extendedValue = obj.getExtended()
217
+ if obj.hasExtended():
218
+ suffix = '[%s]' % extendedValue
219
+ # else:
220
+ # suffix = '[Item %s]' % extendedValue
221
+
222
+ # Tolerate loading projects:
223
+ # When having only the project sqlite..an obj.get() will
224
+ # the load of the set...and if it is missing this whole
225
+ # "thread" fails.
226
+ try:
227
+ labelObjPtr = obj.get()
228
+ if labelObjPtr is None:
229
+ labelObjPtr = obj.getObjValue()
230
+ suffix = ''
231
+
232
+ except Exception:
233
+ return {'parent': parentPtr, 'image': imagePtr, 'text': namePtr,
234
+ 'values': ("Couldn't read object attributes.",)}
235
+ else:
236
+ labelObjPtr = obj.get()
237
+
238
+ objKeyPtr = obj._parentKey + str(labelObjPtr.getObjId())
239
+ labelPtr = self.getObjectLabel(labelObjPtr,
240
+ self.mapper.getParent(labelObjPtr))
241
+ namePtr += ' (from %s %s)' % (labelPtr, suffix)
242
+ valuePtr = labelToValue(labelObjPtr, objKeyPtr, namePtr)
243
+ infoPtr = {'key': objKeyPtr, 'parent': parentPtr, 'image': imagePtr,
244
+ 'text': namePtr, 'values': (valuePtr,)}
245
+
246
+ return infoPtr
247
+
248
+ if obj is None or not obj.hasValue():
249
+ return None
250
+
251
+ if isinstance(obj, pwobj.String) and not obj.getName():
252
+ info = stringToInfo()
253
+ else:
254
+ # All attributes are considered output, unless they are pointers
255
+ image = Icon.ACTION_OUT
256
+ parent = self.outputStr
257
+
258
+ if isinstance(obj, pwobj.Pointer):
259
+ info = pointerToInfo()
260
+ else:
261
+ name = self.getObjectLabel(obj, self.protocol)
262
+ objKey = str(obj.getObjId())
263
+ labelObj = obj
264
+ value = labelToValue(labelObj, objKey, name)
265
+ info = {'key': objKey, 'parent': parent, 'image': image,
266
+ 'text': name, 'values': (value,)}
267
+ return info
268
+
269
+ class ProtocolTreeConfig:
270
+ """ Handler class that groups functions and constants
271
+ related to the protocols tree configuration.
272
+ """
273
+ ALL_PROTOCOLS = "All"
274
+ TAG_PROTOCOL_DISABLED = 'protocol-disabled'
275
+ TAG_PROTOCOL = 'protocol'
276
+ TAG_SECTION = 'section'
277
+ TAG_PROTOCOL_GROUP = 'protocol_group'
278
+ TAG_PROTOCOL_BETA = 'protocol_beta'
279
+ TAG_PROTOCOL_NEW = 'protocol_new'
280
+ TAG_PROTOCOL_UPDATED = 'protocol_updated'
281
+ PLUGIN_CONFIG_PROTOCOLS = 'protocols.conf'
282
+
283
+ @classmethod
284
+ def getProtocolTag(cls, isInstalled, isBeta=False, isNew=False, isUpdated=False):
285
+ """ Return the proper tag depending if the protocol is installed or not.
286
+ """
287
+ if isInstalled:
288
+ if isBeta:
289
+ return cls.TAG_PROTOCOL_BETA
290
+ elif isNew:
291
+ return cls.TAG_PROTOCOL_NEW
292
+ elif isUpdated:
293
+ return cls.TAG_PROTOCOL_UPDATED
294
+ return cls.TAG_PROTOCOL
295
+ else:
296
+ return cls.TAG_PROTOCOL_DISABLED
297
+
298
+ @classmethod
299
+ def __addToTree(cls, menu, item, checkFunction=None):
300
+ """ Helper function to recursively add items to a menu.
301
+ Add item (a dictionary that can contain more dictionaries) to menu
302
+ If check function is added will use it to check if the value must be added.
303
+ """
304
+ children = item.pop('children', [])
305
+
306
+ if checkFunction is not None:
307
+ add = checkFunction(item)
308
+ if not add:
309
+ return
310
+ subMenu = menu.addSubMenu(**item) # we expect item={'text': ...}
311
+ for child in children:
312
+ cls.__addToTree(subMenu, child, checkFunction) # add recursively to sub-menu
313
+
314
+ return subMenu
315
+
316
+ @classmethod
317
+ def __inSubMenu(cls, child, subMenu):
318
+ """
319
+ Return True if child belongs to subMenu
320
+ """
321
+ for ch in subMenu:
322
+ if cls.__isProtocol(child):
323
+ if ch.value is not None and ch.value == child['value']:
324
+ return ch
325
+ elif ch.text == child['text']:
326
+ return ch
327
+ return None
328
+
329
+ @classmethod
330
+ def _orderSubMenu(cls, session):
331
+ """
332
+ Sort all children of a given section:
333
+ The protocols first, then the sections (the 'more' section at the end)
334
+ """
335
+
336
+ def sortWhenLastIsAProtocol():
337
+ """ Sorts children when the last is a protocol"""
338
+ for i in range(lastChildPos - 1, -1, -1):
339
+ if childs[i].tag == cls.TAG_PROTOCOL:
340
+ break
341
+ else:
342
+ tmp = childs[i + 1]
343
+ childs[i + 1] = childs[i]
344
+ childs[i] = tmp
345
+
346
+ def sortWhenLastIsNotAProtocol():
347
+ """ Sorts children when the last is NOT a protocol"""
348
+ for i in range(lastChildPos - 1, -1, -1):
349
+ if childs[i].tag == cls.TAG_PROTOCOL:
350
+ break
351
+ elif 'more' in str(childs[i].text).lower():
352
+ tmp = childs[i + 1]
353
+ childs[i + 1] = childs[i]
354
+ childs[i] = tmp
355
+
356
+ lengthSession = len(session.childs)
357
+ if lengthSession > 1:
358
+ childs = session.childs
359
+ lastChildPos = lengthSession - 1
360
+ if childs[lastChildPos].tag == cls.TAG_PROTOCOL:
361
+ sortWhenLastIsAProtocol()
362
+ else:
363
+ sortWhenLastIsNotAProtocol()
364
+
365
+ @classmethod
366
+ def __findTreeLocation(cls, subMenu, children, parent):
367
+ """
368
+ Locate the protocol position in the given view
369
+ """
370
+ for child in children:
371
+ sm = cls.__inSubMenu(child, subMenu)
372
+ if sm is None:
373
+ cls.__addToTree(parent, child, cls.__checkItem)
374
+ cls._orderSubMenu(parent)
375
+ elif child['tag'] == cls.TAG_PROTOCOL_GROUP or child['tag'] == cls.TAG_SECTION:
376
+ cls.__findTreeLocation(sm.childs, child['children'], sm)
377
+ @classmethod
378
+ def __isProtocol(cls, dict):
379
+ """ True inf the item has a key named tag with protocol as value"""
380
+ return dict["tag"] == cls.TAG_PROTOCOL
381
+
382
+ @classmethod
383
+ def __isProtocolNode(cls, node):
384
+ """ True if tag attribute is protocol"""
385
+ return node.tag == cls.TAG_PROTOCOL
386
+
387
+
388
+ @classmethod
389
+ def __checkItem(cls, item):
390
+ """ Function to check if the protocol has to be added or not.
391
+ Params:
392
+ item: {"tag": "protocol", "value": "ProtImportMovies",
393
+ "text": "import movies"}
394
+ """
395
+ if not cls.__isProtocol(item):
396
+ return True
397
+
398
+ # It is a protocol as this point, get the class name and
399
+ # check if it is disabled
400
+ protClassName = item["value"]
401
+ protClass = Config.getDomain().getProtocols().get(protClassName)
402
+ icon = Icon.PRODUCTION
403
+ if protClass is not None:
404
+ if protClass.isBeta():
405
+ icon = Icon.BETA
406
+ elif protClass.isNewDev():
407
+ icon = Icon.NEW
408
+ elif protClass.isUpdated():
409
+ icon = Icon.UPDATED
410
+ item['icon'] = icon
411
+ return False if protClass is None else not protClass.isDisabled()
412
+
413
+ @classmethod
414
+ def __addAllProtocols(cls, domain, protocols):
415
+ # Add all protocols
416
+ allProts = domain.getProtocols()
417
+
418
+ # Sort the list
419
+ allProtsSorted = sorted(allProts.items(), key=lambda e: e[1].getClassLabel())
420
+
421
+ allProtMenu = ProtocolConfig(cls.ALL_PROTOCOLS)
422
+ packages = {}
423
+
424
+ # Group protocols by package name
425
+ for k, v in allProtsSorted:
426
+ if isAFinalProtocol(v, k):
427
+ packageName = v.getPlugin().getName()
428
+
429
+ # Get the package submenu
430
+ packageMenu = packages.get(packageName)
431
+
432
+ # If no package menu available
433
+ if packageMenu is None:
434
+ # Add it to the menu ...
435
+ packageLine = {"tag": "package", "value": packageName,
436
+ "text": packageName}
437
+ packageMenu = cls.__addToTree(allProtMenu, packageLine)
438
+
439
+ # Store it in the dict
440
+ packages[packageName] = packageMenu
441
+
442
+ # Add the protocol
443
+ tag = cls.getProtocolTag(v.isInstalled(), v.isBeta(), v.isNewDev(), v.isUpdated())
444
+
445
+ protLine = {"tag": tag, "value": k,
446
+ "text": v.getClassLabel(prependPackageName=False)}
447
+
448
+ cls.__addToTree(packageMenu, protLine)
449
+
450
+ protocols[cls.ALL_PROTOCOLS] = allProtMenu
451
+
452
+ @classmethod
453
+ def __addProtocolsFromConf(cls, protocols, protocolsConfPath):
454
+ """
455
+ Load the protocols in the tree from a given protocols.conf file,
456
+ either the global one in Scipion or defined in a plugin.
457
+ """
458
+
459
+ def addProtocols():
460
+ """ Adds protocols defined in the "PROTOCOLS" section of the config file. """
461
+ for menuName in cp.options('PROTOCOLS'):
462
+ if menuName not in protocols: # The view has not been inserted
463
+ menu = ProtocolConfig(menuName)
464
+ children = json.loads(cp.get('PROTOCOLS', menuName))
465
+ for child in children:
466
+ cls.__addToTree(menu, child, cls.__checkItem)
467
+ protocols[menuName] = menu
468
+ else: # The view has been inserted
469
+ menu = protocols.get(menuName)
470
+ children = json.loads(cp.get('PROTOCOLS',
471
+ menuName))
472
+ cls.__findTreeLocation(menu.childs, children, menu)
473
+
474
+ # Populate the protocols menu from the plugin config file.
475
+ if os.path.exists(protocolsConfPath):
476
+ cp = ConfigParser()
477
+ cp.optionxform = str # keep case
478
+ cp.read(protocolsConfPath)
479
+ # Ensure that the protocols section exists
480
+ if cp.has_section('PROTOCOLS'):
481
+ addProtocols()
482
+
483
+ @classmethod
484
+ def load(cls, domain, protocolsConf):
485
+ """ Read the protocol configuration from a .conf file similar to the
486
+ one in scipion/config/protocols.conf,
487
+ which is the default one when no file is passed.
488
+ """
489
+ protocols = dict()
490
+ # Read the protocols.conf from Scipion (base) and create an initial
491
+ # tree view
492
+ cls.__addProtocolsFromConf(protocols, protocolsConf)
493
+
494
+ # Read the protocols.conf of any installed plugin
495
+ pluginDict = domain.getPlugins()
496
+
497
+ for pluginName in pluginDict.keys():
498
+ try:
499
+
500
+ # if the plugin has a path
501
+ if pwutils.isModuleLoaded(pluginName) and pwutils.isModuleAFolder(pluginName):
502
+ # Locate the plugin protocols.conf file
503
+ protocolsConfPath = os.path.join(
504
+ pluginDict[pluginName].__path__[0],
505
+ cls.PLUGIN_CONFIG_PROTOCOLS)
506
+ cls.__addProtocolsFromConf(protocols, protocolsConfPath)
507
+
508
+ except Exception as e:
509
+ logger.info(f'Failed to read protocols.conf from {pluginName}. The reported error was:\n {e}\n')
510
+
511
+ # Clean empty sections
512
+ cls._hideEmptySections(protocols)
513
+
514
+ # Add all protocols to All view
515
+ cls.__addAllProtocols(domain, protocols)
516
+
517
+ return protocols
518
+
519
+ @classmethod
520
+ def _hideEmptySections(cls, protocols):
521
+ """ Cleans all empty sections in the tree"""
522
+
523
+ for protConf in protocols.values():
524
+ cls._setVisibility(protConf)
525
+
526
+ @classmethod
527
+ def _setVisibility(cls, node):
528
+ """ Sets the visibility of a node based on the presence of a leaf hanging form it"""
529
+ if cls.__isProtocolNode(node):
530
+ # Default visibility value is true. No need to set it again
531
+ return True
532
+
533
+ anyLeaf = False
534
+
535
+ for child in node.childs:
536
+ # NOTE: since python short circuits this, _setVisibility must be called always. So not swap!!
537
+ anyLeaf = cls._setVisibility(child) or anyLeaf
538
+
539
+ node.visible = anyLeaf
540
+
541
+ return anyLeaf
542
+
543
+
544
+ class ProtocolConfig(MenuConfig):
545
+ """Store protocols configuration """
546
+
547
+ def __init__(self, text=None, value=None, **args):
548
+ MenuConfig.__init__(self, text, value, **args)
549
+ if 'openItem' not in args:
550
+ self.openItem = self.tag != 'protocol_base'
551
+
552
+ def addSubMenu(self, text, value=None, shortCut=None, **args):
553
+ if 'icon' not in args:
554
+ tag = args.get('tag', None)
555
+ if tag == 'protocol_base':
556
+ args['icon'] = Icon.GROUP
557
+
558
+ args['shortCut'] = shortCut
559
+ return MenuConfig.addSubMenu(self, text, value, **args)
560
+
561
+ def __str__(self):
562
+ return self.text