org.apache.zeppelin.python.PythonCondaInterpreter Maven / Gradle / Ivy
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.zeppelin.python;
import org.apache.commons.lang3.StringUtils;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterException;
import org.apache.zeppelin.interpreter.InterpreterOutput;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterResult.Code;
import org.apache.zeppelin.interpreter.InterpreterResult.Type;
import org.apache.zeppelin.scheduler.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Conda support
* TODO(zjffdu) Add removing conda env
*/
public class PythonCondaInterpreter extends Interpreter {
private static Logger logger = LoggerFactory.getLogger(PythonCondaInterpreter.class);
public static final String ZEPPELIN_PYTHON = "zeppelin.python";
public static final String CONDA_PYTHON_PATH = "/bin/python";
public static final String DEFAULT_ZEPPELIN_PYTHON = "python";
public static final Pattern PATTERN_OUTPUT_ENV_LIST = Pattern.compile("([^\\s]*)[\\s*]*\\s(.*)");
public static final Pattern PATTERN_COMMAND_ENV_LIST = Pattern.compile("env\\s*list\\s?");
public static final Pattern PATTERN_COMMAND_ENV = Pattern.compile("env\\s*(.*)");
public static final Pattern PATTERN_COMMAND_LIST = Pattern.compile("list");
public static final Pattern PATTERN_COMMAND_CREATE = Pattern.compile("create\\s*(.*)");
public static final Pattern PATTERN_COMMAND_ACTIVATE = Pattern.compile("activate\\s*(.*)");
public static final Pattern PATTERN_COMMAND_DEACTIVATE = Pattern.compile("deactivate");
public static final Pattern PATTERN_COMMAND_INSTALL = Pattern.compile("install\\s*(.*)");
public static final Pattern PATTERN_COMMAND_UNINSTALL = Pattern.compile("uninstall\\s*(.*)");
public static final Pattern PATTERN_COMMAND_HELP = Pattern.compile("help");
public static final Pattern PATTERN_COMMAND_INFO = Pattern.compile("info");
private String currentCondaEnvName = StringUtils.EMPTY;
public PythonCondaInterpreter(Properties property) {
super(property);
}
@Override
public void open() throws InterpreterException {
}
@Override
public void close() {
}
@Override
public InterpreterResult interpret(String st, InterpreterContext context)
throws InterpreterException {
InterpreterOutput out = context.out;
Matcher activateMatcher = PATTERN_COMMAND_ACTIVATE.matcher(st);
Matcher createMatcher = PATTERN_COMMAND_CREATE.matcher(st);
Matcher installMatcher = PATTERN_COMMAND_INSTALL.matcher(st);
Matcher uninstallMatcher = PATTERN_COMMAND_UNINSTALL.matcher(st);
Matcher envMatcher = PATTERN_COMMAND_ENV.matcher(st);
try {
if (PATTERN_COMMAND_ENV_LIST.matcher(st).matches()) {
String result = runCondaEnvList();
return new InterpreterResult(Code.SUCCESS, Type.HTML, result);
} else if (envMatcher.matches()) {
// `envMatcher` should be used after `listEnvMatcher`
String result = runCondaEnv(getRestArgsFromMatcher(envMatcher));
return new InterpreterResult(Code.SUCCESS, Type.HTML, result);
} else if (PATTERN_COMMAND_LIST.matcher(st).matches()) {
String result = runCondaList();
return new InterpreterResult(Code.SUCCESS, Type.HTML, result);
} else if (createMatcher.matches()) {
String result = runCondaCreate(getRestArgsFromMatcher(createMatcher));
return new InterpreterResult(Code.SUCCESS, Type.HTML, result);
} else if (activateMatcher.matches()) {
String envName = activateMatcher.group(1).trim();
return runCondaActivate(envName);
} else if (PATTERN_COMMAND_DEACTIVATE.matcher(st).matches()) {
return runCondaDeactivate();
} else if (installMatcher.matches()) {
String result = runCondaInstall(getRestArgsFromMatcher(installMatcher));
return new InterpreterResult(Code.SUCCESS, Type.HTML, result);
} else if (uninstallMatcher.matches()) {
String result = runCondaUninstall(getRestArgsFromMatcher(uninstallMatcher));
return new InterpreterResult(Code.SUCCESS, Type.HTML, result);
} else if (st == null || PATTERN_COMMAND_HELP.matcher(st).matches()) {
runCondaHelp(out);
return new InterpreterResult(Code.SUCCESS);
} else if (PATTERN_COMMAND_INFO.matcher(st).matches()) {
String result = runCondaInfo();
return new InterpreterResult(Code.SUCCESS, Type.HTML, result);
} else {
return new InterpreterResult(Code.ERROR, "Not supported command: " + st);
}
} catch (RuntimeException | IOException | InterruptedException e) {
throw new InterpreterException(e);
}
}
public String getCurrentCondaEnvName() {
return currentCondaEnvName;
}
public void setCurrentCondaEnvName(String currentCondaEnvName) {
if (currentCondaEnvName == null) {
currentCondaEnvName = StringUtils.EMPTY;
}
this.currentCondaEnvName = currentCondaEnvName;
}
private void changePythonEnvironment(String envName)
throws IOException, InterruptedException, InterpreterException {
String binPath = null;
if (envName == null) {
binPath = getProperty(ZEPPELIN_PYTHON);
if (binPath == null) {
binPath = DEFAULT_ZEPPELIN_PYTHON;
}
} else {
Map envList = getCondaEnvs();
for (String name : envList.keySet()) {
if (envName.equals(name)) {
binPath = envList.get(name) + CONDA_PYTHON_PATH;
break;
}
}
}
setCurrentCondaEnvName(envName);
getInterpreterInTheSameSessionByClassName(PythonInterpreter.class, false)
.setPythonExec(binPath);
}
private void restartPythonProcess() throws InterpreterException {
logger.debug("Restarting PythonInterpreter");
PythonInterpreter pythonInterpreter =
getInterpreterInTheSameSessionByClassName(PythonInterpreter.class, false);
pythonInterpreter.close();
pythonInterpreter.open();
}
public static String runCondaCommandForTextOutput(String title, List commands)
throws IOException, InterruptedException {
String result = runCommand(commands);
return wrapCondaBasicOutputStyle(title, result);
}
private String runCondaCommandForTableOutput(String title, List commands)
throws IOException, InterruptedException {
StringBuilder sb = new StringBuilder();
String result = runCommand(commands);
// use table output for pretty output
Map envPerName = parseCondaCommonStdout(result);
return wrapCondaTableOutputStyle(title, envPerName);
}
protected Map getCondaEnvs()
throws IOException, InterruptedException {
String result = runCommand("conda", "env", "list");
Map envList = parseCondaCommonStdout(result);
return envList;
}
private String runCondaEnvList() throws IOException, InterruptedException {
return wrapCondaTableOutputStyle("Environment List", getCondaEnvs());
}
private String runCondaEnv(List restArgs)
throws IOException, InterruptedException {
restArgs.add(0, "conda");
restArgs.add(1, "env");
restArgs.add(3, "--yes"); // --yes should be inserted after command
return runCondaCommandForTextOutput(null, restArgs);
}
private InterpreterResult runCondaActivate(String envName)
throws IOException, InterruptedException, InterpreterException {
if (null == envName || envName.isEmpty()) {
return new InterpreterResult(Code.ERROR, "Env name should be specified");
}
changePythonEnvironment(envName);
restartPythonProcess();
return new InterpreterResult(Code.SUCCESS, "'" + envName + "' is activated");
}
private InterpreterResult runCondaDeactivate()
throws IOException, InterruptedException, InterpreterException {
changePythonEnvironment(null);
restartPythonProcess();
return new InterpreterResult(Code.SUCCESS, "Deactivated");
}
private String runCondaList() throws IOException, InterruptedException {
List commands = new ArrayList();
commands.add(0, "conda");
commands.add(1, "list");
if (!getCurrentCondaEnvName().isEmpty()) {
commands.add(2, "-n");
commands.add(3, getCurrentCondaEnvName());
}
return runCondaCommandForTableOutput("Installed Package List", commands);
}
private void runCondaHelp(InterpreterOutput out) {
try {
out.setType(InterpreterResult.Type.HTML);
out.writeResource("output_templates/conda_usage.html");
} catch (IOException e) {
logger.error("Can't print usage", e);
}
}
private String runCondaInfo() throws IOException, InterruptedException {
List commands = new ArrayList();
commands.add("conda");
commands.add("info");
return runCondaCommandForTextOutput("Conda Information", commands);
}
private String runCondaCreate(List restArgs)
throws IOException, InterruptedException {
restArgs.add(0, "conda");
restArgs.add(1, "create");
restArgs.add(2, "--yes");
return runCondaCommandForTextOutput("Environment Creation", restArgs);
}
private String runCondaInstall(List restArgs)
throws IOException, InterruptedException {
restArgs.add(0, "conda");
restArgs.add(1, "install");
restArgs.add(2, "--yes");
if (!getCurrentCondaEnvName().isEmpty()) {
restArgs.add(3, "-n");
restArgs.add(4, getCurrentCondaEnvName());
}
return runCondaCommandForTextOutput("Package Installation", restArgs);
}
private String runCondaUninstall(List restArgs)
throws IOException, InterruptedException {
restArgs.add(0, "conda");
restArgs.add(1, "uninstall");
restArgs.add(2, "--yes");
if (!getCurrentCondaEnvName().isEmpty()) {
restArgs.add(3, "-n");
restArgs.add(4, getCurrentCondaEnvName());
}
return runCondaCommandForTextOutput("Package Uninstallation", restArgs);
}
public static String wrapCondaBasicOutputStyle(String title, String content) {
StringBuilder sb = new StringBuilder();
if (null != title && !title.isEmpty()) {
sb.append("").append(title).append("
\n")
.append("