Merge ppa-dev-tools:error-handling-cleanup into ppa-dev-tools:main

Proposed by Bryce Harrington
Status: Merged
Merged at revision: 03b736c6a37efe9f7fd81238c7a6ec451d7e5018
Proposed branch: ppa-dev-tools:error-handling-cleanup
Merge into: ppa-dev-tools:main
Diff against target: 385 lines (+65/-36)
2 files modified
ppa/ppa.py (+10/-10)
scripts/ppa (+55/-26)
Reviewer Review Type Date Requested Status
Athos Ribeiro (community) Approve
Canonical Server Reporter Pending
Review via email: mp+461580@code.launchpad.net

Description of the change

Several cleanups relating to exception handling and error exit codes.

These aren't fixing specific issues that have come up in practice, but rather were noticed in recent refactoring and code cleanup efforts on other branches. I think these are worth getting reviewed and landed first.

To post a comment you must log in.
Revision history for this message
Athos Ribeiro (athos-ribeiro) wrote :

Thanks, Bryce.

This LGTM.

I left a small comment nitpick regarding moving a magic number to a variable so it is more readable.

review: Approve
Revision history for this message
Bryce Harrington (bryce) wrote :

Thanks Athos, that's a good idea. I've added it defined as EX_KEYBOARD_INTERRUPT. In fact while adding it I spotted a couple other places for it.

$ git merge --ff-only error-handling-cleanup
Updating 1a457e7..03b736c
Fast-forward
 ppa/ppa.py | 20 ++++++++++----------
 scripts/ppa | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
 2 files changed, 65 insertions(+), 36 deletions(-)
$ git push
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To git+ssh://git.launchpad.net/ppa-dev-tools
   1a457e7..03b736c main -> main

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/ppa/ppa.py b/ppa/ppa.py
index bb28c21..5fd8056 100755
--- a/ppa/ppa.py
+++ b/ppa/ppa.py
@@ -35,7 +35,7 @@ class PendingReason(enum.Enum):
35 BUILD_MISSING = enum.auto() # Build was expected, but missing35 BUILD_MISSING = enum.auto() # Build was expected, but missing
3636
3737
38class PpaDoesNotExist(BaseException):38class PpaNotFoundError(Exception):
39 """Exception indicating a requested PPA could not be found."""39 """Exception indicating a requested PPA could not be found."""
4040
41 def __init__(self, ppa_name, owner_name, message=None):41 def __init__(self, ppa_name, owner_name, message=None):
@@ -124,7 +124,7 @@ class Ppa:
124124
125 :rtype: archive125 :rtype: archive
126 :returns: The Launchpad archive object.126 :returns: The Launchpad archive object.
127 :raises PpaDoesNotExist: Raised if a PPA does not exist in Launchpad.127 :raises PpaNotFoundError: Raised if a PPA does not exist in Launchpad.
128 """128 """
129 if not self._service:129 if not self._service:
130 raise AttributeError("Ppa object not connected to the Launchpad service")130 raise AttributeError("Ppa object not connected to the Launchpad service")
@@ -132,7 +132,7 @@ class Ppa:
132 owner = self._service.people[self.owner_name]132 owner = self._service.people[self.owner_name]
133 return owner.getPPAByName(name=self.ppa_name)133 return owner.getPPAByName(name=self.ppa_name)
134 except NotFound:134 except NotFound:
135 raise PpaDoesNotExist(self.ppa_name, self.owner_name)135 raise PpaNotFoundError(self.ppa_name, self.owner_name)
136136
137 @lru_cache137 @lru_cache
138 def exists(self) -> bool:138 def exists(self) -> bool:
@@ -140,7 +140,7 @@ class Ppa:
140 try:140 try:
141 self.archive141 self.archive
142 return True142 return True
143 except PpaDoesNotExist:143 except PpaNotFoundError:
144 return False144 return False
145145
146 @property146 @property
@@ -189,7 +189,7 @@ class Ppa:
189 self.ppa_description = description189 self.ppa_description = description
190 try:190 try:
191 archive = self.archive191 archive = self.archive
192 except PpaDoesNotExist as e:192 except PpaNotFoundError as e:
193 print(e)193 print(e)
194 return False194 return False
195 archive.description = description195 archive.description = description
@@ -243,7 +243,7 @@ class Ppa:
243 """243 """
244 try:244 try:
245 return [proc.name for proc in self.archive.processors]245 return [proc.name for proc in self.archive.processors]
246 except PpaDoesNotExist as e:246 except PpaNotFoundError as e:
247 sys.stderr.write(e)247 sys.stderr.write(e)
248 return None248 return None
249249
@@ -268,7 +268,7 @@ class Ppa:
268 try:268 try:
269 self.archive.setProcessors(processors=procs)269 self.archive.setProcessors(processors=procs)
270 return True270 return True
271 except PpaDoesNotExist as e:271 except PpaNotFoundError as e:
272 sys.stderr.write(e)272 sys.stderr.write(e)
273 return False273 return False
274274
@@ -345,7 +345,7 @@ class Ppa:
345 self.archive.getPublishedBinaries(345 self.archive.getPublishedBinaries(
346 created_since_date=created_since_date, status="Published",346 created_since_date=created_since_date, status="Published",
347 binary_name=name))347 binary_name=name))
348 except PpaDoesNotExist as e:348 except PpaNotFoundError as e:
349 print(e)349 print(e)
350 return None350 return None
351 # elif series:351 # elif series:
@@ -386,7 +386,7 @@ class Ppa:
386 created_since_date=created_since_date,386 created_since_date=created_since_date,
387 status="Published",387 status="Published",
388 source_name=name))388 source_name=name))
389 except PpaDoesNotExist as e:389 except PpaNotFoundError as e:
390 print(e)390 print(e)
391 return None391 return None
392392
@@ -403,7 +403,7 @@ class Ppa:
403 """403 """
404 try:404 try:
405 return self.archive.lp_delete()405 return self.archive.lp_delete()
406 except PpaDoesNotExist as e:406 except PpaNotFoundError as e:
407 print(e)407 print(e)
408 return True408 return True
409 except BadRequest:409 except BadRequest:
diff --git a/scripts/ppa b/scripts/ppa
index 76c32c5..2d2937e 100755
--- a/scripts/ppa
+++ b/scripts/ppa
@@ -82,7 +82,7 @@ from ppa.ppa import (
82 get_ppa,82 get_ppa,
83 ppa_address_split,83 ppa_address_split,
84 Ppa,84 Ppa,
85 PpaDoesNotExist,85 PpaNotFoundError,
86 PendingReason86 PendingReason
87)87)
88from ppa.ppa_group import PpaGroup, PpaAlreadyExists88from ppa.ppa_group import PpaGroup, PpaAlreadyExists
@@ -94,6 +94,8 @@ from ppa.trigger import get_triggers, show_triggers
94import ppa.debug94import ppa.debug
95from ppa.debug import dbg, warn, error95from ppa.debug import dbg, warn, error
9696
97EX_KEYBOARD_INTERRUPT = 130
98
9799
98def UNIMPLEMENTED():100def UNIMPLEMENTED():
99 """Marks functionality that's not yet been coded"""101 """Marks functionality that's not yet been coded"""
@@ -482,6 +484,12 @@ def create_config(lp: Lp, args: argparse.Namespace) -> dict[str, Any]:
482 # Assume defaults484 # Assume defaults
483 dbg("Using default config since no config file found at {config_path}")485 dbg("Using default config since no config file found at {config_path}")
484 config = dict(DEFAULT_CONFIG)486 config = dict(DEFAULT_CONFIG)
487 except OSError as err:
488 error(f"Could not open {config_path}: {str(err)}")
489 return None
490 except YAMLError as err:
491 error(f"Invalid config file {config_path}: {str(err)}")
492 return None
485493
486 # Map all non-empty elements from argparse Namespace into config dict494 # Map all non-empty elements from argparse Namespace into config dict
487 config.update({k: v for k, v in vars(args).items() if v is not None})495 config.update({k: v for k, v in vars(args).items() if v is not None})
@@ -538,6 +546,9 @@ def command_create(lp: Lp, config: dict[str, str]) -> int:
538 :rtype: int546 :rtype: int
539 :returns: Status code OK (0) on success, non-zero on error.547 :returns: Status code OK (0) on success, non-zero on error.
540 """548 """
549 if not lp:
550 return os.EX_UNAVAILABLE
551
541 # Take description from stdin if it's not a tty552 # Take description from stdin if it's not a tty
542 description = config.get('description')553 description = config.get('description')
543 if not description and not sys.stdin.isatty():554 if not description and not sys.stdin.isatty():
@@ -599,9 +610,9 @@ def command_create(lp: Lp, config: dict[str, str]) -> int:
599 return os.EX_NOUSER610 return os.EX_NOUSER
600 except PpaAlreadyExists as e:611 except PpaAlreadyExists as e:
601 warn(o2str(e.message))612 warn(o2str(e.message))
602 return 3613 return os.EX_CANTCREAT
603 except KeyboardInterrupt:614 except KeyboardInterrupt:
604 return 2615 return EX_KEYBOARD_INTERRUPT
605 print("Unhandled error")616 print("Unhandled error")
606 return 1617 return 1
607618
@@ -614,6 +625,9 @@ def command_credentials(lp: Lp, config: dict[str, str]) -> int:
614 :rtype: int625 :rtype: int
615 :returns: Status code OK (0) on success, non-zero on error.626 :returns: Status code OK (0) on success, non-zero on error.
616 """627 """
628 if not lp:
629 return os.EX_UNAVAILABLE
630
617 try:631 try:
618 credentials_filename = config.get(632 credentials_filename = config.get(
619 'credentials_filename',633 'credentials_filename',
@@ -623,7 +637,7 @@ def command_credentials(lp: Lp, config: dict[str, str]) -> int:
623 print(f"Launchpad credentials written to {credentials_filename}")637 print(f"Launchpad credentials written to {credentials_filename}")
624 return os.EX_OK638 return os.EX_OK
625 except KeyboardInterrupt:639 except KeyboardInterrupt:
626 return 2640 return EX_KEYBOARD_INTERRUPT
627 print("Unhandled error")641 print("Unhandled error")
628 return 1642 return 1
629643
@@ -635,6 +649,9 @@ def command_desc(lp: Lp, config: dict[str, str]) -> int:
635 :rtype: int649 :rtype: int
636 :returns: Status code OK (0) on success, non-zero on error.650 :returns: Status code OK (0) on success, non-zero on error.
637 """651 """
652 if not lp:
653 return os.EX_UNAVAILABLE
654
638 if not sys.stdin.isatty():655 if not sys.stdin.isatty():
639 description = sys.stdin.read()656 description = sys.stdin.read()
640 else:657 else:
@@ -652,7 +669,7 @@ def command_desc(lp: Lp, config: dict[str, str]) -> int:
652669
653 return the_ppa.set_description(description)670 return the_ppa.set_description(description)
654 except KeyboardInterrupt:671 except KeyboardInterrupt:
655 return 2672 return EX_KEYBOARD_INTERRUPT
656 print("Unhandled error")673 print("Unhandled error")
657 return 1674 return 1
658675
@@ -665,6 +682,9 @@ def command_destroy(lp: Lp, config: dict[str, str]) -> int:
665 :rtype: int682 :rtype: int
666 :returns: Status code OK (0) on success, non-zero on error.683 :returns: Status code OK (0) on success, non-zero on error.
667 """684 """
685 if not lp:
686 return os.EX_UNAVAILABLE
687
668 try:688 try:
669 the_ppa = get_ppa(lp, config)689 the_ppa = get_ppa(lp, config)
670 if not config.get('dry_run'):690 if not config.get('dry_run'):
@@ -672,7 +692,7 @@ def command_destroy(lp: Lp, config: dict[str, str]) -> int:
672 the_ppa.destroy()692 the_ppa.destroy()
673 return os.EX_OK693 return os.EX_OK
674 except KeyboardInterrupt:694 except KeyboardInterrupt:
675 return 2695 return EX_KEYBOARD_INTERRUPT
676 print("Unhandled error")696 print("Unhandled error")
677 return 1697 return 1
678698
@@ -694,7 +714,7 @@ def command_list(lp: Lp, config: dict[str, str], filter_func=None) -> int:
694 # - filter_older: Ones older than a given date714 # - filter_older: Ones older than a given date
695 # - Status of the PPAs715 # - Status of the PPAs
696 if not lp:716 if not lp:
697 return 1717 return os.EX_UNAVAILABLE
698718
699 owner_name = config.get('owner_name')719 owner_name = config.get('owner_name')
700 if not owner_name:720 if not owner_name:
@@ -710,7 +730,7 @@ def command_list(lp: Lp, config: dict[str, str], filter_func=None) -> int:
710 print(p.address)730 print(p.address)
711 return os.EX_OK731 return os.EX_OK
712 except KeyboardInterrupt:732 except KeyboardInterrupt:
713 return 2733 return EX_KEYBOARD_INTERRUPT
714 print("Unhandled error")734 print("Unhandled error")
715 return 1735 return 1
716736
@@ -723,12 +743,15 @@ def command_exists(lp: Lp, config: dict[str, str]) -> int:
723 :rtype: int743 :rtype: int
724 :returns: Status code OK (0) on success, non-zero on error.744 :returns: Status code OK (0) on success, non-zero on error.
725 """745 """
746 if not lp:
747 return os.EX_UNAVAILABLE
748
726 try:749 try:
727 the_ppa = get_ppa(lp, config)750 the_ppa = get_ppa(lp, config)
728 if the_ppa.archive is not None:751 if the_ppa.archive is not None:
729 return os.EX_OK752 return os.EX_OK
730 except KeyboardInterrupt:753 except KeyboardInterrupt:
731 return 2754 return EX_KEYBOARD_INTERRUPT
732 return 1755 return 1
733756
734757
@@ -740,6 +763,9 @@ def command_set(lp: Lp, config: dict[str, str]) -> int:
740 :rtype: int763 :rtype: int
741 :returns: Status code OK (0) on success, non-zero on error.764 :returns: Status code OK (0) on success, non-zero on error.
742 """765 """
766 if not lp:
767 return os.EX_UNAVAILABLE
768
743 try:769 try:
744 the_ppa = get_ppa(lp, config)770 the_ppa = get_ppa(lp, config)
745771
@@ -772,14 +798,14 @@ def command_set(lp: Lp, config: dict[str, str]) -> int:
772 else:798 else:
773 error(f"Insufficient authorization to modify PPA '{the_ppa.name}'.")799 error(f"Insufficient authorization to modify PPA '{the_ppa.name}'.")
774 return os.EX_NOPERM800 return os.EX_NOPERM
775 except PpaDoesNotExist as e:801 except PpaNotFoundError as e:
776 print(e)802 print(e)
777 return 1803 return os.EX_NOTFOUND
778 except ValueError as e:804 except ValueError as e:
779 print(f"Error: {e}")805 print(f"Error: {e}")
780 return os.EX_USAGE806 return os.EX_USAGE
781 except KeyboardInterrupt:807 except KeyboardInterrupt:
782 return 2808 return EX_KEYBOARD_INTERRUPT
783 print("Unhandled error")809 print("Unhandled error")
784 return 1810 return 1
785811
@@ -792,6 +818,9 @@ def command_show(lp: Lp, config: dict[str, str]) -> int:
792 :rtype: int818 :rtype: int
793 :returns: Status code OK (0) on success, non-zero on error.819 :returns: Status code OK (0) on success, non-zero on error.
794 """820 """
821 if not lp:
822 return os.EX_UNAVAILABLE
823
795 distro = None824 distro = None
796 series = None825 series = None
797 arch = None826 arch = None
@@ -833,11 +862,11 @@ def command_show(lp: Lp, config: dict[str, str]) -> int:
833 total_downloads += binary.getDownloadCount()862 total_downloads += binary.getDownloadCount()
834 print("downloads: %d" % (total_downloads))863 print("downloads: %d" % (total_downloads))
835 return os.EX_OK864 return os.EX_OK
836 except PpaDoesNotExist as e:865 except PpaNotFoundError as e:
837 print(e)866 print(e)
838 return 1867 return os.EX_NOTFOUND
839 except KeyboardInterrupt:868 except KeyboardInterrupt:
840 return 2869 return EX_KEYBOARD_INTERRUPT
841 print("Unhandled error")870 print("Unhandled error")
842 return 1871 return 1
843872
@@ -863,7 +892,7 @@ def command_wait(lp: Lp, config: dict[str, str]) -> int:
863 :returns: Status code OK (0) on success, non-zero on error.892 :returns: Status code OK (0) on success, non-zero on error.
864 """893 """
865 if not lp:894 if not lp:
866 return 1895 return os.EX_UNAVAILABLE
867896
868 try:897 try:
869 wait_max_age_hours = config.get('wait_max_age_hours')898 wait_max_age_hours = config.get('wait_max_age_hours')
@@ -904,14 +933,14 @@ def command_wait(lp: Lp, config: dict[str, str]) -> int:
904 return os.EX_TEMPFAIL933 return os.EX_TEMPFAIL
905 time.sleep(config['wait_seconds'])934 time.sleep(config['wait_seconds'])
906 return os.EX_OK935 return os.EX_OK
907 except PpaDoesNotExist as e:936 except PpaNotFoundError as e:
908 print(e)937 print(e)
909 return 1938 return os.EX_NOTFOUND
910 except ValueError as e:939 except ValueError as e:
911 print(f"Error: {e}")940 print(f"Error: {e}")
912 return os.EX_USAGE941 return os.EX_USAGE
913 except KeyboardInterrupt:942 except KeyboardInterrupt:
914 return 2943 return EX_KEYBOARD_INTERRUPT
915 print("Unhandled error")944 print("Unhandled error")
916 return 1945 return 1
917946
@@ -925,7 +954,7 @@ def command_tests(lp: Lp, config: dict[str, str]) -> int:
925 :returns: Status code OK (0) on success, non-zero on error.954 :returns: Status code OK (0) on success, non-zero on error.
926 """955 """
927 if not lp:956 if not lp:
928 return 1957 return os.EX_UNAVAILABLE
929958
930 apt_repository = None959 apt_repository = None
931 if config.get("show_rdepends"):960 if config.get("show_rdepends"):
@@ -934,7 +963,7 @@ def command_tests(lp: Lp, config: dict[str, str]) -> int:
934 apt_repository = Repository(cache_dir=local_dists_path)963 apt_repository = Repository(cache_dir=local_dists_path)
935 except FileNotFoundError as e:964 except FileNotFoundError as e:
936 error(f'Missing checkout\n{LOCAL_REPOSITORY_MIRRORING_DIRECTIONS}: {e}')965 error(f'Missing checkout\n{LOCAL_REPOSITORY_MIRRORING_DIRECTIONS}: {e}')
937 return 1966 return os.EX_NOTFOUND
938967
939 releases = config.get('releases', None)968 releases = config.get('releases', None)
940 if releases is None:969 if releases is None:
@@ -956,7 +985,7 @@ def command_tests(lp: Lp, config: dict[str, str]) -> int:
956 the_ppa = get_ppa(lp, config)985 the_ppa = get_ppa(lp, config)
957 if not the_ppa.exists():986 if not the_ppa.exists():
958 error(f"PPA {the_ppa.name} does not exist for user {the_ppa.owner_name}")987 error(f"PPA {the_ppa.name} does not exist for user {the_ppa.owner_name}")
959 return 1988 return os.EX_NOTFOUND
960989
961 architectures = config.get('architectures', ARCHES_AUTOPKGTEST)990 architectures = config.get('architectures', ARCHES_AUTOPKGTEST)
962 if isinstance(architectures, str):991 if isinstance(architectures, str):
@@ -1035,7 +1064,7 @@ def command_tests(lp: Lp, config: dict[str, str]) -> int:
10351064
1036 return os.EX_OK1065 return os.EX_OK
1037 except KeyboardInterrupt:1066 except KeyboardInterrupt:
1038 return 21067 return EX_KEYBOARD_INTERRUPT
1039 print("Unhandled error")1068 print("Unhandled error")
1040 return 11069 return 1
10411070
@@ -1068,7 +1097,7 @@ def main(args: argparse.Namespace) -> int:
1068 lp = create_lp('ppa-dev-tools', args)1097 lp = create_lp('ppa-dev-tools', args)
1069 config = create_config(lp, args)1098 config = create_config(lp, args)
1070 except KeyboardInterrupt:1099 except KeyboardInterrupt:
1071 return 21100 return EX_KEYBOARD_INTERRUPT
1072 except ValueError as e:1101 except ValueError as e:
1073 error(e)1102 error(e)
1074 return os.EX_CONFIG1103 return os.EX_CONFIG
@@ -1086,7 +1115,7 @@ def main(args: argparse.Namespace) -> int:
1086 func, param = COMMANDS[command]1115 func, param = COMMANDS[command]
1087 except KeyError:1116 except KeyError:
1088 parser.error(f"No such command {args.command}")1117 parser.error(f"No such command {args.command}")
1089 return 11118 return os.EX_USAGE
1090 if param:1119 if param:
1091 return func(lp, config, param)1120 return func(lp, config, param)
1092 return func(lp, config)1121 return func(lp, config)
@@ -1101,6 +1130,6 @@ if __name__ == "__main__":
1101 if retval == os.EX_USAGE:1130 if retval == os.EX_USAGE:
1102 print()1131 print()
1103 parser.print_help()1132 parser.print_help()
1104 elif retval == 2:1133 elif retval == EX_KEYBOARD_INTERRUPT:
1105 sys.stderr.write(" (user interrupt)\n")1134 sys.stderr.write(" (user interrupt)\n")
1106 sys.exit(retval)1135 sys.exit(retval)

Subscribers

People subscribed via source and target branches

to all changes: