Initial import

This commit is contained in:
baztian
2010-08-10 17:41:17 +02:00
commit 61a40112af
16 changed files with 2095 additions and 0 deletions
+85
View File
@@ -0,0 +1,85 @@
# The default ``config.py``
def set_prefs(prefs):
"""This function is called before opening the project"""
# Specify which files and folders to ignore in the project.
# Changes to ignored resources are not added to the history and
# VCSs. Also they are not returned in `Project.get_files()`.
# Note that ``?`` and ``*`` match all characters but slashes.
# '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
# 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
# '.svn': matches 'pkg/.svn' and all of its children
# 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
# 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject',
'.hg', '.svn', '_svn', '.git']
# Specifies which files should be considered python files. It is
# useful when you have scripts inside your project. Only files
# ending with ``.py`` are considered to be python files by
# default.
#prefs['python_files'] = ['*.py']
# Custom source folders: By default rope searches the project
# for finding source folders (folders that should be searched
# for finding modules). You can add paths to that list. Note
# that rope guesses project source folders correctly most of the
# time; use this if you have any problems.
# The folders should be relative to project root and use '/' for
# separating folders regardless of the platform rope is running on.
# 'src/my_source_folder' for instance.
#prefs.add('source_folders', 'src')
# You can extend python path for looking up modules
#prefs.add('python_path', '~/python/')
# Should rope save object information or not.
prefs['save_objectdb'] = True
prefs['compress_objectdb'] = False
# If `True`, rope analyzes each module when it is being saved.
prefs['automatic_soa'] = True
# The depth of calls to follow in static object analysis
prefs['soa_followed_calls'] = 0
# If `False` when running modules or unit tests "dynamic object
# analysis" is turned off. This makes them much faster.
prefs['perform_doa'] = True
# Rope can check the validity of its object DB when running.
prefs['validate_objectdb'] = True
# How many undos to hold?
prefs['max_history_items'] = 32
# Shows whether to save history across sessions.
prefs['save_history'] = True
prefs['compress_history'] = False
# Set the number spaces used for indenting. According to
# :PEP:`8`, it is best to use 4 spaces. Since most of rope's
# unit-tests use 4 spaces it is more reliable, too.
prefs['indent_size'] = 4
# Builtin and c-extension modules that are allowed to be imported
# and inspected by rope.
prefs['extension_modules'] = []
# Add all standard c-extensions to extension_modules list.
prefs['import_dynload_stdmods'] = True
# If `True` modules with syntax errors are considered to be empty.
# The default value is `False`; When `False` syntax errors raise
# `rope.base.exceptions.ModuleSyntaxError` exception.
prefs['ignore_syntax_errors'] = False
# If `True`, rope ignores unresolvable imports. Otherwise, they
# appear in the importing namespace.
prefs['ignore_bad_imports'] = False
def project_opened(project):
"""This function is called after opening the project"""
# Do whatever you like here!
+154
View File
@@ -0,0 +1,154 @@
Metadata-Version: 1.0
Name: JayDeBeApi
Version: 0.1
Summary: A bridge from JDBC database drivers to Python DB-API.
Home-page: https://code.launchpad.net/jaydebeapi
Author: Bastian Bowe
Author-email: bastian.bowe@gmail.com
License: GNU LGPL
Description: =====================================================================
JayDeBeApi - bridge from JDBC Database drivers to Python DB-API
=====================================================================
The JayDeBeApi library allows you to connect from Python code to
databases using Java `JDBC
<http://java.sun.com/products/jdbc/overview.html>`_. It provides a
Python DB-API_ v2.0 to that database.
It works on ordinary Python (cPython) using the JPype_ Java
integration or on `Jython <http://www.jython.org/>`_ to make use of
the Java JDBC driver.
It has been tested with `Hypersonic SQL (HSQLDB)
<http://hsqldb.org/>`_ and `IBM DB2
<http://www.ibm.com/software/data/db2/>`_ for z/OS.
In contrast to zxJDBC from the Jython project JayDeBeApi let's you
access a database with Jython AND Python with only minor code
modifications. JayDeBeApi's future goal is to provide a unique and
fast interface to different types of JDBC-Drivers through a flexible
plug-in mechanism.
.. contents::
Install
=======
You can get and install JayDeBeApi with `easy_install
<http://peak.telecommunity.com/DevCenter/EasyInstall>`_ ::
$ easy_install JayDeBeApi
If you want to install JayDeBeApi in Jython make sure to have
EasyInstall available for it.
Or you can get a copy of the source branch using `bzr
<http://bazaar.canonical.com/>`_ by running ::
$ bzr branch lp:jaydebeapi
and install it with ::
$ python setup.py install
or if you are using Jython use ::
$ jython setup.py install
It has been tested with Jython 2.5.2.
If you are using cPython ensure that you have installed JPype_
properly. It has been tested with JPype 0.5.4.
Usage
=====
Basically you just import the ``jaydebeapi`` Python module and execute
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.
Here is an example:
>>> import jaydebeapi
>>> conn = jaydebeapi.connect('org.hsqldb.jdbcDriver',
... 'jdbc:hsqldb:mem', 'SA', '')
>>> curs = conn.cursor()
>>> curs.execute('create table CUSTOMER'
... '("CUST_ID" INTEGER not null,'
... ' "NAME" VARCHAR not null,'
... ' primary key ("CUST_ID"))'
... )
>>> curs.execute("insert into CUSTOMER values (1, 'John')")
>>> curs.execute("select * from CUSTOMER")
>>> curs.fetchall()
[(1, u'John')]
You probably have to configure Jython or JPype to actually be able to
access the database driver's jar files. If I want to connect to a HSQL
in memory database on my Ubuntu machine I'm starting Python by running ::
$ JAVA_HOME=/usr/lib/jvm/java-6-openjdk python
Now I have to configure JPype
>>> jar = '/path/to/my/driver/hsqldb.jar'
>>> args='-Djava.class.path=%s' % jar
>>> jpype.startJVM(jvm_path, args)
or in Jython I have to
>>> jar = '/path/to/my/driver/hsqldb.jar'
>>> import sys
>>> sys.path.append(jar)
Contributing
============
Please submit `bugs and patches
<https://bugs.launchpad.net/jaydebeapi>`_. All contributors will be
acknowledged. Thanks!
License
=======
JayDeBeApi is released under the GNU Lesser General Public license
(LGPL). See the file ``COPYING`` and ``COPYING.LESSER`` in the
distribution for details.
Changelog
=========
- 0.1
- Initial release
To do
=====
- Extract Java calls to seperate Java methods to increase performance.
- Check if https://code.launchpad.net/dbapi-compliance can help making
JayDeBeApi more DB-API complient.
- Test it on different databases and provide a flexible db specific
pluign mechanism.
- SQLAlchemy modules (seperate project)
.. _DB-API: http://www.python.org/dev/peps/pep-0249/
.. _JPype: http://jpype.sourceforge.net/
Keywords: db api java jdbc bridge connect sql jpype jython
Platform: UNKNOWN
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
Classifier: Programming Language :: Java
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Libraries :: Java Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
+7
View File
@@ -0,0 +1,7 @@
setup.py
src/JayDeBeApi.egg-info/PKG-INFO
src/JayDeBeApi.egg-info/SOURCES.txt
src/JayDeBeApi.egg-info/dependency_links.txt
src/JayDeBeApi.egg-info/top_level.txt
src/jaydebeapi/__init__.py
src/jaydebeapi/dbapi2.py
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
jaydebeapi
+1
View File
@@ -0,0 +1 @@
from dbapi2 import *
+385
View File
@@ -0,0 +1,385 @@
#-*- coding: utf-8 -*-
# Copyright 2010 Bastian Bowe
#
# This file is part of JayDeBeApi.
# JayDeBeApi is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# JayDeBeApi is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with JayDeBeApi. If not, see
# <http://www.gnu.org/licenses/>.
import datetime
import exceptions
import time
import sys
_jdbc_connect = None
def _jdbc_connect_jython(jclassname, *args):
# register driver for DriverManager
__import__(jclassname)
from java.sql import DriverManager
return DriverManager.getConnection(*args)
def _prepare_jython():
global _jdbc_connect
_jdbc_connect = _jdbc_connect_jython
# TODO: find solution for jython
# Binary = buffer
def _jdbc_connect_jpype(jclassname, *args):
import jpype
if not jpype.isJVMStarted():
jpype.startJVM(jpype.getDefaultJVMPath())
if not jpype.isThreadAttachedToJVM():
jpype.attachThreadToJVM()
# register driver for DriverManager
jpype.JClass(jclassname)
return jpype.java.sql.DriverManager.getConnection(*args)
def _prepare_jpype():
global _jdbc_connect
_jdbc_connect = _jdbc_connect_jpype
# TODO: doesn't work for Jython
# global Binary
# Binary = buffer
if sys.platform.lower().startswith('java'):
_prepare_jython()
else:
_prepare_jpype()
apilevel = '2.0'
threadsafety = 1
paramstyle = 'qmark'
class DBAPITypeObject(object):
def __init__(self,*values):
self.values = values
def __cmp__(self,other):
if other in self.values:
return 0
if other < self.values:
return 1
else:
return -1
STRING = DBAPITypeObject("CHARACTER", "CHAR", "VARCHAR",
"CHARACTER VARYING", "CHAR VARYING", "STRING",)
TEXT = DBAPITypeObject("CLOB", "CHARACTER LARGE OBJECT",
"CHAR LARGE OBJECT", "XML",)
BINARY = DBAPITypeObject("BLOB", "BINARY LARGE OBJECT",)
NUMBER = DBAPITypeObject("INTEGER", "INT", "SMALLINT", "BIGINT",)
FLOAT = DBAPITypeObject("FLOAT", "REAL", "DOUBLE", "DECFLOAT")
DECIMAL = DBAPITypeObject("DECIMAL", "DEC", "NUMERIC", "NUM",)
DATE = DBAPITypeObject("DATE",)
TIME = DBAPITypeObject("TIME",)
DATETIME = DBAPITypeObject("TIMESTAMP",)
ROWID = DBAPITypeObject(())
# DB-API 2.0 Module Interface Exceptions
class Error(exceptions.StandardError):
pass
class Warning(exceptions.StandardError):
pass
class InterfaceError(Error):
pass
class DatabaseError(Error):
pass
class InternalError(DatabaseError):
pass
class OperationalError(DatabaseError):
pass
class ProgrammingError(DatabaseError):
pass
class IntegrityError(DatabaseError):
pass
class DataError(DatabaseError):
pass
class NotSupportedError(DatabaseError):
pass
# DB-API 2.0 Type Objects and Constructors
Date = datetime.date
Time = datetime.time
Timestamp = datetime.datetime
def DateFromTicks(ticks):
return apply(Date, time.localtime(ticks)[:3])
def TimeFromTicks(ticks):
return apply(Time, time.localtime(ticks)[3:6])
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)
return Connection(jconn)
# DB-API 2.0 Connection Object
class Connection(object):
jconn = None
def __init__(self, jconn):
self.jconn = jconn
def close(self):
self.jconn.close()
def commit(self):
return self.jconn.commit()
def rollback(self):
return self.jconn.rollback()
def cursor(self):
return Cursor(self)
# DB-API 2.0 Cursor Object
class Cursor(object):
rowcount = -1
_meta = None
_prep = None
_rs = None
_description = None
def __init__(self, connection):
self._connection = connection
@property
def description(self):
if self._description:
return self._description
m = self._meta
if m:
count = m.getColumnCount()
self._description = []
for col in range(1, count + 1):
size = m.getColumnDisplaySize(col)
col_desc = ( m.getColumnName(col),
m.getColumnTypeName(col),
size,
size,
m.getPrecision(col),
m.getScale(col),
m.isNullable(col),
)
self._description.append(col_desc)
return self._description
# optional callproc(self, procname, *parameters) unsupported
def close(self):
self._close_last()
self.connection = None
def _close_last(self):
"""Close the resultset and reset collected meta data.
"""
if self._rs:
self._rs.close()
if self._prep:
self._prep.close()
self._rs = None
self._meta = None
self._description = None
def _set_stmt_parms(self, prep_stmt, *parameters):
for i in range(len(parameters)):
sqltype = TYPE_MAP[type(parameters[i])]
setter = getattr(prep_stmt, 'set%s' % METHOD_MAP[sqltype])
setter(i + 1, parameters[i])
def execute(self, operation, *parameters):
self._close_last()
self._prep = self._connection.jconn.prepareStatement(operation)
self._set_stmt_parms(self._prep, *parameters)
is_rs = self._prep.execute()
self.update_count = self._prep.getUpdateCount()
if is_rs:
self._rs = self._prep.getResultSet()
self._meta = self._rs.getMetaData()
# self._prep.getWarnings() ???
def executemany(self, operation, seq_of_parameters):
self._close_last()
self._prep = self._connection.jconn.prepareStatement(operation)
for parameters in seq_of_parameters:
self._set_stmt_parms(self._prep, *parameters)
self._prep.addBatch()
update_counts = self._prep.executeBatch()
# self._prep.getWarnings() ???
self.rowcount = sum(update_counts)
def fetchone(self):
#raise if not rs
if not self._rs.next():
return None
row = []
for col in range(1, self._meta.getColumnCount() + 1):
sqltype = self._meta.getColumnType(col)
getter = getattr(self._rs, 'get%s' % METHOD_MAP[sqltype])
v = getter(col)
converter = TO_PY_MAP.get(sqltype)
if converter:
v = converter(v)
row.append(v)
return tuple(row)
def fetchmany(self, size=None):
if size is None:
size = self.arraysize
self._rs.setFetchSize(size)
rows = []
row = None
for i in xrange(size):
row = self.fetchone()
if row is None:
break
else:
rows.append(row)
# reset fetch size
if row:
self._rs.setFetchSize(0)
return rows
def fetchall(self):
rows = []
while True:
row = self.fetchone()
if row is None:
break
else:
rows.append(row)
return rows
# optional nextset() unsupported
arraysize = 1
def setinputsizes(self, sizes):
pass
def setoutputsize(self, size, column):
pass
class JavaSqlTypes(object):
# from java.sql.Types
ARRAY=2003
BIGINT=-5
BINARY=-2
BIT=-7
BLOB=2004
BOOLEAN=16
CHAR=1
CLOB=2005
DATALINK=70
DATE=91
DECIMAL=3
DISTINCT=2001
DOUBLE=8
FLOAT=6
INTEGER=4
JAVA_OBJECT=2000
LONGVARBINARY=-4
LONGVARCHAR=-1
NULL=0
NUMERIC=2
OTHER=1111
REAL=7
REF=2006
SMALLINT=5
STRUCT=2002
TIME=92
TIMESTAMP=93
TINYINT=-6
VARBINARY=-3
VARCHAR=12
METHOD_MAP = {
JavaSqlTypes.ARRAY: 'Array',
#AsciiStream
JavaSqlTypes.NUMERIC: 'BigDecimal',
JavaSqlTypes.DECIMAL: 'BigDecimal',
#BinaryStream
JavaSqlTypes.BLOB: 'Blob',
JavaSqlTypes.BOOLEAN: 'Boolean',
JavaSqlTypes.CHAR: 'String',
JavaSqlTypes.BINARY: 'Bytes',
#CharacterStream
JavaSqlTypes.CLOB: 'Clob',
JavaSqlTypes.DOUBLE: 'Double',
JavaSqlTypes.DATE: 'Date',
JavaSqlTypes.FLOAT: 'Float',
JavaSqlTypes.INTEGER: 'Int',
JavaSqlTypes.BIGINT: 'Long',
JavaSqlTypes.SMALLINT: 'Short',
JavaSqlTypes.VARCHAR: 'String',
JavaSqlTypes.TIME: 'Time',
JavaSqlTypes.TIMESTAMP: 'Timestamp',
JavaSqlTypes.NULL: 'Null',
}
TYPE_MAP = {
int: JavaSqlTypes.INTEGER,
long: JavaSqlTypes.BIGINT,
float: JavaSqlTypes.DOUBLE,
type(None): JavaSqlTypes.NULL,
str: JavaSqlTypes.VARCHAR,
bool: JavaSqlTypes.BOOLEAN,
# buffer: JavaSqlTypes.BINARY,
unicode: JavaSqlTypes.VARCHAR,
}
def to_datetime(java_val):
# d=datetime.datetime.strptime(timestmp.toString()[:-7], "%Y-%m-%d %H:%M:%S")
# return d.replace(microsecond=int(str(timestmp.getNanos())[:6]))
return java_val.toString()
def to_date(java_val):
# d=datetime.datetime.strptime(timestmp.toString()[:-7], "%Y-%m-%d %H:%M:%S")
# return d.replace(microsecond=int(str(timestmp.getNanos())[:6]))
return java_val.toString()
def to_float(java_val):
return java_val.doubleValue()
TO_PY_MAP = {
JavaSqlTypes.TIMESTAMP: to_datetime,
JavaSqlTypes.DATE: to_date,
JavaSqlTypes.DECIMAL: to_float,
JavaSqlTypes.NUMERIC: to_float,
}
+7
View File
@@ -0,0 +1,7 @@
create table konto (
"KONTO_ID" TIMESTAMP default CURRENT_TIMESTAMP not null,
"KONTO_NR" INTEGER not null,
"SALDO" DECIMAL default 0.0 not null,
primary key ("KONTO_ID")
);
+2
View File
@@ -0,0 +1,2 @@
insert into Konto values ('2009-09-10 14:15:22.123456', 18, 12.4);
insert into Konto values ('2009-09-11 14:15:22.123456', 19, 12.9);
+145
View File
@@ -0,0 +1,145 @@
#-*- coding: utf-8 -*-
# Copyright 2010 Bastian Bowe
#
# This file is part of JayDeBeApi.
# JayDeBeApi is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# JayDeBeApi is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with JayDeBeApi. If not, see
# <http://www.gnu.org/licenses/>.
import os
from unittest import TestCase
import jaydebeapi
import sys
this_dir = os.path.dirname(os.path.abspath(__file__))
jar_dir = os.path.abspath(os.path.join(this_dir, '..', '..',
'build', 'lib'))
create_sql = os.path.join(this_dir, 'data', 'create.sql')
insert_sql = os.path.join(this_dir, 'data', 'insert.sql')
def is_jython():
return sys.platform.lower().startswith('java')
class IntegrationTest(TestCase):
def sql_file(self, filename):
f = open(filename, 'r')
try:
lines = f.readlines()
finally:
f.close()
stmt = []
stmts = []
for i in lines:
stmt.append(i)
if ";" in i:
stmts.append(" ".join(stmt))
stmt = []
cursor = self.conn.cursor()
for i in stmts:
cursor.execute(i)
def setup_jpype(self, jars):
import jpype
if not jpype.isJVMStarted():
jvm_path = jpype.getDefaultJVMPath()
#jvm_path = ('/usr/lib/jvm/java-6-openjdk'
# '/jre/lib/i386/client/libjvm.so')
args='-Djava.class.path=%s' % jars
jpype.startJVM(jvm_path, args)
if not jpype.isThreadAttachedToJVM():
jpype.attachThreadToJVM()
def setUp(self):
# TODO support more than one jar
#jars='/usr/share/java/hsqldb.jar'
jars=os.path.join(jar_dir, 'hsqldb.jar')
#jars=jars_path(r'C:\Programme\DbVisualizer-4.3.5\jdbc')
if is_jython():
sys.path.append(jars)
else:
self.setup_jpype(jars)
self.conn = jaydebeapi.connect('org.hsqldb.jdbcDriver',
'jdbc:hsqldb:mem', 'SA', '')
#conn = jaydebeapi.connect('com.ibm.db2.jcc.DB2Driver',
# 'jdbc:db2://4.100.73.81:50000/db2t',
# getpass.getuser(), getpass.getpass())
self.sql_file(create_sql)
self.sql_file(insert_sql)
def tearDown(self):
cursor = self.conn.cursor()
cursor.execute("drop table konto");
self.conn.close()
def test_execute_and_fetch_no_data(self):
cursor = self.conn.cursor()
stmt = "select * from konto where konto_id is null"
cursor.execute(stmt)
assert [] == cursor.fetchall()
def test_execute_and_fetch(self):
cursor = self.conn.cursor()
cursor.execute("select * from konto")
result = cursor.fetchall()
assert [(u'2009-09-10 14:15:22.123456', 18, 12.4),
(u'2009-09-11 14:15:22.123456', 19, 12.9)] == result
def test_execute_and_fetch_parameter(self):
cursor = self.conn.cursor()
cursor.execute("select * from konto where konto_nr = ?", 18)
result = cursor.fetchall()
assert [(u'2009-09-10 14:15:22.123456', 18, 12.4)] == result
def test_execute_and_fetchone(self):
cursor = self.conn.cursor()
cursor.execute("select * from konto order by konto_nr")
result = cursor.fetchone()
assert (u'2009-09-10 14:15:22.123456', 18, 12.4) == result
def test_execute_reset_description_without_execute_result(self):
"""Excpect the descriptions property being reset when no query
has been made via execute method.
"""
cursor = self.conn.cursor()
cursor.execute("select * from konto")
assert None != cursor.description
cursor.fetchone()
cursor.execute("delete from konto")
assert None == cursor.description
def test_execute_and_fetchone_after_end(self):
cursor = self.conn.cursor()
cursor.execute("select * from konto where konto_nr = ?", 18)
cursor.fetchone()
result = cursor.fetchone()
assert None is result
def test_execute_and_fetchmany(self):
cursor = self.conn.cursor()
cursor.execute("select * from konto order by konto_nr")
result = cursor.fetchmany()
assert [(u'2009-09-10 14:15:22.123456', 18, 12.4)] == result
def test_executemany(self):
cursor = self.conn.cursor()
stmt = "insert into Konto (KONTO_ID, KONTO_NR, SALDO) values (?, ?, ?)"
parms = (
( '2009-09-11 14:15:22.123450', 20, 13.1 ),
( '2009-09-11 14:15:22.123451', 21, 13.2 ),
( '2009-09-11 14:15:22.123452', 22, 13.3 ),
)
cursor.executemany(stmt, parms)
assert cursor.rowcount == 3