scipion-pyworkflow 3.7.0__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 (140) hide show
  1. pyworkflow/__init__.py +33 -0
  2. pyworkflow/apps/__init__.py +29 -0
  3. pyworkflow/apps/pw_manager.py +37 -0
  4. pyworkflow/apps/pw_plot.py +51 -0
  5. pyworkflow/apps/pw_project.py +113 -0
  6. pyworkflow/apps/pw_protocol_list.py +143 -0
  7. pyworkflow/apps/pw_protocol_run.py +51 -0
  8. pyworkflow/apps/pw_run_tests.py +267 -0
  9. pyworkflow/apps/pw_schedule_run.py +322 -0
  10. pyworkflow/apps/pw_sleep.py +37 -0
  11. pyworkflow/apps/pw_sync_data.py +439 -0
  12. pyworkflow/apps/pw_viewer.py +78 -0
  13. pyworkflow/config.py +536 -0
  14. pyworkflow/constants.py +212 -0
  15. pyworkflow/exceptions.py +18 -0
  16. pyworkflow/gui/__init__.py +36 -0
  17. pyworkflow/gui/browser.py +726 -0
  18. pyworkflow/gui/canvas.py +1190 -0
  19. pyworkflow/gui/dialog.py +976 -0
  20. pyworkflow/gui/form.py +2627 -0
  21. pyworkflow/gui/graph.py +247 -0
  22. pyworkflow/gui/graph_layout.py +271 -0
  23. pyworkflow/gui/gui.py +566 -0
  24. pyworkflow/gui/matplotlib_image.py +233 -0
  25. pyworkflow/gui/plotter.py +247 -0
  26. pyworkflow/gui/project/__init__.py +25 -0
  27. pyworkflow/gui/project/base.py +192 -0
  28. pyworkflow/gui/project/constants.py +139 -0
  29. pyworkflow/gui/project/labels.py +205 -0
  30. pyworkflow/gui/project/project.py +484 -0
  31. pyworkflow/gui/project/searchprotocol.py +154 -0
  32. pyworkflow/gui/project/searchrun.py +181 -0
  33. pyworkflow/gui/project/steps.py +166 -0
  34. pyworkflow/gui/project/utils.py +332 -0
  35. pyworkflow/gui/project/variables.py +179 -0
  36. pyworkflow/gui/project/viewdata.py +472 -0
  37. pyworkflow/gui/project/viewprojects.py +510 -0
  38. pyworkflow/gui/project/viewprotocols.py +2093 -0
  39. pyworkflow/gui/project/viewprotocols_extra.py +560 -0
  40. pyworkflow/gui/text.py +771 -0
  41. pyworkflow/gui/tooltip.py +185 -0
  42. pyworkflow/gui/tree.py +684 -0
  43. pyworkflow/gui/widgets.py +307 -0
  44. pyworkflow/mapper/__init__.py +26 -0
  45. pyworkflow/mapper/mapper.py +222 -0
  46. pyworkflow/mapper/sqlite.py +1578 -0
  47. pyworkflow/mapper/sqlite_db.py +145 -0
  48. pyworkflow/object.py +1512 -0
  49. pyworkflow/plugin.py +712 -0
  50. pyworkflow/project/__init__.py +31 -0
  51. pyworkflow/project/config.py +451 -0
  52. pyworkflow/project/manager.py +179 -0
  53. pyworkflow/project/project.py +1990 -0
  54. pyworkflow/project/scripts/clean_projects.py +77 -0
  55. pyworkflow/project/scripts/config.py +92 -0
  56. pyworkflow/project/scripts/create.py +77 -0
  57. pyworkflow/project/scripts/edit_workflow.py +90 -0
  58. pyworkflow/project/scripts/fix_links.py +39 -0
  59. pyworkflow/project/scripts/load.py +87 -0
  60. pyworkflow/project/scripts/refresh.py +83 -0
  61. pyworkflow/project/scripts/schedule.py +111 -0
  62. pyworkflow/project/scripts/stack2volume.py +41 -0
  63. pyworkflow/project/scripts/stop.py +81 -0
  64. pyworkflow/protocol/__init__.py +38 -0
  65. pyworkflow/protocol/bibtex.py +48 -0
  66. pyworkflow/protocol/constants.py +86 -0
  67. pyworkflow/protocol/executor.py +334 -0
  68. pyworkflow/protocol/hosts.py +313 -0
  69. pyworkflow/protocol/launch.py +270 -0
  70. pyworkflow/protocol/package.py +42 -0
  71. pyworkflow/protocol/params.py +744 -0
  72. pyworkflow/protocol/protocol.py +2554 -0
  73. pyworkflow/resources/Imagej.png +0 -0
  74. pyworkflow/resources/chimera.png +0 -0
  75. pyworkflow/resources/fa-exclamation-triangle_alert.png +0 -0
  76. pyworkflow/resources/fa-info-circle_alert.png +0 -0
  77. pyworkflow/resources/fa-search.png +0 -0
  78. pyworkflow/resources/fa-times-circle_alert.png +0 -0
  79. pyworkflow/resources/file_vol.png +0 -0
  80. pyworkflow/resources/loading.gif +0 -0
  81. pyworkflow/resources/no-image128.png +0 -0
  82. pyworkflow/resources/scipion_bn.png +0 -0
  83. pyworkflow/resources/scipion_icon.png +0 -0
  84. pyworkflow/resources/scipion_icon.svg +397 -0
  85. pyworkflow/resources/scipion_icon_proj.png +0 -0
  86. pyworkflow/resources/scipion_icon_projs.png +0 -0
  87. pyworkflow/resources/scipion_icon_prot.png +0 -0
  88. pyworkflow/resources/scipion_logo.png +0 -0
  89. pyworkflow/resources/scipion_logo_normal.png +0 -0
  90. pyworkflow/resources/scipion_logo_small.png +0 -0
  91. pyworkflow/resources/sprites.png +0 -0
  92. pyworkflow/resources/sprites.xcf +0 -0
  93. pyworkflow/resources/wait.gif +0 -0
  94. pyworkflow/template.py +322 -0
  95. pyworkflow/tests/__init__.py +29 -0
  96. pyworkflow/tests/test_utils.py +25 -0
  97. pyworkflow/tests/tests.py +341 -0
  98. pyworkflow/utils/__init__.py +38 -0
  99. pyworkflow/utils/dataset.py +414 -0
  100. pyworkflow/utils/echo.py +104 -0
  101. pyworkflow/utils/graph.py +196 -0
  102. pyworkflow/utils/log.py +284 -0
  103. pyworkflow/utils/path.py +527 -0
  104. pyworkflow/utils/process.py +132 -0
  105. pyworkflow/utils/profiler.py +92 -0
  106. pyworkflow/utils/progressbar.py +154 -0
  107. pyworkflow/utils/properties.py +627 -0
  108. pyworkflow/utils/reflection.py +129 -0
  109. pyworkflow/utils/utils.py +877 -0
  110. pyworkflow/utils/which.py +229 -0
  111. pyworkflow/viewer.py +328 -0
  112. pyworkflow/webservices/__init__.py +8 -0
  113. pyworkflow/webservices/config.py +11 -0
  114. pyworkflow/webservices/notifier.py +162 -0
  115. pyworkflow/webservices/repository.py +59 -0
  116. pyworkflow/webservices/workflowhub.py +74 -0
  117. pyworkflow/wizard.py +64 -0
  118. pyworkflowtests/__init__.py +51 -0
  119. pyworkflowtests/bibtex.py +51 -0
  120. pyworkflowtests/objects.py +830 -0
  121. pyworkflowtests/protocols.py +154 -0
  122. pyworkflowtests/tests/__init__.py +0 -0
  123. pyworkflowtests/tests/test_canvas.py +72 -0
  124. pyworkflowtests/tests/test_domain.py +45 -0
  125. pyworkflowtests/tests/test_logs.py +74 -0
  126. pyworkflowtests/tests/test_mappers.py +392 -0
  127. pyworkflowtests/tests/test_object.py +507 -0
  128. pyworkflowtests/tests/test_project.py +42 -0
  129. pyworkflowtests/tests/test_protocol_execution.py +72 -0
  130. pyworkflowtests/tests/test_protocol_export.py +78 -0
  131. pyworkflowtests/tests/test_protocol_output.py +158 -0
  132. pyworkflowtests/tests/test_streaming.py +47 -0
  133. pyworkflowtests/tests/test_utils.py +210 -0
  134. scipion_pyworkflow-3.7.0.dist-info/LICENSE.txt +674 -0
  135. scipion_pyworkflow-3.7.0.dist-info/METADATA +107 -0
  136. scipion_pyworkflow-3.7.0.dist-info/RECORD +140 -0
  137. scipion_pyworkflow-3.7.0.dist-info/WHEEL +5 -0
  138. scipion_pyworkflow-3.7.0.dist-info/dependency_links.txt +1 -0
  139. scipion_pyworkflow-3.7.0.dist-info/entry_points.txt +5 -0
  140. scipion_pyworkflow-3.7.0.dist-info/top_level.txt +2 -0
pyworkflow/plugin.py ADDED
@@ -0,0 +1,712 @@
1
+ # **************************************************************************
2
+ # *
3
+ # * Authors: Yaiza Rancel (cyrancel@cnb.csic.es) [1]
4
+ # * Pablo Conesa (pconesa@cnb.csic.es) [1]
5
+ # * J.M. De la Rosa Trevin (delarosatrevin@scilifelab.se) [2]
6
+ # *
7
+ # * [1] Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
8
+ # * [2] SciLifeLab, Stockholm University
9
+ # *
10
+ # * This program is free software; you can redistribute it and/or modify
11
+ # * it under the terms of the GNU General Public License as published by
12
+ # * the Free Software Foundation; either version 3 of the License, or
13
+ # * (at your option) any later version.
14
+ # *
15
+ # * This program is distributed in the hope that it will be useful,
16
+ # * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
+ # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
+ # * GNU General Public License for more details.
19
+ # *
20
+ # * You should have received a copy of the GNU General Public License
21
+ # * along with this program; if not, write to the Free Software
22
+ # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
23
+ # * 02111-1307 USA
24
+ # *
25
+ # * All comments concerning this program package may be sent to the
26
+ # * e-mail address 'scipion@cnb.csic.es'
27
+ # *
28
+ # **************************************************************************
29
+ import logging
30
+ import sys
31
+
32
+ from pyworkflow import Variable, VariablesRegistry, VarTypes
33
+ from .protocol import Protocol
34
+ from .viewer import Viewer
35
+ from .wizard import Wizard
36
+
37
+ logger = logging.getLogger(__name__)
38
+ import glob
39
+ import os
40
+ import importlib
41
+ import inspect
42
+ import traceback
43
+ import types
44
+ from abc import ABCMeta, abstractmethod
45
+
46
+ import pyworkflow as pw
47
+ import pyworkflow.utils as pwutils
48
+ import pyworkflow.object as pwobj
49
+ from pyworkflow.template import LocalTemplate
50
+ from pyworkflow.utils import sortListByList
51
+
52
+ try:
53
+ import importlib_metadata
54
+ except ModuleNotFoundError:
55
+ raise ModuleNotFoundError('You are missing importlib-metadata package. '
56
+ 'Please run: scipion3 pip install importlib-metadata')
57
+
58
+ from .constants import *
59
+
60
+
61
+ class Domain:
62
+ """ Class to represent the application domain.
63
+ It will allow to specify new objects, protocols, viewers and wizards
64
+ through the registration of new plugins.
65
+ """
66
+
67
+ # The following classes should be defined in subclasses of Domain
68
+ _name = __name__
69
+ _protocolClass = Protocol
70
+ _objectClass = pwobj.Object
71
+ _viewerClass = Viewer
72
+ _wizardClass = Wizard
73
+ _baseClasses = {} # Update this with the base classes of the Domain
74
+
75
+ # Dictionaries to store different type of objects
76
+ _plugins = {}
77
+ _protocols = {}
78
+ _objects = {}
79
+ _viewers = {}
80
+ _wizards = {}
81
+
82
+ @classmethod
83
+ def registerPlugin(cls, name):
84
+ """ Register a new plugin. This function should only be called when
85
+ creating a class with __metaclass__=PluginMeta that will trigger this.
86
+ """
87
+ try:
88
+ m = importlib.import_module(name)
89
+
90
+ # Define variables
91
+ m._pluginInstance = m.Plugin()
92
+ # This needs an explanation. _defineVariables() are classmethods, therfore we need a class variable and thus .name can't be used.
93
+ # Ideally, since Plugin class is instantiated we could transform _defineVariables methods into instance methods and only then we
94
+ # could use .name.
95
+ Plugin._tmpName = name
96
+ m._pluginInstance.name = name
97
+ m._pluginInstance._defineVariables()
98
+
99
+ m.Domain = cls # Register the domain class for this module
100
+ # TODO avoid loading bibtex here and make a lazy load like the rest.
101
+ # Load bibtex
102
+ m._bibtex = {}
103
+ bib = cls.__getSubmodule(name, 'bibtex')
104
+ if bib is not None:
105
+ if hasattr(bib, "_bibtex"):
106
+ logger.info("WARNING FOR DEVELOPERS: %s/%s._bibtex unnecessarily declared. Just the doc string is enough." % (name, "bibtex"))
107
+ else:
108
+ try:
109
+ m._bibtex = pwutils.LazyDict(lambda: pwutils.parseBibTex(bib.__doc__))
110
+ except Exception:
111
+ pass
112
+ cls._plugins[name] = m # Register the name to as a plugin
113
+
114
+ # Catch any import exception, warn about it but continue.
115
+ except ModuleNotFoundError:
116
+ # This is probably due to a priority package like pwchem not being installed
117
+ pass
118
+ except Exception as e:
119
+
120
+ (pwutils.yellow("WARNING!!: Plugin containing module %s does not import properly. "
121
+ "All its content will be missing in this execution." % name))
122
+ logger.info("Please, contact developers at %s and send this ugly information below. They'll understand it!." % DOCSITEURLS.CONTACTUS)
123
+ logger.info(pwutils.yellow(traceback.format_exc()))
124
+
125
+ @classmethod
126
+ def getPlugins(cls):
127
+ """ Return existing plugins for this Domain. """
128
+ loaded = getattr(cls, '_pluginsLoaded', False)
129
+ if not loaded: # Load plugin only once
130
+ cls._discoverPlugins()
131
+ cls._pluginsLoaded = True
132
+ return dict(cls._plugins)
133
+
134
+ @classmethod
135
+ def _discoverPlugins(cls):
136
+ # Get the list of plugins registered
137
+ plugin_modules = importlib_metadata.entry_points(group='pyworkflow.plugin')
138
+
139
+ # Sort the list taking into account the priority
140
+ plugin_modules = sortListByList(plugin_modules.names, pw.Config.getPriorityPackageList())
141
+
142
+ for module in plugin_modules:
143
+ cls.registerPlugin(module)
144
+
145
+ @classmethod
146
+ def _discoverGUIPlugins(cls):
147
+ for entry_point in importlib_metadata.entry_points(group='pyworkflow.guiplugin'):
148
+ try:
149
+ entry_point.load()
150
+ except Exception as e:
151
+ logger.warning("Can't import %s GUI plugin: %s" % (entry_point, e))
152
+
153
+ @classmethod
154
+ def getPluginModule(cls, name):
155
+ """ Return the root of a plugin module initialized properly """
156
+ cls.registerPlugin(name)
157
+
158
+ return cls._plugins[name]
159
+
160
+ @classmethod
161
+ def getPlugin(cls, name):
162
+ logger.warning("This method will return the plugin class in the future. Please use getPluginModule instead")
163
+ return cls.getPluginModule(name)
164
+
165
+ @classmethod
166
+ def refreshPlugin(cls, name):
167
+ """ Refresh a given plugin name. """
168
+ plugin = cls.getPlugin(name)
169
+ if plugin is not None:
170
+ fn = plugin.__file__
171
+ fn_dir = os.path.dirname(fn) + os.sep
172
+ module_visit = {plugin}
173
+ module_visit_path = {fn}
174
+ del fn
175
+
176
+ def get_recursive_modules(module):
177
+ """ Get all plugin modules recursively """
178
+ for module_child in vars(module).values():
179
+ if isinstance(module_child, types.ModuleType):
180
+ fn_child = getattr(module_child, "__file__", None)
181
+ if (fn_child is not None) and fn_child.startswith(
182
+ fn_dir):
183
+ if fn_child not in module_visit_path:
184
+ module_visit.add(module_child)
185
+ module_visit_path.add(fn_child)
186
+ get_recursive_modules(module_child)
187
+
188
+ get_recursive_modules(plugin)
189
+ # reload all plugin modules
190
+
191
+ while module_visit:
192
+ for module in module_visit:
193
+ try:
194
+ importlib.reload(module)
195
+ module_visit.remove(module)
196
+ break
197
+ except Exception as ex:
198
+ pass
199
+
200
+ @classmethod
201
+ def __getSubclasses(cls, submoduleName, BaseClass,
202
+ updateBaseClasses=False, setPackage=False):
203
+ """ Load all detected subclasses of a given BaseClass.
204
+ Params:
205
+ updateBaseClasses: if True, it will try to load classes from the
206
+ Domain submodule that was not imported as globals()
207
+ """
208
+ subclasses = getattr(cls, '_%s' % submoduleName)
209
+
210
+ if not subclasses: # Only discover subclasses once
211
+ if updateBaseClasses:
212
+ sub = cls.__getSubmodule(cls.getName(), submoduleName)
213
+ if sub is not None:
214
+ for name in cls.getModuleClasses(sub):
215
+ attr = getattr(sub, name)
216
+ if inspect.isclass(attr) and issubclass(attr, BaseClass):
217
+ cls._baseClasses[name] = attr
218
+
219
+ for pluginName, module in cls.getPlugins().items():
220
+ sub = cls.__getSubmodule(pluginName, submoduleName)
221
+ if sub is not None:
222
+ for name in cls.getModuleClasses(sub):
223
+ attr = getattr(sub, name)
224
+ if inspect.isclass(attr) and issubclass(attr, BaseClass):
225
+
226
+ # Check if the class already exists (to prevent
227
+ # naming collisions)
228
+ if name in subclasses:
229
+ # Get already added class plugin
230
+ pluginCollision = subclasses[name]._package.__name__
231
+ logger.info("ERROR: Name collision (%s) detected "
232
+ "while discovering %s.%s.\n"
233
+ " It conflicts with %s" %
234
+ (name, pluginName, submoduleName,
235
+ pluginCollision))
236
+ else:
237
+ # Set this special property used by Scipion
238
+ # Protocols need the package to be set
239
+ if setPackage:
240
+
241
+ attr._plugin = getattr(module, "_pluginInstance", None)
242
+ attr._package = module
243
+
244
+ subclasses[name] = attr
245
+ subclasses.update(
246
+ pwutils.getSubclasses(BaseClass, cls._baseClasses))
247
+
248
+ return subclasses
249
+
250
+ @classmethod
251
+ def getModuleClasses(cls, module):
252
+ # Dir was used before but dir returns all imported elements
253
+ # included those imported to be BaseClasses.
254
+ # return dir(module)
255
+
256
+ # Get the module name
257
+ moduleName = module.__name__
258
+
259
+ # Get any module class
260
+ for name, declaredClass in inspect.getmembers(module, inspect.isclass):
261
+ if moduleName in declaredClass.__module__:
262
+ yield name
263
+
264
+ @classmethod
265
+ def getProtocols(cls):
266
+ """ Return all Protocol subclasses from all plugins for this domain."""
267
+ return cls.__getSubclasses('protocols', cls._protocolClass, setPackage=True)
268
+
269
+ @classmethod
270
+ def getObjects(cls):
271
+ """ Return all EMObject subclasses from all plugins for this domain."""
272
+ return cls.__getSubclasses('objects', cls._objectClass)
273
+
274
+ @classmethod
275
+ def viewersLoaded(cls):
276
+ """ Returns true if viewers have been already discovered"""
277
+ return len(cls._viewers) != 0
278
+
279
+ @classmethod
280
+ def getViewers(cls):
281
+ """ Return all Viewer subclasses from all plugins for this domain."""
282
+ return cls.__getSubclasses('viewers', cls._viewerClass,
283
+ updateBaseClasses=True, setPackage=True)
284
+
285
+ @classmethod
286
+ def getWizards(cls):
287
+ """ Return all Wizard subclasses from all plugins for this domain."""
288
+ return cls.__getSubclasses('wizards', cls._wizardClass)
289
+
290
+ @classmethod
291
+ def getMapperDict(cls):
292
+ """ Return a dictionary that can be used with subclasses of Mapper
293
+ to store/retrieve objects (including protocols) defined in this
294
+ Domain. """
295
+ mapperDict = getattr(cls, '__mapperDict', None)
296
+
297
+ if mapperDict is None:
298
+ mapperDict = dict(pwobj.OBJECTS_DICT)
299
+ mapperDict.update(cls.getObjects())
300
+ mapperDict.update(cls.getProtocols())
301
+ cls.__mapperDict = mapperDict
302
+
303
+ return mapperDict
304
+
305
+ @classmethod
306
+ def getName(cls):
307
+ """ Return the name of this Domain. """
308
+ return cls._name
309
+
310
+ @staticmethod
311
+ def importFromPlugin(module, objects=None, errorMsg='', doRaise=False):
312
+ """
313
+ This method try to import either a list of objects from the
314
+ module/plugin or the whole module/plugin and returns what is
315
+ imported if not fails.
316
+ When the import fails (due to the plugin or the object is not found),
317
+ it prints a common message + optional errorMsg;
318
+ or it raise an error with the same message, if doRaise is True.
319
+
320
+ :param module: Module name to import
321
+ :param objects: Optional, string with objects to return present in module
322
+ :param errorMsg: Optional, extra error message to append to the main message.
323
+ :param doRaise: If True it will raise an exception instead of tolerating the import error
324
+
325
+ Usages::
326
+
327
+ # Import the whole plugin 'plugin1' as 'plug1'
328
+ plug1 = importFromPlugin('plugin1')
329
+
330
+ # Import a plugin's module
331
+ pl1Cons = importFromPlugin('plug1.constants')
332
+
333
+ # Import a single class from a plugin's module
334
+ p1prot1 = importFromPlugin('plug1.protocols', 'prot1')
335
+
336
+ # Import some classes from a plugin's module,
337
+ # the returned tuple has the same length of the second argument
338
+ pt1, pt2, ... = importFromPlugin('plugin1.protocols',
339
+ ['pt1', 'pt2', ...])
340
+ """
341
+ def _tryImportFromPlugin(submodule=None):
342
+ try:
343
+ if submodule is None: # Import only the module
344
+ output = importlib.import_module(module)
345
+ else: # Import the class of that module
346
+ output = getattr(importlib.import_module(module), submodule)
347
+ return output
348
+ except Exception as e:
349
+ plugName = module.split('.')[0] # The Main module is the plugin
350
+ errMsg = (str(e) if errorMsg == ''
351
+ else "%s. %s" % (str(e), errorMsg))
352
+ Domain.__pluginNotFound(plugName, errMsg, doRaise)
353
+
354
+ if objects is None or isinstance(objects, str):
355
+ output = _tryImportFromPlugin(objects)
356
+ else:
357
+ output = tuple()
358
+ for obj in objects:
359
+ output += (_tryImportFromPlugin(obj), ) # append in tuple
360
+ return output
361
+
362
+ @classmethod
363
+ def findClass(cls, className):
364
+ """ Find a class object given its name.
365
+ The search will start with protocols and then with protocols.
366
+ """
367
+ # FIXME: Why not also Viewers, Wizards?
368
+ c = cls.getProtocols().get(className,
369
+ cls.getObjects().get(className, None))
370
+ if c is None:
371
+ raise Exception("findClass: class '%s' not found." % className)
372
+ return c
373
+
374
+ @classmethod
375
+ def findSubClasses(cls, classDict, className):
376
+ """ Find all subclasses of a give className. """
377
+ clsObj = classDict[className]
378
+ subclasses = {}
379
+
380
+ for k, v in classDict.items():
381
+ if issubclass(v, clsObj):
382
+ subclasses[k] = v
383
+ return subclasses
384
+
385
+ @classmethod
386
+ def getPreferredViewers(cls, className):
387
+ """ Find and import the preferred viewers for this class. """
388
+ viewerNames = pw.Config.VIEWERS.get(className, [])
389
+ if not isinstance(viewerNames, list):
390
+ viewerNames = [viewerNames]
391
+ viewers = [] # we will try to import them and store here
392
+ for prefViewerStr in viewerNames:
393
+ try:
394
+ viewerModule, viewerClassName = prefViewerStr.rsplit('.', 1)
395
+ prefViewer = cls.importFromPlugin(viewerModule,
396
+ viewerClassName,
397
+ doRaise=True)
398
+ viewers.append(prefViewer)
399
+ except Exception as e:
400
+ logger.error("Couldn't load \"%s\" as preferred viewer for %s.\n"
401
+ "There might be a typo in your VIEWERS variable "
402
+ "or an error in the viewer's plugin installation"
403
+ % (prefViewerStr, className), exc_info=e)
404
+ return viewers
405
+
406
+ @classmethod
407
+ def findViewers(cls, className, environment):
408
+ """ Find the available viewers in this Domain for this class. """
409
+ viewers = []
410
+ try:
411
+ clazz = cls.findClass(className)
412
+ baseClasses = clazz.mro()
413
+ preferredViewers = cls.getPreferredViewers(className)
414
+ preferedFlag = 0
415
+
416
+ for viewer in cls.getViewers().values():
417
+ viewerAdded=False
418
+ if environment in viewer._environments:
419
+ for t in viewer._targets:
420
+ if t in baseClasses:
421
+ for prefViewer in preferredViewers:
422
+ if viewer is prefViewer:
423
+ viewers.insert(0, viewer)
424
+ viewerAdded=True
425
+ preferedFlag = 1
426
+ break
427
+ else:
428
+ if t == clazz:
429
+ viewers.insert(preferedFlag, viewer)
430
+ viewerAdded = True
431
+ else:
432
+ viewers.append(viewer)
433
+ viewerAdded = True
434
+ break
435
+
436
+ if viewerAdded:
437
+ break
438
+
439
+ except Exception as e:
440
+ # Catch if there is a missing plugin, we will get Legacy which triggers and Exception
441
+ pass
442
+
443
+ return viewers
444
+
445
+ @classmethod
446
+ def findWizards(cls, protocol, environment):
447
+ """
448
+ Find available wizards for this class, in this Domain.
449
+
450
+ :param protocol: Protocol instance for which wizards will be search.
451
+ :param environment: The environment name for wizards (e.g TKINTER)
452
+
453
+ :return A dict with the paramName and wizards for the protocol passed."""
454
+ return cls.__findWizardsFromDict(protocol, environment,
455
+ cls.getWizards())
456
+
457
+ @classmethod
458
+ def printInfo(cls):
459
+ """ Simple function (mainly for debugging) that prints basic
460
+ information about this Domain. """
461
+ logger.info("Domain: %s" % cls._name)
462
+ logger.info(" objects: %s" % len(cls._objects))
463
+ logger.info(" protocols: %s" % len(cls._protocols))
464
+ logger.info(" viewers: %s" % len(cls._viewers))
465
+ logger.info(" wizards: %s" % len(cls._wizards))
466
+
467
+ # ---------- Private methods of Domain class ------------------------------
468
+ @staticmethod
469
+ def __pluginNotFound(plugName, errorMsg='', doRaise=False):
470
+ """ Prints or raise (depending on the doRaise) telling why it is failing
471
+ """
472
+ hint = (" Check the plugin manager (Configuration->Plugins in "
473
+ "Scipion manager window) \n")
474
+ # the str casting is to work with Exceptions as errorMsg
475
+ if 'No module named %s' % plugName in str(errorMsg):
476
+ msgStr = " > %s plugin not found. %s" % (plugName, errorMsg)
477
+ hint += " or use 'scipion installp --help' in the command line "
478
+ hint += "to install it."
479
+ else:
480
+ msgStr = " > error when importing from %s: %s" % (plugName, errorMsg)
481
+ if errorMsg != '': # if empty we know nothing...
482
+ hint += (" or use 'scipion installp --help --checkUpdates' "
483
+ "in the command line to check for upgrades,\n "
484
+ "it could be a versions compatibility issue.")
485
+
486
+ stackList = traceback.extract_stack()
487
+ if len(stackList) > 3:
488
+ callIdx = -3 # We use the most probable index as default
489
+ for idx, stackLine in enumerate(stackList):
490
+ if stackLine[0].endswith('/unittest/loader.py'):
491
+ callIdx = idx + 1
492
+ else:
493
+ callIdx = 0
494
+
495
+ callBy = stackList[callIdx][0]
496
+ if callBy.endswith('pyworkflow/plugin.py'):
497
+ # This special case is to know why is failing and not where is called
498
+ # because we know that we call all plugins.protocols at the beginning
499
+ calling = traceback.format_exc().split('\n')[-4]
500
+ else:
501
+ line = stackList[callIdx][1]
502
+ calling = " Called by %s, line %s" % (callBy, line)
503
+
504
+ raiseMsg = "%s\n %s\n%s\n" % (msgStr, calling, hint)
505
+ if doRaise:
506
+ raise Exception("\n\n" + raiseMsg)
507
+ else:
508
+ logger.info(raiseMsg)
509
+
510
+ @staticmethod
511
+ def __getSubmodule(name, subname):
512
+ try:
513
+ completeModuleText = '%s.%s' % (name, subname)
514
+ if pwutils.isModuleAFolder(name):
515
+ return importlib.import_module(completeModuleText)
516
+ except Exception as e:
517
+ msg = str(e)
518
+ # FIXME: The following is a quick and dirty way to filter
519
+ # when the submodule is not present
520
+ if msg != 'No module named \'%s\'' % completeModuleText:
521
+ Domain.__pluginNotFound(completeModuleText, msg)
522
+ return None
523
+
524
+ @classmethod
525
+ def __isPlugin(cls, m):
526
+ """ Return True if the module is a Scipion plugin. """
527
+ return m.__name__ in cls._plugins
528
+
529
+ @staticmethod
530
+ def __findWizardsFromDict(protocol, environment, wizDict):
531
+ wizards = {}
532
+ baseClasses = [c.__name__ for c in protocol.getClass().mro()]
533
+
534
+ for wiz in wizDict.values():
535
+ if environment in wiz._environments:
536
+ for c, params in wiz._targets:
537
+ if c.__name__ in baseClasses:
538
+ for p in params:
539
+ wizards[p] = wiz
540
+ return wizards
541
+
542
+
543
+ class Plugin:
544
+ __metaclass__ = ABCMeta
545
+
546
+ _vars = {}
547
+ _homeVar = '' # Change in subclasses to define the "home" variable
548
+ _pathVars = []
549
+ _supportedVersions = []
550
+ _url = "" # For the plugin
551
+ _condaActivationCmd = None
552
+ _tmpName = None # This would be temporary. It will hold the plugin name during the call to defineVariables
553
+
554
+ def __init__(self):
555
+ self._path = None
556
+ self._inDevelMode = None
557
+ self._name = None
558
+
559
+ @classmethod
560
+ def _defineVar(cls, varName, defaultValue, description="Missing", var_type:VarTypes=None):
561
+ """ Internal method to define variables trying to get it from the environment first. """
562
+ cls._addVar(varName, os.environ.get(varName, defaultValue), default=defaultValue, description=description, var_type=var_type)
563
+
564
+ @classmethod
565
+ def _addVar(cls, varName, value, default=None, description="Missing", var_type:VarTypes=None):
566
+ """ Adds a variable to the local variable dictionary directly. Avoiding the environment"""
567
+
568
+ var = Variable(varName, description, cls._tmpName, value, default, var_type=var_type)
569
+ VariablesRegistry.register(var)
570
+ cls._vars[varName] = value
571
+
572
+ @classmethod
573
+ @abstractmethod
574
+ def getEnviron(cls):
575
+ """ Set up the environment variables needed to launch programs. """
576
+ pass
577
+
578
+ @classmethod
579
+ def getCondaActivationCmd(cls):
580
+ """ Returns the conda activation command with && at the end if defined otherwise empty"""
581
+ if cls._condaActivationCmd is None:
582
+ condaActivationCmd = pw.Config.CONDA_ACTIVATION_CMD
583
+ if not condaActivationCmd:
584
+ logger.info("WARNING!!_condaActivationCmd: %s variable not defined. "
585
+ "Relying on conda being in the PATH" % CONDA_ACTIVATION_CMD_VAR)
586
+ elif condaActivationCmd[-1] not in [";", "&"]:
587
+ condaActivationCmd += "&&"
588
+
589
+ cls._condaActivationCmd = condaActivationCmd
590
+
591
+ return cls._condaActivationCmd
592
+
593
+ @abstractmethod
594
+ def _defineVariables(cls):
595
+ """ Method to define variables and their default values.
596
+ It will use the method _defineVar that will take a variable value
597
+ from the environment or from an optional default value.
598
+
599
+ This method is not supposed to be called from client code,
600
+ except from the Domain class when registering a Plugin.
601
+ """
602
+ pass
603
+
604
+ @classmethod
605
+ @abstractmethod
606
+ def defineBinaries(cls, env):
607
+ """ Define required binaries in the given Environment. """
608
+ pass
609
+
610
+ @classmethod
611
+ def getVar(cls, varName, defaultValue=None):
612
+ """ Return the value of a given variable. """
613
+ return cls._vars.get(varName, defaultValue)
614
+
615
+ @classmethod
616
+ def getVars(cls):
617
+ """ Return the value of a given variable. """
618
+ return cls._vars
619
+
620
+ @classmethod
621
+ def getHome(cls, *paths):
622
+ """ Return a path from the "home" of the package
623
+ if the _homeVar is defined in the plugin. """
624
+ home = cls.getVar(cls._homeVar)
625
+ return os.path.join(home, *paths) if home else ''
626
+
627
+ @classmethod
628
+ def getSupportedVersions(cls):
629
+ """ Return the list of supported binary versions. """
630
+ return cls._supportedVersions
631
+
632
+ @classmethod
633
+ def getActiveVersion(cls, home=None, versions=None):
634
+ """
635
+ Returns the version of the binaries that are currently active.
636
+ In the current implementation it will be inferred from the *_HOME
637
+ variable, so it should contain the version number in it. """
638
+
639
+ # FIXME: (JMRT) Use the basename might alleviate the issue with matching
640
+ # the binaries version, but we might consider to find a better solution
641
+ home = os.path.basename(home or cls.getHome())
642
+ versions = versions or cls.getSupportedVersions()
643
+
644
+ for v in versions:
645
+ if v in home:
646
+ return v
647
+
648
+ return ''
649
+
650
+ @classmethod
651
+ def validateInstallation(cls):
652
+ """
653
+ Check if the binaries are properly installed and if not, return
654
+ a list with the error messages.
655
+
656
+ The default implementation will check if the _pathVars exists.
657
+ """
658
+ try:
659
+ missing = ["%s: %s" % (var, cls.getVar(var))
660
+ for var in cls._pathVars if not os.path.exists(cls.getVar(var))]
661
+
662
+ return (["Missing paths: the variables below point to non existing paths."]
663
+ + missing + [
664
+ "Either install the software ( %s )" % DOCSITEURLS.PLUGIN_MANAGER,
665
+ "or edit the config file ( %s )" % DOCSITEURLS.CONFIG]) if missing else []
666
+ except Exception as e:
667
+ return ["validateInstallation fails: %s" % e]
668
+
669
+ @classmethod
670
+ def getUrl(cls, protClass=None):
671
+ """ Url for the plugin to point users to it"""
672
+ return cls._url
673
+
674
+ # -------------- Instance methods ----------------
675
+ def getName(self):
676
+ if self._name is None:
677
+ self._name= self.__class__.__module__
678
+
679
+ return self._name
680
+
681
+ def getPluginDir(self):
682
+ return self.getPath()
683
+
684
+ def getPluginTemplateDir(self):
685
+ return os.path.join(self.getPath(), 'templates')
686
+
687
+
688
+ def getTemplates(self):
689
+ """ Get the plugin templates from the templates directory.
690
+ If more than one template is found or passed, a dialog is raised
691
+ to choose one.
692
+ """
693
+ tempList = []
694
+ pluginName = self.getName()
695
+ tDir = self.getPluginTemplateDir()
696
+ if os.path.exists(tDir):
697
+ for file in glob.glob1(tDir, "*" + SCIPION_JSON_TEMPLATES):
698
+ t = LocalTemplate(pluginName, os.path.join(tDir, file))
699
+ tempList.append(t)
700
+
701
+ return tempList
702
+
703
+ def getPath(self):
704
+ if self._path is None:
705
+ self._path = sys.modules[self.__class__.__module__].__path__[0]
706
+
707
+ return self._path
708
+ def inDevelMode(self)-> bool:
709
+ """ Returns true if code is not in python's site-packages folder"""
710
+ if self._inDevelMode is None:
711
+ self._inDevelMode = pwutils.getPythonPackagesFolder() not in self.getPath()
712
+ return self._inDevelMode