package de.mpicbg.knime.scripting.python;
import de.mpicbg.knime.knutils.Utils;
import de.mpicbg.knime.scripting.core.AbstractScriptingNodeModel;
import de.mpicbg.knime.scripting.core.exceptions.KnimeScriptingException;
import de.mpicbg.knime.scripting.core.rgg.TemplateUtils;
import de.mpicbg.knime.scripting.python.prefs.PythonPreferenceInitializer;
import de.mpicbg.knime.scripting.python.scripts.PythonScripts;
import de.mpicbg.knime.scripting.python.srv.LocalPythonClient;
import de.mpicbg.knime.scripting.python.srv.Python;
import de.mpicbg.knime.scripting.python.srv.PythonTempFile;
import org.eclipse.jface.preference.IPreferenceStore;
import org.knime.core.node.BufferedDataTable;
import org.knime.core.node.ExecutionContext;
import org.knime.core.node.NodeLogger;
import org.knime.core.node.port.PortObject;
import org.knime.core.node.port.PortType;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.io.*;
public abstract class AbstractPythonScriptingNodeModel extends AbstractScriptingNodeModel {
// Temp files for reading/writing the table and the script
protected PythonTempFile kInFile;
protected PythonTempFile pyOutFile;
protected PythonTempFile scriptFile;
protected Python python;
protected IPreferenceStore preferences = PythonScriptingBundleActivator.getDefault().getPreferenceStore();
protected AbstractPythonScriptingNodeModel(PortType[] inPorts, PortType[] outports) {
super(inPorts, outports, new PythonColumnSupport());
}
protected void prepareScript(Writer writer, boolean useScript) throws IOException {
// CSV read/write functions
InputStream utilsStream = PythonScripts.class.getResourceAsStream("PythonCSVUtils.py");
String readCSVCmd = kInFile != null ? "kIn = read_csv(r\"" + kInFile.getServerPath() + "\", True)" : "";
String writeCSVCmd = pyOutFile != null ? "write_csv(r\"" + pyOutFile.getServerPath() + "\", pyOut, True)" : "";
// Write the script file
writer.write(TemplateUtils.convertStreamToString(utilsStream));
writer.write("\n" + readCSVCmd + "\n");
// Insert the user-defined script here
if(useScript)
writer.write("\n" + super.prepareScript() + "\n");
writer.write("\n" + writeCSVCmd + "\n");
return;
}
@Override
public String prepareScript() throws RuntimeException {
try {
Writer writer = new BufferedWriter(new FileWriter(scriptFile.getClientFile()));
try {
prepareScript(writer, true);
} finally {
writer.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
scriptFile.upload();
return scriptFile.getServerPath();
}
/**
* Create necessary temp files
*/
protected void createTempFiles() throws RuntimeException {
try {
// Delete the previous set if they're still around
deleteTempFiles();
// Create a new set
kInFile = new PythonTempFile(python, "knime2python", ".csv");
pyOutFile = new PythonTempFile(python, "python2knime", ".csv");
scriptFile = new PythonTempFile(python, "analyze", ".py");
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
/**
* Delete all temp files if they exist and the node is so configured
*/
protected void deleteTempFiles() {
if (kInFile != null) kInFile.delete();
if (pyOutFile != null) pyOutFile.delete();
if (scriptFile != null) scriptFile.delete();
}
/**
* opens python externally and loads KNIME input data, script is put into clipboard
* @param inData
* @param exec
* @param logger
* @throws KnimeScriptingException
*/
protected void openInPython(PortObject[] inData, ExecutionContext exec, NodeLogger logger) throws KnimeScriptingException {
IPreferenceStore preferences = PythonScriptingBundleActivator.getDefault().getPreferenceStore();
// boolean local = preferences.getBoolean(PythonPreferenceInitializer.PYTHON_LOCAL);
// if (!local) throw new RuntimeException("This node can only be used with a local python executable");
python = new LocalPythonClient();
createTempFiles();
pyOutFile = null;
// Write data into csv
BufferedDataTable[] inTables = castToBDT(inData);
logger.info("Writing table to CSV file");
PythonTableConverter.convertTableToCSV(exec, inTables[0], kInFile.getClientFile(), logger);
// Create and execute script
String pythonExecPath = preferences.getString(PythonPreferenceInitializer.PYTHON_EXECUTABLE);
// get the full path of the python executable for MacOS
String pythonExecPathFull = pythonExecPath;
try {
if (Utils.isMacOSPlatform()) {
Runtime r = Runtime.getRuntime();
Process p = r.exec("which " + pythonExecPath);
p.waitFor();
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
pythonExecPathFull = reader.readLine();
}
} catch (Exception e) {
logger.error(e);
}
try {
Writer writer = new BufferedWriter(new FileWriter(scriptFile.getClientFile()));
try {
// Write a shebang to invoke the python interpreter
writer.write("#! " + pythonExecPathFull + " -i\n");
prepareScript(writer, false);
} finally {
writer.close();
}
scriptFile.getClientFile().setExecutable(true);
// Run the script
if (Utils.isMacOSPlatform()) {
Runtime.getRuntime().exec("open -a Terminal " + " " + scriptFile.getClientPath());
} else if (Utils.isWindowsPlatform()) {
Runtime.getRuntime().exec(new String[] {
"cmd",
"/k",
"start",
pythonExecPath,
"-i",
"\"" + scriptFile.getClientPath() + "\""
});
} else logger.error("Unsupported platform");
// copy the script in the clipboard
String actualScript = super.prepareScript();
if (!actualScript.isEmpty()) {
StringSelection data = new StringSelection(actualScript);
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(data, data);
}
} catch (Exception e) {
throw new KnimeScriptingException("Failed to open in Python\n" + e);
}
}
}