From 453eb6159813b2d709b3c2d64b5b590622e080b3 Mon Sep 17 00:00:00 2001 From: baztian Date: Sun, 26 Apr 2015 18:38:21 +0200 Subject: [PATCH] Add Python 3 compatibility. --- .travis.yml | 1 + README.rst | 5 ++- ci/before_install_nonjython.sh | 5 ++- jaydebeapi/__init__.py | 67 ++++++++++++++++++++++++++-------- requirements-python-2.6.txt | 1 + requirements-python-2.7.txt | 1 + requirements-python.txt | 1 - setup.py | 7 ++-- test/test_integration.py | 16 +++++--- test/test_mock.py | 12 +++--- 10 files changed, 83 insertions(+), 33 deletions(-) create mode 100644 requirements-python-2.6.txt create mode 100644 requirements-python-2.7.txt delete mode 100644 requirements-python.txt diff --git a/.travis.yml b/.travis.yml index 0e64cf7..91d8a66 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ deploy: python: - '2.6' - '2.7' + - '3.4' env: matrix: diff --git a/README.rst b/README.rst index 8ad7dea..02dbe30 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,7 @@ .. image:: https://img.shields.io/coveralls/baztian/jaydebeapi/master.svg :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 .. image:: https://img.shields.io/badge/jython-2.5.3,_2.7--rc1-blue.svg @@ -168,6 +168,9 @@ Changelog ========= - Next version - unreleased + + - Python 3 support (requires JPype1 >= 0.6.0). + - 0.1.6 - 2015-04-10 - Fix Jython handling of Java exceptions that don't subclass python Exception diff --git a/ci/before_install_nonjython.sh b/ci/before_install_nonjython.sh index e099d34..86cb755 100755 --- a/ci/before_install_nonjython.sh +++ b/ci/before_install_nonjython.sh @@ -1,5 +1,8 @@ #!/bin/bash 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 diff --git a/jaydebeapi/__init__.py b/jaydebeapi/__init__.py index 1f17487..f769dbe 100644 --- a/jaydebeapi/__init__.py +++ b/jaydebeapi/__init__.py @@ -21,7 +21,6 @@ __version_info__ = (0, 1, 6) __version__ = ".".join(str(i) for i in __version_info__) import datetime -import exceptions import glob import os import time @@ -29,6 +28,40 @@ import re import sys 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 _jdbc_name_to_const = None @@ -48,7 +81,7 @@ def _handle_sql_exception_jython(): exc_type = DatabaseError else: 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): if _jdbc_name_to_const is None: @@ -112,7 +145,7 @@ def _handle_sql_exception_jpype(): exc_type = DatabaseError else: 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): import jpype @@ -191,7 +224,7 @@ class DBAPITypeObject(object): self.values = values for type_name in values: 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 def __cmp__(self, other): if other in self.values: @@ -239,10 +272,10 @@ DATETIME = DBAPITypeObject('TIMESTAMP') ROWID = DBAPITypeObject('ROWID') # DB-API 2.0 Module Interface Exceptions -class Error(exceptions.StandardError): +class Error(Exception): pass -class Warning(exceptions.StandardError): +class Warning(Exception): pass 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 library by the JDBC driver """ - if isinstance(driver_args, basestring): + if isinstance(driver_args, string_type): driver_args = [ driver_args ] if jars: - if isinstance(jars, basestring): + if isinstance(jars, string_type): jars = [ jars ] else: jars = [] if libs: - if isinstance(libs, basestring): + if isinstance(libs, string_type): libs = [ libs ] else: libs = [] @@ -347,7 +380,7 @@ class Connection(object): def close(self): if self._closed: - raise Error + raise Error() self.jconn.close() self._closed = True @@ -436,7 +469,7 @@ class Cursor(object): def execute(self, operation, parameters=None): if self._connection._closed: - raise Error + raise Error() if not parameters: parameters = () self._close_last() @@ -467,7 +500,7 @@ class Cursor(object): def fetchone(self): if not self._rs: - raise Error + raise Error() if not self._rs.next(): return None row = [] @@ -480,14 +513,14 @@ class Cursor(object): def fetchmany(self, size=None): if not self._rs: - raise Error + raise Error() if size is None: size = self.arraysize # TODO: handle SQLException if not supported by db self._rs.setFetchSize(size) rows = [] row = None - for i in xrange(size): + for i in range(size): row = self.fetchone() if row is None: break @@ -554,7 +587,9 @@ def _java_to_py(java_method): java_val = rs.getObject(col) if java_val is None: 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 getattr(java_val, java_method)() return to_py @@ -567,7 +602,7 @@ def _init_types(types_map): global _jdbc_name_to_const _jdbc_name_to_const = types_map 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) def _init_converters(types_map): diff --git a/requirements-python-2.6.txt b/requirements-python-2.6.txt new file mode 100644 index 0000000..87a36b2 --- /dev/null +++ b/requirements-python-2.6.txt @@ -0,0 +1 @@ +JPype1==0.6.0 diff --git a/requirements-python-2.7.txt b/requirements-python-2.7.txt new file mode 100644 index 0000000..87a36b2 --- /dev/null +++ b/requirements-python-2.7.txt @@ -0,0 +1 @@ +JPype1==0.6.0 diff --git a/requirements-python.txt b/requirements-python.txt deleted file mode 100644 index a6ded87..0000000 --- a/requirements-python.txt +++ /dev/null @@ -1 +0,0 @@ -JPype1==0.5.7 diff --git a/setup.py b/setup.py index adbef1c..2a505cd 100644 --- a/setup.py +++ b/setup.py @@ -32,16 +32,17 @@ setup( author_email = 'bastian.dev@gmail.com', license = 'GNU LGPL', url='https://github.com/baztian/jaydebeapi', - description=('A bridge from JDBC database drivers to Python DB-API.'), - long_description=file('README.rst').read(), + description=('Use JDBC database drivers from Python 2/3 or Jython with a DB-API.'), + long_description=open('README.rst').read(), keywords = ('db api java jdbc bridge connect sql jpype jython'), classifiers = [ - 'Development Status :: 3 - Alpha', + 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', 'Programming Language :: Java', 'Programming Language :: Python', 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', 'Topic :: Database', 'Topic :: Software Development :: Libraries :: Java Libraries', 'Topic :: Software Development :: Libraries :: Python Modules', diff --git a/test/test_integration.py b/test/test_integration.py index 65ea680..ae07e4c 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -27,9 +27,14 @@ import unittest2 as unittest _THIS_DIR = os.path.dirname(os.path.abspath(__file__)) +PY26 = not sys.version_info >= (2, 7) + def is_jython(): return sys.platform.lower().startswith('java') +if PY26 and not is_jython: + memoryview = buffer + class IntegrationTestBase(object): def sql_file(self, filename): @@ -140,12 +145,12 @@ class IntegrationTestBase(object): "BLOCKING, DBL_COL, OPENED_AT, VALID, PRODUCT_NAME) " \ "values (?, ?, ?, ?, ?, ?, ?, ?)" 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 balance = 1.2 blocking = 10.0 dbl_col = 3.5 - opened_at = d.Date(2008, 02, 27) + opened_at = d.Date(2008, 2, 27) valid = 1 product_name = u'Savings account' parms = (account_id, account_no, balance, blocking, dbl_col, @@ -168,7 +173,7 @@ class IntegrationTestBase(object): "OPENED_AT_TIME) " \ "values (?, ?, ?, ?)" 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 balance = 1.2 opened_at_time = d.Time(13, 59, 59) @@ -209,7 +214,8 @@ class SqliteTestBase(IntegrationTestBase): cursor = self.conn.cursor() stmt = "insert into ACCOUNT (ACCOUNT_ID, ACCOUNT_NO, BALANCE, " \ "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) cursor.execute(stmt, parms) stmt = "select STUFF from ACCOUNT where ACCOUNT_NO = ?" @@ -218,7 +224,7 @@ class SqliteTestBase(IntegrationTestBase): result = cursor.fetchone() cursor.close() value = result[0] - self.assertEqual(value, buffer('abcdef')) + self.assertEqual(value, memoryview(binary_stuff)) @unittest.skipIf(is_jython(), "requires python") class SqlitePyTest(SqliteTestBase, unittest.TestCase): diff --git a/test/test_mock.py b/test/test_mock.py index e2bc7d6..0f2d796 100644 --- a/test/test_mock.py +++ b/test/test_mock.py @@ -53,7 +53,7 @@ class MockTest(unittest.TestCase): try: cursor.execute("dummy stmt") fail("expected exception") - except jaydebeapi.DatabaseError, e: + except jaydebeapi.DatabaseError as e: self.assertEquals(str(e), "java.sql.SQLException: expected") def test_runtime_exception_on_execute(self): @@ -62,7 +62,7 @@ class MockTest(unittest.TestCase): try: cursor.execute("dummy stmt") fail("expected exception") - except jaydebeapi.InterfaceError, e: + except jaydebeapi.InterfaceError as e: self.assertEquals(str(e), "java.lang.RuntimeException: expected") def test_sql_exception_on_commit(self): @@ -70,7 +70,7 @@ class MockTest(unittest.TestCase): try: self.conn.commit() fail("expected exception") - except jaydebeapi.DatabaseError, e: + except jaydebeapi.DatabaseError as e: self.assertEquals(str(e), "java.sql.SQLException: expected") def test_runtime_exception_on_commit(self): @@ -78,7 +78,7 @@ class MockTest(unittest.TestCase): try: self.conn.commit() fail("expected exception") - except jaydebeapi.InterfaceError, e: + except jaydebeapi.InterfaceError as e: self.assertEquals(str(e), "java.lang.RuntimeException: expected") def test_sql_exception_on_rollback(self): @@ -86,7 +86,7 @@ class MockTest(unittest.TestCase): try: self.conn.rollback() fail("expected exception") - except jaydebeapi.DatabaseError, e: + except jaydebeapi.DatabaseError as e: self.assertEquals(str(e), "java.sql.SQLException: expected") def test_runtime_exception_on_rollback(self): @@ -94,5 +94,5 @@ class MockTest(unittest.TestCase): try: self.conn.rollback() fail("expected exception") - except jaydebeapi.InterfaceError, e: + except jaydebeapi.InterfaceError as e: self.assertEquals(str(e), "java.lang.RuntimeException: expected")