# Perforce Defect Tracking Integration Project # # # 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; # . # # [Dustman 2001-05-11] "MySQLdb: a Python interface for MySQL" (release # 0.9.1); Andy Dustman; 2001-05-11; # . # # # 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 $