# Perforce Defect Tracking Integration Project
# <http://www.ravenbrook.com/project/p4dti/>
#
# MYSQLDB_SUPPORT.PY -- CHECK RELEASE OF MYSQLDB MODULE
#
# Nick Barnes, Ravenbrook Limited, 2001-10-24
#
#
# 1. INTRODUCTION
#
# This module provides a function connect(). That function takes a
# P4DTI configuration, attempts to determine the release of the MySQLdb
# library, checks whether the release is supported by the P4DTI, and
# returns a database connection.
#
# It also notifies the P4DTI administrator as follows:
#
# - If the MySQLdb release is supported by the P4DTI, it generates an
# informative message.
#
# - If the MySQLdb release is known to be incompatible with the P4DTI,
# it raises a fatal exception.
#
# - If the MySQLdb release is unsupported, but not known to be
# incompatible, it generates a warning message.
#
# See job000317, job000413, job000411 for problems addressed by
# identifying the MySQLdb release.
#
# When support for other MySQLdb releases is added or changed, the table
# 'MySQLdb_support' in section 5 must be modified.
#
# The intended readership of this document is project developers.
#
# This document is not confidential.
import types
import MySQLdb
import catalog
error = "MySQLdb module support error"
# 2. IDENTIFYING MYSQLDB RELEASE
#
# This section provides a function what_release() which attempts to
# identify a MySQLdb release.
#
# Functions in this section take a MySQLdb module as argument M. This
# is to allow us to test this section without having to install and
# uninstall MySQLdb releases.
# 2.1. Distinguish between release 0.3.0 and release 0.3.1
# Also distinguish those releases from unknown other releases which
# resemble those releases.
#
# Release 0.3.0 has MySQLdb.type_conv[MySQLdb.FIELD_TYPE.LONG] == int.
# Release 0.3.1 has MySQLdb.type_conv[MySQLdb.FIELD_TYPE.LONG] == long.
def distinguish_030_031(M):
if hasattr(M, 'type_conv'):
converter = M.type_conv[M.FIELD_TYPE.LONG]
if converter == int:
return '0.3.0'
elif converter == long:
return '0.3.1'
else:
return None
else:
return None
# 2.2. Map __version__ strings to release names
#
# This table maps the MySQLdb __version__ string to either a string
# (in which case it's the MySQLdb release name) or a function, which
# should be called with the MySQLdb module as an argument and which
# returns the MySQLdb release name, or None if it can't tell.
MySQLdb_versions = {
'1.21': '0.2.0',
'1.24': '0.2.1',
'1.29': '0.2.2',
'1.32': distinguish_030_031,
'1.33': '0.3.2',
'1.34': '0.3.3',
'1.39': '0.3.4',
'1.40': '0.3.5',
'0.9.0': '0.9.0',
'0.9.1g2': '0.9.1c1',
'0.9.1': '0.9.1',
'0.9.2': '0.9.2',
}
# 2.3. Determine the MySQLdb release
# Returns a pair (version, release), where version is the MySQLdb
# module's version string and release is the identified MySQLdb
# release.
def what_release(M):
version = getattr(M, '__version__', 'No __version__')
release = MySQLdb_versions.get(version, None)
if isinstance(release, types.FunctionType):
release = release(M)
if release is None:
release = 'Unknown'
return version, release
# 3. SUPPORT NOTIFICATION
#
# These functions notify the P4DTI administrator about whether their
# release of MySQLdb is supported, unsupported (not known not to work),
# or broken (known not to work). If it's known not to work with the
# P4DTI then the function raises an error.
def supported(version, release, config):
# "MySQLdb version '%s' (release '%s') detected.
# This release is supported by the P4DTI."
msg = catalog.msg(1007, (version, release))
config.logger.log(msg)
def unsupported(version, release, config):
# "MySQLdb version '%s' (release '%s') detected.
# This release is not supported by the P4DTI, but may work."
msg = catalog.msg(1006, (version, release))
config.logger.log(msg)
def broken(version, release, config):
# "MySQLdb version '%s' (release '%s') detected.
# This release is incompatible with the P4DTI."
raise error, catalog.msg(1005, (version, release))
# 4. TURN OFF TYPE CONVERSION
#
# These functions turn off MySQLdb's type conversion for dates and
# times. The P4DTI needs the raw date/time string from the MySQL
# database. However, if you have the mx.DateTime modules installed,
# then MySQL will convert dates and times into DateTime objects.
#
# These functions are needed because type conversion is handled
# differently in different MySQLdb releases.
#
# Each of these functions returns a dictionary of extra keyword
# arguments to be passed to MySQLdb.connect.
MySQLdb_date_time_types = [
MySQLdb.FIELD_TYPE.DATETIME,
MySQLdb.FIELD_TYPE.DATE,
MySQLdb.FIELD_TYPE.TIME,
MySQLdb.FIELD_TYPE.TIMESTAMP,
]
# 4.1. Releases with type_conv
#
# In releases prior to 0.9.0, type conversion is handled by the two
# dictionaries MySQLdb.type_conv (MySQL to Python) and
# MySQLdb.quote_conv (Python to MySQL). So turn off type conversion by
# deleting entries from MySQLdb.type_conv. No extra arguments are
# returned. See [Dustman 2000-11-30].
def type_conv():
for t in MySQLdb_date_time_types:
if MySQLdb.type_conv.has_key(t):
del MySQLdb.type_conv[t]
return {}
# 4.2. Releases needing a conv argument
#
# In release 0.9.0 and later, you must construct a dictionary to perform
# both kinds of type conversion and pass it as the 'conv' argument to
# MySQLdb.connect. See [Dustman 2001-05-11, 3.1].
def conv_argument():
import MySQLdb.converters
conv = MySQLdb.converters.conversions.copy()
for t in MySQLdb_date_time_types:
if conv.has_key(t):
del conv[t]
return { 'conv': conv }
# 4.3. Guess what to do
#
# When we encounter an unrecognized release, we do our best to support
# it by taking a guess as to what to do by seeing if MySQLdb.type_conv
# exists, and calling one of the two functions above, according.
def guess():
if hasattr(MySQLdb, 'type_conv'):
return type_conv()
else:
return conv_argument()
# 5. SUPPORT TABLE
#
# This table maps the MySQLdb release name (as determined by the
# what_release, section 2.3) to a pair of functions that we call when we
# identify that release.
#
# The first function takes three arguments: the MySQLdb.__version__
# string, the MySQLdb release name (or None if undetermined), and the
# P4DTI configuration (which must be set up to have a logger). This
# function must report on whether the MySQLdb release is supported, and
# raise an error if it is known not to work.
#
# The second function takes no arguments. It must turn off type
# conversion for date and time values, and return a dictionary of
# keyword arguments to be passed to MySQLdb.connect in addition to the
# basic five arguments.
#
# This table will be updated as we test against and support more MySQLdb
# releases.
MySQLdb_support = {
# These old versions lack an escape_string method; see job000317.
'0.2.0': (broken, None),
'0.2.1': (broken, None),
# Newer versions are 'supported' if we've tested against them,
# 'unsupported' if not tested against but believed to work.
'0.2.2': (supported, type_conv),
'0.3.0': (supported, type_conv),
'0.9.0': (unsupported, conv_argument),
'0.9.1c1': (unsupported, conv_argument),
'0.9.1': (supported, conv_argument),
'0.9.2': (supported, conv_argument),
}
# 6. CONNECT TO MYSQL
def connect(config):
version, release = what_release(MySQLdb)
support, args = MySQLdb_support.get(release, (unsupported, guess))
support(version, release, config)
connect_args = {
'host': config.dbms_host,
'port': config.dbms_port,
'db': config.dbms_database,
'user': config.dbms_user,
'passwd': config.dbms_password,
}
assert args
connect_args.update(args())
return apply(MySQLdb.connect, [], connect_args)
# A. REFERENCES
#
# [Dustman 2000-11-30] "MySQLdb: a Python interface for MySQL" (release
# 0.3.5); Andy Dustman; 2000-11-30;
# <http://www.ravenbrook.com/project/p4dti/import/2001-03-25/MySQL-python-0.3.5/MySQL-python-0.3.5/MySQLdb.py>.
#
# [Dustman 2001-05-11] "MySQLdb: a Python interface for MySQL" (release
# 0.9.1); Andy Dustman; 2001-05-11;
# <http://www.ravenbrook.com/project/p4dti/import/2001-10-16/MySQL-python-0.9.1/MySQL-python-0.9.1/doc/MySQLdb.html>.
#
#
# B. DOCUMENT HISTORY
#
# 2001-10-25 NB Created.
#
# 2001-11-21 GDR Improved text of warning for unsupported releases.
# what_release now copes if MySQLdb.__version__ is missing.
#
# 2002-01-31 GDR Support MySQLdb release 0.9.1; include code for turning
# off date/time conversion; replace check_supported method with connect.
#
#
# C. COPYRIGHT AND LICENSE
#
# This file is copyright (c) 2001 Perforce Software, Inc. All rights
# reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.
#
#
# $Id: //info.ravenbrook.com/project/p4dti/version/2.1/code/replicator/mysqldb_support.py#1 $