# HG changeset patch # User Daniele Nicolodi # Date 1309184167 -7200 # Node ID 04b93e15bb7102136c7380a6c4501c5f1a546cf2 # Parent e02c16da590d1c5f69b0dae5f066a85fa9590aee Rework command line administration tool. Use 'argparse' module for command line parsing. Therefore add argparse to installed eggs. Remove code duplication from admin.py and web interface support classes. diff -r e02c16da590d -r 04b93e15bb71 buildout.cfg --- a/buildout.cfg Mon Jun 27 16:16:05 2011 +0200 +++ b/buildout.cfg Mon Jun 27 16:16:07 2011 +0200 @@ -8,6 +8,7 @@ Flask WTForms ordereddict + argparse ltpdarepo zope.testbrowser [wsgi] interpreter = python diff -r e02c16da590d -r 04b93e15bb71 src/ltpdarepo/admin.py --- a/src/ltpdarepo/admin.py Mon Jun 27 16:16:05 2011 +0200 +++ b/src/ltpdarepo/admin.py Mon Jun 27 16:16:07 2011 +0200 @@ -1,62 +1,134 @@ +import argparse +import logging + +from contextlib import contextmanager +from pprint import pprint +from string import lower + import MySQLdb as mysql -from pprint import pprint + +from . import app +app.config.from_pyfile('config.py') + +from .user import User +from .database import Database +from .config import HOSTNAME, DATABASE, USERNAME, PASSWORD + -from config import HOSTNAME, DATABASE, USERNAME, PASSWORD +@contextmanager +def interact(app): + # fake request + ctx = app.test_request_context('') + ctx.push() + # execute before request handlers + app.preprocess_request() + + yield -from install import install -from upgrade import upgrade + # execute after request handlers + app.process_response(app.response_class()) + ctx.pop() + + +class verbosity(argparse.Action): + def __call__(self, *args, **kwargs): + # increse logging level + logger = logging.getLogger('') + logger.setLevel(logger.level - 10) -def adduser(username, password='', name='', surname='', email='', telephone='', institution=''): - conn = mysql.connect(host=HOSTNAME, db=DATABASE, user=USERNAME, passwd=PASSWORD) - curs = conn.cursor() +class Commands(object): + def __init__(self): + self.params = argparse.ArgumentParser() + self.commands = self.params.add_subparsers(title='commands', + description='') + self.params.add_argument('-v', '--verbose', action=verbosity, + nargs=0, dest=argparse.SUPPRESS, + help='increase verbosity') - for host in ('localhost', '%'): - curs.execute('''CREATE USER %s@%s IDENTIFIED BY %s''', (username, host, password)) + def add_argument(self, *args, **kwargs): + return self.params.add_argument(*args, **kwargs) - curs.execute('''INSERT INTO users (username, given_name, family_name, - email, telephone, institution, is_admin) - VALUES (%s, %s, %s, %s, %s, %s, 0)''', - (username, name, surname, email, telephone, institution)) + def add(self, func, name=None): + name = name or func.__name__ + parser = self.commands.add_parser(name, help=func.__doc__, + description=func.__doc__, + add_help=False) + parser.set_defaults(command=func) + return parser + + def parse(self, *args): + return self.params.parse_args(*args) - conn.commit() - conn.close() + def dispatch(self): + args = self.params.parse_args() + command = args.command + del args.command + command(**vars(args)) + + def __iter__(self): + return iter(sorted(self.commands.choices.keys())) + + def __getitem__(self, name): + return self.commands.choices[name] -def deluser(username): - conn = mysql.connect(host=HOSTNAME, db=DATABASE, user=USERNAME, passwd=PASSWORD) - curs = conn.cursor() +commands = Commands() + + +from .install import install +cmd = commands.add(install) + +from .upgrade import upgrade +cmd = commands.add(upgrade) +cmd.add_argument('--from', type=float, dest='fromrev', metavar='REV') + +def useradd(username, **kwargs): + with interact(app): + user = User.load(username) + if user is not None: + raise Exception('User already present') + user = User(username, **kwargs) + user.create() - curs.execute('''DELETE FROM users WHERE username=%s''', username) - curs.execute('''SELECT Host FROM mysql.user WHERE User=%s''', username) - hosts = [row[0] for row in curs.fetchall()] - for host in hosts: - curs.execute('''DROP USER %s@%s''', (username, host)) +cmd = commands.add(useradd) +cmd.add_argument('username', metavar='USERNAME') +cmd.add_argument('-p', '--password', default='') +cmd.add_argument('-n', '--name', default='') +cmd.add_argument('-s', '--surname', default='') +cmd.add_argument('-e', '--email', default='') + - conn.commit() - conn.close() +def userdel(username): + with interact(app): + user = User.load(username) + if user is None: + raise Exception('user not found') + user.delete() + +cmd = commands.add(userdel) +cmd.add_argument('username', metavar='USERNAME') def passwd(username, password): - conn = mysql.connect(host=HOSTNAME, db=DATABASE, user=USERNAME, passwd=PASSWORD) - curs = conn.cursor() + with interact(app): + user = User.load(username) + if user is None: + raise Exception('user not found') + user.passwd(password) - curs.execute('''SELECT Host FROM mysql.user WHERE User=%s''', username) - hosts = [row[0] for row in curs.fetchall()] - for host in hosts: - curs.execute('''SET PASSWORD FOR %s@%s = PASSWORD(%s)''', (username, host, password)) - - conn.commit() - conn.close() +cmd = commands.add(passwd) +cmd.add_argument('username', metavar='USERNAME') +cmd.add_argument('password', metavar='PASSWORD') def grant(username, database, privs): conn = mysql.connect(host=HOSTNAME, db=DATABASE, user=USERNAME, passwd=PASSWORD) curs = conn.cursor() - privs = set([p.upper() for p in privs.split(',')]) - if privs.difference(frozenset(['SELECT', 'INSERT', 'UPDATE', 'DELETE'])): - raise ValueError + if 'admin' in privs: + curs.execute('''UPDATE users SET is_admin=1 WHERE username=%s''', username) + privs.remove('admin') for priv in privs: curs.execute('''GRANT %s ON %s.* TO %%s@%%s''' % (priv, database), (username, '%')) @@ -64,210 +136,59 @@ conn.commit() conn.close() +cmd = commands.add(grant) +cmd.add_argument('username', metavar='USERNAME') +cmd.add_argument('database', metavar='DATABASE') +cmd.add_argument('privs', metavar='PRIV', nargs='+', type=lower, + choices=frozenset(['select', 'insert', 'update', 'delete', 'admin'])) -def privileges(username, verbose=False): + +def privileges(username): conn = mysql.connect(host=HOSTNAME, db=DATABASE, user=USERNAME, passwd=PASSWORD) curs = conn.cursor() - curs.execute('''SELECT DISTINCT Db, Select_priv, Insert_priv, Update_priv, Delete_priv - FROM mysql.db WHERE User=%s''', username) - privs = {} + curs.execute('''SELECT DISTINCT Db, Select_priv, Insert_priv, + Update_priv, Delete_priv FROM mysql.db WHERE User=%s''', username) for row in curs.fetchall(): privs[row[0]] = {'select': row[1] == 'Y', 'insert': row[2] == 'Y', 'update': row[3] == 'Y', 'delete': row[4] == 'Y'} - conn.close() - - if verbose: - pprint(privs) - return privs - + pprint(privs) -def admin(username): - conn = mysql.connect(host=HOSTNAME, db=DATABASE, user=USERNAME, passwd=PASSWORD) - curs = conn.cursor() - - curs.execute('''UPDATE users SET is_admin = 1 WHERE username=%s''', username) - - conn.commit() - conn.close() +cmd = commands.add(privileges) +cmd.add_argument('username', metavar='USERNAME') -def initdb_v01(database): - conn = mysql.connect(host=HOSTNAME, db=database, user=USERNAME, passwd=PASSWORD) - curs = conn.cursor() - - curs.execute("""CREATE TABLE `ao` ( - `id` int(10) unsigned NOT NULL auto_increment COMMENT 'Unique ID of every data set in this table', - `obj_id` int(11) default NULL COMMENT 'ID of the object this data set belongs to', - `data_type` text COMMENT 'Data type of the object, see corresponding table', - `data_id` int(11) default NULL COMMENT 'ID of the data set in the corresponding table', - `description` text COMMENT 'Description of the object', - `mfilename` text, - `mdlfilename` text, - PRIMARY KEY (`id`) - ) ENGINE=MyISAM DEFAULT CHARSET=latin1""") - - curs.execute("""CREATE TABLE `bobjs` ( - `id` int(10) unsigned NOT NULL auto_increment COMMENT 'Unique ID of every data set in this table', - `obj_id` int(11) default NULL COMMENT 'ID of the object this data set belongs to', - `mat` longblob COMMENT 'Binary version of the object', - PRIMARY KEY (`id`), - KEY `object_index` (`obj_id`) - ) ENGINE=MyISAM DEFAULT CHARSET=latin1""") - - curs.execute("""CREATE TABLE `cdata` ( - `id` int(10) unsigned NOT NULL auto_increment COMMENT 'Unique ID of every data set in this table', - `xunits` text COMMENT 'Units of the x axis', - `yunits` text COMMENT 'Units of the y axis', - PRIMARY KEY (`id`) - ) ENGINE=MyISAM DEFAULT CHARSET=latin1""") - - curs.execute("""CREATE TABLE `collections` ( - `id` int(10) unsigned NOT NULL auto_increment COMMENT 'Unique ID of every data set in this table', - `nobjs` int(11) default NULL COMMENT 'Number of objects in a collection', - `obj_ids` text COMMENT 'List of objects in a collection', - PRIMARY KEY (`id`) - ) ENGINE=MyISAM DEFAULT CHARSET=latin1""") - - curs.execute("""CREATE TABLE `fsdata` ( - `id` int(10) unsigned NOT NULL auto_increment COMMENT 'Unique ID of every data set in this table', - `xunits` text COMMENT 'Units of the x axis', - `yunits` text COMMENT 'Units of the y axis', - `fs` DOUBLE default NULL, - PRIMARY KEY (`id`) - ) ENGINE=MyISAM DEFAULT CHARSET=latin1""") - - curs.execute("""CREATE TABLE `mfir` ( - `id` int(10) unsigned NOT NULL auto_increment COMMENT 'Unique ID of every data set in this table', - `obj_id` int(11) default NULL COMMENT 'The ID of the object this data set belongs to', - `in_file` text, - `fs` DOUBLE default NULL, - PRIMARY KEY (`id`) - ) ENGINE=MyISAM DEFAULT CHARSET=latin1""") - - curs.execute("""CREATE TABLE `miir` ( - `id` int(10) unsigned NOT NULL auto_increment COMMENT 'Unique ID of every data set in this table', - `obj_id` int(11) default NULL COMMENT 'ID of the object this data set belongs to', - `in_file` text, - `fs` DOUBLE default NULL, - PRIMARY KEY (`id`) - ) ENGINE=MyISAM DEFAULT CHARSET=latin1""") +def createdb(database, name='', description=''): + with interact(app): + db = Database.load(database) + if db is not None: + raise Exception('database "%s" exists' % database) + db = Database(database, name=name, description=description) + db.create() - curs.execute("""CREATE TABLE `objmeta` ( - `id` int(10) unsigned NOT NULL auto_increment COMMENT 'A unique ID of every data set in this table', - `obj_id` int(11) default NULL COMMENT 'The ID of the object this data set belongs to', - `obj_type` text COMMENT 'Object type, e.g. ao, mfir, miir', - `name` text COMMENT 'Name of an object', - `created` datetime default NULL COMMENT 'Creation time of an object', - `version` text COMMENT 'Version string of an object', - `ip` text COMMENT 'IP address of the creator', - `hostname` text COMMENT 'Hostname of the ceator', - `os` text COMMENT 'Operating system of the creator', - `submitted` datetime default NULL COMMENT 'Submission time of an object', - `experiment_title` text COMMENT 'Experiment title', - `experiment_desc` text COMMENT 'Experiment description', - `analysis_desc` text COMMENT 'Analysis description', - `quantity` text COMMENT 'Quantity', - `additional_authors` text COMMENT 'Additional authors of an object', - `additional_comments` text COMMENT 'Additional comments to an object', - `keywords` text COMMENT 'Keywords', - `reference_ids` text COMMENT 'Reference IDs', - `validated` tinyint(4) default NULL COMMENT 'Validated', - `vdate` datetime default NULL COMMENT 'Validation time', - `author` TEXT DEFAULT NULL COMMENT 'Author of the object', - PRIMARY KEY (`id`) - ) ENGINE=MyISAM DEFAULT CHARSET=latin1""") - - curs.execute("""CREATE TABLE `objs` ( - `id` int(10) unsigned NOT NULL auto_increment COMMENT 'Unique ID of every object in this database', - `xml` longtext COMMENT 'Raw XML representation of the object', - `uuid` text COMMENT 'Unique Global Identifier for this object', - `hash` text COMMENT 'MD5 hash of an object', - PRIMARY KEY (`id`) - ) ENGINE=MyISAM DEFAULT CHARSET=latin1""") - - curs.execute("""CREATE TABLE `transactions` ( - `id` int(10) unsigned NOT NULL auto_increment COMMENT 'Unique ID of every data set in this table', - `obj_id` int(11) default NULL COMMENT 'ID of the object the transaction belongs to', - `user_id` int(11) default NULL COMMENT 'ID of the User of the transactions', - `transdate` datetime default NULL COMMENT 'Date and time of the transaction', - `direction` text COMMENT 'Direction of the transaction', - PRIMARY KEY (`id`) - ) ENGINE=MyISAM DEFAULT CHARSET=latin1""") - - curs.execute("""CREATE TABLE `tsdata` ( - `id` int(10) unsigned NOT NULL auto_increment COMMENT 'Unique ID of every data set in this table', - `xunits` text COMMENT 'Units of the x axis', - `yunits` text COMMENT 'Units of the y axis', - `fs` DOUBLE default NULL COMMENT 'Sample frequency [Hz]', - `nsecs` DOUBLE default NULL COMMENT 'Number of nanoseconds', - `t0` datetime default NULL COMMENT 'Starting time of the time series', - PRIMARY KEY (`id`) - ) ENGINE=MyISAM DEFAULT CHARSET=latin1""") - - curs.execute("""CREATE TABLE `users` ( - `id` int(10) unsigned NOT NULL auto_increment COMMENT 'Unique ID of every data set in this table', - `firstname` text COMMENT 'The first name of the user', - `familyname` text COMMENT 'The family name of the user', - `username` text COMMENT 'The username/login of the user', - `email` text COMMENT 'The email address of the user', - `telephone` text COMMENT 'Telephone number of the user', - `institution` text COMMENT 'Institution of the user', - PRIMARY KEY (`id`) - ) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=latin1""") - - curs.execute("""CREATE TABLE `xydata` ( - `id` int(10) unsigned NOT NULL auto_increment COMMENT 'Unique ID of every data set in this table', - `xunits` text COMMENT 'Units of the x axis', - `yunits` text COMMENT 'Units of the y axis', - PRIMARY KEY (`id`) - ) ENGINE=MyISAM DEFAULT CHARSET=latin1""") - - conn.commit() - conn.close() +cmd = commands.add(createdb, name='create-database') +cmd.add_argument('database', help='database name') +cmd.add_argument('-n', '--name', default='') +cmd.add_argument('-d', '--description', default='') -def initdb(database): - initdb_v01(database) - - -def create_database(database, name='', description=''): - conn = mysql.connect(host=HOSTNAME, db=DATABASE, user=USERNAME, passwd=PASSWORD) - curs = conn.cursor() +def dropdb(database): + with interact(app): + db = Database.load(database) + if db is None: + raise Exception('database "%s" not found' % database) + db.drop() - curs.execute("""CREATE DATABASE `%s`""" % database) - curs.execute("""INSERT INTO available_dbs (db_name, name, description) - VALUES (%s, %s, %s)""", (database, name, description)) - - initdb(database) - - conn.commit() - conn.close() +cmd = commands.add(dropdb, name='drop-database') +cmd.add_argument('database', help='database name') -def drop_database(database): - conn = mysql.connect(host=HOSTNAME, db=DATABASE, user=USERNAME, passwd=PASSWORD) - conn.close() - - -def setup(): - install() - upgrade() - adduser('u1', 'u1') - admin('u1') - create_database('db1', description=u'Test database One') - create_database('db2', description=u'Test database Two \2766') - populate('db1', 30) - grant('u1', 'db1', 'select') - - -def wipe(): - # delete all possible content generated during testing including - # LTPDA repository management database +def wipe(yes=True): + """delete all database content""" conn = mysql.connect(host=HOSTNAME, db='', user=USERNAME, passwd=PASSWORD) curs = conn.cursor() @@ -294,11 +215,12 @@ conn.commit() conn.close() +cmd = commands.add(wipe) +cmd.add_argument('--yes', action='store_true', required=True) + def populate(database, nobjs): - # populate a dababase witn nobjs fake objects - - nobjs = int(nobjs) + """populate a dababase witn fake objects""" conn = mysql.connect(host=HOSTNAME, db=database, user=USERNAME, passwd=PASSWORD) curs = conn.cursor() @@ -307,7 +229,7 @@ import uuid import random import re - + words = re.split('\W+', lorem) sentences = [s + '.' for s in [s.strip() for s in lorem.split('.')] if s] @@ -333,33 +255,61 @@ conn.commit() +cmd = commands.add(populate) +cmd.add_argument('database', help='database') +cmd.add_argument('nobjs', nargs='?', default=30, type=int, help='number of obejcts') + + +def setup(): + """setup test environment""" + + install() + upgrade() + useradd('u1', password='u1') + grant('u1', '%', ['admin', ]) + createdb('db1', description=u'Test database One') + createdb('db2', description=u'Test database Two \u2766') + populate('db1', 30) + upgrade(fromrev=2.6) + grant('u1', 'db1', ['select', 'insert', 'update', 'delete']) + +cmd = commands.add(setup) + + +def help(name=None): + """show help for a specific command or commands overview + + With no arguments, print a list of commands with short help + messages. Given a command name, print help for that command. + """ + + if name is not None: + print commands[name].format_help().strip() + return + + print "LTPDA Repository administration tool" + print "" + print "commands:" + print "" + for cmd in commands: + doc = commands[cmd].description or ' ' + print " %-15s %s" % (cmd, doc.splitlines()[0]) + print "" + +cmd = commands.add(help) +cmd.add_argument('name', nargs='?', help='command', metavar='NAME') + def main(): - import sys - import functools + # setup logging + handler = logging.StreamHandler() + handler.setLevel(logging.DEBUG) + logger = logging.getLogger('ltpdarepo') + logger.addHandler(handler) + logger.setLevel(logging.INFO) - actions = {'install': install, - 'adduser': adduser, - 'deluser': deluser, - 'passwd': passwd, - 'grant': grant, - 'privs': functools.partial(privileges, verbose=True), - 'admin': admin, - 'create-database': create_database, - 'drop-database': drop_database, - 'upgrade': upgrade, - 'wipe': wipe, - 'setup': setup, - 'populate': populate} - - if len(sys.argv) > 1: - action = actions.get(sys.argv[1]) - if action is not None: - action(*sys.argv[2:]) - return - - for action in sorted(actions.keys()): - print action #, actions[action].__doc__ + # dispatch command line + return commands.dispatch() if __name__ == '__main__':