BAEL-3925 - How to call Python from Java? (#9277)

* BAEL-3491 - Check for null before calling parse in the
Double.parseDouble

* BAEL-3491 - Check for null before calling parse in the
Double.parseDouble

- Return to indentation with spaces.

* BAEL-3854 - Pattern Matching for instanceof in Java 14

* BAEL-3854 - Pattern Matching for instanceof in Java 14 - add unit test

* BAEL-3868 - Fix the integrations tests in mocks

* BAEL-3925 - How to call Python from Java

Co-authored-by: Jonathan Cook <jcook@sciops.esa.int>
This commit is contained in:
Jonathan Cook 2020-05-13 03:46:33 +02:00 committed by GitHub
parent 5d81fd3f6f
commit 00f5e0012c
7 changed files with 241 additions and 1 deletions

View File

@ -0,0 +1,5 @@
## Java Python Interop
This module contains articles about Java and Python interoperability.
### Relevant Articles:

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" ?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>java-python-interop</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>java-python-interop</name>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-modules</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.python</groupId>
<artifactId>jython-slim</artifactId>
<version>${jython.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>${commons-exec.version}</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>java-python-interop</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<properties>
<jython.version>2.7.2</jython.version>
<commons-exec.version>1.3</commons-exec.version>
<assertj.version>3.6.1</assertj.version>
</properties>
</project>

View File

@ -0,0 +1,34 @@
package com.baeldung.python.interop;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
public class ScriptEngineManagerUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(ScriptEngineManagerUtils.class);
private ScriptEngineManagerUtils() {
}
public static void listEngines() {
ScriptEngineManager manager = new ScriptEngineManager();
List<ScriptEngineFactory> engines = manager.getEngineFactories();
for (ScriptEngineFactory engine : engines) {
LOGGER.info("Engine name: {}", engine.getEngineName());
LOGGER.info("Version: {}", engine.getEngineVersion());
LOGGER.info("Language: {}", engine.getLanguageName());
LOGGER.info("Short Names:");
for (String names : engine.getNames()) {
LOGGER.info(names);
}
}
}
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -0,0 +1,131 @@
package com.baeldung.python.interop;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.util.List;
import java.util.stream.Collectors;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.SimpleScriptContext;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.PumpStreamHandler;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.python.core.PyException;
import org.python.core.PyObject;
import org.python.util.PythonInterpreter;
public class JavaPythonInteropUnitTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void givenPythonScript_whenPythonProcessInvoked_thenSuccess() throws Exception {
ProcessBuilder processBuilder = new ProcessBuilder("python", resolvePythonScriptPath("hello.py"));
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
List<String> results = readProcessOutput(process.getInputStream());
assertThat("Results should not be empty", results, is(not(empty())));
assertThat("Results should contain output of script: ", results, hasItem(containsString("Hello Baeldung Readers!!")));
int exitCode = process.waitFor();
assertEquals("No errors should be detected", 0, exitCode);
}
@Test
public void givenPythonScriptEngineIsAvailable_whenScriptInvoked_thenOutputDisplayed() throws Exception {
StringWriter output = new StringWriter();
ScriptContext context = new SimpleScriptContext();
context.setWriter(output);
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("python");
engine.eval(new FileReader(resolvePythonScriptPath("hello.py")), context);
assertEquals("Should contain script output: ", "Hello Baeldung Readers!!", output.toString()
.trim());
}
@Test
public void givenPythonInterpreter_whenPrintExecuted_thenOutputDisplayed() {
try (PythonInterpreter pyInterp = new PythonInterpreter()) {
StringWriter output = new StringWriter();
pyInterp.setOut(output);
pyInterp.exec("print('Hello Baeldung Readers!!')");
assertEquals("Should contain script output: ", "Hello Baeldung Readers!!", output.toString()
.trim());
}
}
@Test
public void givenPythonInterpreter_whenNumbersAdded_thenOutputDisplayed() {
try (PythonInterpreter pyInterp = new PythonInterpreter()) {
pyInterp.exec("x = 10+10");
PyObject x = pyInterp.get("x");
assertEquals("x: ", 20, x.asInt());
}
}
@Test
public void givenPythonInterpreter_whenErrorOccurs_thenExceptionIsThrown() {
thrown.expect(PyException.class);
thrown.expectMessage("ImportError: No module named syds");
try (PythonInterpreter pyInterp = new PythonInterpreter()) {
pyInterp.exec("import syds");
}
}
@Test
public void givenPythonScript_whenPythonProcessExecuted_thenSuccess() throws ExecuteException, IOException {
String line = "python " + resolvePythonScriptPath("hello.py");
CommandLine cmdLine = CommandLine.parse(line);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
DefaultExecutor executor = new DefaultExecutor();
executor.setStreamHandler(streamHandler);
int exitCode = executor.execute(cmdLine);
assertEquals("No errors should be detected", 0, exitCode);
assertEquals("Should contain script output: ", "Hello Baeldung Readers!!", outputStream.toString()
.trim());
}
private List<String> readProcessOutput(InputStream inputStream) throws IOException {
try (BufferedReader output = new BufferedReader(new InputStreamReader(inputStream))) {
return output.lines()
.collect(Collectors.toList());
}
}
private String resolvePythonScriptPath(String filename) {
File file = new File("src/test/resources/" + filename);
return file.getAbsolutePath();
}
}

View File

@ -0,0 +1 @@
print("Hello Baeldung Readers!!")

View File

@ -461,7 +461,8 @@
<module>java-lite</module>
<module>java-numbers</module>
<module>java-numbers-2</module>
<module>java-numbers-3</module>
<module>java-numbers-3</module>
<module>java-python-interop</module>
<module>java-rmi</module>
<module>java-spi</module>
<module>java-vavr-stream</module>