I am trying to get used to python+java interaction and so I wrote a little python-script that I wanted to execute that script from my Spring Boot Application
. That script is located in the (relative from the .java
-file) path /scripts_py/getStockPrice.py
that contains the getStockPrice
-method (see code below). So I integrated jython
and tried to execute the following CronJob
:
@Component
public class CronService {
private PythonScriptSingleton pss = PythonScriptSingleton.getInstance();
private final Logger logger = LoggerFactory.getLogger(CronService.class);
//call every 5 sec
@Scheduled(fixedRate = 5000)
public void initStockPriceAPICall() {
this.getStockPrice("NFLX");
}
public void getStockPrice(String ticker) {
String result = (String) (Object) this.pss.getFunc_getPriceForTicker().__call__(new PyString(ticker));
try {
logger.info("Price is " + result);
} catch (NullPointerException e) {
logger.info("Catched NPE");
}
}
}
The PythongScriptSingleton
(the idea was, that I probably need to execute that script a lot of times - so I tried to go for a singleton
instance of this class that holds the script so I do not need to recompile it every time:
public class PythonScriptSingleton {
private static PythonScriptSingleton ps;
public static PythonScriptSingleton getInstance() {
if (ps == null) {
ps = new PythonScriptSingleton();
ps.initScript();
}
return ps;
}
private PyObject func_getPriceForTicker;
private PythonScriptSingleton() {
}
private void initScript() {
PythonInterpreter interpreter = new PythonInterpreter();
String fileUrlPath = "/scripts_py";
String scriptName = "getStockPrice.py";
interpreter.exec("import sys\n" + "import os \n" + "sys.path.append('" + fileUrlPath + "')\n" + "from "
+ scriptName + " import * \n");
String funcName = "getStockPrice";
PyObject someFunc = interpreter.get(funcName);
this.func_getPriceForTicker = someFunc;
interpreter.close();
}
public PyObject getFunc_getPriceForTicker() {
return func_getPriceForTicker;
}
}
The Python Script:
from yahoo_fin import stock_info as si
def getStockPrice(ticker):
price = si.get_live_price(ticker)
return price
I am using the embedded tomcat
with Spring Boot
(executable JAR
-File) and jython-standalone 2.7.2
:
<dependency>
<groupId>org.python</groupId>
<artifactId>jython-standalone</artifactId>
<version>2.7.2</version>
</dependency>
The error i keep running into says, that the script is not defined - I tried defining it with and without the file ending (.py
), both did not change anything:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [de.fr.stockticker.pythoninteraction.CronService]: Constructor threw exception; nested exception is Traceback (most recent call last):
File "<string>", line 4, in <module>
ImportError: No module named getStockPrice
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:217) ~[spring-beans-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:87) ~[spring-beans-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1312) ~[spring-beans-5.2.5.RELEASE.jar:5.2.5.RELEASE]
... 81 common frames omitted
Caused by: org.python.core.PyException: ImportError: No module named getStockPrice
at org.python.core.Py.ImportError(Py.java:329) ~[jython-standalone-2.7.2.jar:2.7.2]
at org.python.core.imp.import_first(imp.java:1230) ~[jython-standalone-2.7.2.jar:2.7.2]
at org.python.core.imp.import_module_level(imp.java:1361) ~[jython-standalone-2.7.2.jar:2.7.2]
at org.python.core.imp.importName(imp.java:1528) ~[jython-standalone-2.7.2.jar:2.7.2]
at org.python.core.ImportFunction.__call__(__builtin__.java:1285) ~[jython-standalone-2.7.2.jar:2.7.2]
at org.python.core.PyObject.__call__(PyObject.java:433) ~[jython-standalone-2.7.2.jar:2.7.2]
at org.python.core.__builtin__.__import__(__builtin__.java:1232) ~[jython-standalone-2.7.2.jar:2.7.2]
at org.python.core.imp.importAll(imp.java:1647) ~[jython-standalone-2.7.2.jar:2.7.2]
at org.python.pycode._pyx0.f$0(<string>:4) ~[na:na]
at org.python.pycode._pyx0.call_function(<string>) ~[na:na]
at org.python.core.PyTableCode.call(PyTableCode.java:173) ~[jython-standalone-2.7.2.jar:2.7.2]
at org.python.core.PyCode.call(PyCode.java:18) ~[jython-standalone-2.7.2.jar:2.7.2]
at org.python.core.Py.runCode(Py.java:1687) ~[jython-standalone-2.7.2.jar:2.7.2]
at org.python.core.Py.exec(Py.java:1731) ~[jython-standalone-2.7.2.jar:2.7.2]
at org.python.util.PythonInterpreter.exec(PythonInterpreter.java:268) ~[jython-standalone-2.7.2.jar:2.7.2]
at de.fr.stockticker.pythoninteraction.PythonScriptSingleton.initScript(PythonScriptSingleton.java:28) ~[classes/:na]
at de.fr.stockticker.pythoninteraction.PythonScriptSingleton.getInstance(PythonScriptSingleton.java:13) ~[classes/:na]
at de.fr.stockticker.pythoninteraction.CronService.<init>(CronService.java:12) ~[classes/:na]
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_251]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_251]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_251]
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_251]
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:204) ~[spring-beans-5.2.5.RELEASE.jar:5.2.5.RELEASE]
... 83 common frames omitted
I was able to run your program partially using Jython - I couldn't get the stock process from yahoo which is internally depends on numpy and looks like Jython doesn't support numpy as it being cpython library.
private void initScript() {
PythonInterpreter interpreter = new PythonInterpreter();
String fileUrlPath = "/users/sagar/demo/src/main/resources/python";
interpreter.exec("import sys");
interpreter.exec("sys.path.append('" + fileUrlPath + "')");
interpreter.exec("from getStockPrice import *"); this.func_getPriceForTicker =
interpreter.get("getStockPrice", PyFunction.class);
interpreter.close();
}
You will be able to get past your error but you will see error
Missing required dependencies ['numpy']
So I tried using another java python library jep and made changes as follow
@SpringBootApplication
@EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
private final Logger logger = LoggerFactory.getLogger(DemoApplication.class);
//call every 5 sec
@Scheduled(fixedRate = 5000)
public void initStockPriceAPICall() throws JepException {
this.getStockPrice("NFLX");
}
private void getStockPrice(String ticker) throws JepException {
try (Interpreter interp = new SharedInterpreter()) {
String fileUrlPath = "/users/sagar/demo/src/main/resources/python";
interp.exec("import sys");
interp.exec("sys.path.append('" + fileUrlPath + "')");
interp.exec("from getStockPrice import *");
interp.set("ticker", ticker);
interp.exec("price = getStockPrice(ticker)");
Object result = interp.getValue("price");
logger.info("Price is " + result);
}
}
}
pom.xml
<dependency>
<groupId>black.ninia</groupId>
<artifactId>jep</artifactId>
<version>3.9.0</version>
</dependency>
Make sure to install jep module - it has native library
pip install jeb
Add java library path to load native library
-Djava.library.path=/usr/local/lib/python2.7/site-packages/jep
Reference - https://github.com/ninia/jep/wiki/Getting-Started