Source code for scipion.scripts.kickoff

# -*- coding: utf-8 -*-
#!/usr/bin/env python
# **************************************************************************
# *
# * Authors:     Pablo Conesa (pconesa@cnb.csic.es)
# *
# * Unidad de  Bioinformatica of Centro Nacional de Biotecnologia , CSIC
# *
# * This program is free software; you can redistribute it and/or modify
# * it under the terms of the GNU General Public License as published by
# * the Free Software Foundation; either version 3 of the License, or
# * (at your option) any later version.
# *
# * This program is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# * GNU General Public License for more details.
# *
# * You should have received a copy of the GNU General Public License
# * along with this program; if not, write to the Free Software
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
# * 02111-1307  USA
# *
# *  All comments concerning this program package may be sent to the
# *  e-mail address 'scipion@cnb.csic.es'
# *
# **************************************************************************
"""
Creates a scipion workflow file (json formatted) base on a template.
The template may have some ~placeholders~ that will be overwritten with values
Template may look like this, separator is "~" and within it you can define:
~title|value|type~
Template string sits at the end of the file ready for a running streaming demo.
"""
import subprocess
import sys
import os
import re
import tkinter as tk
import tkinter.font as tkFont
import traceback
import time

import pyworkflow as pw
import pyworkflow.utils as pwutils
from pyworkflow.gui import Message, dialog
from pyworkflow.plugin import SCIPION_JSON_TEMPLATES, Template
from pyworkflow.project import ProjectSettings, Project
import pyworkflow.gui as pwgui
from pyworkflow.gui.project.base import ProjectBaseWindow
from pyworkflow.gui.widgets import HotButton, Button
from pyworkflow.template import TemplateList
from scipion.constants import SCIPION_EP, MODE_PROJECT

# Custom labels
from scipion.utils import getExternalJsonTemplates

ENTRY = 'entry'
LABEL = 'label'
CHECKBUTTON = 'Checkbutton'
YES = "Yes"
NO = "No"

FLAG_PARAM = "--"
NOGUI_FLAG = FLAG_PARAM + "nogui"
NOSCHEDULE_FLAG = FLAG_PARAM + "noschedule"


START_BUTTON = "Start"
LEN_LABEL_IN_CHARS = 30
LABEL_ALIGN_PATTERN = '{:>%s}' % LEN_LABEL_IN_CHARS
LEN_BUTTON_IN_CHARS = 12
BUTTON_ALIGN_PATTERN = "{:^%s}" % LEN_BUTTON_IN_CHARS

FIELD_SEP = '~'
VIEW_WIZARD = 'wizardview'

# Project name files
PROJECT_NAME = 'Project name'
DO_NOT_SCHEDULE = "Cancel schedule"
DO_NOT_SHOW_GUI = "Don't show the project"

# Project regex to validate the session id name
PROJECT_PATTERN = "^\w{2}\d{4,6}-\d+$"
PROJECT_REGEX = re.compile(PROJECT_PATTERN)


[docs]class KickoffWindow(ProjectBaseWindow): """ Windows to manage all projects. """ def __init__(self, **kwargs): try: title = '%s (%s on %s)' % ('Workflow template customizer', pwutils.getLocalUserName(), pwutils.getLocalHostName()) except Exception: title = Message.LABEL_PROJECTS settings = ProjectSettings() self.generalCfg = settings.getConfig() ProjectBaseWindow.__init__(self, title, minsize=(800, 350), **kwargs) self.viewFuncs = {VIEW_WIZARD: KickoffView} self.template = kwargs.get('template', None) self.action = Message.LABEL_BUTTON_CANCEL self.switchView(VIEW_WIZARD, **kwargs)
[docs] def close(self, e=None): self.root.destroy() sys.exit(0)
[docs] def getTemplate(self): return self.template
[docs] def getAction(self): return self.action
[docs] def switchView(self, newView, **kwargs): # Destroy the previous view if exists: if self.viewWidget: self.viewWidget.grid_forget() self.viewWidget.destroy() # Create the new view: Instantiates KickoffView HERE!. self.viewWidget = self.viewFuncs[newView](self.footer, self, template=self.template) # Grid in the second row (1) self.viewWidget.grid(row=0, column=0, columnspan=10, sticky='news') self.footer.rowconfigure(0, weight=1) self.footer.columnconfigure(0, weight=1) self.view = newView
[docs]class KickoffView(tk.Frame): def __init__(self, parent, windows, template=None, **kwargs): tk.Frame.__init__(self, parent, bg='white', **kwargs) self.windows = windows self.root = windows.root self.vars = {} self.checkvars = [] self.template = template bigSize = pwgui.cfgFontSize + 2 smallSize = pwgui.cfgFontSize - 2 fontName = pwgui.cfgFontName self.bigFont = tkFont.Font(size=bigSize, family=fontName) self.bigFontBold = tkFont.Font(size=bigSize, family=fontName, weight='bold') self.projDateFont = tkFont.Font(size=smallSize, family=fontName) self.projDelFont = tkFont.Font(size=smallSize, family=fontName, weight='bold') # Body section bodyFrame = tk.Frame(self, bg='white') bodyFrame.columnconfigure(0, minsize=120) bodyFrame.columnconfigure(1, minsize=120, weight=1) bodyFrame.grid(row=0, column=0, sticky='news') self._fillContent(bodyFrame) # Add the create project button btnFrame = tk.Frame(self, bg='white') btn = HotButton(btnFrame, text=START_BUTTON, font=self.bigFontBold, command=self._onReadDataFromTemplateForm) btn.grid(row=0, column=1, sticky='ne', padx=10, pady=10) # Add the Import project button btn = Button(btnFrame, Message.LABEL_BUTTON_CANCEL, font=self.bigFontBold, command=self._closeCallback) btn.grid(row=0, column=0, sticky='ne', pady=10) btnFrame.columnconfigure(0, weight=1) btnFrame.grid(row=1, column=0, sticky='news') self.columnconfigure(0, weight=1) def _closeCallback(self): self.root.destroy() sys.exit() def _fillContent(self, frame): # Add project name self.template.genProjectName() self._addPair(PROJECT_NAME, PROJECT_NAME, 1, frame, value=self.template.projectName) self._addPair(DO_NOT_SCHEDULE, DO_NOT_SCHEDULE, 2, frame, widget=CHECKBUTTON, value=flag2Value(NOSCHEDULE_FLAG)) self._addPair(DO_NOT_SHOW_GUI, DO_NOT_SHOW_GUI, 3, frame, widget=CHECKBUTTON, value=flag2Value(NOGUI_FLAG), pady=(5, 30)) # Add template params self._addTemplateFieldsToForm(frame) def _addPair(self, text, title, r, lf, widget=ENTRY, traceCallback=None, mouseBind=False, value=None, pady=2): label = tk.Label(lf, text=text, bg='white', font=self.bigFont) label.grid(row=r, column=0, padx=(10, 5), pady=pady, sticky='nes') if not widget: return var = tk.StringVar() if value is not None: var.set(value) if widget == ENTRY: widget = tk.Entry(lf, width=30, font=self.bigFont, textvariable=var) if traceCallback: if mouseBind: # call callback on click widget.bind("<Button-1>", traceCallback, "eee") else: # call callback on type var.trace('w', traceCallback) elif widget == LABEL: widget = tk.Label(lf, font=self.bigFont, textvariable=var) elif widget == CHECKBUTTON: widget = tk.Checkbutton(lf, text="", font=self.bigFont, variable=var, onvalue=YES, offvalue=NO, bg="white") self.vars[title] = var widget.grid(row=r, column=1, sticky='news', padx=(5, 10), pady=pady) def _addTemplateFieldsToForm(self, labelFrame): row = 5 for field in self.template.params.values(): alias = field.getAlias() text = field.getTitle() if alias is None else "%s (%s)" % (field.getTitle(), alias) self._addPair(text, field.getTitle(), row, labelFrame, value=field.getValue()) row += 1 def _getVar(self, varKey): return self.vars[varKey] def _getValue(self, varKey): return self.vars[varKey].get() def _setValue(self, varKey, value): return self.vars[varKey].set(value) # noinspection PyUnusedLocal def _onReadDataFromTemplateForm(self, e=None): errors = [] # Check the entered data for field in self.template.params.values(): newValue = self._getValue(field.getTitle()) field.setValue(newValue) if not field.validate(): errors.append("%s value does not validate. Value: %s, Type: %s" % (field.getTitle(), field.getValue(), field.getType())) # Do more checks only if there are not previous errors if errors: errors.insert(0, "*Errors*:") self.windows.showError("\n - ".join(errors)) else: self.template.projectName = self._getValue(PROJECT_NAME) # Set parent with the data self.windows.template = self.template if self._getValue(DO_NOT_SCHEDULE) == YES : sys.argv.append(NOSCHEDULE_FLAG) if self._getValue(DO_NOT_SHOW_GUI) == YES : sys.argv.append(NOGUI_FLAG) self.windows.action = START_BUTTON self.windows.root.quit() self.windows.root.withdraw() return
[docs]def getTemplates(): """ Get a template or templates either from arguments or from the templates directory. If more than one template is found or passed, a dialog is raised to choose one. """ templateFolder = getExternalJsonTemplates() customTemplates = len(sys.argv) > 1 tempList = TemplateList() tempId = None if customTemplates: fileTemplate = sys.argv[1] if os.path.isfile(fileTemplate) and os.path.exists(fileTemplate): t = Template("custom_template", fileTemplate) tempList.addTemplate(t) else: tempId = sys.argv[1] # Try to find all templates from the template folder and the plugins if len(tempList.templates) == 0: tempList.addScipionTemplates(tempId) if not (tempId is not None and len(tempList.templates) == 1): tempList.addPluginTemplates(tempId) if not len(tempList.templates): raise Exception("No valid file found (*.json.template).\n" "Please, add (at least one) at %s " "or pass it/them as argument(s).\n" "\n -> Usage: scipion template [PATH.json.template]\n" "\n see 'scipion help'\n" % templateFolder) return tempList.sortListByPluginName().templates
[docs]def chooseTemplate(templates): if len(templates) == 1: chosenTemplate = templates[0] else: provider = pwgui.tree.ListTreeProviderTemplate(templates) dlg = dialog.ListDialog(None, "Workflow templates", provider, "Select one of the templates.", selectOnDoubleClick=True) if dlg.result == dialog.RESULT_CANCEL: sys.exit() chosenTemplate = dlg.values[0] print("Template to use: %s" % chosenTemplate) # Replace environment variables chosenTemplate.replaceEnvVariables() return chosenTemplate
[docs]def resolveTemplate(template): """ Resolve a template assigning CML params to the template. if not enough, a window will pop pup to ask for missing ones only""" if not assignAllParams(template): wizWindow = KickoffWindow(template=template) wizWindow.show() return wizWindow.action == START_BUTTON else: # All parameters have been assigned and template is fully populated return True
[docs]def assignAllParams(template): """ Assign CML params to the template, if missing params after assignment return False """ paramsSetted = 0 template.parseContent() if len(sys.argv) > 2: attrList = sys.argv[2:] for attr in attrList: # skipp --params if attr.startswith(FLAG_PARAM): continue aliasAttr, valAttr = attr.split('=') try: paramsSetted += template.setParamValue(aliasAttr, valAttr) except Exception as e: print(pwutils.redStr(e)) sys.exit(os.EX_DATAERR) return len(template.params) == paramsSetted return False
[docs]def launchTemplate(template): """ Launches a resolved template""" try: workflow = template.createTemplateFile() except Exception as e: workflow = None errorStr = "Couldn't create the template.\n" + str(e) print(errorStr) traceback.print_exc() if workflow is not None: # Create the project if not template.projectName: template.genProjectName() createProjectFromWorkflow(workflow, template.projectName)
[docs]def createProjectFromWorkflow(workflow, projectName): scipion = SCIPION_EP scriptsPath = pw.join('project', 'scripts') # Clean the project name as pyworkflow will do projectName = Project.cleanProjectName(projectName) # Create the project print("Creating project %s" % projectName) createProjectScript = os.path.join(scriptsPath, 'create.py') os.system("python -m %s python %s %s %s" % (scipion, createProjectScript, projectName, workflow)) # Wait 2 seconds to avoid activity time.sleep(2) if scheduleProject(): # Schedule the project scheduleProjectScript = os.path.join(scriptsPath, 'schedule.py') print("Scheduling project %s" % projectName) subprocess.Popen(["python", "-m", scipion, "python", scheduleProjectScript, projectName]) # Wait 5 seconds to avoid activity time.sleep(5) if launchGUI(): print("Showing project %s" % projectName) # Launch scipion subprocess.Popen(["python", "-m", scipion, MODE_PROJECT, projectName])
[docs]def flag2Value(flag): # Remove the flag from sys.argsv value = getFlagArg(flag) if value: sys.argv.remove(flag) return YES if value else NO
[docs]def launchGUI(): """Checks if project GUI has to be launched. Only if --noGUI param is found in sys.argv it will return False""" return not getFlagArg(NOGUI_FLAG)
[docs]def scheduleProject(): return not getFlagArg(NOSCHEDULE_FLAG)
[docs]def getFlagArg(flag): """Checks if a flag exists (True) or not (False)""" for arg in sys.argv: if flag == arg.lower(): return True # Flag not found return False
[docs]def main(): templates = getTemplates() chosenTemplate = chooseTemplate(templates) if resolveTemplate(chosenTemplate): launchTemplate(chosenTemplate) else: sys.exit(3)
if __name__ == "__main__": main()