diff --git a/.gitignore b/.gitignore index 59a7ef26..adef4716 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ build dist *.pyc MANIFEST +.DS_Store +.idea diff --git a/ChangeLog b/ChangeLog index 0f1a7925..0cf24174 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,14 +1,11 @@ -2012-04-09 Mike Johnson - * setup.py: distutils build, pypi integration - * tests: unittesting implemented in python - * jep: added `jep` command line script for launching console.py - * exception mapping (dynamic creation of python exceptions) no longer supported - * pyjobject.c: implement richcompare using Java .equals - * pyjobject.c: missing attribute raises AttributeError - * pyjobject.c: don't change optimization flag by default - * pyjarray.c: fix for iterator, so list() works on jarrays +2012-05-05 3.1.0 - Mike Johnson + * lazy load classes imported via import hook -2007-03-19 Mike Johnson +2012-03-06 3.0.0 - Mike Johnson + * project uses distutils now, is pip installable + * added a proper import hook + +2007-03-19 Mike Johnson * pyembed.c: patch from Jon Wright to convert packed strings to float array in java. * pyembed.c: return jarray object in invoke and getValue * pyembed.c: throw exception if invocation object not found diff --git a/README.rst b/README.rst index 7b17bd24..01b43444 100644 --- a/README.rst +++ b/README.rst @@ -27,16 +27,16 @@ response. Jep is licensed zlib/libpng license to avoid linking issues. -Dependencies ------------- -* Python version >= 2.6 -* JNI >= 1.4 - Installation ------------ Simply run ``pip install jep``. +Dependencies +------------ +* Python version >= 2.6 +* JNI >= 1.4 + *Building on Mac OS X* OS X requires the `Java Developer Package and Xcode @@ -53,6 +53,19 @@ on Windows has not worked in recent years because the compilers are not widely available. If an OpenJDK build used MinGW, that'd be much more likely to work. +Running scripts +--------------- + +The ``setup.py`` script will provide a ``jep`` shortcut to make launching Java and Python easier. + +:: + + $ jep + >>> from java.lang import System + >>> System.out.println('hello, world') + hello, world + >>> + Running on \*nix ----------------- Due to some (common) difficulties with Java and C projects @@ -60,26 +73,10 @@ that dlopen libraries, you may need to set LD_PRELOAD environment variable. That's in addition to setting LD_LIBRARY_PATH if you've installed libjep into a directory not cached by ld.so. -For example, my Tomcat startup.sh script starts with this: - -:: - - #!/bin/sh - # force system to load python - export LD_PRELOAD=/usr/lib/libpython2.7.so - - # this is where my libjep.so is. - export LD_LIBRARY_PATH=/usr/local/lib - -The libpython used here is whatever you've compiled jep against. If -you don't know, try this command: +See the contents of the installed ``jep`` script for an example how to do this. +The script should have the correct values for your interpreter and virtualenv +(if present). -:: - - $ ldd /usr/local/lib/libjep.so | grep python - /usr/lib/libpython2.7.so (0x00007f74adfbd000) - -That's the libpython you want to set in LD_PRELOAD. Running the tests ----------------- @@ -90,19 +87,6 @@ The tests are run from setup.py: $ python setup.py test -Running scripts ---------------- - -There is a ``jep`` shell script to make launching Java and Python a little easier. - -:: - - $ jep - >>> from java.lang import System - >>> System.out.println('hello, world') - hello, world - >>> - Support ------- diff --git a/commands/test.py b/commands/test.py index 22f81ea9..3956b297 100644 --- a/commands/test.py +++ b/commands/test.py @@ -18,4 +18,4 @@ def finalize_options(self): pass def run(self): - spawn(['java', '-cp', 'build/java/', 'jep.Test']) + spawn(['java', '-cp', 'build/java/:tests/lib/sqlitejdbc-v056.jar', 'jep.Test']) diff --git a/jep/hook.py b/jep/hook.py index a8dbf3dc..39c32d6c 100644 --- a/jep/hook.py +++ b/jep/hook.py @@ -1,6 +1,31 @@ -from _jep import * +from _jep import findClass import sys -import imp +from types import ModuleType + + +class module(ModuleType): + """Lazy load classes not found at runtime. + + Introspecting Java packages is difficult, there is not a good + way to get a list of all classes for a package. By providing + a __getattr__ implementation for modules, this class can + try to find classes manually. + + Due to this Java limitation, some classes will not appear in dir() + but will import correctly. + """ + + def __getattr__(self, name): + try: + return super(module, self).__getattribute__(name) + except AttributeError as ae: + try: + clazz = findClass('{0}.{1}'.format(self.__name__, name)) + setattr(self, name, clazz) + return clazz + except Exception: + # should raise AttributeError, not JepException + raise ae class JepImporter(object): @@ -14,19 +39,22 @@ def find_module(self, fullname, path=None): def load_module(self, fullname): if fullname in sys.modules: - return fullname - - mod = imp.new_module(fullname) - mod.__loader__ = self + return sys.modules[fullname] + + mod = module(fullname) + mod.__dict__.update({ + '__loader__': self, + '__path__': [], + '__file__': '', + }) sys.modules[fullname] = mod - mod.__path__ = [] - mod.__file__ = '' # list of classes in package for name in self.classlist.get(fullname): setattr(mod, name.split('.')[-1], findClass(name)) return mod + sys.meta_path = [importer for importer in sys.meta_path if isinstance(importer, JepImporter)] sys.meta_path.append(JepImporter()) diff --git a/jep/version.py b/jep/version.py index 458fb8bf..346588af 100644 --- a/jep/version.py +++ b/jep/version.py @@ -1,2 +1,2 @@ -__VERSION__ = '3.0.0' +__VERSION__ = '3.0.1' VERSION = __VERSION__ diff --git a/src/jep/pyembed.c b/src/jep/pyembed.c index 98ecda7e..6c8f29fb 100644 --- a/src/jep/pyembed.c +++ b/src/jep/pyembed.c @@ -222,7 +222,7 @@ void pyembed_shutdown(void) { intptr_t pyembed_thread_init(JNIEnv *env, jobject cl, jobject caller) { JepThread *jepThread; - PyObject *tdict, *main_module, *globals; + PyObject *tdict, *mod_main, *globals; if(cl == NULL) { THROW_JEP(env, "Invalid Classloader."); @@ -247,14 +247,14 @@ intptr_t pyembed_thread_init(JNIEnv *env, jobject cl, jobject caller) { if(!cache_primitive_classes(env)) printf("WARNING: failed to get primitive class types.\n"); - main_module = PyImport_AddModule("__main__"); /* borrowed */ - if(main_module == NULL) { + mod_main = PyImport_AddModule("__main__"); /* borrowed */ + if(mod_main == NULL) { THROW_JEP(env, "Couldn't add module __main__."); PyEval_ReleaseLock(); return 0; } - globals = PyModule_GetDict(main_module); + globals = PyModule_GetDict(mod_main); Py_INCREF(globals); // init static module @@ -394,7 +394,7 @@ static PyObject* pyembed_jproxy(PyObject *self, PyObject *args) { jclass clazz; jobject cl; jobject classes; - int inum, i; + Py_ssize_t inum, i; jobject proxy; if(!PyArg_ParseTuple(args, "OO!:jproxy", @@ -437,7 +437,7 @@ static PyObject* pyembed_jproxy(PyObject *self, PyObject *args) { // now convert string list to java array - classes = (*env)->NewObjectArray(env, inum, JSTRING_TYPE, NULL); + classes = (*env)->NewObjectArray(env, (jsize) inum, JSTRING_TYPE, NULL); if(process_java_exception(env) || !classes) return NULL; @@ -448,12 +448,12 @@ static PyObject* pyembed_jproxy(PyObject *self, PyObject *args) { item = PyList_GET_ITEM(interfaces, i); if(!PyString_Check(item)) - return PyErr_Format(PyExc_ValueError, "Item %i not a string.", i); + return PyErr_Format(PyExc_ValueError, "Item %zd not a string.", i); str = PyString_AsString(item); jstr = (*env)->NewStringUTF(env, (const char *) str); - (*env)->SetObjectArrayElement(env, classes, i, jstr); + (*env)->SetObjectArrayElement(env, classes, (jsize) i, jstr); (*env)->DeleteLocalRef(env, jstr); } @@ -507,7 +507,7 @@ static PyObject* pyembed_jimport(PyObject *self, PyObject *args) { jclass clazz; jobject cl; JepThread *jepThread; - int len, i; + Py_ssize_t len, i; jobjectArray jar; char *name; @@ -623,7 +623,7 @@ static PyObject* pyembed_jimport(PyObject *self, PyObject *args) { PyObject *pclass = NULL; PyObject *memberList = NULL; - member = (*env)->GetObjectArrayElement(env, jar, i); + member = (*env)->GetObjectArrayElement(env, jar, (jsize) i); if(process_import_exception(env) || !member) { (*env)->DeleteLocalRef(env, member); continue; @@ -653,7 +653,8 @@ static PyObject* pyembed_jimport(PyObject *self, PyObject *args) { PyString_AsString(PyTuple_GET_ITEM(fromlist, 0))[0] != '*') { PyObject *pymember; - int found, i, len; + int found; + Py_ssize_t i, len; pymember = PyList_GET_ITEM( memberList, @@ -1177,7 +1178,7 @@ jobject pyembed_box_py(JNIEnv *env, PyObject *result) { if(PyInt_Check(result)) { jclass clazz; - jlong i = PyInt_AS_LONG(result); + jint i = (jint) PyInt_AS_LONG(result); clazz = (*env)->FindClass(env, "java/lang/Integer"); @@ -1547,7 +1548,7 @@ static int maybe_pyc_file(FILE *fp, /* Read only two bytes of the magic. If the file was opened in text mode, the bytes 3 and 4 of the magic (\r\n) might not be read as they are on disk. */ - long halfmagic = PyImport_GetMagicNumber() & 0xFFFF; + unsigned int halfmagic = (unsigned int) PyImport_GetMagicNumber() & 0xFFFF; unsigned char buf[2]; /* Mess: In case of -x, the stream is NOT at its start now, and ungetc() was used to push back the first newline, diff --git a/src/jep/pyjarray.c b/src/jep/pyjarray.c index b3e1b170..8c212601 100644 --- a/src/jep/pyjarray.c +++ b/src/jep/pyjarray.c @@ -68,7 +68,7 @@ jmethodID objectComponentType = 0; static void pyjarray_dealloc(PyJarray_Object *self); static int pyjarray_init(JNIEnv*, PyJarray_Object*, int, PyObject*); -static Py_ssize_t pyjarray_length(PyJarray_Object *self); +static int pyjarray_length(PyObject *self); // called internally to make new PyJarray_Object instances @@ -132,7 +132,7 @@ PyObject* pyjarray_new_v(PyObject *isnull, PyObject *args) { size = PyInt_AsLong(one); if(PyInt_Check(two)) { - typeId = PyInt_AsLong(two); + typeId = (int) PyInt_AsLong(two); if(size < 0) return PyErr_Format(PyExc_ValueError, "Invalid size %li", size); @@ -837,7 +837,7 @@ static PyObject* pyjarray_item(PyJarray_Object *self, Py_ssize_t pos) { if(self->length < 1) { PyErr_Format(PyExc_IndexError, - "array assignment index out of range: %li", pos); + "array assignment index out of range: %zd", pos); return NULL; } @@ -914,19 +914,19 @@ static PyObject* pyjarray_item(PyJarray_Object *self, Py_ssize_t pos) { } case JBOOLEAN_ID: - ret = Py_BuildValue("i", ((jboolean *) self->pinnedArray)[pos]); + ret = Py_BuildValue("i", ((jboolean *) self->pinnedArray)[(jsize) pos]); break; case JSHORT_ID: - ret = Py_BuildValue("i", ((jshort *) self->pinnedArray)[pos]); + ret = Py_BuildValue("i", ((jshort *) self->pinnedArray)[(jsize) pos]); break; case JINT_ID: - ret = Py_BuildValue("i", ((jint *) self->pinnedArray)[pos]); + ret = Py_BuildValue("i", ((jint *) self->pinnedArray)[(jsize) pos]); break; case JBYTE_ID: - ret = Py_BuildValue("i", ((jbyte *) self->pinnedArray)[pos]); + ret = Py_BuildValue("i", ((jbyte *) self->pinnedArray)[(jsize) pos]); break; case JCHAR_ID: { @@ -938,15 +938,15 @@ static PyObject* pyjarray_item(PyJarray_Object *self, Py_ssize_t pos) { } case JLONG_ID: - ret = PyLong_FromLongLong(((jlong *) self->pinnedArray)[pos]); + ret = PyLong_FromLongLong(((jlong *) self->pinnedArray)[(jsize) pos]); break; case JFLOAT_ID: - ret = PyFloat_FromDouble(((jfloat *) self->pinnedArray)[pos]); + ret = PyFloat_FromDouble(((jfloat *) self->pinnedArray)[(jsize) pos]); break; case JDOUBLE_ID: - ret = PyFloat_FromDouble(((jdouble *) self->pinnedArray)[pos]); + ret = PyFloat_FromDouble(((jdouble *) self->pinnedArray)[(jsize) pos]); break; default: @@ -1279,8 +1279,6 @@ static PyObject* pyjarray_commit(PyJarray_Object *self, PyObject *args) { static int pyjarray_contains(PyJarray_Object *self, PyObject *el) { - pyembed_get_env(); - int pos = pyjarray_index(self, el); if(PyErr_Occurred()) return -1; @@ -1292,12 +1290,12 @@ static int pyjarray_contains(PyJarray_Object *self, PyObject *el) { // shamelessly taken from listobject.c -static PyObject* pyjarray_slice(PyJarray_Object *self, - Py_ssize_t ilow, - Py_ssize_t ihigh) { +static PyObject* pyjarray_slice(PyObject *_self, Py_ssize_t ilow, Py_ssize_t ihigh) { PyJarray_Object *pyarray = NULL; jobjectArray arrayObj = NULL; PyObject *ret = NULL; + + PyJarray_Object *self = (PyJarray_Object *) self; Py_ssize_t len, i; JNIEnv *env = pyembed_get_env(); @@ -1498,13 +1496,11 @@ static PyObject* pyjarray_slice(PyJarray_Object *self, // shamelessly taken from listobject.c static PyObject* pyjarray_subscript(PyJarray_Object *self, PyObject *item) { - pyembed_get_env(); - if(PyInt_Check(item)) { long i = PyInt_AS_LONG(item); if (i < 0) i += self->length; - return pyjarray_item(self, i); + return pyjarray_item(self, (Py_ssize_t) i); } else if(PyLong_Check(item)) { long i = PyLong_AsLong(item); @@ -1512,7 +1508,7 @@ static PyObject* pyjarray_subscript(PyJarray_Object *self, PyObject *item) { return NULL; if (i < 0) i += self->length; - return pyjarray_item(self, i); + return pyjarray_item(self, (Py_ssize_t) i); } else { PyErr_SetString(PyExc_TypeError, "list indices must be integers"); @@ -1523,7 +1519,6 @@ static PyObject* pyjarray_subscript(PyJarray_Object *self, PyObject *item) { static PyObject* pyjarray_str(PyJarray_Object *self) { PyObject *ret; - pyembed_get_env(); if(!self->pinnedArray) { PyErr_SetString(PyExc_RuntimeError, "No pinned array."); @@ -1551,9 +1546,9 @@ static PyObject* pyjarray_str(PyJarray_Object *self) { // -------------------------------------------------- sequence methods -static Py_ssize_t pyjarray_length(PyJarray_Object *self) { - if(self) - return self->length; +static int pyjarray_length(PyObject *self) { + if(self && pyjarray_check(self)) + return ((PyJarray_Object *) self)->length; return 0; } @@ -1706,11 +1701,11 @@ static PyObject *pyjarrayiter_next(PyJarrayIterObject *it) { } static int pyjarrayiter_len(PyJarrayIterObject *it) { - int len; + Py_ssize_t len; if (it->it_seq) { len = (int) (it->it_seq->length - it->it_index); if (len >= 0) - return len; + return (int) len; } return 0; } diff --git a/src/jep/util.c b/src/jep/util.c index 29138bd4..9402df0d 100644 --- a/src/jep/util.c +++ b/src/jep/util.c @@ -114,7 +114,7 @@ PyObject* jobject_topystring(JNIEnv *env, jobject obj, jclass clazz) { PyObject* pystring_split_item(PyObject *str, char *split, int pos) { - PyObject *splitList, *ret; + PyObject *splitList, *ret; Py_ssize_t len; if(pos < 0) { @@ -157,8 +157,8 @@ PyObject* pystring_split_item(PyObject *str, char *split, int pos) { PyObject* pystring_split_last(PyObject *str, char *split) { - PyObject *splitList, *ret; - Py_ssize_t len; + PyObject *splitList, *ret; + Py_ssize_t len; splitList = PyObject_CallMethod(str, "split", "s", split); if(PyErr_Occurred() || !splitList) @@ -320,6 +320,10 @@ int process_java_exception(JNIEnv *env) { char *message; JepThread *jepThread; +#if USE_MAPPED_EXCEPTIONS + PyObject *tmp, *className, *pyerr; +#endif + if(!(*env)->ExceptionCheck(env)) return 0; @@ -1439,9 +1443,8 @@ jvalue convert_pyarg_jvalue(JNIEnv *env, // steals all references. // returns new reference, new reference to Py_None if not found PyObject* tuplelist_getitem(PyObject *list, PyObject *pyname) { - Py_ssize_t i; - Py_ssize_t listSize = 0; - PyObject *ret = NULL; + Py_ssize_t i, listSize; + PyObject *ret = NULL; listSize = PyList_GET_SIZE(list); for(i = 0; i < listSize; i++) { diff --git a/tests/__init__.py b/tests/__init__.py index e8023bf6..7425f092 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,5 @@ -from test_array import * +from test_jdbc import * +from test_import import * from test_call import * from test_types import * from test_exceptions import * diff --git a/tests/lib/sqlitejdbc-v056.jar b/tests/lib/sqlitejdbc-v056.jar new file mode 100644 index 00000000..f95d90eb Binary files /dev/null and b/tests/lib/sqlitejdbc-v056.jar differ diff --git a/tests/test_import.py b/tests/test_import.py new file mode 100644 index 00000000..a1105ade --- /dev/null +++ b/tests/test_import.py @@ -0,0 +1,13 @@ +import unittest +from jep import JepImporter, findClass + + +class TestImport(unittest.TestCase): + def test_java_sql(self): + from java.sql import DriverManager + + def test_not_found(self): + importer = JepImporter() + mod = importer.load_module('java.lang') + mod.Integer + self.assertRaises(AttributeError, mod.__getattr__, 'asdf') diff --git a/tests/test_jdbc.py b/tests/test_jdbc.py new file mode 100644 index 00000000..a74226b7 --- /dev/null +++ b/tests/test_jdbc.py @@ -0,0 +1,52 @@ +import unittest +from jep import findClass + + +class TestJdbc(unittest.TestCase): + def test_something(self): + """ + regression test + + example and library from: http://www.zentus.com/sqlitejdbc/ + """ + findClass('org.sqlite.JDBC') + from java.sql import DriverManager + + conn = DriverManager.getConnection("jdbc:sqlite:build/test.db") + stat = conn.createStatement() + stat.executeUpdate("drop table if exists people") + stat.executeUpdate("create table people (name, occupation)") + prep = conn.prepareStatement("insert into people values (?, ?)") + + prep.setString(1, "Gandhi") + prep.setString(2, "politics") + prep.addBatch() + prep.setString(1, "Turing") + prep.setString(2, "computers") + prep.addBatch() + prep.setString(1, "Wittgenstein") + prep.setString(2, "smartypants") + prep.addBatch() + + conn.setAutoCommit(False) + prep.executeBatch() + conn.setAutoCommit(True) + + rs = stat.executeQuery("select * from people") + self.assertTrue(rs.next()) + self.assertEqual('Gandhi', rs.getString('name')) + self.assertTrue(rs.next()) + self.assertEqual('Turing', rs.getString('name')) + self.assertTrue(rs.next()) + self.assertEqual('Wittgenstein', rs.getString('name')) + + self.assertFalse(rs.next()) + self.assertFalse(rs.next()) + self.assertFalse(rs.next()) + + rs.close() + conn.close() + + +if __name__ == '__main__': + unittest.main()