Embedding Jepp
Running a Python script is straight-forward. The Java package name is "jep" and there's just a single class to use. Here's how I typically use Jepp within a webapp:
Jep jep = new Jep(false, SCRIPT_PATH, cl);
jep.set("query", query);
jep.runScript(SCRIPT_PATH + file);
ep.close();
Of course, this example is missing exception checking for clarity.
Jep jep = new Jep(false, SCRIPT_PATH, cl);
Creates a new Jep instance with interative turned off, the include path set to SCRIPT_PATH, and a classloader cl. Internally, the libjep.so library is loaded and a new subinterpreter is created within Python.
jep.set("query", query);
This sets a Java object in the python jep module created at runtime. As any other Java call, this is done by reference. If the script alters the query object, the caller's query will also be changed. Both the script and the caller refer to the same object instance.
jep.runScript(SCRIPT_PATH + file);
This would simply run the python script specified by the file name as a string. The file must include the full path and must be readable.
Thread.currentThread().getContextClassLoader();
Tomcat uses a custom ClassLoader for each webapp. In order for Jep to access any of the webapp's objects, they must share the ClassLoader. This is the ClassLoader I would use from within Tomcat (the "cl" object passed to the constructor). There is also a setClassLoader() method.
jep.close();
This releases the Python subinterpreter and Python will garbage collect any object created during the script's execution. This must be called or you may see large memory usage on a busy server.
Installing With Tomcat
JNI libraries cannot be loaded more than once. If Jepp is to be used in multiple webapps, this means the jep.jar should be placed in the shared/lib folder. When running a Python script, the correct ClassLoader must be used (as well as the "jep.forName" function in the script). Also, because of some (common) difficulties with Java and C projects that dlopen libraries, you'll need to set LD_PRELOAD environment variable. That's in addition to setting LD_LIBRARY_PATH.
For example, my development Tomcat startup.sh script starts with this:
#!/bin/sh
# java memory setting
export JAVA_OPTS='-Xmx64m'
# force system to load python
export LD_PRELOAD=/usr/lib/libpython2.3.so.1.0
# this is where my libjep.so is.
export LD_LIBRARY_PATH=/share/jepp/jep/.libs
Adding some heap memory is a good idea, too.
The libpython used here is whatever you've compiled jep against. If you don't know, run this command:
$ ldd .libs/libjep.so.1.0.0
< ... blah ... >
libpython2.3.so.1.0 => /usr/lib/libpython2.3.so.1.0 (0x40023000)
That's the libpython you want to set in LD_PRELOAD. Unfortunately, this means you'll have to change the script if you upgrade Python. If it is the wrong library, you'll get an UnsatisfiedLinkError exception. Unset, you'll get a Java exception similar to: "Python Encountered: exceptions.ImportError: /usr/lib/python2.3/lib-dynload/datetime.so: undefined symbol: PyObject_GenericGetAttr". You may ignore setting LD_PRELOAD if you don't intend to use any of Python's native modules.
Using the Console
Jepp's console provides an easy way to examine scripts and learn how to use new APIs. The console.py script included in the distribution is fully readline capable, provided that your platform supports it, and acts similarly to running `python`. Unfortunately, it currently crashes on most platforms if you use Control-C. There's a great disagreement among Python, Java and readline as to who gets to process the INT signal, but hopefully I will find a way to make that work in the future.
That's annoying, but it doesn't prevent the console.py script from being useful. To run a Python script, you can use the Run class provided in the jep.jar file. You should also export the LD_LIBRARY_PATH and LD_PRELOAD variables like above. Then, try this:
$ java -classpath jep.jar jep.Run console.py
Setting up Python interpreter...
>>>
Of course, that assumes the jep.jar and console.py files are in your current folder. That's it!
Features
Jepp should have everything needed to script a Java application. You may call constructors, types are detected and converted, and exceptions are mapped. For example:
import jep
import *
java.io import FileInputStream
try:
fin = FileInputStream('adsf')
except(jep.FileNotFoundException):
print 'Invalid file'
The constructor for FileInputStream declares that FileNotFoundException is thrown. Jepp resolves this to a Python exception by the same name when the method is called.
To make an Integer object:
from java.io import *
integer = Integer(12) # instantiate
This is useful for boxing primitives, but -- like in Java itself -- this is a much slower operation than using a primitive.
You may also use primitive arrays from within Python by using the JArray object. Consider the script:
>>> from jep import *
>>> ar = jarray(5, JINT_ID, 2)
>>> print ar[3]
2
This creates a primitive Java array "ar", length 5, and initializes it with the int value 2. The third parameter to jarray() is optional and is ignored for object arrays. The resulting Python object can be used much like a PyList:
>>> ar[1] = -2
>>> print ar[1:3][0] # slice returns jarray object
-2
>>> for val in ar:
... print val,
...
2 -2 2 2 2
Project Scope
Jep is not an extension of Python, it already has many great modules. If you need to invoke the JVM, the JPype project is your best bet.