George Perry - 2006-10-31

I have been working on code that overrides the MySQLdb.converters.conversions dictionary. However, I have observed that this causes the created connection instance's "string_literal" function to get garbage collected.

Traceback ...
...
  File "/usr/local/lib/python2.4/site-packages/MySQLdb/connections.py", line
146, in string_literal
    return self.string_literal(obj)
ReferenceError: weakly-referenced object no longer exists

I'm trying to deprecate some old code and introduce the new code. However, this is happenning with some difficulty.

conn_args = dict(user='user', passwd='passwd', host='my_host', db='inclause')

import time
import MySQLdb
import MySQLdb.converters

# mysql> create database inclause;
# Query OK, 1 row affected (0.02 sec)
#
# mysql> use inclause
# Database changed
# mysql> create table x (a int(11), b varchar(10), c varchar(10));
# Query OK, 0 rows affected (0.15 sec)
#
# mysql> insert into x values(1, 'hello', 'world');
# Query OK, 1 row affected (0.02 sec)
#
# mysql> insert into x values(2, 'hello', 'dolly');
# Query OK, 1 row affected (0.01 sec)
#
# mysql> insert into x values(3, 'bye', 'world');
# Query OK, 1 row affected (0.02 sec)
#
# mysql> insert into x values(4, 'bye', 'dolly');
# Query OK, 1 row affected (0.02 sec)
#
# mysql> insert into x values(5, 'what up', 'world');
# Query OK, 1 row affected (0.04 sec)
#
# mysql> insert into x values(6, 'what up', 'dolly');
# Query OK, 1 row affected (0.03 sec)

my_conversions = MySQLdb.converters.conversions.copy()

def InClause2Str(o, d):
    default_conv = MySQLdb.converters.escape
    converted = list()
    for x in o:
        conv = d.get(x.__class__, default_conv)
        converted.append(conv(x, d))
    return '('+','.join(converted)+')'

class InClause(list):
    pass

my_conversions[InClause] = InClause2Str

conversions = MySQLdb.converters.conversions

sql_proposed = "select * from x where b in %(b)s and c = %(c)s"
sql_deprecated = "select * from x where b in (%s) and c = %%(c)s"

args = dict()
args['b'] = InClause(['hello','what up'])
args['c'] = 'dolly'

conn= MySQLdb.connect(conv=my_conversions, **conn_args)
try:
    cursor = conn.cursor()
    try:
        print time.ctime(time.time())

        # This is what I would like to use
        cursor.execute(sql_proposed, args)
        print 'proposed results:', cursor.fetchall()

        # This code will be deprecated throughout the code base.
        arg_str = ','.join(map(conn.string_literal, ['hello','what up']))
        sql_real = sql_deprecated % (arg_str,)
        cursor.execute(sql_real, args)
        print 'deprecated results:', cursor.fetchall()

        print
    finally:
        cursor.close()
    time.sleep(60)
finally:
    conn.close()

According to the documentation for the MySQLdb package, what I have done is the best way to do it. However, would it be safer to add the new converter to the connection object and not pass it in as the conv keyword to the connection.

conn= MySQLdb.connect(**conn_args)
conn.converter[InClause] = InClause2Str

Alternatively, is there a wiser way to be doing things?