ArgParse¶
Many people use Python’s argparse
command line argument parser. This parser
can handle sub-commands like git commit -m "message"
where commit is a
sub-command and -m <message>
is an argument of this sub-command parser. It’s
possible to assign a callback function to each individual sub-command parser.
Advantages
Declarative description instead of imperative form.
All options from argparse can be used.
Declare accepted command-line arguments close to the responsible handler method
Complex parsers can be distributed accross multiple classes and merged via multiple inheritance.
Pre-defined argument templates like switch parameters (
--help
).
Classic argparse
Example¶
1import textwrap
2from argparse import ArgumentParser, RawDescriptionHelpFormatter
3from typing import List, Dict
4
5
6class ProgramBase():
7 HeadLine = "Simple ArgParse Test Program"
8
9 def __init__(self):
10 pass
11
12 def PrintHeadline(self):
13 print("{line}".format(line="=" * 80))
14 print("{headline: ^80s}".format(headline=self.HeadLine))
15 print("{line}".format(line="=" * 80))
16
17
18class Program(ProgramBase):
19 MainParser: ArgumentParser = None
20 SubParsers: Dict[str, ArgumentParser] = None
21
22 def __init__(self):
23 super().__init__()
24
25 self._ConstructParser()
26
27 def _ConstructParser(self):
28 # create a commandline argument parser
29 self.MainParser = ArgumentParser(
30 description=textwrap.dedent('''\
31 This is the test program.
32 '''),
33 epilog=textwrap.fill("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam."),
34 formatter_class=RawDescriptionHelpFormatter,
35 add_help=False
36 )
37
38 self.MainParser.add_argument("-q", "--quiet", dest="quiet", action="store_const", const=True, default=False, help="Reduce messages to a minimum.")
39 self.MainParser.add_argument("-v", "--verbose", dest="verbose", action="store_const", const=True, default=False, help="Print out detailed messages.")
40 self.MainParser.add_argument("-d", "--debug", dest="debug", action="store_const", const=True, default=False, help="Enable debug mode.")
41
42 # create subparsers
43 subParsers = self.MainParser.add_subparsers(help='sub-command help')
44 self.SubParsers = {}
45
46 # Default handler
47 self.MainParser.set_defaults(func=self.HandleDefault)
48
49 # Help handler
50 HelpParser = subParsers.add_parser("help", help = "Display help page(s) for the given command name.")
51 HelpParser.add_argument(metavar = "Command", dest = "Command", type = str, nargs = "?", help = "Print help page(s) for a command.")
52 HelpParser.set_defaults(func=self.HandleHelp)
53 self.SubParsers["help"] = HelpParser
54
55 # UserManagement commands
56 CreateUserParser = subParsers.add_parser("new-user", help="Create a new user.")
57 CreateUserParser.add_argument(metavar='<UserID>', dest="UserID", type=str, help="UserID - unique identifier")
58 CreateUserParser.add_argument(metavar='<Name>', dest="Name", type=str, help="The user's display name.")
59 CreateUserParser.set_defaults(func=self.HandleNewUser)
60 self.SubParsers["new-user"] = CreateUserParser
61
62 RemoveUserParser = subParsers.add_parser("delete-user", help="Delete a user.")
63 RemoveUserParser.add_argument(metavar='<UserID>', dest="UserID", type=str, help="UserID - unique identifier")
64 RemoveUserParser.set_defaults(func=self.HandleDeleteUser)
65 self.SubParsers["delete-user"] = RemoveUserParser
66
67 RemoveUserParser = subParsers.add_parser("list-user", help="List users.")
68 RemoveUserParser.add_argument('--all', dest="all", help='List all users.')
69 RemoveUserParser.set_defaults(func=self.HandleListUser)
70 self.SubParsers["list-user"] = RemoveUserParser
71
72 def Run(self):
73 args = self.MainParser.parse_args()
74 args.func(args)
75
76 def HandleDefault(self, args):
77 self.PrintHeadline()
78 # TODO: use dedent() and multi-line string
79 print(f"HandleDefault:\n quiet={args.quiet!s}\n verbose={args.verbose!s}\n debug={args.debug!s}")
80
81 def HandleHelp(self, args):
82 self.PrintHeadline()
83
84 # TODO: use dedent() and multi-line string
85 print(f"HandleHelp:\n quiet={args.quiet!s}\n verbose={args.verbose!s}\n debug={args.debug!s}\n\n command={args.Command!s}\n\n")
86
87 if (args.Command is None):
88 self.MainParser.print_help()
89 elif (args.Command == "help"):
90 print("This is a recursion ...")
91 else:
92 try:
93 self.SubParsers[args.Command].print_help()
94 except KeyError:
95 print(f"Command {args.Command} is unknown.")
96
97 def HandleNewUser(self, args):
98 self.PrintHeadline()
99
100 # TODO: use dedent() and multi-line string
101 print(f"HandleHelp:\n quiet={args.quiet!s}\n verbose={args.verbose!s}\n debug={args.debug!s}\n\n UserID={args.UserID!s} Name={args.Name!s}")
102
103 def HandleDeleteUser(self, args):
104 self.PrintHeadline()
105
106 # TODO: use dedent() and multi-line string
107 print(f"HandleHelp:\n quiet={args.quiet!s}\n verbose={args.verbose!s}\n debug={args.debug!s}\n\n UserID={args.UserID!s}")
108
109 def HandleListUser(self, args):
110 self.PrintHeadline()
111
112 # TODO: use dedent() and multi-line string
113 print(f"HandleHelp:\n quiet={args.quiet!s}\n verbose={args.verbose!s}\n debug={args.debug!s}\n\n all={args.all!s}")
114
115
116if __name__ == "__main__":
117 prog = Program()
118 prog.Run()
New pyAttributes
Approach¶
A better and more descriptive solution could look like this:
1from argparse import ArgumentDefaultsHelpFormatter
2
3from pyAttributes.ArgParseAttributes import ArgParseMixin, CommonSwitchArgumentAttribute, DefaultAttribute, CommandAttribute, CommonArgumentAttribute, ArgumentAttribute, SwitchArgumentAttribute
4
5
6class ProgramBase():
7 HeadLine = "Simple ArgParse Test Program"
8
9 def __init__(self):
10 pass
11
12 def PrintHeadline(self) -> None:
13 print("{line}".format(line="="*80))
14 print("{headline: ^80s}".format(headline=self.HeadLine))
15 print("{line}".format(line="="*80))
16
17
18class Program(ProgramBase, ArgParseMixin):
19 def __init__(self):
20 import textwrap
21
22 # call constructor of the main inheritance tree
23 super().__init__()
24
25 # Call the constructor of the ArgParseMixin
26 ArgParseMixin.__init__(
27 self,
28 description=textwrap.dedent('''\
29 This is the test program.
30 '''),
31 epilog=textwrap.fill("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam."),
32 formatter_class=ArgumentDefaultsHelpFormatter,
33 add_help=False
34 )
35
36 @CommonSwitchArgumentAttribute("-q", "--quiet", dest="quiet", help="Reduce messages to a minimum.")
37 @CommonSwitchArgumentAttribute("-v", "--verbose", dest="verbose", help="Print out detailed messages.")
38 @CommonSwitchArgumentAttribute("-d", "--debug", dest="debug", help="Enable debug mode.")
39 def Run(self) -> None:
40 ArgParseMixin.Run(self)
41
42
43 @DefaultAttribute()
44 def HandleDefault(self, args) -> None:
45 self.PrintHeadline()
46
47 print(f"HandleDefault:\n quiet={args.quiet!s}\n verbose={args.verbose!s}\n debug={args.debug!s}")
48
49
50 @CommandAttribute("help", help="Display help page(s) for the given command name.")
51 @ArgumentAttribute(metavar="Command", dest="Command", type=str, nargs="?", help="Print help page(s) for a command.")
52 def HandleHelp(self, args) -> None:
53 self.PrintHeadline()
54
55 print(f"HandleHelp:\n quiet={args.quiet!s}\n verbose={args.verbose!s}\n debug={args.debug!s}\n\n command={args.Command!s}\n\n")
56
57 if (args.Command is None):
58 self.MainParser.print_help()
59 elif (args.Command == "help"):
60 print("This is a recursion ...")
61 else:
62 try:
63 self.SubParsers[args.Command].print_help()
64 except KeyError:
65 print(f"Command {args.Command} is unknown.")
66
67
68 @CommandAttribute("new-user", help="Create a new user.")
69 @ArgumentAttribute(metavar='<UserID>', dest="UserID", type=str, help="UserID - unique identifier")
70 @ArgumentAttribute(metavar='<Name>', dest="Name", type=str, help="The user's display name.")
71 def HandleNewUser(self, args) -> None:
72 self.PrintHeadline()
73
74 print(f"HandleNewUser:\n quiet={args.quiet!s}\n verbose={args.verbose!s}\n debug={args.debug!s}\n\n UserID={args.UserID!s} Name={args.Name!s} Limit={args.Limit!s}")
75
76
77 @CommandAttribute("delete-user", help="Delete a user.")
78 @ArgumentAttribute(metavar='<UserID>', dest="UserID", type=str, help="UserID - unique identifier")
79 def HandleDeleteUser(self, args) -> None:
80 self.PrintHeadline()
81
82 print(f"HandleDeleteUser:\n quiet={args.quiet!s}\n verbose={args.verbose!s}\n debug={args.debug!s}\n\n UserID={args.UserID!s}")
83
84
85 @CommandAttribute("list-user", help="List users.")
86 @SwitchArgumentAttribute('--all', dest="all", help='List all users.')
87 def HandleListUser(self, args) -> None:
88 self.PrintHeadline()
89
90 print(f"HandleListUser:\n quiet={args.quiet!s}\n verbose={args.verbose!s}\n debug={args.debug!s}\n\n all={args.all!s}")
91
92
93if __name__ == "__main__":
94 prog = Program()
95 prog.Run()