From 027f81aab102997b530c8ed4dd2ff9ab5e2ddda0 Mon Sep 17 00:00:00 2001 From: baztian Date: Thu, 3 Oct 2013 16:24:38 +0200 Subject: [PATCH] More convenient way to specify classpath --- README.rst | 26 ++++++++---- src/jaydebeapi/dbapi2.py | 81 ++++++++++++++++++++++++++++++++---- src/test/integration_test.py | 40 ++++-------------- 3 files changed, 100 insertions(+), 47 deletions(-) diff --git a/README.rst b/README.rst index fb9ed02..e9e1d7f 100644 --- a/README.rst +++ b/README.rst @@ -43,7 +43,7 @@ or if you are using Jython use :: $ jython setup.py install -It has been tested with Jython 2.5.2. +It has been tested with Jython 2.5.3. If you are using cPython ensure that you have installed JPype_ properly. It has been tested with JPype 0.5.4. @@ -56,15 +56,20 @@ the ``connect`` method. This gives you a DB-API_ conform connection to the database. The first argument to ``connect`` is the name of the Java driver -class. The rest of the arguments are internally passed to the Java -``DriverManager.getConnection`` method. See the Javadoc of -``DriverManager`` class for details. +class. Then you can supply a single argument or a sequence of +arguments that are internally passed to the Java +``DriverManager.getConnection`` method. Usually this is the JDBC +connection URL. See the Javadoc of ``DriverManager`` class for +details. As the next parameter you can optionally specify the +jar-Files of the driver if your classpath isn't set up sufficiently +yet. Here is an example: >>> import jaydebeapi >>> conn = jaydebeapi.connect('org.hsqldb.jdbcDriver', -... 'jdbc:hsqldb:mem', 'SA', '') +... ['jdbc:hsqldb:mem:.', 'SA', ''], +... '/path/to/hsqldb.jar',) >>> curs = conn.cursor() >>> curs.execute('create table CUSTOMER' ... '("CUST_ID" INTEGER not null,' @@ -99,7 +104,7 @@ or in Jython I have to Supported databases =================== -In theory every database with a suitable JDBC driver should work. It +In theory *every database with a suitable JDBC driver should work*. It is known to work with the following databases: +-----------------------------------------+------------------------------------------------+---------------+----------------------+ @@ -127,14 +132,16 @@ is known to work with the following databases: |for z/OS | | |without problems. | +-----------------------------------------+------------------------------------------------+---------------+----------------------+ |Oracle 11g |Oracle Thin Driver |Medium |Not thoroughly | -| | | |testst. No support for| +| | | |tests. No support for | | | | |rading of timestamps | | | | |yet. | +-----------------------------------------+------------------------------------------------+---------------+----------------------+ +|Teradata DB |terajdbc4.jar |Medium |A user reported | +| | | |success. | ++-----------------------------------------+------------------------------------------------+---------------+----------------------+ |Other databases |Other JDBC drivers |Unkown |Please test yourself | | | | |and report the | | | | |results. | -| | | | | +-----------------------------------------+------------------------------------------------+---------------+----------------------+ Contributing @@ -157,6 +164,9 @@ Changelog - 0.1.4 + - More convenient way to setup Java classpath. *Important note* + check the changes to the ``connect`` method and adapt your code. + - Set ``.rowcount`` properly. - 0.1.3 diff --git a/src/jaydebeapi/dbapi2.py b/src/jaydebeapi/dbapi2.py index 777426c..7c20fc3 100644 --- a/src/jaydebeapi/dbapi2.py +++ b/src/jaydebeapi/dbapi2.py @@ -1,6 +1,6 @@ #-*- coding: utf-8 -*- -# Copyright 2010 Bastian Bowe +# Copyright 2010, 2011, 2012, 2013 Bastian Bowe # # This file is part of JayDeBeApi. # JayDeBeApi is free software: you can redistribute it and/or modify @@ -19,6 +19,7 @@ import datetime import exceptions +import os import time import re import sys @@ -27,7 +28,7 @@ _jdbc_connect = None _java_array_byte = None -def _jdbc_connect_jython(jclassname, *args): +def _jdbc_connect_jython(jclassname, jars, libs, *args): if _converters is None: from java.sql import Types types = Types @@ -37,24 +38,63 @@ def _jdbc_connect_jython(jclassname, *args): if const_re.match(i): types_map[i] = getattr(types, i) _init_converters(types_map) - # register driver for DriverManager - __import__(jclassname) global _java_array_byte if _java_array_byte is None: import jarray def _java_array_byte(data): return jarray.array(data, 'b') + # register driver for DriverManager + jpackage = jclassname[:jclassname.rfind('.')] + dclassname = jclassname[jclassname.rfind('.') + 1:] + # print jpackage + # print dclassname + # print jpackage + from java.lang import Class + from java.lang import ClassNotFoundException + try: + Class.forName(jclassname).newInstance() + except ClassNotFoundException: + if not jars: + raise + _jython_set_classpath(jars) + Class.forName(jclassname).newInstance() from java.sql import DriverManager return DriverManager.getConnection(*args) +def _jython_set_classpath(jars): + ''' + import a jar at runtime (needed for JDBC [Class.forName]) + + adapted by Bastian Bowe from + http://stackoverflow.com/questions/3015059/jython-classpath-sys-path-and-jdbc-drivers + ''' + from java.net import URL, URLClassLoader + from java.lang import ClassLoader + from java.io import File + m = URLClassLoader.getDeclaredMethod("addURL", [URL]) + m.accessible = 1 + urls = [File(i).toURL() for i in jars] + m.invoke(ClassLoader.getSystemClassLoader(), urls) + def _prepare_jython(): global _jdbc_connect _jdbc_connect = _jdbc_connect_jython -def _jdbc_connect_jpype(jclassname, *args): +def _jdbc_connect_jpype(jclassname, jars, libs, *args): import jpype if not jpype.isJVMStarted(): - jpype.startJVM(jpype.getDefaultJVMPath()) + args = [] + if jars: + class_path = os.path.pathsep.join(jars) + args.append('-Djava.class.path=%s' % class_path) + if libs: + # path to shared libraries + libs_path = os.path.pathsep.join(libs) + args.append('-Djava.library.path=%s' % libs_path) + # jvm_path = ('/usr/lib/jvm/java-6-openjdk' + # '/jre/lib/i386/client/libjvm.so') + jvm_path = jpype.getDefaultJVMPath() + jpype.startJVM(jvm_path, *args) if not jpype.isThreadAttachedToJVM(): jpype.attachThreadToJVM() if _converters is None: @@ -176,8 +216,33 @@ def TimestampFromTicks(ticks): return apply(Timestamp, time.localtime(ticks)[:6]) # DB-API 2.0 Module Interface connect constructor -def connect(jclassname, *args): - jconn = _jdbc_connect(jclassname, *args) +def connect(jclassname, driver_args, jars=None, libs=None): + """Open a connection to a database using a JDBC driver and return + a Connection instance. + + jclassname: Full qualified Java class name of the JDBC driver. + driver_args: Argument or sequence of arguments to be passed to the + Java DriverManager.getConnection method. Usually the + database URL. See + http://docs.oracle.com/javase/6/docs/api/java/sql/DriverManager.html + for more details + jars: Jar filename or sequence of filenames for the JDBC driver + libs: Dll/so filenames or sequence of dlls/sos used as shared + library by the JDBC driver + """ + if isinstance(driver_args, basestring): + driver_args = [ driver_args ] + if jars: + if isinstance(jars, basestring): + jars = [ jars ] + else: + jars = [] + if libs: + if isinstance(libs, basestring): + libs = [ libs ] + else: + libs = [] + jconn = _jdbc_connect(jclassname, jars, libs, *driver_args) return Connection(jconn, _converters) # DB-API 2.0 Connection Object diff --git a/src/test/integration_test.py b/src/test/integration_test.py index 8bf9f73..e6aba1e 100644 --- a/src/test/integration_test.py +++ b/src/test/integration_test.py @@ -50,23 +50,6 @@ class IntegrationTest(TestCase): for i in stmts: cursor.execute(i) - def setup_jpype(self, jars, libs=None): - import jpype - if not jpype.isJVMStarted(): - jvm_path = jpype.getDefaultJVMPath() - #jvm_path = ('/usr/lib/jvm/java-6-openjdk' - # '/jre/lib/i386/client/libjvm.so') - # path to shared libraries - args = [] - if libs: - libs_path = path.pathsep.join(libs) - args.append('-Djava.library.path=%s' % libs_path) - class_path = path.pathsep.join(jars) - args.append('-Djava.class.path=%s' % class_path) - jpype.startJVM(jvm_path, *args) - if not jpype.isThreadAttachedToJVM(): - jpype.attachThreadToJVM() - def connect(self): # rename the latter connect method to run tests against # pysqlite @@ -76,30 +59,25 @@ class IntegrationTest(TestCase): return sqlite3, sqlite3.connect(':memory:') def connect(self): - jar_names = [ 'sqlitejdbc-v056.jar', 'hsqldb.jar', 'sqlite.jar' ] - jars = [ path.join(jar_dir, i) for i in jar_names ] - if is_jython(): - sys.path.extend(jars) - # print "CLASSPATH=%s" % path.pathsep.join(jars) - else: - self.setup_jpype(jars, [jar_dir]) # http://www.zentus.com/sqlitejdbc/ conn = jaydebeapi.connect('org.sqlite.JDBC', - 'jdbc:sqlite::memory:') + 'jdbc:sqlite::memory:', + path.join(jar_dir, 'sqlitejdbc-v056.jar'),) # http://hsqldb.org/ # conn = jaydebeapi.connect('org.hsqldb.jdbcDriver', - # 'jdbc:hsqldb:mem', 'SA', '') + # ['jdbc:hsqldb:mem:.', 'SA', ''], + # 'hsqldb.jar') # conn = jaydebeapi.connect('com.ibm.db2.jcc.DB2Driver', - # 'jdbc:db2://4.100.73.81:50000/db2t', - # user, passwd) + # ['jdbc:db2://4.100.73.81:50000/db2t', + # user, passwd]) # driver from http://www.ch-werner.de/javasqlite/ seems to be # crap as it returns decimal values as VARCHAR type # conn = jaydebeapi.connect('SQLite.JDBCDriver', - # 'jdbc:sqlite:/:memory:') + # 'jdbc:sqlite:/:memory:', 'sqlite.jar') # Oracle Thin Driver # conn = jaydebeapi.connect('oracle.jdbc.OracleDriver', - # 'jdbc:oracle:thin:@//hh-cluster-scan:1521/HH_TPP', - # user, passwd) + # ['jdbc:oracle:thin:@//hh-cluster-scan:1521/HH_TPP', + # user, passwd]) return jaydebeapi, conn def setUp(self):