Add Python 3 compatibility.

master
baztian 2015-04-26 18:38:21 +02:00
parent affe3ae688
commit 453eb61598
10 changed files with 83 additions and 33 deletions

View File

@ -16,6 +16,7 @@ deploy:
python: python:
- '2.6' - '2.6'
- '2.7' - '2.7'
- '3.4'
env: env:
matrix: matrix:

View File

@ -8,7 +8,7 @@
.. image:: https://img.shields.io/coveralls/baztian/jaydebeapi/master.svg .. image:: https://img.shields.io/coveralls/baztian/jaydebeapi/master.svg
:target: https://coveralls.io/r/baztian/jaydebeapi :target: https://coveralls.io/r/baztian/jaydebeapi
.. image:: https://img.shields.io/badge/python-2.6,_2.7-blue.svg .. image:: https://img.shields.io/badge/python-2.6,_2.7,_3.4-blue.svg
:target: https://pypi.python.org/pypi/JayDeBeApi :target: https://pypi.python.org/pypi/JayDeBeApi
.. image:: https://img.shields.io/badge/jython-2.5.3,_2.7--rc1-blue.svg .. image:: https://img.shields.io/badge/jython-2.5.3,_2.7--rc1-blue.svg
@ -168,6 +168,9 @@ Changelog
========= =========
- Next version - unreleased - Next version - unreleased
- Python 3 support (requires JPype1 >= 0.6.0).
- 0.1.6 - 2015-04-10 - 0.1.6 - 2015-04-10
- Fix Jython handling of Java exceptions that don't subclass python Exception - Fix Jython handling of Java exceptions that don't subclass python Exception

View File

@ -1,5 +1,8 @@
#!/bin/bash #!/bin/bash
set -e set -e
cat requirements-python.txt >> requirements.txt if [ -f requirements-python-${TRAVIS_PYTHON_VERSION}.txt ]
then
cat requirements-python-${TRAVIS_PYTHON_VERSION}.txt >> requirements.txt
fi
ln -s $VIRTUAL_ENV $HOME/myvirtualenv ln -s $VIRTUAL_ENV $HOME/myvirtualenv

View File

@ -21,7 +21,6 @@ __version_info__ = (0, 1, 6)
__version__ = ".".join(str(i) for i in __version_info__) __version__ = ".".join(str(i) for i in __version_info__)
import datetime import datetime
import exceptions
import glob import glob
import os import os
import time import time
@ -29,6 +28,40 @@ import re
import sys import sys
import warnings import warnings
PY2 = sys.version_info[0] == 2
if PY2:
# Ideas stolen from the six python 2 and 3 compatibility layer
def exec_(_code_, _globs_=None, _locs_=None):
"""Execute code in a namespace."""
if _globs_ is None:
frame = sys._getframe(1)
_globs_ = frame.f_globals
if _locs_ is None:
_locs_ = frame.f_locals
del frame
elif _locs_ is None:
_locs_ = _globs_
exec("""exec _code_ in _globs_, _locs_""")
exec_("""def reraise(tp, value, tb=None):
raise tp, value, tb
""")
else:
def reraise(tp, value, tb=None):
if value is None:
value = tp()
else:
value = tp(value)
if tb:
raise value.with_traceback(tb)
raise value
if PY2:
string_type = basestring
else:
string_type = str
# Mapping from java.sql.Types attribute name to attribute value # Mapping from java.sql.Types attribute name to attribute value
_jdbc_name_to_const = None _jdbc_name_to_const = None
@ -48,7 +81,7 @@ def _handle_sql_exception_jython():
exc_type = DatabaseError exc_type = DatabaseError
else: else:
exc_type = InterfaceError exc_type = InterfaceError
raise exc_type, exc_info[1], exc_info[2] reraise(exc_type, exc_info[1], exc_info[2])
def _jdbc_connect_jython(jclassname, jars, libs, *args): def _jdbc_connect_jython(jclassname, jars, libs, *args):
if _jdbc_name_to_const is None: if _jdbc_name_to_const is None:
@ -112,7 +145,7 @@ def _handle_sql_exception_jpype():
exc_type = DatabaseError exc_type = DatabaseError
else: else:
exc_type = InterfaceError exc_type = InterfaceError
raise exc_type, exc_info[1], exc_info[2] reraise(exc_type, exc_info[1], exc_info[2])
def _jdbc_connect_jpype(jclassname, jars, libs, *driver_args): def _jdbc_connect_jpype(jclassname, jars, libs, *driver_args):
import jpype import jpype
@ -191,7 +224,7 @@ class DBAPITypeObject(object):
self.values = values self.values = values
for type_name in values: for type_name in values:
if type_name in DBAPITypeObject._mappings: if type_name in DBAPITypeObject._mappings:
raise ValueError, "Non unique mapping for type '%s'" % type_name raise ValueError("Non unique mapping for type '%s'" % type_name)
DBAPITypeObject._mappings[type_name] = self DBAPITypeObject._mappings[type_name] = self
def __cmp__(self, other): def __cmp__(self, other):
if other in self.values: if other in self.values:
@ -239,10 +272,10 @@ DATETIME = DBAPITypeObject('TIMESTAMP')
ROWID = DBAPITypeObject('ROWID') ROWID = DBAPITypeObject('ROWID')
# DB-API 2.0 Module Interface Exceptions # DB-API 2.0 Module Interface Exceptions
class Error(exceptions.StandardError): class Error(Exception):
pass pass
class Warning(exceptions.StandardError): class Warning(Exception):
pass pass
class InterfaceError(Error): class InterfaceError(Error):
@ -311,15 +344,15 @@ def connect(jclassname, driver_args, jars=None, libs=None):
libs: Dll/so filenames or sequence of dlls/sos used as shared libs: Dll/so filenames or sequence of dlls/sos used as shared
library by the JDBC driver library by the JDBC driver
""" """
if isinstance(driver_args, basestring): if isinstance(driver_args, string_type):
driver_args = [ driver_args ] driver_args = [ driver_args ]
if jars: if jars:
if isinstance(jars, basestring): if isinstance(jars, string_type):
jars = [ jars ] jars = [ jars ]
else: else:
jars = [] jars = []
if libs: if libs:
if isinstance(libs, basestring): if isinstance(libs, string_type):
libs = [ libs ] libs = [ libs ]
else: else:
libs = [] libs = []
@ -347,7 +380,7 @@ class Connection(object):
def close(self): def close(self):
if self._closed: if self._closed:
raise Error raise Error()
self.jconn.close() self.jconn.close()
self._closed = True self._closed = True
@ -436,7 +469,7 @@ class Cursor(object):
def execute(self, operation, parameters=None): def execute(self, operation, parameters=None):
if self._connection._closed: if self._connection._closed:
raise Error raise Error()
if not parameters: if not parameters:
parameters = () parameters = ()
self._close_last() self._close_last()
@ -467,7 +500,7 @@ class Cursor(object):
def fetchone(self): def fetchone(self):
if not self._rs: if not self._rs:
raise Error raise Error()
if not self._rs.next(): if not self._rs.next():
return None return None
row = [] row = []
@ -480,14 +513,14 @@ class Cursor(object):
def fetchmany(self, size=None): def fetchmany(self, size=None):
if not self._rs: if not self._rs:
raise Error raise Error()
if size is None: if size is None:
size = self.arraysize size = self.arraysize
# TODO: handle SQLException if not supported by db # TODO: handle SQLException if not supported by db
self._rs.setFetchSize(size) self._rs.setFetchSize(size)
rows = [] rows = []
row = None row = None
for i in xrange(size): for i in range(size):
row = self.fetchone() row = self.fetchone()
if row is None: if row is None:
break break
@ -554,7 +587,9 @@ def _java_to_py(java_method):
java_val = rs.getObject(col) java_val = rs.getObject(col)
if java_val is None: if java_val is None:
return return
if isinstance(java_val, (basestring, int, long, float, bool)): if PY2 and isinstance(java_val, (string_type, int, long, float, bool)):
return java_val
elif isinstance(java_val, (string_type, int, float, bool)):
return java_val return java_val
return getattr(java_val, java_method)() return getattr(java_val, java_method)()
return to_py return to_py
@ -567,7 +602,7 @@ def _init_types(types_map):
global _jdbc_name_to_const global _jdbc_name_to_const
_jdbc_name_to_const = types_map _jdbc_name_to_const = types_map
global _jdbc_const_to_name global _jdbc_const_to_name
_jdbc_const_to_name = dict((y,x) for x,y in types_map.iteritems()) _jdbc_const_to_name = dict((y,x) for x,y in types_map.items())
_init_converters(types_map) _init_converters(types_map)
def _init_converters(types_map): def _init_converters(types_map):

View File

@ -0,0 +1 @@
JPype1==0.6.0

View File

@ -0,0 +1 @@
JPype1==0.6.0

View File

@ -1 +0,0 @@
JPype1==0.5.7

View File

@ -32,16 +32,17 @@ setup(
author_email = 'bastian.dev@gmail.com', author_email = 'bastian.dev@gmail.com',
license = 'GNU LGPL', license = 'GNU LGPL',
url='https://github.com/baztian/jaydebeapi', url='https://github.com/baztian/jaydebeapi',
description=('A bridge from JDBC database drivers to Python DB-API.'), description=('Use JDBC database drivers from Python 2/3 or Jython with a DB-API.'),
long_description=file('README.rst').read(), long_description=open('README.rst').read(),
keywords = ('db api java jdbc bridge connect sql jpype jython'), keywords = ('db api java jdbc bridge connect sql jpype jython'),
classifiers = [ classifiers = [
'Development Status :: 3 - Alpha', 'Development Status :: 4 - Beta',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
'Programming Language :: Java', 'Programming Language :: Java',
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
'Topic :: Database', 'Topic :: Database',
'Topic :: Software Development :: Libraries :: Java Libraries', 'Topic :: Software Development :: Libraries :: Java Libraries',
'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Python Modules',

View File

@ -27,9 +27,14 @@ import unittest2 as unittest
_THIS_DIR = os.path.dirname(os.path.abspath(__file__)) _THIS_DIR = os.path.dirname(os.path.abspath(__file__))
PY26 = not sys.version_info >= (2, 7)
def is_jython(): def is_jython():
return sys.platform.lower().startswith('java') return sys.platform.lower().startswith('java')
if PY26 and not is_jython:
memoryview = buffer
class IntegrationTestBase(object): class IntegrationTestBase(object):
def sql_file(self, filename): def sql_file(self, filename):
@ -140,12 +145,12 @@ class IntegrationTestBase(object):
"BLOCKING, DBL_COL, OPENED_AT, VALID, PRODUCT_NAME) " \ "BLOCKING, DBL_COL, OPENED_AT, VALID, PRODUCT_NAME) " \
"values (?, ?, ?, ?, ?, ?, ?, ?)" "values (?, ?, ?, ?, ?, ?, ?, ?)"
d = self.dbapi d = self.dbapi
account_id = d.Timestamp(2010, 01, 26, 14, 31, 59) account_id = d.Timestamp(2010, 1, 26, 14, 31, 59)
account_no = 20 account_no = 20
balance = 1.2 balance = 1.2
blocking = 10.0 blocking = 10.0
dbl_col = 3.5 dbl_col = 3.5
opened_at = d.Date(2008, 02, 27) opened_at = d.Date(2008, 2, 27)
valid = 1 valid = 1
product_name = u'Savings account' product_name = u'Savings account'
parms = (account_id, account_no, balance, blocking, dbl_col, parms = (account_id, account_no, balance, blocking, dbl_col,
@ -168,7 +173,7 @@ class IntegrationTestBase(object):
"OPENED_AT_TIME) " \ "OPENED_AT_TIME) " \
"values (?, ?, ?, ?)" "values (?, ?, ?, ?)"
d = self.dbapi d = self.dbapi
account_id = d.Timestamp(2010, 01, 26, 14, 31, 59) account_id = d.Timestamp(2010, 1, 26, 14, 31, 59)
account_no = 20 account_no = 20
balance = 1.2 balance = 1.2
opened_at_time = d.Time(13, 59, 59) opened_at_time = d.Time(13, 59, 59)
@ -209,7 +214,8 @@ class SqliteTestBase(IntegrationTestBase):
cursor = self.conn.cursor() cursor = self.conn.cursor()
stmt = "insert into ACCOUNT (ACCOUNT_ID, ACCOUNT_NO, BALANCE, " \ stmt = "insert into ACCOUNT (ACCOUNT_ID, ACCOUNT_NO, BALANCE, " \
"STUFF) values (?, ?, ?, ?)" "STUFF) values (?, ?, ?, ?)"
stuff = self.dbapi.Binary('abcdef') binary_stuff = 'abcdef'.encode('UTF-8')
stuff = self.dbapi.Binary(binary_stuff)
parms = ('2009-09-11 14:15:22.123450', 20, 13.1, stuff) parms = ('2009-09-11 14:15:22.123450', 20, 13.1, stuff)
cursor.execute(stmt, parms) cursor.execute(stmt, parms)
stmt = "select STUFF from ACCOUNT where ACCOUNT_NO = ?" stmt = "select STUFF from ACCOUNT where ACCOUNT_NO = ?"
@ -218,7 +224,7 @@ class SqliteTestBase(IntegrationTestBase):
result = cursor.fetchone() result = cursor.fetchone()
cursor.close() cursor.close()
value = result[0] value = result[0]
self.assertEqual(value, buffer('abcdef')) self.assertEqual(value, memoryview(binary_stuff))
@unittest.skipIf(is_jython(), "requires python") @unittest.skipIf(is_jython(), "requires python")
class SqlitePyTest(SqliteTestBase, unittest.TestCase): class SqlitePyTest(SqliteTestBase, unittest.TestCase):

View File

@ -53,7 +53,7 @@ class MockTest(unittest.TestCase):
try: try:
cursor.execute("dummy stmt") cursor.execute("dummy stmt")
fail("expected exception") fail("expected exception")
except jaydebeapi.DatabaseError, e: except jaydebeapi.DatabaseError as e:
self.assertEquals(str(e), "java.sql.SQLException: expected") self.assertEquals(str(e), "java.sql.SQLException: expected")
def test_runtime_exception_on_execute(self): def test_runtime_exception_on_execute(self):
@ -62,7 +62,7 @@ class MockTest(unittest.TestCase):
try: try:
cursor.execute("dummy stmt") cursor.execute("dummy stmt")
fail("expected exception") fail("expected exception")
except jaydebeapi.InterfaceError, e: except jaydebeapi.InterfaceError as e:
self.assertEquals(str(e), "java.lang.RuntimeException: expected") self.assertEquals(str(e), "java.lang.RuntimeException: expected")
def test_sql_exception_on_commit(self): def test_sql_exception_on_commit(self):
@ -70,7 +70,7 @@ class MockTest(unittest.TestCase):
try: try:
self.conn.commit() self.conn.commit()
fail("expected exception") fail("expected exception")
except jaydebeapi.DatabaseError, e: except jaydebeapi.DatabaseError as e:
self.assertEquals(str(e), "java.sql.SQLException: expected") self.assertEquals(str(e), "java.sql.SQLException: expected")
def test_runtime_exception_on_commit(self): def test_runtime_exception_on_commit(self):
@ -78,7 +78,7 @@ class MockTest(unittest.TestCase):
try: try:
self.conn.commit() self.conn.commit()
fail("expected exception") fail("expected exception")
except jaydebeapi.InterfaceError, e: except jaydebeapi.InterfaceError as e:
self.assertEquals(str(e), "java.lang.RuntimeException: expected") self.assertEquals(str(e), "java.lang.RuntimeException: expected")
def test_sql_exception_on_rollback(self): def test_sql_exception_on_rollback(self):
@ -86,7 +86,7 @@ class MockTest(unittest.TestCase):
try: try:
self.conn.rollback() self.conn.rollback()
fail("expected exception") fail("expected exception")
except jaydebeapi.DatabaseError, e: except jaydebeapi.DatabaseError as e:
self.assertEquals(str(e), "java.sql.SQLException: expected") self.assertEquals(str(e), "java.sql.SQLException: expected")
def test_runtime_exception_on_rollback(self): def test_runtime_exception_on_rollback(self):
@ -94,5 +94,5 @@ class MockTest(unittest.TestCase):
try: try:
self.conn.rollback() self.conn.rollback()
fail("expected exception") fail("expected exception")
except jaydebeapi.InterfaceError, e: except jaydebeapi.InterfaceError as e:
self.assertEquals(str(e), "java.lang.RuntimeException: expected") self.assertEquals(str(e), "java.lang.RuntimeException: expected")