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
@@ -0,0 +1,484 @@
1
+ #!/usr/bin/env python
2
+ # **************************************************************************
3
+ # *
4
+ # * Authors: J.M. De la Rosa Trevin (delarosatrevin@scilifelab.se) [1]
5
+ # *
6
+ # * [1] SciLifeLab, Stockholm University
7
+ # *
8
+ # * This program is free software: you can redistribute it and/or modify
9
+ # * it under the terms of the GNU General Public License as published by
10
+ # * the Free Software Foundation, either version 3 of the License, or
11
+ # * (at your option) any later version.
12
+ # *
13
+ # * This program is distributed in the hope that it will be useful,
14
+ # * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # * GNU General Public License for more details.
17
+ # *
18
+ # * You should have received a copy of the GNU General Public License
19
+ # * along with this program. If not, 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
+ """
26
+ Main Project window implementation.
27
+ It is composed by three panels:
28
+ 1. Left: protocol tree.
29
+ 2. Right upper: VIEWS (Data/Protocols)
30
+ 3. Summary/Details
31
+ """
32
+
33
+ import logging
34
+ logger = logging.getLogger(__name__)
35
+
36
+ import os
37
+ import threading
38
+ import shlex
39
+ import subprocess
40
+ import socketserver
41
+
42
+ import pyworkflow as pw
43
+ import pyworkflow.utils as pwutils
44
+ from pyworkflow.gui.project.utils import OS
45
+ from pyworkflow.project import MenuConfig
46
+ from pyworkflow.gui import Message, Icon
47
+ from pyworkflow.gui.browser import FileBrowserWindow
48
+
49
+ from pyworkflow.gui.plotter import Plotter
50
+ from pyworkflow.gui.text import _open_cmd, openTextFileEditor
51
+ from pyworkflow.webservices import ProjectWorkflowNotifier, WorkflowRepository
52
+
53
+ from .labels import LabelsDialog
54
+ # Import possible Object commands to be handled
55
+ from .base import ProjectBaseWindow, VIEW_PROTOCOLS, VIEW_PROJECTS
56
+
57
+
58
+ class ProjectWindow(ProjectBaseWindow):
59
+ """ Main window for working in a Project. """
60
+ _OBJECT_COMMANDS = {}
61
+
62
+ def __init__(self, path, master=None):
63
+ # Load global configuration
64
+ self.projName = os.path.basename(path)
65
+ try:
66
+ projTitle = '%s (%s on %s)' % (self.projName,
67
+ pwutils.getLocalUserName(),
68
+ pwutils.getLocalHostName())
69
+ except Exception:
70
+ projTitle = self.projName
71
+
72
+ self.projPath = path
73
+ self.project = self.loadProject()
74
+
75
+ # TODO: put the menu part more nicely. From here:
76
+ menu = MenuConfig()
77
+
78
+ projMenu = menu.addSubMenu('Project')
79
+ projMenu.addSubMenu('Browse files', 'browse',
80
+ icon=Icon.FOLDER_OPEN)
81
+ projMenu.addSubMenu('Remove temporary files', 'delete',
82
+ icon=Icon.ACTION_DELETE)
83
+ projMenu.addSubMenu('Toggle color mode', 'color_mode',
84
+ shortCut="Ctrl+t", icon=Icon.ACTION_VISUALIZE)
85
+ projMenu.addSubMenu('Select all protocols', 'select all',
86
+ shortCut="Ctrl+a", icon=Icon.SELECT_ALL)
87
+ projMenu.addSubMenu('Locate a protocol', 'locate protocol',
88
+ shortCut="Ctrl+l")
89
+ projMenu.addSubMenu('', '') # add separator
90
+ projMenu.addSubMenu('Import workflow', 'load_workflow',
91
+ icon=Icon.DOWNLOAD)
92
+ projMenu.addSubMenu('Search workflow', 'search_workflow',
93
+ icon=Icon.ACTION_SEARCH)
94
+
95
+ projMenu.addSubMenu('Configuration', 'configuration',
96
+ icon=Icon.SETTINGS)
97
+
98
+ projMenu.addSubMenu('', '') # add separator
99
+ projMenu.addSubMenu('Debug Mode', 'debug mode',
100
+ shortCut="Ctrl+D", icon=Icon.DEBUG)
101
+ projMenu.addSubMenu('', '') # add separator
102
+ projMenu.addSubMenu('Notes', 'notes', icon=Icon.ACTION_EDIT)
103
+ projMenu.addSubMenu('', '') # add separator
104
+ projMenu.addSubMenu('Exit', 'exit', icon=Icon.ACTION_OUT)
105
+
106
+ helpMenu = menu.addSubMenu('Help')
107
+ helpMenu.addSubMenu('Online help', 'online_help',
108
+ icon=Icon.ACTION_EXPORT)
109
+ helpMenu.addSubMenu('About', 'about',
110
+ icon=Icon.ACTION_HELP)
111
+ helpMenu.addSubMenu('Contact support', 'contact_us',
112
+ icon=Icon.ACTION_HELP)
113
+
114
+ self.menuCfg = menu
115
+
116
+ if self.project.openedAsReadOnly():
117
+ self.projName += "<READ ONLY>"
118
+
119
+ # Notify about the workflow in this project
120
+ self.selectedProtocol = None
121
+ self.showGraph = False
122
+ Plotter.setBackend('TkAgg')
123
+ ProjectBaseWindow.__init__(self, projTitle, master,
124
+ minsize=(90, 50), icon=Icon.SCIPION_ICON_PROJ, _class=self.projName)
125
+
126
+ OS.handler().maximizeWindow(self.root)
127
+
128
+ self.switchView(VIEW_PROTOCOLS)
129
+
130
+ self.initProjectTCPServer() # Socket thread to communicate with clients
131
+
132
+ ProjectWorkflowNotifier(self.project).notifyWorkflow()
133
+
134
+ def createHeaderFrame(self, parent):
135
+ """Create the header and add the view selection frame at the right."""
136
+ header = ProjectBaseWindow.createHeaderFrame(self, parent)
137
+ self.addViewList(header)
138
+ return header
139
+
140
+ def getSettings(self):
141
+ return self.settings
142
+
143
+ def saveSettings(self):
144
+
145
+ try:
146
+ self.settings.write()
147
+ except Exception as ex:
148
+ logger.error(Message.NO_SAVE_SETTINGS, exc_info=ex)
149
+
150
+ def _onClosing(self):
151
+ if not self.project.openedAsReadOnly():
152
+ self.saveSettings()
153
+
154
+ ProjectBaseWindow._onClosing(self)
155
+
156
+ def loadProject(self):
157
+ proj = pw.project.Project(pw.Config.getDomain(), self.projPath)
158
+ proj.configureLogging()
159
+ proj.load()
160
+
161
+ # Check if we have settings.sqlite, generate if not
162
+ settingsPath = os.path.join(proj.path, proj.settingsPath)
163
+ if os.path.exists(settingsPath):
164
+ self.settings = proj.getSettings()
165
+ else:
166
+ logger.info('Warning: settings.sqlite not found! '
167
+ 'Creating default settings..')
168
+ self.settings = proj.createSettings()
169
+
170
+ return proj
171
+
172
+ # The next functions are callbacks from the menu options.
173
+ # See how it is done in pyworkflow/gui/gui.py:Window._addMenuChilds()
174
+ #
175
+ def onBrowseFiles(self):
176
+ # Project -> Browse files
177
+ FileBrowserWindow("Browse Project files",
178
+ self, self.project.getPath(''),
179
+ selectButton=None # we will select nothing
180
+ ).show()
181
+
182
+ def onDebugMode(self):
183
+ pw.Config.toggleDebug()
184
+
185
+ def onNotes(self):
186
+ notes_program = pw.Config.SCIPION_NOTES_PROGRAM
187
+ notes_args = pw.Config.SCIPION_NOTES_ARGS
188
+ args = []
189
+ notes_file = self.project.getPath('Logs', pw.Config.SCIPION_NOTES_FILE)
190
+
191
+ # If notesFile does not exist, it is created and an explanation/documentation comment is added at the top.
192
+ if not os.path.exists(notes_file):
193
+ f = open(notes_file, 'a')
194
+ f.write(pw.genNotesHeading())
195
+ f.close()
196
+
197
+ # Then, it will be opened as specified in the conf
198
+ if notes_program:
199
+ args.append(notes_program)
200
+ # Custom arguments
201
+ if notes_args:
202
+ args.append(notes_args)
203
+ args.append(notes_file)
204
+ subprocess.Popen(args) # nonblocking
205
+ else:
206
+ # if no program has been selected
207
+ # xdg-open will try to guess but
208
+ # if the file does not exist it
209
+ # will return an error so If the file does
210
+ # not exist I will create an empty one
211
+ # 'a' will avoid accidental truncation
212
+ openTextFileEditor(notes_file)
213
+
214
+ def onRemoveTemporaryFiles(self):
215
+ # Project -> Remove temporary files
216
+ tmpPath = os.path.join(self.project.path, self.project.tmpPath)
217
+ n = 0
218
+ try:
219
+ for fname in os.listdir(tmpPath):
220
+ fpath = "%s/%s" % (tmpPath, fname)
221
+ if os.path.isfile(fpath):
222
+ os.remove(fpath)
223
+ n += 1
224
+ # TODO: think what to do with directories. Delete? Report?
225
+ self.showInfo("Deleted content of %s -- %d file(s)." % (tmpPath, n))
226
+ except Exception as e:
227
+ self.showError(str(e))
228
+
229
+ def _loadWorkflow(self, obj):
230
+ try:
231
+ self.getViewWidget().info('Importing workflow %s' % obj.getPath())
232
+ self.project.loadProtocols(obj.getPath())
233
+ self.getViewWidget().updateRunsGraph(True)
234
+ self.getViewWidget().cleanInfo()
235
+ except Exception as ex:
236
+ self.showError(str(ex), exception=ex)
237
+
238
+ def onImportWorkflow(self):
239
+ FileBrowserWindow("Select workflow .json file",
240
+ self, self.project.getPath(''),
241
+ onSelect=self._loadWorkflow,
242
+ selectButton='Import'
243
+ ).show()
244
+
245
+ def onSearchWorkflow(self):
246
+ WorkflowRepository().search()
247
+
248
+ def onToggleColorMode(self):
249
+ self.getViewWidget()._toggleColorScheme(None)
250
+
251
+ def onSelectAllProtocols(self):
252
+ self.getViewWidget()._selectAllProtocols(None)
253
+
254
+ def onLocateAProtocol(self):
255
+ self.getViewWidget()._locateProtocol(None)
256
+
257
+ def manageLabels(self):
258
+
259
+ labels = self.project.settings.getLabels()
260
+ dialog = LabelsDialog(self.root,
261
+ labels,
262
+ allowSelect=True)
263
+
264
+ # Scan for renamed labels to update node info...
265
+ labelsRenamed = dict()
266
+ for label in labels:
267
+ if label.hasOldName():
268
+ oldName = label.getOldName()
269
+ newName = label.getName()
270
+ logger.info("Label %s renamed to %s" % (oldName, newName))
271
+ labelsRenamed[oldName] = newName
272
+ label.clearOldName()
273
+
274
+ # If there are labels renamed
275
+ if labelsRenamed:
276
+ logger.info("Updating labels of protocols after renaming.")
277
+ labels.updateDict()
278
+
279
+ for node in self.project.settings.getNodes():
280
+ nodeLabels = node.getLabels()
281
+ for index, nodeLabel in enumerate(nodeLabels):
282
+
283
+ newLabel = labelsRenamed.get(nodeLabel, None)
284
+ if newLabel is not None:
285
+ logger.info("Label %s found in %s. Updating it to %s" % (nodeLabel,node, newLabel))
286
+ nodeLabels[index] = newLabel
287
+
288
+ return dialog
289
+
290
+ def initProjectTCPServer(self):
291
+ server = ProjectTCPServer((self.project.address, self.project.port),
292
+ ProjectTCPRequestHandler)
293
+ server.project = self.project
294
+ server.window = self
295
+ server_thread = threading.Thread(name="projectTCPserver", target=server.serve_forever)
296
+ # Exit the server thread when the main thread terminates
297
+ server_thread.daemon = True
298
+ server_thread.start()
299
+
300
+ # Seems it is not used and should be in scipion-em
301
+ # Not within scipion but used from ShowJ
302
+ def schedulePlot(self, path, *args):
303
+ # FIXME: This import should not be here
304
+ from pwem.viewers import EmPlotter
305
+ self.enqueue(lambda: EmPlotter.createFromFile(path, *args).show())
306
+
307
+ @classmethod
308
+ def registerObjectCommand(cls, cmd, func):
309
+ """ Register an object command to be handled when receiving the
310
+ action from showj. """
311
+ cls._OBJECT_COMMANDS[cmd] = func
312
+
313
+ def runObjectCommand(self, cmd, inputStrId, objStrId):
314
+ try:
315
+ objId = int(objStrId)
316
+ project = self.project
317
+
318
+ if os.path.isfile(inputStrId) and os.path.exists(inputStrId):
319
+ from pwem.utils import loadSetFromDb
320
+ inputObj = loadSetFromDb(inputStrId)
321
+ else:
322
+ inputId = int(inputStrId)
323
+ inputObj = project.mapper.selectById(inputId)
324
+
325
+ func = self._OBJECT_COMMANDS.get(cmd, None)
326
+
327
+ if func is None:
328
+ logger.info("Error, command '%s' not found. " % cmd)
329
+ else:
330
+ def myfunc():
331
+ func(inputObj, objId)
332
+ inputObj.close()
333
+ self.enqueue(myfunc)
334
+
335
+ except Exception as ex:
336
+ logger.error("There was an error executing object command !!!:", exc_info=ex)
337
+
338
+ def recalculateCTF(self, inputObjId, sqliteFile):
339
+ """ Load the project and launch the protocol to
340
+ create the subset.
341
+ """
342
+ # Retrieve project, input protocol and object from db
343
+ project = self.project
344
+ inputObj = project.mapper.selectById(int(inputObjId))
345
+ parentProtId = inputObj.getObjParentId()
346
+ parentProt = project.mapper.selectById(parentProtId)
347
+ protDep = project._getProtocolsDependencies([parentProt])
348
+ if protDep:
349
+ prot = project.copyProtocol(parentProt)
350
+ prot.continueRun.set(parentProt)
351
+ else:
352
+ prot = parentProt
353
+ prot.isFirstTime.set(True)
354
+
355
+ # Define the input params of the new protocol
356
+ prot.recalculate.set(True)
357
+ prot.sqliteFile.set(sqliteFile)
358
+ # Launch the protocol
359
+ self.getViewWidget().executeProtocol(prot)
360
+
361
+
362
+ class ProjectManagerWindow(ProjectBaseWindow):
363
+ """ Windows to manage all projects. """
364
+ # To allow plugins to add their own menus
365
+ _pluginMenus = dict()
366
+
367
+ def __init__(self, **kwargs):
368
+
369
+ # TODO: put the menu part more nicely. From here:
370
+ menu = MenuConfig()
371
+
372
+ fileMenu = menu.addSubMenu('File')
373
+ fileMenu.addSubMenu('Browse files', 'browse', icon=Icon.FOLDER_OPEN)
374
+ fileMenu.addSubMenu('Exit', 'exit', icon=Icon.ACTION_OUT)
375
+
376
+ confMenu = menu.addSubMenu('Configuration')
377
+ if os.path.exists(pw.Config.SCIPION_CONFIG):
378
+ confMenu.addSubMenu('General', 'general')
379
+ confMenu.addSubMenu('Hosts', 'hosts')
380
+ if os.path.exists(pw.Config.SCIPION_PROTOCOLS):
381
+ confMenu.addSubMenu('Protocols', 'protocols')
382
+ if os.path.exists(pw.Config.SCIPION_LOCAL_CONFIG):
383
+ confMenu.addSubMenu('User', 'user')
384
+
385
+ helpMenu = menu.addSubMenu('Help')
386
+ helpMenu.addSubMenu('Online help', 'online_help', icon=Icon.ACTION_EXPORT)
387
+ helpMenu.addSubMenu('About', 'about', icon=Icon.ACTION_HELP)
388
+
389
+ self.menuCfg = menu
390
+
391
+ try:
392
+ title = '%s (%s on %s)' % (Message.LABEL_PROJECTS,
393
+ pwutils.getLocalUserName(),
394
+ pwutils.getLocalHostName())
395
+ except Exception:
396
+ title = Message.LABEL_PROJECTS
397
+
398
+ ProjectBaseWindow.__init__(self, title, minsize=(750, 500),
399
+ icon=Icon.SCIPION_ICON_PROJS, **kwargs)
400
+ self.manager = pw.project.Manager()
401
+ self.switchView(VIEW_PROJECTS)
402
+
403
+ #
404
+ # The next functions are callbacks from the menu options.
405
+ # See how it is done in pyworkflow/gui/gui.py:Window._addMenuChilds()
406
+ #
407
+ def onBrowseFiles(self):
408
+ # File -> Browse files
409
+ FileBrowserWindow("Browse files", self,
410
+ pw.Config.SCIPION_USER_DATA,
411
+ selectButton=None).show()
412
+
413
+ def onGeneral(self):
414
+ # Config -> General
415
+ self._openConfigFile(pw.Config.SCIPION_CONFIG)
416
+
417
+ @staticmethod
418
+ def _openConfigFile(configFile):
419
+ """ Open an Scipion configuration file, if the user have one defined,
420
+ also open that one with the defined text editor.
421
+ """
422
+ _open_cmd(configFile)
423
+
424
+ @staticmethod
425
+ def onHosts():
426
+ # Config -> Hosts
427
+ ProjectManagerWindow._openConfigFile(pw.Config.SCIPION_HOSTS)
428
+
429
+ @staticmethod
430
+ def onProtocols():
431
+ ProjectManagerWindow._openConfigFile(pw.Config.SCIPION_PROTOCOLS)
432
+
433
+ @staticmethod
434
+ def onUser():
435
+ ProjectManagerWindow._openConfigFile(pw.Config.SCIPION_LOCAL_CONFIG)
436
+
437
+
438
+ class ProjectTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
439
+ pass
440
+
441
+
442
+ class ProjectTCPRequestHandler(socketserver.BaseRequestHandler):
443
+
444
+ def handle(self):
445
+ try:
446
+ project = self.server.project
447
+ window = self.server.window
448
+ msg = self.request.recv(1024)
449
+ msg = msg.decode()
450
+ tokens = shlex.split(msg)
451
+ if msg.startswith('run protocol'):
452
+
453
+ logger.debug("run protocol messaged arrived: %s" % msg)
454
+ protocolName = tokens[2]
455
+ protocolClass = pw.Config.getDomain().getProtocols()[protocolName]
456
+ # Create the new protocol instance and set the input values
457
+ protocol = project.newProtocol(protocolClass)
458
+
459
+ for token in tokens[3:]:
460
+ param, value = token.split('=')
461
+ attr = getattr(protocol, param, None)
462
+ if param == 'label':
463
+ protocol.setObjLabel(value)
464
+ elif attr.isPointer():
465
+ obj = project.getObject(int(value))
466
+ attr.set(obj)
467
+ elif value:
468
+ attr.set(value)
469
+ # project.launchProtocol(protocol)
470
+ # We need to enqueue the action of execute a new protocol
471
+ # to be run in the same GUI thread and avoid concurrent
472
+ # access to the project sqlite database
473
+ window.getViewWidget().executeProtocol(protocol)
474
+ elif msg.startswith('run function'):
475
+ functionName = tokens[2]
476
+ functionPointer = getattr(window, functionName)
477
+ functionPointer(*tokens[3:])
478
+ else:
479
+ answer = b'no answer available\n'
480
+ self.request.sendall(answer)
481
+ except Exception as e:
482
+ print(e)
483
+ import traceback
484
+ traceback.print_stack()
@@ -0,0 +1,154 @@
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 code provider and window to search for a protocol"""
26
+ import tkinter as tk
27
+
28
+ from pyworkflow import Config
29
+ import pyworkflow.gui as pwgui
30
+ import pyworkflow.object as pwobj
31
+ from pyworkflow.gui.dialog import SearchBaseWindow
32
+
33
+ from pyworkflow.gui.project.utils import isAFinalProtocol
34
+ from pyworkflow.gui.project.viewprotocols_extra import ProtocolTreeConfig
35
+
36
+ UPDATED = "updated"
37
+
38
+ NEW = "new"
39
+
40
+ BETA = "beta"
41
+
42
+
43
+ class ProtocolTreeProvider(pwgui.tree.ObjectTreeProvider):
44
+ """Create the tree elements for a Protocol run"""
45
+
46
+ def __init__(self, protocol):
47
+ self.protocol = protocol
48
+ # This list is create to group the protocol parameters
49
+ # in the tree display
50
+ self.status = pwobj.List(objName='_status')
51
+ self.params = pwobj.List(objName='_params')
52
+ self.statusList = ['status', 'initTime', 'endTime', 'error',
53
+ 'interactive', 'mode']
54
+
55
+ objList = [] if protocol is None else [protocol]
56
+ pwgui.tree.ObjectTreeProvider.__init__(self, objList)
57
+
58
+
59
+
60
+ class SearchProtocolWindow(SearchBaseWindow):
61
+
62
+ columnConfig = {
63
+ '#0': ('Status', {'width': 50, 'minwidth': 50, 'stretch': tk.NO}, 5),
64
+ # Heading, tree column kwargs, casting for sorting
65
+ 'protocol': ('Protocol', {'width': 300, 'stretch': tk.FALSE}, 10),
66
+ 'streaming': ('Streamified', {'width': 100, 'stretch': tk.FALSE}, 5),
67
+ 'installed': ('Installation', {'width': 110, 'stretch': tk.FALSE}, 5),
68
+ 'help': ('Help', {'minwidth': 300, 'stretch': tk.YES}, 5),
69
+ 'score': ('Score', {'width': 50, 'stretch': tk.FALSE}, 5, int)
70
+ }
71
+
72
+ def __init__(self, parentWindow, position=None):
73
+
74
+ posStr = "" if position is None else " at (%s,%s)" % position
75
+ self.position = position
76
+ super().__init__(parentWindow,
77
+ title="Add a protocol" + posStr)
78
+
79
+ def _createResultsTree(self, frame, show, columns):
80
+ return self.master.getViewWidget()._createProtocolsTree(frame, show=show, columns=columns, position=self.position)
81
+
82
+ def _onSearchClick(self, e=None):
83
+
84
+ self._resultsTree.clear()
85
+
86
+ protList = self.scoreProtocols()
87
+
88
+ # Sort by weight
89
+ protList.sort(reverse=True, key=lambda x: x[8])
90
+
91
+ self._addProtocolToTree(protList)
92
+
93
+ def scoreProtocols(self):
94
+
95
+ keyword = self._searchVar.get().lower().strip()
96
+ emProtocolsDict = Config.getDomain().getProtocols()
97
+ protList = []
98
+
99
+ for key, prot in emProtocolsDict.items():
100
+ if isAFinalProtocol(prot, key):
101
+ label = prot.getClassLabel().lower()
102
+ line = (key, label,
103
+ "installed" if prot.isInstalled() else "missing installation",
104
+ prot.getHelpText().strip().replace('\r', '').replace('\n', '').lower(),
105
+ "streamified" if prot.worksInStreaming() else "static",
106
+ BETA if prot.isBeta() else "",
107
+ NEW if prot.isNewDev() else "",
108
+ UPDATED if prot.isUpdated() else "")
109
+
110
+ line = self._addSearchWeight(line, keyword)
111
+ # something was found: weight > 0
112
+ if line[8] != 0:
113
+ protList.append(line)
114
+
115
+ return protList
116
+
117
+ @staticmethod
118
+ def _addSearchWeight(line2Search, searchtext):
119
+ # Adds a weight value for the search
120
+ weight = 0
121
+
122
+ # prioritize findings in label
123
+ if searchtext in line2Search[1]:
124
+ weight += 10
125
+
126
+ for value in line2Search[2:]:
127
+ weight += 5 if searchtext in value else 0
128
+
129
+ if " " in searchtext:
130
+ for word in searchtext.split():
131
+ if word in line2Search[1]:
132
+ weight += 3
133
+
134
+ for value in line2Search[2:]:
135
+ weight += 1 if word in value else 0
136
+
137
+ return line2Search + (weight,)
138
+
139
+ def _addProtocolToTree(self, protList):
140
+ """ Adds the items in protList to the tree
141
+
142
+ :param protList: List of tuples with all the values/colunms used in search ans shown in the tree"""
143
+
144
+ for key, label, installed, help, streamified, beta, new, updated, weight in protList:
145
+ tag = ProtocolTreeConfig.getProtocolTag(installed == 'installed',
146
+ beta == BETA,
147
+ new == NEW,
148
+ updated == UPDATED)
149
+
150
+ self._resultsTree.insert(
151
+ '', 'end', key, text="", tags=tag,
152
+ values=(label, streamified, installed, help, weight))
153
+
154
+