Version 0.9.2a1. Unicode + DB-API extensions.

This commit is contained in:
adustman
2001-12-24 01:23:55 +00:00
parent a18138516e
commit 7214eaa31e
6 changed files with 135 additions and 26 deletions

View File

@ -1 +1,6 @@
dist
build
regress.py
test.py
PKG-INFO
*.pyc

View File

@ -1,4 +1,10 @@
post-0.9.1
0.9.1a1
* Added a number of DB-API extensions.
* Unicode instances can now be used as parameters to cursor.execute().
It attempts to use whatever character set MySQL is using. If that
can't be determined, then latin1 is used.
* Mac OS X configuration linkage fix (Dan Grassi)

View File

@ -7,7 +7,16 @@ override Connection.default_cursor with a non-standard Cursor class.
"""
import cursors
from _mysql_exceptions import NotSupportedError, ProgrammingError
from _mysql_exceptions import Warning, Error, InterfaceError, DataError, \
DatabaseError, OperationalError, IntegrityError, InternalError, \
NotSupportedError, ProgrammingError
def defaulterrorhandler(connection, cursor, errorclass, errorvalue):
if cursor:
cursor.messages.append(errorvalue)
else:
connection.messages.append(errorvalue)
raise errorclass, errorvalue
class Connection:
@ -57,8 +66,11 @@ class Connection:
self.cursorclass = self.default_cursor
self._db = apply(connect, args, kwargs2)
self._db.converter[types.StringType] = self._db.string_literal
if hasattr(types, 'UnicodeType'):
self._db.converter[types.UnicodeType] = self.unicode_literal
self._transactional = self._db.server_capabilities & CLIENT.TRANSACTIONS
self.messages = []
def __del__(self):
if hasattr(self, '_db'): self.close()
@ -102,6 +114,14 @@ class Connection:
import _mysql
return _mysql.escape(o, self._db.converter)
def unicode_literal(self, u, dummy=None):
"""Convert a unicode object u to a string using the current
character set as the encoding. If that's not available,
use latin1."""
try: charset = self.character_set_name()
except: charset = 'latin1'
return self.literal(u.encode(charset))
def affected_rows(self): return self._db.affected_rows()
def dump_debug_info(self): return self._db.dump_debug_info()
def escape_string(self, s): return self._db.escape_string(s)
@ -124,8 +144,24 @@ class Connection:
try:
return apply(getattr(self._db, feature), args, kwargs)
except AttributeError:
raise NotSupportedError, "not supported by MySQL library"
self.errorhandler(self, None, NotSupportedError,
"not supported by MySQL library")
def character_set_name(self):
return self._try_feature('character_set_name')
def change_user(self, *args, **kwargs):
return apply(self._try_feature, ('change_user',)+args, kwargs)
Warning = Warning
Error = Error
InterfaceError = InterfaceError
DatabaseError = DatabaseError
DataError = DataError
OperationalError = OperationalError
IntegrityError = IntegrityError
InternalError = InternalError
ProgrammingError = ProgrammingError
NotSupportedError = NotSupportedError
errorhandler = defaulterrorhandler

View File

@ -7,7 +7,16 @@ default, MySQLdb uses the Cursor class.
import re
insert_values = re.compile(r'values\s*(\(.+\))', re.IGNORECASE)
from _mysql import escape, ProgrammingError, Warning
from _mysql_exceptions import Warning, Error, InterfaceError, DataError, \
DatabaseError, OperationalError, IntegrityError, InternalError, \
NotSupportedError, ProgrammingError
import exceptions
if hasattr(exceptions, "StopIteration"):
_EndOfData = exceptions.StopIteration
else:
_EndOfData = exceptions.IndexError
class BaseCursor:
@ -18,10 +27,9 @@ class BaseCursor:
See the MySQL docs for more information."""
from _mysql import MySQLError, Warning, Error, InterfaceError, \
DatabaseError, DataError, OperationalError, \
IntegrityError, InternalError, ProgrammingError, \
NotSupportedError
from _mysql_exceptions import MySQLError, Warning, Error, InterfaceError, \
DatabaseError, DataError, OperationalError, IntegrityError, \
InternalError, ProgrammingError, NotSupportedError
def __init__(self, connection):
self.connection = connection
@ -30,6 +38,8 @@ class BaseCursor:
self.arraysize = 100
self._executed = None
self.lastrowid = None
self.messages = []
self.errorhandler = connection.errorhandler
def __del__(self):
self.close()
@ -41,7 +51,8 @@ class BaseCursor:
def _check_executed(self):
if not self._executed:
raise ProgrammingError, "execute() first"
self.errorhandler(self.connection, self,
ProgrammingError, "execute() first")
def setinputsizes(self, *args):
"""Does nothing, required by DB API."""
@ -51,7 +62,8 @@ class BaseCursor:
def _get_db(self):
if not self.connection:
raise ProgrammingError, "cursor closed"
self.errorhandler(self.connection, self,
ProgrammingError, "cursor closed")
return self.connection._db
def execute(self, query, args=None):
@ -73,7 +85,8 @@ class BaseCursor:
except TypeError, m:
if m.args[0] in ("not enough arguments for format string",
"not all arguments converted"):
raise ProgrammingError, m.args[0]
self.errorhandler(self.connection, self,
ProgrammingError, m.args[0])
else:
raise
self._executed = query
@ -104,7 +117,8 @@ class BaseCursor:
except TypeError, msg:
if msg.args[0] in ("not enough arguments for format string",
"not all arguments converted"):
raise ProgrammingError, msg.args[0]
self.errorhandler(self.connection, self,
ProgrammingError, msg.args[0])
else:
raise
r = self._query(join(q,',\n'))
@ -118,9 +132,11 @@ class BaseCursor:
db.query(q)
self._result = self._get_result()
self.rowcount = db.affected_rows()
self.rownumber = 0
self.description = self._result and self._result.describe() or None
self.lastrowid = db.insert_id()
self._info = db.info()
message = db.info()
if message: self.messages.append(message)
self._check_for_warnings()
return self.rowcount
@ -129,18 +145,45 @@ class BaseCursor:
_query = __do_query
def info(self):
"""Return some information about the last query (db.info())"""
"""Return some information about the last query (db.info())
DEPRECATED: Use messages attribute"""
self._check_executed()
return self._info
if self.messages:
return self.messages[0]
else:
return ''
def insert_id(self):
"""Return the last inserted ID on an AUTO_INCREMENT columns."""
"""Return the last inserted ID on an AUTO_INCREMENT columns.
DEPRECATED: use lastrowid attribute"""
self._check_executed()
return self.lastrowid
def _fetch_row(self, size=1):
return self._result.fetch_row(size, self._fetch_type)
def next(self):
"""Fetches the next row. If using Python 2.1 or newer,
StopIteration is raised when there are no more rows.
Otherwise, IndexError is raised."""
result = self.fetchone()
if result is None:
raise _EndOfData
return result
def __iter__(self): return self # XXX
Warning = Warning
Error = Error
InterfaceError = InterfaceError
DatabaseError = DatabaseError
DataError = DataError
OperationalError = OperationalError
IntegrityError = IntegrityError
InternalError = InternalError
ProgrammingError = ProgrammingError
NotSupportedError = NotSupportedError
class CursorWarningMixIn:
@ -150,10 +193,10 @@ class CursorWarningMixIn:
def _check_for_warnings(self):
from string import atoi, split
if self._info:
warnings = atoi(split(self._info)[-1])
if self.messages:
warnings = atoi(split(self.messages[0])[-1])
if warnings:
raise Warning, self._info
raise Warning, self.messages[0]
class CursorStoreResultMixIn:
@ -173,7 +216,6 @@ class CursorStoreResultMixIn:
def _query(self, q):
rowcount = self._BaseCursor__do_query(q)
self._rows = self._result and self._fetch_row(0) or ()
self.rownumber = 0
del self._result
return rowcount
@ -203,7 +245,7 @@ class CursorStoreResultMixIn:
def seek(self, row, whence=0):
"""seek to a given row of the result set analogously to file.seek().
This is non-standard extension."""
This is non-standard extension. DEPRECATED: Use scroll method"""
self._check_executed()
if whence == 0:
self.rownumber = row
@ -214,10 +256,29 @@ class CursorStoreResultMixIn:
def tell(self):
"""Return the current position in the result set analogously to
file.tell(). This is a non-standard extension."""
file.tell(). This is a non-standard extension. DEPRECATED:
use rownumber attribute"""
self._check_executed()
return self.rownumber
def scroll(self, value, mode='relative'):
"""Scroll the cursor in the result set to a new position according
to mode.
If mode is 'relative' (default), value is taken as offset to
the current position in the result set, if set to 'absolute',
value states an absolute target position."""
self._check_executed()
if mode == 'relative':
r = self.rownumber + value
elif mode == 'absolute':
r = value
else:
raise ProgrammingError, "unknown scroll mode %s" % `mode`
if r < 0 or r >= len(self._rows):
raise IndexError, "out of range"
self.rownumber = r
class CursorUseResultMixIn:

View File

@ -1 +1,2 @@
*.tex
*.html

View File

@ -56,7 +56,7 @@ elif sys.platform == "win32": # Ugh
libraries = [mysqlclient, 'zlib', 'msvcrt', 'libcmt',
'wsock32', 'advapi32']
extra_objects = [r'c:\mysql\lib\opt\mysqlclient.lib']
elif sys.platform == "darwin1": # Mac OS X
elif sys.platform[:6] == "darwin": # Mac OS X
include_dirs = ['/usr/local/mysql/include/mysql']
library_dirs = ['/usr/local/mysql/lib/mysql']
extra_link_args = ['-flat_namespace']
@ -88,7 +88,7 @@ MySQLdb. MySQLdb is free software.
setup (# Distribution meta-data
name = "MySQL-python",
version = "0.9.1",
version = "0.9.2",
description = "An interface to MySQL",
long_description=long_description,
author = "Andy Dustman",