Mercurial > hg > ltpdarepo
changeset 25:04b93e15bb71
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.
author | Daniele Nicolodi <daniele@grinta.net> |
---|---|
date | Mon, 27 Jun 2011 16:16:07 +0200 |
parents | e02c16da590d |
children | eaa352730e44 |
files | buildout.cfg src/ltpdarepo/admin.py |
diffstat | 2 files changed, 200 insertions(+), 249 deletions(-) [+] |
line wrap: on
line diff
--- 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
--- 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__':