Great idea - I was not aware of pymysql. I had assumed web2py depended on an
external MySQLdb module, which I already had installed.
I changed the couple of lines to make it use gluon.contrib.pymysql, and it
appears to still work. Revised copy is attached.
Kevin
'''
Create the web2py code needed to access your mysql legacy db.
Access your tables with:
legacy_db(legacy_db.mytable.id>0).select()
If the script crashes this is might be due to that fact that the data_type_map
dictionary below is incomplete. Please complete it, improve it and continue.
Created by Falko Krause, minor modifications by Massimo Di Pierro,
Ron McOuat, and Kevin Ivarsen
'''
from optparse import OptionParser
import re
import sys
sys.path.append("..")
from gluon.contrib import pymysql
# If true, write some diagnostic messages to stderr. Overridden by -v option
SHOW_LOG_MESSAGES = False
data_type_map = dict(
varchar = 'string',
int = 'integer',
integer = 'integer',
tinyint = 'integer',
smallint = 'integer',
mediumint = 'integer',
bigint = 'integer',
float = 'double',
double = 'double',
char = 'string',
decimal = 'integer',
date = 'date',
#year = 'date',
time = 'time',
timestamp = 'datetime',
datetime = 'datetime',
binary = 'blob',
blob = 'blob',
tinyblob = 'blob',
mediumblob = 'blob',
longblob = 'blob',
text = 'text',
tinytext = 'text',
mediumtext = 'text',
longtext = 'text',
enum = 'string',
)
def main():
options = parse_args()
print extract_mysql(options)
log("")
log("PROCESS COMPLETE!")
def parse_args():
global SHOW_LOG_MESSAGES
optparser = OptionParser(add_help_option=False) # -h for help interferes with -h for host
optparser.add_option("-u", "--user", dest="user", action="store",
type="string", default="root",
help="MySQL username (default: root)")
optparser.add_option("-p", "--password", dest="password", action="store",
type="string", default="",
help="MySQL password (default: none)")
optparser.add_option("-h", "--host", dest="host", action="store",
type="string", default="localhost",
help="MySQL host (default: localhost)")
optparser.add_option("-D", "--database", dest="schema", action="store",
type="string",
help="MySQL database schema to use")
optparser.add_option("-a", "--dalname", dest="dalname", action="store",
type="string", default="legacy_db",
help="Assign DAL to this variable name (default: legacy_db)")
optparser.add_option("-c", "--colcomments", dest="column_comments", action="store_true",
help="Add column definition comments")
optparser.add_option("-C", "--commentalign", dest="alignment", action="store",
type="int", default=45,
help="Align comments this many spaces from start of line")
optparser.add_option("-n", "--notnull", dest="notnulls", action="store_true",
help="Add notnull=True for NOT NULL columns")
optparser.add_option("-v", "--verbose", dest="verbose", action="store_true",
help="Verbose: show what is happening while tables are being parsed")
optparser.add_option("-s", "--singlemigrate", dest="single_migrate", action="store_true",
help="Use a single 'migrate=False' in the DAL() construtor instead of one in each define_table()")
(options, args) = optparser.parse_args()
if options.schema is None:
optparser.print_help()
print
print "Examples:"
print " Read the guestbook schema from localhost, user root, no password"
print " extract_mysql_models.py -D guestbook "
print
print " Do the same, but on a different host with login and password"
print " extract_mysql_models.py -u bob -p secret -h db.example.com -D guestbook"
print
print " Assign the DAL to variable 'db', use a global migrate=False, add comments"
print " describing each column (using long-style args), and add notnull args:"
print " extract_mysql_models.py --database=guestbook --dalname db \\"
print " --singlemigrate --colcomments --notnull"
print
print "You must at least specify a schema to read from."
print
print "Generated database is written to stdout. Add \"> filename.py\""
print "to save to a file"
print
sys.exit(1)
if options.verbose:
SHOW_LOG_MESSAGES = True
return options
def log(text):
if SHOW_LOG_MESSAGES:
sys.stderr.write(text + "\n")
def extract_mysql(opt):
db = pymysql.connect(user=opt.user,
passwd=opt.password,
host=opt.host,
db=opt.schema)
curs = db.cursor()
curs.execute("SHOW TABLES")
tables = curs.fetchall()
tables = [row[0] for row in tables] # Convert tuple of tuples to tuple of strings
if opt.single_migrate:
dal_migrate = ", migrate=False"
else:
dal_migrate = ""
connection_string = "%s = DAL('mysql://%s:%s@localhost/%s'%s)" % (
opt.dalname, opt.user, opt.password, opt.schema, dal_migrate)
legacy_db_table_web2py_code = []
for table_name in tables:
log("Processing table '%s'" % table_name)
curs.execute("SHOW CREATE TABLE " + table_name)
sql_create_stmnt = curs.fetchone()[1]
if 'CREATE' in sql_create_stmnt: # make sure returned statement looks sensible
sql_lines = sql_create_stmnt.split('\n')
# remove garbage lines from sql statement
# n.b. this is a relic of old mysqldump method -- may no longer be
# necessary. SHOW CREATE TABLE doesn't seem to produce any junk
sql_lines = [x for x in sql_lines if not(x.startswith('--') or x.startswith('/*') or x =='')]
log("Processing create statement:")
for line in sql_lines:
log(" " + line)
# generate the web2py code from the create statement
web2py_table_code = ''
table_name = re.search('CREATE TABLE .(\S+). \(', sql_lines[0]).group(1)
log("Extracted table name '%s'" % table_name)
fields = []
for line in sql_lines[1:-1]:
if re.search('PRIMARY', line) or re.search('KEY', line) or re.search(' ID', line) or line.startswith(')'):
continue
hit = re.search('(\S+)\s+(\S+)(,| )( .*)?', line)
if hit != None:
name, coltype = hit.group(1), hit.group(2)
name = re.sub('`', '', name)
coltype = re.sub(r'(\w+)\(.*',r'\1', coltype)
daltype = data_type_map[coltype]
# Is it a primary key with a non-default name?
if daltype == 'integer' and line.find("AUTO_INCREMENT") != -1:
daltype = 'id'
# Extract a column comment, if any.
# Also append the original column definition
comment_index = line.find(" COMMENT ")
if comment_index != -1:
comment = line[comment_index+9:]
comment = comment.strip().lstrip("'").rstrip(",").rstrip("'")
else:
comment = ""
# Check nullable status
if opt.notnulls and line.find(" NOT NULL") != -1:
notnull = ", notnull=True"
else:
notnull = ""
if opt.column_comments:
comment += " (%s)" % line.strip()
if comment != "":
comment = " # " + comment.strip()
web2py_table_code += "\n"
field_definition = " Field('%s', '%s'%s)," % (
name, daltype, notnull)
field_definition = field_definition.ljust(opt.alignment)
field_definition += comment
web2py_table_code += field_definition
if opt.single_migrate:
define_table_migrate = ""
else:
define_table_migrate = "migrate=False"
web2py_table_code = "%s.define_table('%s', %s\n %s)" % (
opt.dalname, table_name, web2py_table_code, define_table_migrate)
legacy_db_table_web2py_code.append(web2py_table_code)
# write the legacy db to file
legacy_db_web2py_code = connection_string+"\n\n"
legacy_db_web2py_code += "\n\n".join(legacy_db_table_web2py_code)
return legacy_db_web2py_code
if __name__ == '__main__':
main()