Mercurial > p > roundup > code
comparison roundup/admin.py @ 7752:b2dbab2b34bc
fix(refactor): multiple fixups using ruff linter; more testing.
Converting to using the ruff linter and its rulesets. Fixed a number
of issues.
admin.py:
sort imports
use immutable tuples as default value markers for parameters where a
None value is valid.
reduced some loops to list comprehensions for performance
used ternary to simplify some if statements
named some variables to make them less magic
(e.g. _default_savepoint_setting = 1000)
fixed some tests for argument counts < 2 becomes != 2 so 3 is an
error.
moved exception handlers outside of loops for performance where
exception handler will abort loop anyway.
renamed variables called 'id' or 'dir' as they shadow builtin
commands.
fix translations of form _("string %s" % value) -> _("string %s") %
value so translation will be looked up with the key before
substitution.
end dicts, tuples with a trailing comma to reduce missing comma
errors if modified
simplified sorted(list(self.setting.keys())) to
sorted(self.setting.keys()) as sorted consumes whole list.
in if conditions put compared variable on left and threshold condition
on right. (no yoda conditions)
multiple noqa: suppression
removed unneeded noqa as lint rulesets are a bit different
do_get - refactor output printing logic: Use fast return if not
special formatting is requested; use isinstance with a tuple
rather than two isinstance calls; cleaned up flow and removed
comments on algorithm as it can be easily read from the code.
do_filter, do_find - refactor output printing logic. Reduce
duplicate code.
do_find - renamed variable 'value' that was set inside a loop. The
loop index variable was also named 'value'.
do_pragma - added hint to use list subcommand if setting was not
found. Replaced condition 'type(x) is bool' with 'isinstance(x,
bool)' for various types.
test_admin.py
added testing for do_list
better test coverage for do_get includes: -S and -d for multilinks,
error case for -d with non-link.
better testing for do_find including all output modes
better testing for do_filter including all output modes
fixed expected output for do_pragma that now includes hint to use
pragma list if setting not found.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Fri, 01 Mar 2024 14:53:18 -0500 |
| parents | 4dda4a9dfe0b |
| children | 09b216591db5 |
comparison
equal
deleted
inserted
replaced
| 7751:bd013590d8d6 | 7752:b2dbab2b34bc |
|---|---|
| 30 import os | 30 import os |
| 31 import re | 31 import re |
| 32 import shutil | 32 import shutil |
| 33 import sys | 33 import sys |
| 34 | 34 |
| 35 import roundup.instance | |
| 36 from roundup import __version__ as roundup_version | |
| 35 from roundup import date, hyperdb, init, password, token_r | 37 from roundup import date, hyperdb, init, password, token_r |
| 36 from roundup import __version__ as roundup_version | |
| 37 import roundup.instance | |
| 38 from roundup.configuration import (CoreConfig, NoConfigError, Option, | |
| 39 OptionUnsetError, OptionValueError, | |
| 40 ParsingOptionError, UserConfig) | |
| 41 from roundup.i18n import _, get_translation | |
| 42 from roundup.exceptions import UsageError | |
| 43 from roundup.anypy.my_input import my_input | 38 from roundup.anypy.my_input import my_input |
| 44 from roundup.anypy.strings import repr_export | 39 from roundup.anypy.strings import repr_export |
| 40 from roundup.configuration import ( | |
| 41 CoreConfig, | |
| 42 NoConfigError, | |
| 43 Option, | |
| 44 OptionUnsetError, | |
| 45 OptionValueError, | |
| 46 ParsingOptionError, | |
| 47 UserConfig, | |
| 48 ) | |
| 49 from roundup.exceptions import UsageError | |
| 50 from roundup.i18n import _, get_translation | |
| 45 | 51 |
| 46 try: | 52 try: |
| 47 from UserDict import UserDict | 53 from UserDict import UserDict |
| 48 except ImportError: | 54 except ImportError: |
| 49 from collections import UserDict | 55 from collections import UserDict |
| 52 class CommandDict(UserDict): | 58 class CommandDict(UserDict): |
| 53 """Simple dictionary that lets us do lookups using partial keys. | 59 """Simple dictionary that lets us do lookups using partial keys. |
| 54 | 60 |
| 55 Original code submitted by Engelbert Gruber. | 61 Original code submitted by Engelbert Gruber. |
| 56 """ | 62 """ |
| 57 _marker = [] | 63 _marker = ('CommandDictMarker') |
| 58 | 64 |
| 59 def get(self, key, default=_marker): | 65 def get(self, key, default=_marker): |
| 60 if key in self.data: | 66 if key in self.data: |
| 61 return [(key, self.data[key])] | 67 return [(key, self.data[key])] |
| 62 keylist = sorted(self.data) | 68 keylist = sorted(self.data) |
| 63 matching_keys = [] | 69 |
| 64 for ki in keylist: | 70 matching_keys = [(ki, self.data[ki]) for ki in keylist |
| 65 if ki.startswith(key): | 71 if ki.startswith(key)] |
| 66 matching_keys.append((ki, self.data[ki])) | 72 |
| 67 if not matching_keys and default is self._marker: | 73 if not matching_keys and default is self._marker: |
| 68 raise KeyError(key) | 74 raise KeyError(key) |
| 69 # FIXME: what happens if default is not self._marker but | 75 # FIXME: what happens if default is not self._marker but |
| 70 # there are no matching keys? Should (default, self.data[default]) | 76 # there are no matching keys? Should (default, self.data[default]) |
| 71 # be returned??? | 77 # be returned??? |
| 101 self.help[k[5:]] = getattr(self, k) | 107 self.help[k[5:]] = getattr(self, k) |
| 102 self.tracker = None | 108 self.tracker = None |
| 103 self.tracker_home = '' | 109 self.tracker_home = '' |
| 104 self.db = None | 110 self.db = None |
| 105 self.db_uncommitted = False | 111 self.db_uncommitted = False |
| 112 self._default_savepoint_setting = 10000 | |
| 106 self.force = None | 113 self.force = None |
| 107 self.settings = { | 114 self.settings = { |
| 108 'display_header': False, | 115 'display_header': False, |
| 109 'display_protected': False, | 116 'display_protected': False, |
| 110 'indexer_backend': "as set in config.ini", | 117 'indexer_backend': "as set in config.ini", |
| 111 '_reopen_tracker': False, | 118 '_reopen_tracker': False, |
| 112 'savepoint_limit': 10000, | 119 'savepoint_limit': self._default_savepoint_setting, |
| 113 'show_retired': "no", | 120 'show_retired': "no", |
| 114 '_retired_val': False, | 121 '_retired_val': False, |
| 115 'verbose': False, | 122 'verbose': False, |
| 116 '_inttest': 3, | 123 '_inttest': 3, |
| 117 '_floattest': 3.5, | 124 '_floattest': 3.5, |
| 153 | 160 |
| 154 def props_from_args(self, args): | 161 def props_from_args(self, args): |
| 155 """ Produce a dictionary of prop: value from the args list. | 162 """ Produce a dictionary of prop: value from the args list. |
| 156 | 163 |
| 157 The args list is specified as ``prop=value prop=value ...``. | 164 The args list is specified as ``prop=value prop=value ...``. |
| 165 A missing value is recorded as None. | |
| 158 """ | 166 """ |
| 159 props = {} | 167 props = {} |
| 160 for arg in args: | 168 for arg in args: |
| 161 key_val = arg.split('=', 1) | 169 key_val = arg.split('=', 1) |
| 162 # if = not in string, will return one element | 170 # if = not in string, will return one element |
| 163 if len(key_val) < 2: | 171 if len(key_val) != 2: |
| 164 raise UsageError(_('argument "%(arg)s" not propname=value') % | 172 raise UsageError(_('argument "%(arg)s" not propname=value') % |
| 165 locals()) | 173 locals()) |
| 166 key, value = key_val | 174 key, value = key_val |
| 167 if value: | 175 if value: |
| 168 props[key] = value | 176 props[key] = value |
| 210 h = _(command.__doc__).split('\n')[0] | 218 h = _(command.__doc__).split('\n')[0] |
| 211 commands.append(' '+h[7:]) | 219 commands.append(' '+h[7:]) |
| 212 commands.sort() | 220 commands.sort() |
| 213 commands.append(_( | 221 commands.append(_( |
| 214 """Commands may be abbreviated as long as the abbreviation | 222 """Commands may be abbreviated as long as the abbreviation |
| 215 matches only one command, e.g. l == li == lis == list.""")) # noqa: E122 | 223 matches only one command, e.g. l == li == lis == list.""")) |
| 216 sys.stdout.write('\n'.join(commands) + '\n\n') | 224 sys.stdout.write('\n'.join(commands) + '\n\n') |
| 217 | 225 |
| 218 indent_re = re.compile(r'^(\s+)\S+') | 226 indent_re = re.compile(r'^(\s+)\S+') |
| 219 | 227 |
| 220 def help_commands_html(self, indent_re=indent_re): | 228 def help_commands_html(self, indent_re=indent_re): |
| 425 argument = self.my_input('%s [%s]: ' % (prompt, default)) | 433 argument = self.my_input('%s [%s]: ' % (prompt, default)) |
| 426 if not argument: | 434 if not argument: |
| 427 return default | 435 return default |
| 428 return argument | 436 return argument |
| 429 | 437 |
| 430 def do_commit(self, args): | 438 def do_commit(self, args): # noqa: ARG002 |
| 431 ''"""Usage: commit | 439 ''"""Usage: commit |
| 432 Commit changes made to the database during an interactive session. | 440 Commit changes made to the database during an interactive session. |
| 433 | 441 |
| 434 The changes made during an interactive session are not | 442 The changes made during an interactive session are not |
| 435 automatically written to the database - they must be committed | 443 automatically written to the database - they must be committed |
| 489 props[key] = value | 497 props[key] = value |
| 490 else: | 498 else: |
| 491 props = self.props_from_args(args[1:]) | 499 props = self.props_from_args(args[1:]) |
| 492 | 500 |
| 493 # convert types | 501 # convert types |
| 494 for propname in props: | 502 try: |
| 495 try: | 503 for propname in props: |
| 496 props[propname] = hyperdb.rawToHyperdb(self.db, cl, None, | 504 props[propname] = hyperdb.rawToHyperdb(self.db, cl, None, |
| 497 propname, | 505 propname, |
| 498 props[propname]) | 506 props[propname]) |
| 499 except hyperdb.HyperdbValueError as message: | 507 except hyperdb.HyperdbValueError as message: |
| 500 raise UsageError(message) | 508 raise UsageError(message) |
| 501 | 509 |
| 502 # check for the key property | 510 # check for the key property |
| 503 propname = cl.getkey() | 511 propname = cl.getkey() |
| 504 if propname and propname not in props: | 512 if propname and propname not in props: |
| 505 raise UsageError(_('you must provide the "%(propname)s" ' | 513 raise UsageError(_('you must provide the "%(propname)s" ' |
| 540 # get the class | 548 # get the class |
| 541 cl = self.get_class(classname) | 549 cl = self.get_class(classname) |
| 542 | 550 |
| 543 # display the values | 551 # display the values |
| 544 normal_props = sorted(cl.properties) | 552 normal_props = sorted(cl.properties) |
| 545 if display_protected: | 553 |
| 546 keys = sorted(cl.getprops()) | 554 keys = sorted(cl.getprops()) if display_protected else normal_props |
| 547 else: | |
| 548 keys = normal_props | |
| 549 | 555 |
| 550 if display_header: | 556 if display_header: |
| 551 status = "retired" if cl.is_retired(nodeid) else "active" | 557 status = "retired" if cl.is_retired(nodeid) else "active" |
| 552 print('\n[%s (%s)]' % (designator, status)) | 558 print('\n[%s (%s)]' % (designator, status)) |
| 553 for key in keys: | 559 for key in keys: |
| 554 value = cl.get(nodeid, key) | 560 value = cl.get(nodeid, key) |
| 555 # prepend * for protected properties else just indent | 561 # prepend * for protected properties else just indent |
| 556 # with space. | 562 # with space. |
| 557 if display_protected or display_header: | 563 if display_protected or display_header: # noqa: SIM108 |
| 558 protected = "*" if key not in normal_props else ' ' | 564 protected = "*" if key not in normal_props else ' ' |
| 559 else: | 565 else: |
| 560 protected = "" | 566 protected = "" |
| 561 print(_('%(protected)s%(key)s: %(value)s') % locals()) | 567 print(_('%(protected)s%(key)s: %(value)s') % locals()) |
| 562 | 568 |
| 575 """ | 581 """ |
| 576 # grab the directory to export to | 582 # grab the directory to export to |
| 577 if len(args) < 1: | 583 if len(args) < 1: |
| 578 raise UsageError(_('Not enough arguments supplied')) | 584 raise UsageError(_('Not enough arguments supplied')) |
| 579 | 585 |
| 580 dir = args[-1] | 586 export_dir = args[-1] |
| 581 | 587 |
| 582 # get the list of classes to export | 588 # get the list of classes to export |
| 583 if len(args) == 2: | 589 if len(args) == 2: |
| 584 if args[0].startswith('-'): | 590 if args[0].startswith('-'): |
| 585 classes = [c for c in self.db.classes | 591 classes = [c for c in self.db.classes |
| 591 | 597 |
| 592 class colon_separated(csv.excel): | 598 class colon_separated(csv.excel): |
| 593 delimiter = ':' | 599 delimiter = ':' |
| 594 | 600 |
| 595 # make sure target dir exists | 601 # make sure target dir exists |
| 596 if not os.path.exists(dir): | 602 if not os.path.exists(export_dir): |
| 597 os.makedirs(dir) | 603 os.makedirs(export_dir) |
| 598 | 604 |
| 599 # maximum csv field length exceeding configured size? | 605 # maximum csv field length exceeding configured size? |
| 600 max_len = self.db.config.CSV_FIELD_SIZE | 606 max_len = self.db.config.CSV_FIELD_SIZE |
| 601 | 607 |
| 602 # do all the classes specified | 608 # do all the classes specified |
| 605 | 611 |
| 606 if not export_files and hasattr(cl, 'export_files'): | 612 if not export_files and hasattr(cl, 'export_files'): |
| 607 sys.stdout.write('Exporting %s WITHOUT the files\r\n' % | 613 sys.stdout.write('Exporting %s WITHOUT the files\r\n' % |
| 608 classname) | 614 classname) |
| 609 | 615 |
| 610 with open(os.path.join(dir, classname+'.csv'), 'w') as f: | 616 with open(os.path.join(export_dir, classname+'.csv'), 'w') as f: |
| 611 writer = csv.writer(f, colon_separated) | 617 writer = csv.writer(f, colon_separated) |
| 612 | 618 |
| 613 propnames = cl.export_propnames() | 619 propnames = cl.export_propnames() |
| 614 fields = propnames[:] | 620 fields = propnames[:] |
| 615 fields.append('is retired') | 621 fields.append('is retired') |
| 624 # on imports to rdbms. | 630 # on imports to rdbms. |
| 625 all_nodes = cl.getnodeids() | 631 all_nodes = cl.getnodeids() |
| 626 | 632 |
| 627 classkey = cl.getkey() | 633 classkey = cl.getkey() |
| 628 if classkey: # False sorts before True, so negate is_retired | 634 if classkey: # False sorts before True, so negate is_retired |
| 629 keysort = lambda i: (cl.get(i, classkey), # noqa: E731 | 635 keysort = lambda i: ( # noqa: E731 |
| 630 not cl.is_retired(i)) | 636 cl.get(i, classkey), # noqa: B023 cl is not loop var |
| 637 not cl.is_retired(i), # noqa: B023 cl is not loop var | |
| 638 ) | |
| 631 all_nodes.sort(key=keysort) | 639 all_nodes.sort(key=keysort) |
| 632 # if there is no classkey no need to sort | 640 # if there is no classkey no need to sort |
| 633 | 641 |
| 634 for nodeid in all_nodes: | 642 for nodeid in all_nodes: |
| 635 if self.verbose: | 643 if self.verbose: |
| 649 ll = len(repr_export(node[p])) + d | 657 ll = len(repr_export(node[p])) + d |
| 650 if ll > max_len: | 658 if ll > max_len: |
| 651 max_len = ll | 659 max_len = ll |
| 652 writer.writerow(exp) | 660 writer.writerow(exp) |
| 653 if export_files and hasattr(cl, 'export_files'): | 661 if export_files and hasattr(cl, 'export_files'): |
| 654 cl.export_files(dir, nodeid) | 662 cl.export_files(export_dir, nodeid) |
| 655 | 663 |
| 656 # export the journals | 664 # export the journals |
| 657 with open(os.path.join(dir, classname+'-journals.csv'), 'w') as jf: | 665 with open(os.path.join(export_dir, classname+'-journals.csv'), 'w') as jf: |
| 658 if self.verbose: | 666 if self.verbose: |
| 659 sys.stdout.write("\nExporting Journal for %s\n" % | 667 sys.stdout.write("\nExporting Journal for %s\n" % |
| 660 classname) | 668 classname) |
| 661 sys.stdout.flush() | 669 sys.stdout.flush() |
| 662 journals = csv.writer(jf, colon_separated) | 670 journals = csv.writer(jf, colon_separated) |
| 701 # handle the propname=value argument | 709 # handle the propname=value argument |
| 702 props = self.props_from_args(args[1:]) | 710 props = self.props_from_args(args[1:]) |
| 703 | 711 |
| 704 # convert the user-input value to a value used for filter | 712 # convert the user-input value to a value used for filter |
| 705 # multiple , separated values become a list | 713 # multiple , separated values become a list |
| 706 for propname, value in props.items(): | 714 for propname, prop_value in props.items(): |
| 707 if ',' in value: | 715 values = prop_value.split(',') if ',' in prop_value \ |
| 708 values = value.split(',') | 716 else [prop_value] |
| 709 else: | |
| 710 values = [value] | |
| 711 | 717 |
| 712 props[propname] = [] | 718 props[propname] = [] |
| 713 # start handling transitive props | 719 # start handling transitive props |
| 714 # given filter issue assignedto.roles=Admin | 720 # given filter issue assignedto.roles=Admin |
| 715 # start at issue | 721 # start at issue |
| 725 try: | 731 try: |
| 726 curclassname = curclass.getprops()[pn].classname | 732 curclassname = curclass.getprops()[pn].classname |
| 727 except KeyError: | 733 except KeyError: |
| 728 raise UsageError(_( | 734 raise UsageError(_( |
| 729 "Class %(curclassname)s has " | 735 "Class %(curclassname)s has " |
| 730 "no property %(pn)s in %(propname)s." % | 736 "no property %(pn)s in %(propname)s.") % |
| 731 locals())) | 737 locals()) |
| 732 # get class object | 738 # get class object |
| 733 curclass = self.get_class(curclassname) | 739 curclass = self.get_class(curclassname) |
| 734 except AttributeError: | 740 except AttributeError: |
| 735 # curclass.getprops()[pn].classname raises this | 741 # curclass.getprops()[pn].classname raises this |
| 736 # when we are at a non link/multilink property | 742 # when we are at a non link/multilink property |
| 740 val = hyperdb.rawToHyperdb(self.db, curclass, None, | 746 val = hyperdb.rawToHyperdb(self.db, curclass, None, |
| 741 lastprop, value) | 747 lastprop, value) |
| 742 props[propname].append(val) | 748 props[propname].append(val) |
| 743 | 749 |
| 744 # now do the filter | 750 # now do the filter |
| 751 props = {"filterspec": props} | |
| 745 try: | 752 try: |
| 746 id = [] | 753 output_items = cl.filter(None, **props) |
| 747 designator = [] | 754 if self.print_designator: |
| 748 props = {"filterspec": props} | 755 output_items = [ classname + i for i in output_items ] |
| 749 | 756 |
| 750 if self.separator: | 757 if self.separator: |
| 751 if self.print_designator: | 758 print(self.separator.join(output_items)) |
| 752 id = cl.filter(None, **props) | |
| 753 for i in id: | |
| 754 designator.append(classname + i) | |
| 755 print(self.separator.join(designator)) | |
| 756 else: | |
| 757 print(self.separator.join(cl.filter(None, **props))) | |
| 758 else: | 759 else: |
| 759 if self.print_designator: | 760 print(output_items) |
| 760 id = cl.filter(None, **props) | |
| 761 for i in id: | |
| 762 designator.append(classname + i) | |
| 763 print(designator) | |
| 764 else: | |
| 765 print(cl.filter(None, **props)) | |
| 766 except KeyError: | 761 except KeyError: |
| 767 raise UsageError(_('%(classname)s has no property ' | 762 raise UsageError(_('%(classname)s has no property ' |
| 768 '"%(propname)s"') % locals()) | 763 '"%(propname)s"') % locals()) |
| 769 except (ValueError, TypeError) as message: | 764 except (ValueError, TypeError) as message: |
| 770 raise UsageError(message) | 765 raise UsageError(message) |
| 786 | 781 |
| 787 # handle the propname=value argument | 782 # handle the propname=value argument |
| 788 props = self.props_from_args(args[1:]) | 783 props = self.props_from_args(args[1:]) |
| 789 | 784 |
| 790 # convert the user-input value to a value used for find() | 785 # convert the user-input value to a value used for find() |
| 791 for propname, value in props.items(): | 786 for propname, prop_value in props.items(): |
| 792 if ',' in value: | 787 values = prop_value.split(',') if ',' in prop_value \ |
| 793 values = value.split(',') | 788 else [prop_value] |
| 794 else: | 789 |
| 795 values = [value] | |
| 796 d = props[propname] = {} | 790 d = props[propname] = {} |
| 797 for value in values: | 791 for value in values: |
| 798 value = hyperdb.rawToHyperdb(self.db, cl, None, | 792 val = hyperdb.rawToHyperdb(self.db, cl, None, |
| 799 propname, value) | 793 propname, value) |
| 800 if isinstance(value, list): | 794 if isinstance(val, list): |
| 801 for entry in value: | 795 for entry in val: |
| 802 d[entry] = 1 | 796 d[entry] = 1 |
| 803 else: | 797 else: |
| 804 d[value] = 1 | 798 d[val] = 1 |
| 805 | 799 |
| 806 # now do the find | 800 # now do the find |
| 807 try: | 801 try: |
| 808 id = [] | 802 output_items = cl.find(**props) |
| 809 designator = [] | 803 if self.print_designator: |
| 804 output_items = [ classname + i for i in output_items ] | |
| 805 | |
| 810 if self.separator: | 806 if self.separator: |
| 811 if self.print_designator: | 807 print(self.separator.join(output_items)) |
| 812 id = cl.find(**props) | |
| 813 for i in id: | |
| 814 designator.append(classname + i) | |
| 815 print(self.separator.join(designator)) | |
| 816 else: | |
| 817 print(self.separator.join(cl.find(**props))) | |
| 818 | |
| 819 else: | 808 else: |
| 820 if self.print_designator: | 809 print(output_items) |
| 821 id = cl.find(**props) | |
| 822 for i in id: | |
| 823 designator.append(classname + i) | |
| 824 print(designator) | |
| 825 else: | |
| 826 print(cl.find(**props)) | |
| 827 except KeyError: | 810 except KeyError: |
| 828 raise UsageError(_('%(classname)s has no property ' | 811 raise UsageError(_('%(classname)s has no property ' |
| 829 '"%(propname)s"') % locals()) | 812 '"%(propname)s"') % locals()) |
| 830 except (ValueError, TypeError) as message: | 813 except (ValueError, TypeError) as message: |
| 831 raise UsageError(message) | 814 raise UsageError(message) |
| 854 " 'password_pbkdf2_default_rounds'\n" | 837 " 'password_pbkdf2_default_rounds'\n" |
| 855 "from old default of %(old_number)s to new " | 838 "from old default of %(old_number)s to new " |
| 856 "default of %(new_number)s.") % { | 839 "default of %(new_number)s.") % { |
| 857 "old_number": | 840 "old_number": |
| 858 config.PASSWORD_PBKDF2_DEFAULT_ROUNDS, | 841 config.PASSWORD_PBKDF2_DEFAULT_ROUNDS, |
| 859 "new_number": default_ppdr | 842 "new_number": default_ppdr, |
| 860 }) | 843 }) |
| 861 config.PASSWORD_PBKDF2_DEFAULT_ROUNDS = default_ppdr | 844 config.PASSWORD_PBKDF2_DEFAULT_ROUNDS = default_ppdr |
| 862 | 845 |
| 863 if config.PASSWORD_PBKDF2_DEFAULT_ROUNDS < default_ppdr: | 846 if default_ppdr > config.PASSWORD_PBKDF2_DEFAULT_ROUNDS: |
| 864 print(_("Update " | 847 print(_("Update " |
| 865 "'password_pbkdf2_default_rounds' " | 848 "'password_pbkdf2_default_rounds' " |
| 866 "to a number equal to or larger\nthan %s.") % | 849 "to a number equal to or larger\nthan %s.") % |
| 867 default_ppdr) | 850 default_ppdr) |
| 868 else: | 851 else: |
| 894 raise UsageError(message) | 877 raise UsageError(message) |
| 895 | 878 |
| 896 # get the class | 879 # get the class |
| 897 cl = self.get_class(classname) | 880 cl = self.get_class(classname) |
| 898 try: | 881 try: |
| 899 id = [] | 882 if not (self.separator or self.print_designator): |
| 883 print(cl.get(nodeid, propname)) | |
| 884 continue | |
| 885 | |
| 886 if not (isinstance(prop_obj, | |
| 887 (hyperdb.Link, hyperdb.Multilink))): | |
| 888 raise UsageError(_( | |
| 889 'property %s is not of type' | |
| 890 ' Multilink or Link so -d flag does not ' | |
| 891 'apply.') % propname) | |
| 892 propclassname = self.db.getclass( | |
| 893 prop_obj.classname).classname | |
| 894 | |
| 895 output_items = cl.get(nodeid, propname) | |
| 896 if self.print_designator: | |
| 897 output_items = [propclassname + i for i in output_items] | |
| 898 | |
| 900 if self.separator: | 899 if self.separator: |
| 901 if self.print_designator: | 900 print(self.separator.join(output_items)) |
| 902 # see if property is a link or multilink for | |
| 903 # which getting a desginator make sense. | |
| 904 # Algorithm: Get the properties of the | |
| 905 # current designator's class. (cl.getprops) | |
| 906 # get the property object for the property the | |
| 907 # user requested (properties[propname]) | |
| 908 # verify its type (isinstance...) | |
| 909 # raise error if not link/multilink | |
| 910 # get class name for link/multilink property | |
| 911 # do the get on the designators | |
| 912 # append the new designators | |
| 913 # print | |
| 914 properties = cl.getprops() | |
| 915 property = properties[propname] | |
| 916 if not (isinstance(property, hyperdb.Multilink) or | |
| 917 isinstance(property, hyperdb.Link)): | |
| 918 raise UsageError(_( | |
| 919 'property %s is not of type' | |
| 920 ' Multilink or Link so -d flag does not ' | |
| 921 'apply.') % propname) | |
| 922 propclassname = self.db.getclass(property.classname).classname | |
| 923 id = cl.get(nodeid, propname) | |
| 924 for i in id: | |
| 925 linked_props.append(propclassname + i) | |
| 926 else: | |
| 927 id = cl.get(nodeid, propname) | |
| 928 for i in id: | |
| 929 linked_props.append(i) | |
| 930 else: | 901 else: |
| 931 if self.print_designator: | 902 # default is to list each on a line |
| 932 properties = cl.getprops() | 903 print('\n'.join(output_items)) |
| 933 property = properties[propname] | 904 |
| 934 if not (isinstance(property, hyperdb.Multilink) or | |
| 935 isinstance(property, hyperdb.Link)): | |
| 936 raise UsageError(_( | |
| 937 'property %s is not of type' | |
| 938 ' Multilink or Link so -d flag does not ' | |
| 939 'apply.') % propname) | |
| 940 propclassname = self.db.getclass(property.classname).classname | |
| 941 id = cl.get(nodeid, propname) | |
| 942 for i in id: | |
| 943 print(propclassname + i) | |
| 944 else: | |
| 945 print(cl.get(nodeid, propname)) | |
| 946 except IndexError: | 905 except IndexError: |
| 947 raise UsageError(_('no such %(classname)s node ' | 906 raise UsageError(_('no such %(classname)s node ' |
| 948 '"%(nodeid)s"') % locals()) | 907 '"%(nodeid)s"') % locals()) |
| 949 except KeyError: | 908 except KeyError: |
| 950 raise UsageError(_('no such %(classname)s property ' | 909 raise UsageError(_('no such %(classname)s property ' |
| 951 '"%(propname)s"') % locals()) | 910 '"%(propname)s"') % locals()) |
| 952 if self.separator: | |
| 953 print(self.separator.join(linked_props)) | |
| 954 | |
| 955 return 0 | 911 return 0 |
| 956 | 912 |
| 957 def do_help(self, args, nl_re=nl_re, indent_re=indent_re): | 913 def do_help(self, args, nl_re=nl_re, indent_re=indent_re): |
| 958 ''"""Usage: help topic | 914 ''"""Usage: help topic |
| 959 Give help about topic. | 915 Give help about topic. |
| 961 commands -- list commands | 917 commands -- list commands |
| 962 <command> -- help specific to a command | 918 <command> -- help specific to a command |
| 963 initopts -- init command options | 919 initopts -- init command options |
| 964 all -- all available help | 920 all -- all available help |
| 965 """ | 921 """ |
| 966 if len(args) > 0: | 922 topic = args[0] if len(args) > 0 else 'help' |
| 967 topic = args[0] | |
| 968 else: | |
| 969 topic = 'help' | |
| 970 | 923 |
| 971 # try help_ methods | 924 # try help_ methods |
| 972 if topic in self.help: | 925 if topic in self.help: |
| 973 self.help[topic]() | 926 self.help[topic]() |
| 974 return 0 | 927 return 0 |
| 979 except KeyError: | 932 except KeyError: |
| 980 print(_('Sorry, no help for "%(topic)s"') % locals()) | 933 print(_('Sorry, no help for "%(topic)s"') % locals()) |
| 981 return 1 | 934 return 1 |
| 982 | 935 |
| 983 # display the help for each match, removing the docstring indent | 936 # display the help for each match, removing the docstring indent |
| 984 for _name, help in cmd_docs: | 937 for _name, do_function in cmd_docs: |
| 985 lines = nl_re.split(_(help.__doc__)) | 938 lines = nl_re.split(_(do_function.__doc__)) |
| 986 print(lines[0]) | 939 print(lines[0]) |
| 987 indent = indent_re.match(lines[1]) | 940 indent = indent_re.match(lines[1]) |
| 988 if indent: indent = len(indent.group(1)) # noqa: E701 | 941 if indent: indent = len(indent.group(1)) # noqa: E701 |
| 989 for line in lines[1:]: | 942 for line in lines[1:]: |
| 990 if indent: | 943 if indent: |
| 1055 if hasattr(csv, 'field_size_limit'): | 1008 if hasattr(csv, 'field_size_limit'): |
| 1056 csv.field_size_limit(self.db.config.CSV_FIELD_SIZE) | 1009 csv.field_size_limit(self.db.config.CSV_FIELD_SIZE) |
| 1057 | 1010 |
| 1058 # default value is 10000, only go through this if default | 1011 # default value is 10000, only go through this if default |
| 1059 # is different. | 1012 # is different. |
| 1060 if self.settings['savepoint_limit'] != 10000: | 1013 if self.settings['savepoint_limit'] != self._default_savepoint_setting: |
| 1061 # create a new option on the fly in the config under the | 1014 # create a new option on the fly in the config under the |
| 1062 # rdbms section. It is used by the postgresql backend's | 1015 # rdbms section. It is used by the postgresql backend's |
| 1063 # checkpoint_data method. | 1016 # checkpoint_data method. |
| 1064 self.db.config.add_option(Option(self.db.config, | 1017 self.db.config.add_option(Option(self.db.config, |
| 1065 "rdbms", "savepoint_limit")) | 1018 "rdbms", "savepoint_limit")) |
| 1066 self.db.config.options["RDBMS_SAVEPOINT_LIMIT"].set( | 1019 self.db.config.options["RDBMS_SAVEPOINT_LIMIT"].set( |
| 1067 self.settings['savepoint_limit']) | 1020 self.settings['savepoint_limit']) |
| 1068 | 1021 |
| 1069 # directory to import from | 1022 # directory to import from |
| 1070 dir = args[0] | 1023 import_dir = args[0] |
| 1071 | 1024 |
| 1072 class colon_separated(csv.excel): | 1025 class colon_separated(csv.excel): |
| 1073 delimiter = ':' | 1026 delimiter = ':' |
| 1074 | 1027 |
| 1075 # import all the files | 1028 # import all the files |
| 1076 for file in os.listdir(dir): | 1029 for file in os.listdir(import_dir): |
| 1077 classname, ext = os.path.splitext(file) | 1030 classname, ext = os.path.splitext(file) |
| 1078 # we only care about CSV files | 1031 # we only care about CSV files |
| 1079 if ext != '.csv' or classname.endswith('-journals'): | 1032 if ext != '.csv' or classname.endswith('-journals'): |
| 1080 continue | 1033 continue |
| 1081 | 1034 |
| 1082 cl = self.get_class(classname) | 1035 cl = self.get_class(classname) |
| 1083 | 1036 |
| 1084 # ensure that the properties and the CSV file headings match | 1037 # ensure that the properties and the CSV file headings match |
| 1085 with open(os.path.join(dir, file), 'r') as f: | 1038 with open(os.path.join(import_dir, file), 'r') as f: |
| 1086 reader = csv.reader(f, colon_separated) | 1039 reader = csv.reader(f, colon_separated) |
| 1087 file_props = None | 1040 file_props = None |
| 1088 maxid = 1 | 1041 maxid = 1 |
| 1089 # loop through the file and create a node for each entry | 1042 # loop through the file and create a node for each entry |
| 1090 for n, r in enumerate(reader): | 1043 for n, r in enumerate(reader): |
| 1097 sys.stdout.flush() | 1050 sys.stdout.flush() |
| 1098 | 1051 |
| 1099 # do the import and figure the current highest nodeid | 1052 # do the import and figure the current highest nodeid |
| 1100 nodeid = cl.import_list(file_props, r) | 1053 nodeid = cl.import_list(file_props, r) |
| 1101 if hasattr(cl, 'import_files') and import_files: | 1054 if hasattr(cl, 'import_files') and import_files: |
| 1102 cl.import_files(dir, nodeid) | 1055 cl.import_files(import_dir, nodeid) |
| 1103 maxid = max(maxid, int(nodeid)) | 1056 maxid = max(maxid, int(nodeid)) |
| 1104 | 1057 |
| 1105 # (print to sys.stdout here to allow tests to squash it .. ugh) | 1058 # (print to sys.stdout here to allow tests to squash it .. ugh) |
| 1106 print(file=sys.stdout) | 1059 print(file=sys.stdout) |
| 1107 | 1060 |
| 1108 # import the journals | 1061 # import the journals |
| 1109 with open(os.path.join(args[0], classname + '-journals.csv'), 'r') as f: | 1062 with open(os.path.join(import_dir, classname + '-journals.csv'), 'r') as f: |
| 1110 reader = csv.reader(f, colon_separated) | 1063 reader = csv.reader(f, colon_separated) |
| 1111 cl.import_journals(reader) | 1064 cl.import_journals(reader) |
| 1112 | 1065 |
| 1113 # (print to sys.stdout here to allow tests to squash it .. ugh) | 1066 # (print to sys.stdout here to allow tests to squash it .. ugh) |
| 1114 print('setting', classname, maxid+1, file=sys.stdout) | 1067 print('setting', classname, maxid+1, file=sys.stdout) |
| 1158 if tracker.exists(): | 1111 if tracker.exists(): |
| 1159 if not self.force: | 1112 if not self.force: |
| 1160 ok = self.my_input(_( | 1113 ok = self.my_input(_( |
| 1161 """WARNING: The database is already initialised! | 1114 """WARNING: The database is already initialised! |
| 1162 If you re-initialise it, you will lose all the data! | 1115 If you re-initialise it, you will lose all the data! |
| 1163 Erase it? Y/N: """)) # noqa: E122 | 1116 Erase it? Y/N: """)) |
| 1164 if ok.strip().lower() != 'y': | 1117 if ok.strip().lower() != 'y': |
| 1165 return 0 | 1118 return 0 |
| 1166 | 1119 |
| 1167 # nuke it | 1120 # nuke it |
| 1168 tracker.nuke() | 1121 tracker.nuke() |
| 1216 os.path.join(tracker_home, 'config.py')])): | 1169 os.path.join(tracker_home, 'config.py')])): |
| 1217 if not self.force: | 1170 if not self.force: |
| 1218 ok = self.my_input(_( | 1171 ok = self.my_input(_( |
| 1219 """WARNING: There appears to be a tracker in "%(tracker_home)s"! | 1172 """WARNING: There appears to be a tracker in "%(tracker_home)s"! |
| 1220 If you re-install it, you will lose all the data! | 1173 If you re-install it, you will lose all the data! |
| 1221 Erase it? Y/N: """) % locals()) # noqa: E122 | 1174 Erase it? Y/N: """) % locals()) |
| 1222 if ok.strip().lower() != 'y': | 1175 if ok.strip().lower() != 'y': |
| 1223 return 0 | 1176 return 0 |
| 1224 | 1177 |
| 1225 # clear it out so the install isn't confused | 1178 # clear it out so the install isn't confused |
| 1226 shutil.rmtree(tracker_home) | 1179 shutil.rmtree(tracker_home) |
| 1260 # load config_ini.ini from template if it exists. | 1213 # load config_ini.ini from template if it exists. |
| 1261 # it sets parameters like template_engine that are | 1214 # it sets parameters like template_engine that are |
| 1262 # template specific. | 1215 # template specific. |
| 1263 template_config = UserConfig(templates[template]['path'] + | 1216 template_config = UserConfig(templates[template]['path'] + |
| 1264 "/config_ini.ini") | 1217 "/config_ini.ini") |
| 1265 for k in template_config.keys(): | 1218 |
| 1219 # .keys() is required. UserConfig has no __iter__ or __next__ | |
| 1220 for k in template_config.keys(): # noqa: SIM118 | |
| 1266 if k == 'HOME': # ignore home. It is a default param. | 1221 if k == 'HOME': # ignore home. It is a default param. |
| 1267 continue | 1222 continue |
| 1268 defns[k] = template_config[k] | 1223 defns[k] = template_config[k] |
| 1269 | 1224 |
| 1270 # install! | 1225 # install! |
| 1306 | 1261 |
| 1307 You MUST run the "roundup-admin initialise" command once you've performed | 1262 You MUST run the "roundup-admin initialise" command once you've performed |
| 1308 the above steps. | 1263 the above steps. |
| 1309 --------------------------------------------------------------------------- | 1264 --------------------------------------------------------------------------- |
| 1310 """) % {'database_config_file': os.path.join(tracker_home, 'schema.py'), | 1265 """) % {'database_config_file': os.path.join(tracker_home, 'schema.py'), |
| 1311 'database_init_file': os.path.join(tracker_home, 'initial_data.py')}) \ | 1266 'database_init_file': os.path.join(tracker_home, 'initial_data.py')}) |
| 1312 # noqa: E122 | |
| 1313 return 0 | 1267 return 0 |
| 1314 | 1268 |
| 1315 def do_list(self, args): | 1269 def do_list(self, args): |
| 1316 ''"""Usage: list classname [property] | 1270 ''"""Usage: list classname [property] |
| 1317 List the instances of a class. | 1271 List the instances of a class. |
| 1336 | 1290 |
| 1337 # get the class | 1291 # get the class |
| 1338 cl = self.get_class(classname) | 1292 cl = self.get_class(classname) |
| 1339 | 1293 |
| 1340 # figure the property | 1294 # figure the property |
| 1341 if len(args) > 1: | 1295 propname = args[1] if len(args) > 1 else cl.labelprop() |
| 1342 propname = args[1] | |
| 1343 else: | |
| 1344 propname = cl.labelprop() | |
| 1345 | 1296 |
| 1346 if self.separator: | 1297 if self.separator: |
| 1347 if len(args) == 2: | 1298 if len(args) == 2: |
| 1348 # create a list of propnames since user specified propname | 1299 # create a list of propnames since user specified propname |
| 1349 proplist = [] | 1300 proplist = [] |
| 1350 for nodeid in cl.getnodeids(retired=retired): | 1301 try: |
| 1351 try: | 1302 proplist = [ cl.get(nodeid, propname) for nodeid in |
| 1352 proplist.append(cl.get(nodeid, propname)) | 1303 cl.getnodeids(retired=retired)] |
| 1353 except KeyError: | 1304 except KeyError: |
| 1354 raise UsageError(_('%(classname)s has no property ' | 1305 raise UsageError(_('%(classname)s has no property ' |
| 1355 '"%(propname)s"') % locals()) | 1306 '"%(propname)s"') % locals()) |
| 1356 print(self.separator.join(proplist)) | 1307 print(self.separator.join(proplist)) |
| 1357 else: | 1308 else: |
| 1358 # create a list of index id's since user didn't specify | 1309 # create a list of index id's since user didn't specify |
| 1359 # otherwise | 1310 # otherwise |
| 1360 print(self.separator.join(cl.getnodeids(retired=retired))) | 1311 print(self.separator.join(cl.getnodeids(retired=retired))) |
| 1361 else: | 1312 else: |
| 1362 for nodeid in cl.getnodeids(retired=retired): | 1313 try: |
| 1363 try: | 1314 for nodeid in cl.getnodeids(retired=retired): |
| 1364 value = cl.get(nodeid, propname) | 1315 value = cl.get(nodeid, propname) |
| 1365 except KeyError: | 1316 print(_('%(nodeid)4s: %(value)s') % locals()) |
| 1366 raise UsageError(_('%(classname)s has no property ' | 1317 except KeyError: |
| 1367 '"%(propname)s"') % locals()) | 1318 raise UsageError(_('%(classname)s has no property ' |
| 1368 print(_('%(nodeid)4s: %(value)s') % locals()) | 1319 '"%(propname)s"') % locals()) |
| 1369 return 0 | 1320 return 0 |
| 1370 | 1321 |
| 1371 def do_migrate(self, args): | 1322 def do_migrate(self, args): # noqa: ARG002 - args unused |
| 1372 ''"""Usage: migrate | 1323 ''"""Usage: migrate |
| 1373 | 1324 |
| 1374 Update a tracker's database to be compatible with the Roundup | 1325 Update a tracker's database to be compatible with the Roundup |
| 1375 codebase. | 1326 codebase. |
| 1376 | 1327 |
| 1488 tic = perf_counter() | 1439 tic = perf_counter() |
| 1489 pw_hash = password.encodePassword( | 1440 pw_hash = password.encodePassword( |
| 1490 "this is a long password to hash", | 1441 "this is a long password to hash", |
| 1491 props['scheme'], | 1442 props['scheme'], |
| 1492 None, | 1443 None, |
| 1493 config=self.db.config | 1444 config=self.db.config, |
| 1494 ) | 1445 ) |
| 1495 toc = perf_counter() | 1446 toc = perf_counter() |
| 1496 except password.PasswordValueError as e: | 1447 except password.PasswordValueError as e: |
| 1497 print(e) | 1448 print(e) |
| 1498 print_supported_schemes() | 1449 print_supported_schemes() |
| 1548 args[0]) | 1499 args[0]) |
| 1549 else: | 1500 else: |
| 1550 print(_("Current settings and values " | 1501 print(_("Current settings and values " |
| 1551 "(NYI - not yet implemented):")) | 1502 "(NYI - not yet implemented):")) |
| 1552 is_verbose = self.settings['verbose'] | 1503 is_verbose = self.settings['verbose'] |
| 1553 for key in sorted(list(self.settings.keys())): | 1504 for key in sorted(self.settings.keys()): |
| 1554 if key.startswith('_') and not is_verbose: | 1505 if key.startswith('_') and not is_verbose: |
| 1555 continue | 1506 continue |
| 1556 print(" %s=%s" % (key, self.settings[key])) | 1507 print(" %s=%s" % (key, self.settings[key])) |
| 1557 if is_verbose: | 1508 if is_verbose: |
| 1558 print(" %s" % self.settings_help[key]) | 1509 print(" %s" % self.settings_help[key]) |
| 1559 | 1510 |
| 1560 return | 1511 return |
| 1561 | 1512 |
| 1562 if setting not in self.settings: | 1513 if setting not in self.settings: |
| 1563 raise UsageError(_('Unknown setting %s.') % setting) | 1514 raise UsageError(_('Unknown setting %s. Try "pragma list".') |
| 1564 if type(self.settings[setting]) is bool: | 1515 % setting) |
| 1516 if isinstance(self.settings[setting], bool): | |
| 1565 value = value.lower() | 1517 value = value.lower() |
| 1566 if value in ("yes", "true", "on", "1"): | 1518 if value in ("yes", "true", "on", "1"): |
| 1567 value = True | 1519 value = True |
| 1568 elif value in ("no", "false", "off", "0"): | 1520 elif value in ("no", "false", "off", "0"): |
| 1569 value = False | 1521 value = False |
| 1570 else: | 1522 else: |
| 1571 raise UsageError(_( | 1523 raise UsageError(_( |
| 1572 'Incorrect value for boolean setting %(setting)s: ' | 1524 'Incorrect value for boolean setting %(setting)s: ' |
| 1573 '%(value)s.') % {"setting": setting, "value": value}) | 1525 '%(value)s.') % {"setting": setting, "value": value}) |
| 1574 elif type(self.settings[setting]) is int: | 1526 elif isinstance(self.settings[setting], int): |
| 1575 try: | 1527 try: |
| 1576 _val = int(value) | 1528 _val = int(value) |
| 1577 except ValueError: | 1529 except ValueError: |
| 1578 raise UsageError(_( | 1530 raise UsageError(_( |
| 1579 'Incorrect value for integer setting %(setting)s: ' | 1531 'Incorrect value for integer setting %(setting)s: ' |
| 1580 '%(value)s.') % {"setting": setting, "value": value}) | 1532 '%(value)s.') % {"setting": setting, "value": value}) |
| 1581 value = _val | 1533 value = _val |
| 1582 elif type(self.settings[setting]) is str: | 1534 elif isinstance(self.settings[setting], str): |
| 1583 if setting == "show_retired": | 1535 if setting == "show_retired": |
| 1584 if value not in ["no", "only", "both"]: | 1536 if value not in ["no", "only", "both"]: |
| 1585 raise UsageError(_( | 1537 raise UsageError(_( |
| 1586 'Incorrect value for setting %(setting)s: ' | 1538 'Incorrect value for setting %(setting)s: ' |
| 1587 '%(value)s. Should be no, both, or only.') % { | 1539 '%(value)s. Should be no, both, or only.') % { |
| 1704 raise UsageError(_('no such %(classname)s node ' | 1656 raise UsageError(_('no such %(classname)s node ' |
| 1705 '"%(nodeid)s"') % locals()) | 1657 '"%(nodeid)s"') % locals()) |
| 1706 self.db_uncommitted = True | 1658 self.db_uncommitted = True |
| 1707 return 0 | 1659 return 0 |
| 1708 | 1660 |
| 1709 def do_rollback(self, args): | 1661 def do_rollback(self, args): # noqa: ARG002 - args unused |
| 1710 ''"""Usage: rollback | 1662 ''"""Usage: rollback |
| 1711 Undo all changes that are pending commit to the database. | 1663 Undo all changes that are pending commit to the database. |
| 1712 | 1664 |
| 1713 The changes made during an interactive session are not | 1665 The changes made during an interactive session are not |
| 1714 automatically written to the database - they must be committed | 1666 automatically written to the database - they must be committed |
| 1829 # now do the set for all the nodes | 1781 # now do the set for all the nodes |
| 1830 for classname, itemid in designators: | 1782 for classname, itemid in designators: |
| 1831 props = copy.copy(propset) # make a new copy for every designator | 1783 props = copy.copy(propset) # make a new copy for every designator |
| 1832 cl = self.get_class(classname) | 1784 cl = self.get_class(classname) |
| 1833 | 1785 |
| 1834 for key, value in list(props.items()): | 1786 try: |
| 1835 try: | 1787 for key, value in list(props.items()): |
| 1836 # You must reinitialize the props every time though. | 1788 # You must reinitialize the props every time though. |
| 1837 # if props['nosy'] = '+admin' initally, it gets | 1789 # if props['nosy'] = '+admin' initally, it gets |
| 1838 # set to 'demo,admin' (assuming it was set to demo | 1790 # set to 'demo,admin' (assuming it was set to demo |
| 1839 # in the db) after rawToHyperdb returns. | 1791 # in the db) after rawToHyperdb returns. |
| 1840 # This new value is used for all the rest of the | 1792 # This new value is used for all the rest of the |
| 1841 # designators if not reinitalized. | 1793 # designators if not reinitalized. |
| 1842 props[key] = hyperdb.rawToHyperdb(self.db, cl, itemid, | 1794 props[key] = hyperdb.rawToHyperdb(self.db, cl, itemid, |
| 1843 key, value) | 1795 key, value) |
| 1844 except hyperdb.HyperdbValueError as message: | 1796 except hyperdb.HyperdbValueError as message: |
| 1845 raise UsageError(message) | 1797 raise UsageError(message) |
| 1846 | 1798 |
| 1847 # try the set | 1799 # try the set |
| 1848 try: | 1800 try: |
| 1849 cl.set(itemid, **props) | 1801 cl.set(itemid, **props) |
| 1850 except (TypeError, IndexError, ValueError) as message: | 1802 except (TypeError, IndexError, ValueError) as message: |
| 1864 # get the class | 1816 # get the class |
| 1865 cl = self.get_class(classname) | 1817 cl = self.get_class(classname) |
| 1866 | 1818 |
| 1867 # get the key property | 1819 # get the key property |
| 1868 keyprop = cl.getkey() | 1820 keyprop = cl.getkey() |
| 1869 if self.settings['display_protected']: | 1821 properties = cl.getprops() if self.settings['display_protected'] \ |
| 1870 properties = cl.getprops() | 1822 else cl.properties |
| 1871 else: | 1823 |
| 1872 properties = cl.properties | |
| 1873 for key in properties: | 1824 for key in properties: |
| 1874 value = properties[key] | 1825 value = properties[key] |
| 1875 if keyprop == key: | 1826 if keyprop == key: |
| 1876 sys.stdout.write(_('%(key)s: %(value)s (key property)\n') % | 1827 sys.stdout.write(_('%(key)s: %(value)s (key property)\n') % |
| 1877 locals()) | 1828 locals()) |
| 1995 if args and args[0] == "trace_search": | 1946 if args and args[0] == "trace_search": |
| 1996 trace_search = True | 1947 trace_search = True |
| 1997 | 1948 |
| 1998 templates = self.listTemplates(trace_search=trace_search) | 1949 templates = self.listTemplates(trace_search=trace_search) |
| 1999 | 1950 |
| 2000 for name in sorted(list(templates.keys())): | 1951 for name in sorted(templates.keys()): |
| 2001 templates[name]['description'] = textwrap.fill( | 1952 templates[name]['description'] = textwrap.fill( |
| 2002 "\n".join([line.lstrip() for line in | 1953 "\n".join([line.lstrip() for line in |
| 2003 templates[name]['description'].split("\n")]), | 1954 templates[name]['description'].split("\n")]), |
| 2004 70, | 1955 70, |
| 2005 subsequent_indent=" " | 1956 subsequent_indent=" ", |
| 2006 ) | 1957 ) |
| 2007 print(""" | 1958 print(""" |
| 2008 Name: %(name)s | 1959 Name: %(name)s |
| 2009 Path: %(path)s | 1960 Path: %(path)s |
| 2010 Desc: %(description)s | 1961 Desc: %(description)s |
| 2031 | 1982 |
| 2032 # handle help now | 1983 # handle help now |
| 2033 if command == 'help': | 1984 if command == 'help': |
| 2034 if len(args) > 1: | 1985 if len(args) > 1: |
| 2035 self.do_help(args[1:]) | 1986 self.do_help(args[1:]) |
| 2036 return 0 | 1987 else: |
| 2037 self.do_help(['help']) | 1988 self.do_help(['help']) |
| 2038 return 0 | 1989 return 0 |
| 2039 if command == 'morehelp': | 1990 if command == 'morehelp': |
| 2040 self.do_help(['help']) | 1991 self.do_help(['help']) |
| 2041 self.help_commands() | 1992 self.help_commands() |
| 2042 self.help_all() | 1993 self.help_all() |
| 2061 | 2012 |
| 2062 if command in ['genconfig', 'templates']: | 2013 if command in ['genconfig', 'templates']: |
| 2063 try: | 2014 try: |
| 2064 ret = function(args[1:]) | 2015 ret = function(args[1:]) |
| 2065 return ret | 2016 return ret |
| 2066 except UsageError as message: # noqa F841 | 2017 except UsageError as message: |
| 2067 return self.usageError_feedback(message, function) | 2018 return self.usageError_feedback(message, function) |
| 2068 | 2019 |
| 2069 # make sure we have a tracker_home | 2020 # make sure we have a tracker_home |
| 2070 while not self.tracker_home: | 2021 while not self.tracker_home: |
| 2071 if not self.force: | 2022 if not self.force: |
| 2075 | 2026 |
| 2076 # before we open the db, we may be doing an install or init | 2027 # before we open the db, we may be doing an install or init |
| 2077 if command == 'initialise': | 2028 if command == 'initialise': |
| 2078 try: | 2029 try: |
| 2079 return self.do_initialise(self.tracker_home, args) | 2030 return self.do_initialise(self.tracker_home, args) |
| 2080 except UsageError as message: # noqa: F841 | 2031 except UsageError as message: |
| 2081 return self.usageError_feedback(message, function) | 2032 return self.usageError_feedback(message, function) |
| 2082 elif command == 'install': | 2033 elif command == 'install': |
| 2083 try: | 2034 try: |
| 2084 return self.do_install(self.tracker_home, args) | 2035 return self.do_install(self.tracker_home, args) |
| 2085 except UsageError as message: # noqa: F841 | 2036 except UsageError as message: |
| 2086 return self.usageError_feedback(message, function) | 2037 return self.usageError_feedback(message, function) |
| 2087 | 2038 |
| 2088 # get the tracker | 2039 # get the tracker |
| 2089 try: | 2040 try: |
| 2090 if self.tracker and not self.settings['_reopen_tracker']: | 2041 if self.tracker and not self.settings['_reopen_tracker']: |
| 2094 print("Reopening tracker") | 2045 print("Reopening tracker") |
| 2095 tracker = roundup.instance.open(self.tracker_home) | 2046 tracker = roundup.instance.open(self.tracker_home) |
| 2096 self.tracker = tracker | 2047 self.tracker = tracker |
| 2097 self.settings['indexer_backend'] = self.tracker.config['INDEXER'] | 2048 self.settings['indexer_backend'] = self.tracker.config['INDEXER'] |
| 2098 | 2049 |
| 2099 except ValueError as message: # noqa: F841 | 2050 except ValueError as message: # noqa: F841 -- used from locals |
| 2100 self.tracker_home = '' | 2051 self.tracker_home = '' |
| 2101 print(_("Error: Couldn't open tracker: %(message)s") % locals()) | 2052 print(_("Error: Couldn't open tracker: %(message)s") % locals()) |
| 2102 return 1 | 2053 return 1 |
| 2103 except NoConfigError as message: # noqa: F841 | 2054 except NoConfigError as message: # noqa: F841 -- used from locals |
| 2104 self.tracker_home = '' | 2055 self.tracker_home = '' |
| 2105 print(_("Error: Couldn't open tracker: %(message)s") % locals()) | 2056 print(_("Error: Couldn't open tracker: %(message)s") % locals()) |
| 2106 return 1 | 2057 return 1 |
| 2107 # message used via locals | 2058 # message used via locals |
| 2108 except ParsingOptionError as message: # noqa: F841 | 2059 except ParsingOptionError as message: # noqa: F841 -- used from locals |
| 2109 print("%(message)s" % locals()) | 2060 print("%(message)s" % locals()) |
| 2110 return 1 | 2061 return 1 |
| 2111 | 2062 |
| 2112 # only open the database once! | 2063 # only open the database once! |
| 2113 if not self.db: | 2064 if not self.db: |
| 2122 | 2073 |
| 2123 # do the command | 2074 # do the command |
| 2124 ret = 0 | 2075 ret = 0 |
| 2125 try: | 2076 try: |
| 2126 ret = function(args[1:]) | 2077 ret = function(args[1:]) |
| 2127 except UsageError as message: # noqa: F841 | 2078 except UsageError as message: |
| 2128 ret = self.usageError_feedback(message, function) | 2079 ret = self.usageError_feedback(message, function) |
| 2129 except Exception: | 2080 except Exception: |
| 2130 import traceback | 2081 import traceback |
| 2131 traceback.print_exc() | 2082 traceback.print_exc() |
| 2132 ret = 1 | 2083 ret = 1 |
| 2133 return ret | 2084 return ret |
| 2134 | 2085 |
| 2135 def interactive(self): | 2086 def interactive(self): |
| 2136 """Run in an interactive mode | 2087 """Run in an interactive mode |
| 2137 """ | 2088 """ |
| 2138 print(_('Roundup %s ready for input.\nType "help" for help.' | 2089 print(_('Roundup %s ready for input.\nType "help" for help.') |
| 2139 % roundup_version)) | 2090 % roundup_version) |
| 2140 try: | 2091 try: |
| 2141 import readline # noqa: F401 | 2092 import readline # noqa: F401 |
| 2142 except ImportError: | 2093 except ImportError: |
| 2143 print(_('Note: command history and editing not available')) | 2094 print(_('Note: command history and editing not available')) |
| 2144 | 2095 |
| 2162 commit = self.my_input(_('There are unsaved changes. Commit them (y/N)? ')) | 2113 commit = self.my_input(_('There are unsaved changes. Commit them (y/N)? ')) |
| 2163 if commit and commit[0].lower() == 'y': | 2114 if commit and commit[0].lower() == 'y': |
| 2164 self.db.commit() | 2115 self.db.commit() |
| 2165 return 0 | 2116 return 0 |
| 2166 | 2117 |
| 2167 def main(self): | 2118 def main(self): # noqa: PLR0912, PLR0911 |
| 2168 try: | 2119 try: |
| 2169 opts, args = getopt.getopt(sys.argv[1:], 'i:u:hcdP:sS:vV') | 2120 opts, args = getopt.getopt(sys.argv[1:], 'i:u:hcdP:sS:vV') |
| 2170 except getopt.GetoptError as e: | 2121 except getopt.GetoptError as e: |
| 2171 self.usage(str(e)) | 2122 self.usage(str(e)) |
| 2172 return 1 | 2123 return 1 |
