Mercurial > p > roundup > code
diff roundup/admin.py @ 6638:e1588ae185dc issue2550923_computed_property
merge from default branch. Fix travis.ci so CI builds don't error out
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Thu, 21 Apr 2022 16:54:17 -0400 |
| parents | d4371b131c9c |
| children | 408fd477761f |
line wrap: on
line diff
--- a/roundup/admin.py Fri Oct 08 00:37:16 2021 -0400 +++ b/roundup/admin.py Thu Apr 21 16:54:17 2022 -0400 @@ -23,12 +23,13 @@ __docformat__ = 'restructuredtext' -import csv, getopt, getpass, os, re, shutil, sys, operator +import csv, getopt, getpass, operator, os, re, shutil, sys from roundup import date, hyperdb, init, password, token from roundup import __version__ as roundup_version import roundup.instance -from roundup.configuration import CoreConfig, NoConfigError, UserConfig +from roundup.configuration import (CoreConfig, NoConfigError, + ParsingOptionError, UserConfig) from roundup.i18n import _ from roundup.exceptions import UsageError from roundup.anypy.my_input import my_input @@ -51,13 +52,16 @@ if key in self.data: return [(key, self.data[key])] keylist = sorted(self.data) - l = [] + matching_keys = [] for ki in keylist: if ki.startswith(key): - l.append((ki, self.data[ki])) - if not l and default is self._marker: + matching_keys.append((ki, self.data[ki])) + if not matching_keys and default is self._marker: raise KeyError(key) - return l + # FIXME: what happens if default is not self._marker but + # there are no matching keys? Should (default, self.data[default]) + # be returned??? + return matching_keys class AdminTool: @@ -101,12 +105,12 @@ """ props = {} for arg in args: - l = arg.split('=', 1) + key_val = arg.split('=', 1) # if = not in string, will return one element - if len(l) < 2: + if len(key_val) < 2: raise UsageError(_('argument "%(arg)s" not propname=value') % locals()) - key, value = l + key, value = key_val if value: props[key] = value else: @@ -117,7 +121,7 @@ """ Display a simple usage message. """ if message: - message = _('Problem: %(message)s\n\n')% locals() + message = _('Problem: %(message)s\n\n') % locals() sys.stdout.write(_("""%(message)sUsage: roundup-admin [options] [<command> <arguments>] Options: @@ -267,13 +271,13 @@ # try command docstrings try: - l = self.commands.get(topic) + cmd_docs = self.commands.get(topic) except KeyError: print(_('Sorry, no help for "%(topic)s"') % locals()) return 1 # display the help for each match, removing the docstring indent - for _name, help in l: + for _name, help in cmd_docs: lines = nl_re.split(_(help.__doc__)) print(lines[0]) indent = indent_re.match(lines[1]) @@ -295,12 +299,15 @@ 2. <prefix>/share/roundup/templates/* this should be the standard place to find them when Roundup is installed - 3. <roundup.admin.__file__>/../templates/* + 3. <roundup.admin.__file__>/../../<sys.prefix>/share/\ + roundup/templates/* which is where they will be found if + roundup is installed as a wheel using pip install + 4. <roundup.admin.__file__>/../templates/* this will be used if Roundup's run in the distro (aka. source) directory - 4. <current working dir>/* + 5. <current working dir>/* this is for when someone unpacks a 3rd-party template - 5. <current working dir> + 6. <current working dir> this is for someone who "cd"s to the 3rd-party template dir """ # OK, try <prefix>/share/roundup/templates @@ -324,6 +331,25 @@ templates = init.listTemplates(tdir) break + # search for data files parallel to the roundup + # install dir. E.G. a wheel install + # use roundup.__path__ and go up a level then use sys.prefix + # to create a base path for searching. + + import sys + # __file__ should be something like: + # /usr/local/lib/python3.10/site-packages/roundup/admin.py + # os.prefix should be /usr, /usr/local or root of virtualenv + # strip leading / to make os.path.join work right. + path = __file__ + for _N in 1, 2: + path = os.path.dirname(path) + # path is /usr/local/lib/python3.10/site-packages + tdir = os.path.join(path, sys.prefix[1:], 'share', + 'roundup', 'templates') + if os.path.isdir(tdir): + templates.update(init.listTemplates(tdir)) + # OK, now try as if we're in the roundup source distribution # directory, so this module will be in .../roundup-*/roundup/admin.py # and we're interested in the .../roundup-*/ part. @@ -435,7 +461,7 @@ # it sets parameters like template_engine that are # template specific. template_config = UserConfig(templates[template]['path'] + - "/config_ini.ini") + "/config_ini.ini") for k in template_config.keys(): if k == 'HOME': # ignore home. It is a default param. continue @@ -583,7 +609,7 @@ raise UsageError(_('Not enough arguments supplied')) propname = args[0] designators = args[1].split(',') - l = [] + linked_props = [] for designator in designators: # decode the node designator try: @@ -619,11 +645,11 @@ propclassname = self.db.getclass(property.classname).classname id = cl.get(nodeid, propname) for i in id: - l.append(propclassname + i) + linked_props.append(propclassname + i) else: id = cl.get(nodeid, propname) for i in id: - l.append(i) + linked_props.append(i) else: if self.print_designator: properties = cl.getprops() @@ -646,7 +672,7 @@ raise UsageError(_('no such %(classname)s property ' '"%(propname)s"') % locals()) if self.separator: - print(self.separator.join(l)) + print(self.separator.join(linked_props)) return 0 @@ -743,20 +769,20 @@ if ',' in value: values = value.split(',') else: - values = [ value ] + values = [value] props[propname] = [] # start handling transitive props # given filter issue assignedto.roles=Admin # start at issue curclass = cl - lastprop = propname # handle case 'issue assignedto=admin' + lastprop = propname # handle case 'issue assignedto=admin' if '.' in propname: # start splitting transitive prop into components # we end when we have no more links for pn in propname.split('.'): try: - lastprop=pn # get current component + lastprop = pn # get current component # get classname for this link try: curclassname = curclass.getprops()[pn].classname @@ -779,7 +805,7 @@ try: id = [] designator = [] - props = { "filterspec": props } + props = {"filterspec": props} if self.separator: if self.print_designator: @@ -967,7 +993,7 @@ for propname in props: try: props[propname] = hyperdb.rawToHyperdb(self.db, cl, None, - propname, props[propname]) + propname, props[propname]) except hyperdb.HyperdbValueError as message: raise UsageError(message) @@ -975,7 +1001,7 @@ propname = cl.getkey() if propname and propname not in props: raise UsageError(_('you must provide the "%(propname)s" ' - 'property.') % locals()) + 'property.') % locals()) # do the actual create try: @@ -1099,7 +1125,7 @@ if ':' in spec: name, width = spec.split(':') if width == '': - # spec includes trailing :, use label/name width + # spec includes trailing :, use label/name width props.append((name, len(name))) else: try: @@ -1123,7 +1149,7 @@ # and the table data for nodeid in cl.list(): - l = [] + table_columns = [] for name, width in props: if name != 'id': try: @@ -1136,8 +1162,8 @@ else: value = str(nodeid) f = '%%-%ds' % width - l.append(f % value[:width]) - print(' '.join(l)) + table_columns.append(f % value[:width]) + print(' '.join(table_columns)) return 0 def do_history(self, args): @@ -1259,7 +1285,7 @@ raise UsageError(e.args[0]) except IndexError: raise UsageError(_('no such %(classname)s node ' - '" % (nodeid)s"')%locals()) + '" % (nodeid)s"') % locals()) self.db_uncommitted = True return 0 @@ -1286,7 +1312,7 @@ if len(args) == 2: if args[0].startswith('-'): classes = [c for c in self.db.classes - if c not in args[0][1:].split(',')] + if c not in args[0][1:].split(',')] else: classes = args[0].split(',') else: @@ -1308,12 +1334,11 @@ if not export_files and hasattr(cl, 'export_files'): sys.stdout.write('Exporting %s WITHOUT the files\r\n' % - classname) + classname) with open(os.path.join(dir, classname+'.csv'), 'w') as f: writer = csv.writer(f, colon_separated) - properties = cl.getprops() propnames = cl.export_propnames() fields = propnames[:] fields.append('is retired') @@ -1329,7 +1354,7 @@ all_nodes = cl.getnodeids() classkey = cl.getkey() - if classkey: # False sorts before True, so negate is_retired + if classkey: # False sorts before True, so negate is_retired keysort = lambda i: (cl.get(i, classkey), not cl.is_retired(i)) all_nodes.sort(key=keysort) @@ -1337,12 +1362,13 @@ for nodeid in all_nodes: if self.verbose: - sys.stdout.write('\rExporting %s - %s' % + sys.stdout.write('\rExporting %s - %s' % (classname, nodeid)) sys.stdout.flush() node = cl.getnode(nodeid) exp = cl.export_list(propnames, nodeid) - lensum = sum([len(repr_export(node[p])) for p in propnames]) + lensum = sum([len(repr_export(node[p])) for + p in propnames]) # for a safe upper bound of field length we add # difference between CSV len and sum of all field lengths d = sum([len(x) for x in exp]) - lensum @@ -1359,7 +1385,8 @@ # export the journals with open(os.path.join(dir, classname+'-journals.csv'), 'w') as jf: if self.verbose: - sys.stdout.write("\nExporting Journal for %s\n" % classname) + sys.stdout.write("\nExporting Journal for %s\n" % + classname) sys.stdout.flush() journals = csv.writer(jf, colon_separated) for row in cl.export_journals(): @@ -1642,12 +1669,12 @@ except KeyError: # not a valid command print(_('Unknown command "%(command)s" ("help commands" for a ' - 'list)') % locals()) + 'list)') % locals()) return 1 # check for multiple matches if len(functions) > 1: - print(_('Multiple commands match "%(command)s": %(list)s') % \ + print(_('Multiple commands match "%(command)s": %(list)s') % {'command': command, 'list': ', '.join([i[0] for i in functions])}) return 1 @@ -1685,6 +1712,9 @@ self.tracker_home = '' print(_("Error: Couldn't open tracker: %(message)s") % locals()) return 1 + except ParsingOptionError as message: # message used via locals + print("%(message)s" % locals()) + return 1 # only open the database once! if not self.db: @@ -1751,10 +1781,10 @@ self.name = 'admin' self.password = '' # unused if 'ROUNDUP_LOGIN' in os.environ: - l = os.environ['ROUNDUP_LOGIN'].split(':') - self.name = l[0] - if len(l) > 1: - self.password = l[1] + login_env = os.environ['ROUNDUP_LOGIN'].split(':') + self.name = login_env[0] + if len(login_env) > 1: + self.password = login_env[1] self.separator = None self.print_designator = 0 self.verbose = 0 @@ -1788,10 +1818,10 @@ elif opt == '-d': self.print_designator = 1 elif opt == '-u': - l = arg.split(':') - self.name = l[0] - if len(l) > 1: - self.password = l[1] + login_opt = arg.split(':') + self.name = login_opt[0] + if len(login_opt) > 1: + self.password = login_opt[1] # if no command - go interactive # wrap in a try/finally so we always close off the db
