From 3779cf0eb9a3f2c2a32359edd49cc075045735da Mon Sep 17 00:00:00 2001 From: richard Date: Fri, 9 Nov 2001 10:11:08 +0000 Subject: [PATCH] . roundup-admin now handles all hyperdb exceptions git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@388 57a73879-2fb5-44c3-a270-3262357dd7e2 --- CHANGES.txt | 2 + roundup-admin | 302 ++++++++++++++++++++++++++++++++++----------- roundup/hyperdb.py | 7 +- 3 files changed, 238 insertions(+), 73 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index c79c3af..5a02713 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -11,6 +11,7 @@ Feature: . "roundup.cgi" is now installed to "/share/roundup/cgi-bin" . roundup-admin now accepts abbreviated commands (eg. l = li = lis = list) . roundup-mailgw now supports unix mailbox and POP as sources of mail. + . roundup-admin now handles all hyperdb exceptions Fixed: . Fixed a bug in HTMLTemplate changes. @@ -30,6 +31,7 @@ Fixed: . bug #477892 ] Password edit doesn't fix login cookie . newuser_action now presents error messages rather than tracebacks. . bug #479511 ] mailgw to pop + . bad error report in hyperdb 2001-10-23 - 0.3.0 pre 3 Feature: diff --git a/roundup-admin b/roundup-admin index ef2ea2a..f36e35d 100755 --- a/roundup-admin +++ b/roundup-admin @@ -16,7 +16,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: roundup-admin,v 1.41 2001-11-09 01:25:40 richard Exp $ +# $Id: roundup-admin,v 1.42 2001-11-09 10:11:08 richard Exp $ import sys if int(sys.version[0]) < 2: @@ -50,6 +50,9 @@ class CommandDict(UserDict.UserDict): raise KeyError, key return l +class UsageError(ValueError): + pass + class AdminTool: def __init__(self): @@ -159,14 +162,14 @@ Command help: # try help_ methods if self.help.has_key(topic): self.help[topic]() - return + return 0 # try command docstrings try: l = self.commands.get(topic) except KeyError: print 'Sorry, no help for "%s"'%topic - return + return 1 # display the help for each match, removing the docsring indent for name, help in l: @@ -179,6 +182,7 @@ Command help: print line[indent:] else: print line + return 0 def help_initopts(self): import roundup.templates @@ -242,15 +246,27 @@ Command help: designators = string.split(args[1], ',') l = [] for designator in designators: + # decode the node designator try: classname, nodeid = roundupdb.splitDesignator(designator) except roundupdb.DesignatorError, message: - print 'Error: %s'%message - return 1 - if self.comma_sep: - l.append(self.db.getclass(classname).get(nodeid, propname)) - else: - print self.db.getclass(classname).get(nodeid, propname) + raise UsageError, message + + # get the class + try: + cl = self.db.getclass(classname) + except KeyError: + raise UsageError, 'invalid class "%s"'%classname + try: + if self.comma_sep: + l.append(cl.get(nodeid, propname)) + else: + print cl.get(nodeid, propname) + except IndexError: + raise UsageError, 'no such %s node "%s"'%(classname, nodeid) + except KeyError: + raise UsageError, 'no such %s property "%s"'%(classname, + propname) if self.comma_sep: print ','.join(l) return 0 @@ -267,15 +283,26 @@ Command help: designators = string.split(args[0], ',') props = {} for prop in args[1:]: - key, value = prop.split('=') + if prop.find('=') == -1: + raise UsageError, 'argument "%s" not propname=value'%prop + try: + key, value = prop.split('=') + except (TypeError, ValueError): + raise UsageError, 'argument "%s" not propname=value'%prop props[key] = value for designator in designators: + # decode the node designator try: classname, nodeid = roundupdb.splitDesignator(designator) except roundupdb.DesignatorError, message: - print 'Error: %s'%message - return 1 - cl = self.db.getclass(classname) + raise UsageError, message + + # get the class + try: + cl = self.db.getclass(classname) + except KeyError: + raise UsageError, 'invalid class "%s"'%classname + properties = cl.getprops() for key, value in props.items(): type = properties[key] @@ -284,14 +311,25 @@ Command help: elif isinstance(type, hyperdb.Password): props[key] = password.Password(value) elif isinstance(type, hyperdb.Date): - props[key] = date.Date(value) + try: + props[key] = date.Date(value) + except ValueError, message: + raise UsageError, '"%s": %s'%(value, message) elif isinstance(type, hyperdb.Interval): - props[key] = date.Interval(value) + try: + props[key] = date.Interval(value) + except ValueError, message: + raise UsageError, '"%s": %s'%(value, message) elif isinstance(type, hyperdb.Link): props[key] = value elif isinstance(type, hyperdb.Multilink): props[key] = value.split(',') - apply(cl.set, (nodeid, ), props) + + # try the set + try: + apply(cl.set, (nodeid, ), props) + except (TypeError, IndexError, ValueError), message: + raise UsageError, message return 0 def do_find(self, args): @@ -302,25 +340,58 @@ Command help: value may be either the nodeid of the linked node, or its key value. ''' classname = args[0] - cl = self.db.getclass(classname) + # get the class + try: + cl = self.db.getclass(classname) + except KeyError: + raise UsageError, 'invalid class "%s"'%classname + + # TODO: handle > 1 argument + # handle the propname=value argument + if prop.find('=') == -1: + raise UsageError, 'argument "%s" not propname=value'%prop + try: + propname, value = args[1].split('=') + except (TypeError, ValueError): + raise UsageError, 'argument "%s" not propname=value'%prop - # look up the linked-to class and get the nodeid that has the value - propname, value = args[1].split('=') + # if the value isn't a number, look up the linked class to get the + # number num_re = re.compile('^\d+$') if not num_re.match(value): - propcl = cl.properties[propname] - if (not isinstance(propcl, hyperdb.Link) and not + # get the property + try: + property = cl.properties[propname] + except KeyError: + raise UsageError, '%s has no property "%s"'%(classname, + propname) + + # make sure it's a link + if (not isinstance(property, hyperdb.Link) and not isinstance(type, hyperdb.Multilink)): - print 'You may only "find" link properties' - return 1 - propcl = self.db.getclass(propcl.classname) - value = propcl.lookup(value) + raise UsageError, 'You may only "find" link properties' - # now do the find - if self.comma_sep: - print ','.join(apply(cl.find, (), {propname: value})) - else: - print apply(cl.find, (), {propname: value}) + # get the linked-to class and look up the key property + link_class = self.db.getclass(property.classname) + try: + value = link_class.lookup(value) + except TypeError: + raise UsageError, '%s has no key property"'%link_class.classname + except KeyError: + raise UsageError, '%s has no entry "%s"'%(link_class.classname, + propname) + + # now do the find + try: + if self.comma_sep: + print ','.join(apply(cl.find, (), {propname: value})) + else: + print apply(cl.find, (), {propname: value}) + except KeyError: + raise UsageError, '%s has no property "%s"'%(classname, + propname) + except (ValueError, TypeError), message: + raise UsageError, message return 0 def do_specification(self, args): @@ -330,7 +401,13 @@ Command help: This lists the properties for a given class. ''' classname = args[0] - cl = self.db.getclass(classname) + # get the class + try: + cl = self.db.getclass(classname) + except KeyError: + raise UsageError, 'invalid class "%s"'%classname + + # get the key property keyprop = cl.getkey() for key, value in cl.properties.items(): if keyprop == key: @@ -349,7 +426,14 @@ Command help: from roundup import hyperdb classname = args[0] - cl = self.db.getclass(classname) + + # get the class + try: + cl = self.db.getclass(classname) + except KeyError: + raise UsageError, 'invalid class "%s"'%classname + + # now do a create props = {} properties = cl.getprops(protected = 0) if len(args) == 1: @@ -372,26 +456,46 @@ Command help: else: # use the args for prop in args[1:]: - key, value = prop.split('=') + if prop.find('=') == -1: + raise UsageError, 'argument "%s" not propname=value'%prop + try: + key, value = prop.split('=') + except (TypeError, ValueError): + raise UsageError, 'argument "%s" not propname=value'%prop props[key] = value # convert types for key in props.keys(): - type = properties[key] + # get the property + try: + type = properties[key] + except KeyError: + raise UsageError, '%s has no property "%s"'%(classname, key) + if isinstance(type, hyperdb.Date): - props[key] = date.Date(value) + try: + props[key] = date.Date(value) + except ValueError, message: + raise UsageError, '"%s": %s'%(value, message) elif isinstance(type, hyperdb.Interval): - props[key] = date.Interval(value) + try: + props[key] = date.Interval(value) + except ValueError, message: + raise UsageError, '"%s": %s'%(value, message) elif isinstance(type, hyperdb.Password): props[key] = password.Password(value) elif isinstance(type, hyperdb.Multilink): props[key] = value.split(',') + # check for the key property if cl.getkey() and not props.has_key(cl.getkey()): - print "You must provide the '%s' property."%cl.getkey() - else: - print apply(cl.create, (), props) + raise UsageError, "you must provide the '%s' property."%cl.getkey() + # do the actual create + try: + print apply(cl.create, (), props) + except (TypeError, IndexError, ValueError), message: + raise UsageError, message return 0 def do_list(self, args): @@ -404,16 +508,27 @@ Command help: alphabetically. ''' classname = args[0] - cl = self.db.getclass(classname) + + # get the class + try: + cl = self.db.getclass(classname) + except KeyError: + raise UsageError, 'invalid class "%s"'%classname + + # figure the property if len(args) > 1: key = args[1] else: key = cl.labelprop() + if self.comma_sep: print ','.join(cl.list()) else: for nodeid in cl.list(): - value = cl.get(nodeid, key) + try: + value = cl.get(nodeid, key) + except KeyError: + raise UsageError, '%s has no property "%s"'%(classname, key) print "%4s: %s"%(nodeid, value) return 0 @@ -433,25 +548,44 @@ Command help: 4 feature ''' classname = args[0] - cl = self.db.getclass(classname) + + # get the class + try: + cl = self.db.getclass(classname) + except KeyError: + raise UsageError, 'invalid class "%s"'%classname + + # figure the property names to display if len(args) > 1: prop_names = args[1].split(',') else: prop_names = cl.getprops().keys() + + # now figure column widths props = [] - for name in prop_names: - if ':' in name: - name, width = name.split(':') - props.append((name, int(width))) + for spec in prop_names: + if ':' in spec: + try: + name, width = spec.split(':') + except (ValueError, TypeError): + raise UsageError, '"%s" not name:width'%spec + props.append((spec, int(width))) else: - props.append((name, len(name))) + props.append((spec, len(spec))) + # now display the heading print ' '.join([string.capitalize(name) for name, width in props]) + + # and the table data for nodeid in cl.list(): l = [] for name, width in props: if name != 'id': - value = str(cl.get(nodeid, name)) + try: + value = str(cl.get(nodeid, name)) + except KeyError: + raise UsageError, '%s has no property "%s"'%(classname, + name) else: value = str(nodeid) f = '%%-%ds'%width @@ -468,10 +602,15 @@ Command help: try: classname, nodeid = roundupdb.splitDesignator(args[0]) except roundupdb.DesignatorError, message: - print 'Error: %s'%message - return 1 + raise UsageError, message + # TODO: handle the -c option? - print self.db.getclass(classname).history(nodeid) + try: + print self.db.getclass(classname).history(nodeid) + except KeyError: + raise UsageError, 'no such class "%s"'%classname + except IndexError: + raise UsageError, 'no such %s node "%s"'%(classname, nodeid) return 0 def do_retire(self, args): @@ -486,9 +625,13 @@ Command help: try: classname, nodeid = roundupdb.splitDesignator(designator) except roundupdb.DesignatorError, message: - print 'Error: %s'%message - return 1 - self.db.getclass(classname).retire(nodeid) + raise UsageError, message + try: + self.db.getclass(classname).retire(nodeid) + except KeyError: + raise UsageError, 'no such class "%s"'%classname + except IndexError: + raise UsageError, 'no such %s node "%s"'%(classname, nodeid) return 0 def do_export(self, args): @@ -511,7 +654,10 @@ Command help: # do all the classes specified for classname in classes: - cl = self.db.getclass(classname) + try: + cl = self.db.getclass(classname) + except KeyError: + raise UsageError, 'no such class "%s"'%classname f = open(os.path.join(dir, classname+'.csv'), 'w') f.write(string.join(cl.properties.keys(), ':') + '\n') @@ -556,17 +702,19 @@ Command help: the old data.) ''' if len(args) < 2: - print do_import.__doc__ - return 1 + raise UsageError, 'Not enough arguments supplied' if csv is None: - print 'Sorry, you need the csv module to use this function.' - print 'Get it from: http://www.object-craft.com.au/projects/csv/' - return 1 + raise UsageError, \ + 'Sorry, you need the csv module to use this function.\n'\ + 'Get it from: http://www.object-craft.com.au/projects/csv/' from roundup import hyperdb # ensure that the properties and the CSV file headings match - cl = self.db.getclass(args[0]) + try: + cl = self.db.getclass(classname) + except KeyError: + raise UsageError, 'no such class "%s"'%classname f = open(args[1]) p = csv.parser(field_sep=':') file_props = p.parse(f.readline()) @@ -575,8 +723,8 @@ Command help: m.sort() props.sort() if m != props: - print 'Import file doesn\'t define the same properties as "%s".'%args[0] - return 1 + raise UsageError, 'Import file doesn\'t define the same '\ + 'properties as "%s".'%args[0] # loop through the file and create a node for each entry n = range(len(props)) @@ -663,12 +811,18 @@ Command help: return 1 # do the command + ret = 0 try: - return function(args[1:]) - finally: - self.db.close() - - return 1 + ret = function(args[1:]) + except UsageError, message: + print 'Error: %s'%message + print function.__doc__ + ret = 1 + except: + import traceback + traceback.print_exc() + ret = 1 + return ret def interactive(self, ws_re=re.compile(r'\s+')): '''Run in an interactive mode @@ -713,10 +867,13 @@ Command help: self.comma_sep = 1 # if no command - go interactive + ret = 0 if not args: - return self.interactive() - - self.run_command(args) + self.interactive() + else: + ret = self.run_command(args) + self.db.close() + return ret if __name__ == '__main__': @@ -725,6 +882,9 @@ if __name__ == '__main__': # # $Log: not supported by cvs2svn $ +# Revision 1.41 2001/11/09 01:25:40 richard +# Should parse with python 1.5.2 now. +# # Revision 1.40 2001/11/08 04:42:00 richard # Expanded the already-abbreviated "initialise" and "specification" commands, # and added a comment to the command help about the abbreviation. diff --git a/roundup/hyperdb.py b/roundup/hyperdb.py index 8c6ed34..49a6db6 100644 --- a/roundup/hyperdb.py +++ b/roundup/hyperdb.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: hyperdb.py,v 1.29 2001-10-27 00:17:41 richard Exp $ +# $Id: hyperdb.py,v 1.30 2001-11-09 10:11:08 richard Exp $ # standard python modules import cPickle, re, string @@ -507,7 +507,7 @@ class Class: if not isinstance(prop, Link) and not isinstance(prop, Multilink): raise TypeError, "'%s' not a Link/Multilink property"%propname if not self.db.hasnode(prop.classname, nodeid): - raise ValueError, '%s has no node %s'%(link_class, nodeid) + raise ValueError, '%s has no node %s'%(prop.classname, nodeid) # ok, now do the find cldb = self.db.getclassdb(self.classname) @@ -849,6 +849,9 @@ def Choice(name, *options): # # $Log: not supported by cvs2svn $ +# Revision 1.29 2001/10/27 00:17:41 richard +# Made Class.stringFind() do caseless matching. +# # Revision 1.28 2001/10/21 04:44:50 richard # bug #473124: UI inconsistency with Link fields. # This also prompted me to fix a fairly long-standing usability issue - -- 2.30.2