zeppelin-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From m...@apache.org
Subject zeppelin git commit: [ZEPPELIN-1671] Conda interpreter
Date Mon, 21 Nov 2016 16:32:39 GMT
Repository: zeppelin
Updated Branches:
  refs/heads/master 1375379a2 -> 366590150


[ZEPPELIN-1671] Conda interpreter

### What is this PR for?
Conda interpreter that manages conda environment for PythonInterpreter

### What type of PR is it?
Feature

### Todos
* [x] - Basic impl
* [x] - update doc

### What is the Jira issue?
https://issues.apache.org/jira/browse/ZEPPELIN-1671

### How should this be tested?
Recreate(or create new) your python interpreter setting in gui.

List all conda env
```
%python.conda
```

Activate env
```
%python.conda activate [name]
```

Deactivate env
```
%python.conda deactivate
```

### Screenshots (if appropriate)
![conda](https://cloud.githubusercontent.com/assets/1540981/20334729/68a7ff0e-ab71-11e6-9456-b88fc252cb17.gif)

### Questions:
* Does the licenses files need update? no
* Is there breaking changes for older versions? no
* Does this needs documentation? yes

Author: Lee moon soo <moon@apache.org>

Closes #1645 from Leemoonsoo/conda and squashes the following commits:

4842b0a [Lee moon soo] Add usage in doc
d979c6a [Lee moon soo] Add unittest
b889443 [Lee moon soo] add usage template
9ae553b [Lee moon soo] Format output and add usage command
171cbeb [Lee moon soo] make sure single char interpreter name can be parsed
6b9525f [Lee moon soo] Fix unittest
1223796 [Lee moon soo] Remove unnecessary log
394cf8c [Lee moon soo] Conda interpreter implementation


Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/36659015
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/36659015
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/36659015

Branch: refs/heads/master
Commit: 366590150488e81691f9711bff9b4e2724afb8be
Parents: 1375379
Author: Lee moon soo <moon@apache.org>
Authored: Thu Nov 17 21:59:12 2016 -0800
Committer: Lee moon soo <moon@apache.org>
Committed: Mon Nov 21 08:32:35 2016 -0800

----------------------------------------------------------------------
 conf/zeppelin-site.xml.template                 |   2 +-
 docs/install/install.md                         |   2 +-
 docs/interpreter/python.md                      |  25 +++
 .../zeppelin/python/PythonCondaInterpreter.java | 192 +++++++++++++++++++
 .../zeppelin/python/PythonInterpreter.java      |  35 +++-
 .../apache/zeppelin/python/PythonProcess.java   |  18 +-
 .../src/main/resources/interpreter-setting.json |  11 ++
 .../main/resources/output_templates/usage.html  |  27 +++
 .../python/PythonCondaInterpreterTest.java      | 116 +++++++++++
 .../zeppelin/python/PythonInterpreterTest.java  |  20 ++
 ...ythonInterpreterWithPythonInstalledTest.java |  15 ++
 .../zeppelin/conf/ZeppelinConfiguration.java    |   1 +
 .../org/apache/zeppelin/notebook/Paragraph.java |  29 +--
 .../apache/zeppelin/notebook/ParagraphTest.java |  15 ++
 14 files changed, 486 insertions(+), 22 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/36659015/conf/zeppelin-site.xml.template
----------------------------------------------------------------------
diff --git a/conf/zeppelin-site.xml.template b/conf/zeppelin-site.xml.template
index bde66b9..310ffbf 100755
--- a/conf/zeppelin-site.xml.template
+++ b/conf/zeppelin-site.xml.template
@@ -190,7 +190,7 @@
 
 <property>
   <name>zeppelin.interpreters</name>
-  <value>org.apache.zeppelin.spark.SparkInterpreter,org.apache.zeppelin.spark.PySparkInterpreter,org.apache.zeppelin.rinterpreter.RRepl,org.apache.zeppelin.rinterpreter.KnitR,org.apache.zeppelin.spark.SparkRInterpreter,org.apache.zeppelin.spark.SparkSqlInterpreter,org.apache.zeppelin.spark.DepInterpreter,org.apache.zeppelin.markdown.Markdown,org.apache.zeppelin.angular.AngularInterpreter,org.apache.zeppelin.shell.ShellInterpreter,org.apache.zeppelin.file.HDFSFileInterpreter,org.apache.zeppelin.flink.FlinkInterpreter,,org.apache.zeppelin.python.PythonInterpreter,org.apache.zeppelin.lens.LensInterpreter,org.apache.zeppelin.ignite.IgniteInterpreter,org.apache.zeppelin.ignite.IgniteSqlInterpreter,org.apache.zeppelin.cassandra.CassandraInterpreter,org.apache.zeppelin.geode.GeodeOqlInterpreter,org.apache.zeppelin.postgresql.PostgreSqlInterpreter,org.apache.zeppelin.jdbc.JDBCInterpreter,org.apache.zeppelin.kylin.KylinInterpreter,org.apache.zeppelin.elasticsearch.ElasticsearchInterpreter,or
 g.apache.zeppelin.scalding.ScaldingInterpreter,org.apache.zeppelin.alluxio.AlluxioInterpreter,org.apache.zeppelin.hbase.HbaseInterpreter,org.apache.zeppelin.livy.LivySparkInterpreter,org.apache.zeppelin.livy.LivyPySparkInterpreter,org.apache.zeppelin.livy.LivySparkRInterpreter,org.apache.zeppelin.livy.LivySparkSQLInterpreter,org.apache.zeppelin.bigquery.BigQueryInterpreter,org.apache.zeppelin.beam.BeamInterpreter,org.apache.zeppelin.pig.PigInterpreter,org.apache.zeppelin.pig.PigQueryInterpreter,org.apache.zeppelin.scio.ScioInterpreter</value>
+  <value>org.apache.zeppelin.spark.SparkInterpreter,org.apache.zeppelin.spark.PySparkInterpreter,org.apache.zeppelin.rinterpreter.RRepl,org.apache.zeppelin.rinterpreter.KnitR,org.apache.zeppelin.spark.SparkRInterpreter,org.apache.zeppelin.spark.SparkSqlInterpreter,org.apache.zeppelin.spark.DepInterpreter,org.apache.zeppelin.markdown.Markdown,org.apache.zeppelin.angular.AngularInterpreter,org.apache.zeppelin.shell.ShellInterpreter,org.apache.zeppelin.file.HDFSFileInterpreter,org.apache.zeppelin.flink.FlinkInterpreter,,org.apache.zeppelin.python.PythonInterpreter,org.apache.zeppelin.python.PythonInterpreterPandasSql,org.apache.zeppelin.python.PythonCondaInterpreter,org.apache.zeppelin.lens.LensInterpreter,org.apache.zeppelin.ignite.IgniteInterpreter,org.apache.zeppelin.ignite.IgniteSqlInterpreter,org.apache.zeppelin.cassandra.CassandraInterpreter,org.apache.zeppelin.geode.GeodeOqlInterpreter,org.apache.zeppelin.postgresql.PostgreSqlInterpreter,org.apache.zeppelin.jdbc.JDBCInterpreter,
 org.apache.zeppelin.kylin.KylinInterpreter,org.apache.zeppelin.elasticsearch.ElasticsearchInterpreter,org.apache.zeppelin.scalding.ScaldingInterpreter,org.apache.zeppelin.alluxio.AlluxioInterpreter,org.apache.zeppelin.hbase.HbaseInterpreter,org.apache.zeppelin.livy.LivySparkInterpreter,org.apache.zeppelin.livy.LivyPySparkInterpreter,org.apache.zeppelin.livy.LivySparkRInterpreter,org.apache.zeppelin.livy.LivySparkSQLInterpreter,org.apache.zeppelin.bigquery.BigQueryInterpreter,org.apache.zeppelin.beam.BeamInterpreter,org.apache.zeppelin.pig.PigInterpreter,org.apache.zeppelin.pig.PigQueryInterpreter,org.apache.zeppelin.scio.ScioInterpreter</value>
   <description>Comma separated interpreter configurations. First interpreter become
a default</description>
 </property>
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/36659015/docs/install/install.md
----------------------------------------------------------------------
diff --git a/docs/install/install.md b/docs/install/install.md
index bccd129..4660247 100644
--- a/docs/install/install.md
+++ b/docs/install/install.md
@@ -95,7 +95,7 @@ Congratulations, you have successfully installed Apache Zeppelin! Here are
few s
  * Check [JDBC Interpreter](../interpreter/jdbc.html) to know more about configure and uses
multiple JDBC data sources.
 
 #### Zeppelin with Python ...
- * Check [Python interpreter](../interpreter/python.html) to know more about Matplotlib,
Pandas integration.
+ * Check [Python interpreter](../interpreter/python.html) to know more about Matplotlib,
Pandas, Conda integration.
 
 
 #### Multi-user environment ...

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/36659015/docs/interpreter/python.md
----------------------------------------------------------------------
diff --git a/docs/interpreter/python.md b/docs/interpreter/python.md
index 95069d5..19b1346 100644
--- a/docs/interpreter/python.md
+++ b/docs/interpreter/python.md
@@ -59,6 +59,31 @@ To access the help, type **help()**
 ## Python modules
 The interpreter can use all modules already installed (with pip, easy_install...)
 
+## Conda
+[Conda](http://conda.pydata.org/) is an package management system and environment management
system for python.
+`%python.conda` interpreter lets you change between environments.
+
+#### Usage
+
+List your environments
+
+```
+%python.conda
+```
+
+Activate an environment
+
+```
+%python.conda activate [ENVIRONMENT_NAME]
+```
+
+Deactivate
+
+```
+%python.conda deactivate
+```
+
+
 ## Using Zeppelin Dynamic Forms
 You can leverage [Zeppelin Dynamic Form]({{BASE_PATH}}/manual/dynamicform.html) inside your
Python code.
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/36659015/python/src/main/java/org/apache/zeppelin/python/PythonCondaInterpreter.java
----------------------------------------------------------------------
diff --git a/python/src/main/java/org/apache/zeppelin/python/PythonCondaInterpreter.java b/python/src/main/java/org/apache/zeppelin/python/PythonCondaInterpreter.java
new file mode 100644
index 0000000..6311abf
--- /dev/null
+++ b/python/src/main/java/org/apache/zeppelin/python/PythonCondaInterpreter.java
@@ -0,0 +1,192 @@
+/*
+* 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.zeppelin.interpreter.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Conda support
+ */
+public class PythonCondaInterpreter extends Interpreter {
+  Logger logger = LoggerFactory.getLogger(PythonCondaInterpreter.class);
+
+  Pattern condaEnvListPattern = Pattern.compile("([^\\s]*)[\\s*]*\\s(.*)");
+  Pattern listPattern = Pattern.compile("env\\s*list\\s?");
+  Pattern activatePattern = Pattern.compile("activate\\s*(.*)");
+  Pattern deactivatePattern = Pattern.compile("deactivate");
+  Pattern helpPattern = Pattern.compile("help");
+  String pythonCommand = null;
+
+  public PythonCondaInterpreter(Properties property) {
+    super(property);
+  }
+
+  @Override
+  public void open() {
+
+  }
+
+  @Override
+  public void close() {
+
+  }
+
+  @Override
+  public InterpreterResult interpret(String st, InterpreterContext context) {
+    InterpreterOutput out = context.out;
+
+    Matcher listMatcher = listPattern.matcher(st);
+    Matcher activateMatcher = activatePattern.matcher(st);
+    Matcher deactivateMatcher = deactivatePattern.matcher(st);
+    Matcher helpMatcher = helpPattern.matcher(st);
+
+    if (st == null || st.isEmpty() || listMatcher.matches()) {
+      listEnv(out);
+      return new InterpreterResult(InterpreterResult.Code.SUCCESS);
+    } else if (activateMatcher.matches()) {
+      String envName = activateMatcher.group(1);
+      pythonCommand = "conda run -n " + envName + " \"python -iu\"";
+      restartPythonProcess();
+      return new InterpreterResult(InterpreterResult.Code.SUCCESS, "\"" + envName + "\" activated");
+    } else if (deactivateMatcher.matches()) {
+      pythonCommand = null;
+      restartPythonProcess();
+      return new InterpreterResult(InterpreterResult.Code.SUCCESS, "Deactivated");
+    } else if (helpMatcher.matches()) {
+      printUsage(out);
+      return new InterpreterResult(InterpreterResult.Code.SUCCESS);
+    } else {
+      return new InterpreterResult(InterpreterResult.Code.ERROR, "Not supported command:
" + st);
+    }
+  }
+
+  private void restartPythonProcess() {
+    PythonInterpreter python = getPythonInterpreter();
+    python.close();
+    python.open();
+  }
+
+  protected PythonInterpreter getPythonInterpreter() {
+    LazyOpenInterpreter lazy = null;
+    PythonInterpreter python = null;
+    Interpreter p = getInterpreterInTheSameSessionByClassName(PythonInterpreter.class.getName());
+
+    while (p instanceof WrappedInterpreter) {
+      if (p instanceof LazyOpenInterpreter) {
+        lazy = (LazyOpenInterpreter) p;
+      }
+      p = ((WrappedInterpreter) p).getInnerInterpreter();
+    }
+    python = (PythonInterpreter) p;
+
+    if (lazy != null) {
+      lazy.open();
+    }
+    return python;
+  }
+
+  public String getPythonCommand() {
+    return pythonCommand;
+  }
+
+  private void listEnv(InterpreterOutput out) {
+    StringBuilder sb = createStringBuilder();
+    try {
+      int exit = runCommand(sb, "conda", "env", "list");
+      if (exit == 0) {
+        out.setType(InterpreterResult.Type.HTML);
+        out.write("<h4>Conda environments</h4>\n");
+        // start table
+        out.write("<div style=\"display:table\">\n");
+        String[] lines = sb.toString().split("\n");
+        for (String s : lines) {
+          if (s == null || s.isEmpty() || s.startsWith("#")) {
+            continue;
+          }
+          Matcher match = condaEnvListPattern.matcher(s);
+
+          if (!match.matches()) {
+            continue;
+          }
+          out.write(String.format("<div style=\"display:table-row\">" +
+              "<div style=\"display:table-cell;width:150px\">%s</div>" +
+              "<div style=\"display:table-cell;\">%s</div>" +
+              "</div>\n",
+              match.group(1), match.group(2)));
+        }
+        // end table
+        out.write("</div><br />\n");
+        out.write("<small><code>%python.conda help</code> for the usage</small>\n");
+      } else {
+        out.write("Failed to run 'conda' " + exit + "\n");
+      }
+    } catch (IOException | InterruptedException e) {
+      throw new InterpreterException(e);
+    }
+  }
+
+  private void printUsage(InterpreterOutput out) {
+    try {
+      out.setType(InterpreterResult.Type.HTML);
+      out.writeResource("output_templates/usage.html");
+    } catch (IOException e) {
+      logger.error("Can't print usage", e);
+    }
+  }
+
+  @Override
+  public void cancel(InterpreterContext context) {
+
+  }
+
+  @Override
+  public FormType getFormType() {
+    return FormType.NONE;
+  }
+
+  @Override
+  public int getProgress(InterpreterContext context) {
+    return 0;
+  }
+
+  protected int runCommand(StringBuilder sb, String ... command)
+      throws IOException, InterruptedException {
+    ProcessBuilder builder = new ProcessBuilder(command);
+    builder.redirectErrorStream(true);
+    Process process = builder.start();
+    InputStream stdout = process.getInputStream();
+    BufferedReader br = new BufferedReader(new InputStreamReader(stdout));
+    String line;
+    while ((line = br.readLine()) != null) {
+      sb.append(line);
+      sb.append("\n");
+    }
+    int r = process.waitFor(); // Let the process finish.
+    return r;
+  }
+
+  protected StringBuilder createStringBuilder() {
+    return new StringBuilder();
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/36659015/python/src/main/java/org/apache/zeppelin/python/PythonInterpreter.java
----------------------------------------------------------------------
diff --git a/python/src/main/java/org/apache/zeppelin/python/PythonInterpreter.java b/python/src/main/java/org/apache/zeppelin/python/PythonInterpreter.java
index e2fb999..e4d0615 100644
--- a/python/src/main/java/org/apache/zeppelin/python/PythonInterpreter.java
+++ b/python/src/main/java/org/apache/zeppelin/python/PythonInterpreter.java
@@ -28,12 +28,9 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.apache.zeppelin.display.GUI;
-import org.apache.zeppelin.interpreter.Interpreter;
-import org.apache.zeppelin.interpreter.InterpreterContext;
-import org.apache.zeppelin.interpreter.InterpreterResult;
+import org.apache.zeppelin.interpreter.*;
 import org.apache.zeppelin.interpreter.InterpreterResult.Code;
 import org.apache.zeppelin.interpreter.InterpreterHookRegistry.HookType;
-import org.apache.zeppelin.interpreter.InterpreterGroup;
 import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
 import org.apache.zeppelin.scheduler.Job;
 import org.apache.zeppelin.scheduler.Scheduler;
@@ -123,6 +120,7 @@ public class PythonInterpreter extends Interpreter {
     try {
       if (process != null) {
         process.close();
+        process = null;
       }
       if (gatewayServer != null) {
         gatewayServer.shutdown();
@@ -201,7 +199,12 @@ public class PythonInterpreter extends Interpreter {
 
   public PythonProcess getPythonProcess() {
     if (process == null) {
-      return new PythonProcess(getProperty(ZEPPELIN_PYTHON));
+      PythonCondaInterpreter conda = getCondaInterpreter();
+      String binPath = getProperty(ZEPPELIN_PYTHON);
+      if (conda != null && conda.getPythonCommand() != null) {
+        binPath = conda.getPythonCommand();
+      }
+      return new PythonProcess(binPath);
     } else {
       return process;
     }
@@ -281,5 +284,25 @@ public class PythonInterpreter extends Interpreter {
   public int getMaxResult() {
     return maxResult;
   }
-  
+
+
+  private PythonCondaInterpreter getCondaInterpreter() {
+    LazyOpenInterpreter lazy = null;
+    PythonCondaInterpreter conda = null;
+    Interpreter p = getInterpreterInTheSameSessionByClassName(
+        PythonCondaInterpreter.class.getName());
+
+    while (p instanceof WrappedInterpreter) {
+      if (p instanceof LazyOpenInterpreter) {
+        lazy = (LazyOpenInterpreter) p;
+      }
+      p = ((WrappedInterpreter) p).getInnerInterpreter();
+    }
+    conda = (PythonCondaInterpreter) p;
+
+    if (lazy != null) {
+      lazy.open();
+    }
+    return conda;
+  }
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/36659015/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java
----------------------------------------------------------------------
diff --git a/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java b/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java
index 190b3da..d5308e9 100644
--- a/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java
+++ b/python/src/main/java/org/apache/zeppelin/python/PythonProcess.java
@@ -49,7 +49,23 @@ public class PythonProcess {
   }
 
   public void open() throws IOException {
-    ProcessBuilder builder = new ProcessBuilder(binPath, "-iu");
+    ProcessBuilder builder;
+    boolean hasParams = binPath.split(" ").length > 1;
+    if (System.getProperty("os.name").toLowerCase().contains("windows")) {
+      if (hasParams) {
+        builder = new ProcessBuilder(binPath.split(" "));
+      } else {
+        builder = new ProcessBuilder(binPath, "-iu");
+      }
+    } else {
+      String cmd;
+      if (hasParams) {
+        cmd = binPath;
+      } else {
+        cmd = binPath + " -iu";
+      }
+      builder = new ProcessBuilder("bash", "-c", cmd);
+    }
 
     builder.redirectErrorStream(true);
     process = builder.start();

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/36659015/python/src/main/resources/interpreter-setting.json
----------------------------------------------------------------------
diff --git a/python/src/main/resources/interpreter-setting.json b/python/src/main/resources/interpreter-setting.json
index 85f57f8..953f8b4 100644
--- a/python/src/main/resources/interpreter-setting.json
+++ b/python/src/main/resources/interpreter-setting.json
@@ -32,5 +32,16 @@
       "language": "sql",
       "editOnDblClick": false
     }
+  },
+  {
+    "group": "python",
+    "name": "conda",
+    "className": "org.apache.zeppelin.python.PythonCondaInterpreter",
+    "properties": {
+    },
+    "editor":{
+      "language": "sh",
+      "editOnDblClick": false
+    }
   }
 ]

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/36659015/python/src/main/resources/output_templates/usage.html
----------------------------------------------------------------------
diff --git a/python/src/main/resources/output_templates/usage.html b/python/src/main/resources/output_templates/usage.html
new file mode 100644
index 0000000..79191b8
--- /dev/null
+++ b/python/src/main/resources/output_templates/usage.html
@@ -0,0 +1,27 @@
+<!--
+Licensed 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.
+-->
+<h4>Usage</h4>
+<div>
+  Activate an environment (python interpreter will be restarted)
+  <pre>%python.conda activate [ENV NAME]</pre>
+</div>
+<div>
+  Deactivate
+  <pre>%python.conda deactivate</pre>
+</div>
+<div>
+  List the Conda environments
+  <pre>%python.conda</pre>
+</div>
+

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/36659015/python/src/test/java/org/apache/zeppelin/python/PythonCondaInterpreterTest.java
----------------------------------------------------------------------
diff --git a/python/src/test/java/org/apache/zeppelin/python/PythonCondaInterpreterTest.java
b/python/src/test/java/org/apache/zeppelin/python/PythonCondaInterpreterTest.java
new file mode 100644
index 0000000..1622c7d
--- /dev/null
+++ b/python/src/test/java/org/apache/zeppelin/python/PythonCondaInterpreterTest.java
@@ -0,0 +1,116 @@
+/*
+* 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.zeppelin.display.GUI;
+import org.apache.zeppelin.interpreter.*;
+import org.apache.zeppelin.user.AuthenticationInfo;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Properties;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.*;
+
+public class PythonCondaInterpreterTest implements InterpreterOutputListener {
+  private PythonCondaInterpreter conda;
+  private PythonInterpreter python;
+
+  @Before
+  public void setUp() {
+    conda = spy(new PythonCondaInterpreter(new Properties()));
+    python = mock(PythonInterpreter.class);
+
+    InterpreterGroup group = new InterpreterGroup();
+    group.put("note", Arrays.asList(python, conda));
+    python.setInterpreterGroup(group);
+    conda.setInterpreterGroup(group);
+
+    doReturn(python).when(conda).getPythonInterpreter();
+  }
+
+  @Test
+  public void testListEnv() throws IOException, InterruptedException {
+    InterpreterContext context = getInterpreterContext();
+
+    StringBuilder sb = new StringBuilder();
+    sb.append("#comment\n\nenv1   *  /path1\nenv2\t/path2\n");
+
+    doReturn(sb).when(conda).createStringBuilder();
+    doReturn(0).when(conda)
+        .runCommand(any(StringBuilder.class), anyString(), anyString(), anyString());
+
+    // list available env
+    InterpreterResult result = conda.interpret("", context);
+    assertEquals(InterpreterResult.Code.SUCCESS, result.code());
+
+    String out = new String(context.out.toByteArray());
+    assertTrue(out.contains(">env1<"));
+    assertTrue(out.contains(">/path1<"));
+    assertTrue(out.contains(">env2<"));
+    assertTrue(out.contains(">/path2<"));
+  }
+
+  @Test
+  public void testActivateEnv() {
+    InterpreterContext context = getInterpreterContext();
+    conda.interpret("activate env", context);
+    verify(python, times(1)).open();
+    verify(python, times(1)).close();
+    assertEquals("conda run -n env \"python -iu\"", conda.getPythonCommand());
+  }
+
+  @Test
+  public void testDeactivate() {
+    InterpreterContext context = getInterpreterContext();
+    conda.interpret("deactivate", context);
+    verify(python, times(1)).open();
+    verify(python, times(1)).close();
+    assertEquals(null, conda.getPythonCommand());
+  }
+
+  private InterpreterContext getInterpreterContext() {
+    return new InterpreterContext(
+        "noteId",
+        "paragraphId",
+        "paragraphTitle",
+        "paragraphText",
+        new AuthenticationInfo(),
+        new HashMap<String, Object>(),
+        new GUI(),
+        null,
+        null,
+        null,
+        new InterpreterOutput(this));
+  }
+
+  @Override
+  public void onAppend(InterpreterOutput out, byte[] line) {
+
+  }
+
+  @Override
+  public void onUpdate(InterpreterOutput out, byte[] output) {
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/36659015/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterTest.java
----------------------------------------------------------------------
diff --git a/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterTest.java b/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterTest.java
index 1228ec4..52a6914 100644
--- a/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterTest.java
+++ b/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterTest.java
@@ -37,9 +37,13 @@ import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.net.Socket;
 import java.net.SocketAddress;
+import java.util.LinkedList;
 import java.util.Properties;
 import java.util.concurrent.TimeUnit;
 
+import org.apache.zeppelin.interpreter.ClassloaderInterpreter;
+import org.apache.zeppelin.interpreter.Interpreter;
+import org.apache.zeppelin.interpreter.InterpreterGroup;
 import org.apache.zeppelin.interpreter.InterpreterResult;
 import org.junit.Before;
 import org.junit.Test;
@@ -82,8 +86,15 @@ public class PythonInterpreterTest {
       }
     });
 
+    // python interpreter
     pythonInterpreter = spy(new PythonInterpreter(getPythonTestProperties()));
 
+    // create interpreter group
+    InterpreterGroup group = new InterpreterGroup();
+    group.put("note", new LinkedList<Interpreter>());
+    group.get("note").add(pythonInterpreter);
+    pythonInterpreter.setInterpreterGroup(group);
+
     when(pythonInterpreter.getPythonProcess()).thenReturn(mockPythonProcess);
     when(mockPythonProcess.sendAndGetResult(eq("\n\nimport py4j\n"))).thenReturn("ImportError");
   }
@@ -220,15 +231,24 @@ public class PythonInterpreterTest {
 
   @Test
   public void checkMultiRowErrorFails() {
+
     PythonInterpreter pythonInterpreter = new PythonInterpreter(
       PythonInterpreterTest.getPythonTestProperties()
     );
+    // create interpreter group
+    InterpreterGroup group = new InterpreterGroup();
+    group.put("note", new LinkedList<Interpreter>());
+    group.get("note").add(pythonInterpreter);
+    pythonInterpreter.setInterpreterGroup(group);
+
     pythonInterpreter.open();
+
     String codeRaiseException = "raise Exception(\"test exception\")";
     InterpreterResult ret = pythonInterpreter.interpret(codeRaiseException, null);
 
     assertNotNull("Interpreter result for raise exception is Null", ret);
 
+    System.err.println("ret = '" + ret + "'");
     assertEquals(InterpreterResult.Code.ERROR, ret.code());
     assertTrue(ret.message().length() > 0);
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/36659015/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterWithPythonInstalledTest.java
----------------------------------------------------------------------
diff --git a/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterWithPythonInstalledTest.java
b/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterWithPythonInstalledTest.java
index 383533b..bb73f6c 100644
--- a/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterWithPythonInstalledTest.java
+++ b/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterWithPythonInstalledTest.java
@@ -21,9 +21,13 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import org.apache.zeppelin.interpreter.Interpreter;
+import org.apache.zeppelin.interpreter.InterpreterGroup;
 import org.apache.zeppelin.interpreter.InterpreterResult;
 import org.junit.Test;
 
+import java.util.Arrays;
+
 /**
  * Python interpreter unit test that user real Python
  *
@@ -46,6 +50,11 @@ public class PythonInterpreterWithPythonInstalledTest {
     //given
     PythonInterpreter realPython = new PythonInterpreter(
         PythonInterpreterTest.getPythonTestProperties());
+    // create interpreter group
+    InterpreterGroup group = new InterpreterGroup();
+    group.put("note", Arrays.asList((Interpreter) realPython));
+    realPython.setInterpreterGroup(group);
+
     realPython.open();
 
     //when
@@ -65,6 +74,9 @@ public class PythonInterpreterWithPythonInstalledTest {
     //given
     PythonInterpreter realPython = new PythonInterpreter(
         PythonInterpreterTest.getPythonTestProperties());
+    InterpreterGroup group = new InterpreterGroup();
+    group.put("note", Arrays.asList((Interpreter) realPython));
+    realPython.setInterpreterGroup(group);
     realPython.open();
 
     //when
@@ -84,6 +96,9 @@ public class PythonInterpreterWithPythonInstalledTest {
     //given
     PythonInterpreter realPython = new PythonInterpreter(
             PythonInterpreterTest.getPythonTestProperties());
+    InterpreterGroup group = new InterpreterGroup();
+    group.put("note", Arrays.asList((Interpreter) realPython));
+    realPython.setInterpreterGroup(group);
     realPython.open();
 
     //when

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/36659015/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
index 368458b..e14d7cf 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
@@ -537,6 +537,7 @@ public class ZeppelinConfiguration extends XMLConfiguration {
         + "org.apache.zeppelin.flink.FlinkInterpreter,"
         + "org.apache.zeppelin.python.PythonInterpreter,"
         + "org.apache.zeppelin.python.PythonInterpreterPandasSql,"
+        + "org.apache.zeppelin.python.PythonCondaInterpreter,"
         + "org.apache.zeppelin.ignite.IgniteInterpreter,"
         + "org.apache.zeppelin.ignite.IgniteSqlInterpreter,"
         + "org.apache.zeppelin.lens.LensInterpreter,"

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/36659015/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java
index a536978..394483b 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Paragraph.java
@@ -157,24 +157,25 @@ public class Paragraph extends Job implements Serializable, Cloneable
{
       return null;
     }
 
+    String trimmed = text.trim();
+    if (!trimmed.startsWith("%")) {
+      return null;
+    }
+
     // get script head
     int scriptHeadIndex = 0;
-    for (int i = 0; i < text.length(); i++) {
-      char ch = text.charAt(i);
-      if (Character.isWhitespace(ch) || ch == '(') {
-        scriptHeadIndex = i;
+    for (int i = 0; i < trimmed.length(); i++) {
+      char ch = trimmed.charAt(i);
+      if (Character.isWhitespace(ch) || ch == '(' || ch == '\n') {
         break;
       }
+      scriptHeadIndex = i;
     }
-    if (scriptHeadIndex == 0) {
-      return null;
-    }
-    String head = text.substring(0, scriptHeadIndex);
-    if (head.startsWith("%")) {
-      return head.substring(1);
-    } else {
+    if (scriptHeadIndex < 1) {
       return null;
     }
+    String head = text.substring(1, scriptHeadIndex + 1);
+    return head;
   }
 
   public String getScriptBody() {
@@ -190,10 +191,12 @@ public class Paragraph extends Job implements Serializable, Cloneable
{
     if (magic == null) {
       return text;
     }
-    if (magic.length() + 1 >= text.length()) {
+
+    String trimmed = text.trim();
+    if (magic.length() + 1 >= trimmed.length()) {
       return "";
     }
-    return text.substring(magic.length() + 1).trim();
+    return trimmed.substring(magic.length() + 1).trim();
   }
 
   public Interpreter getRepl(String name) {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/36659015/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTest.java
b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTest.java
index 668914a..f1ebe3e 100644
--- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTest.java
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/ParagraphTest.java
@@ -53,6 +53,21 @@ public class ParagraphTest {
   }
 
   @Test
+  public void replNameAndNoBody() {
+    String text = "%md";
+    assertEquals("md", Paragraph.getRequiredReplName(text));
+    assertEquals("", Paragraph.getScriptBody(text));
+  }
+
+
+  @Test
+  public void replSingleCharName() {
+    String text = "%r a";
+    assertEquals("r", Paragraph.getRequiredReplName(text));
+    assertEquals("a", Paragraph.getScriptBody(text));
+  }
+
+  @Test
   public void replNameEndsWithWhitespace() {
     String text = "%md\r\n###Hello";
     assertEquals("md", Paragraph.getRequiredReplName(text));


Mime
View raw message