Skip to content

Commit dece47e

Browse files
committed
Refactor CLI argument parsing to use Command and CommandArgs classes for improved structure and readability
1 parent 7e27e28 commit dece47e

File tree

1 file changed

+159
-104
lines changed

1 file changed

+159
-104
lines changed

src/iop/_cli.py

Lines changed: 159 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,141 +1,196 @@
1+
from __future__ import annotations
12
import argparse
23
import json
34
import os
5+
from dataclasses import dataclass
6+
from enum import Enum, auto
7+
import sys
8+
from typing import Optional, Callable
49
from importlib.metadata import version
510

611
from iop._director import _Director
712
from iop._utils import _Utils
813

9-
def parse_args():
10-
# parse arguments
11-
main_parser = argparse.ArgumentParser()
12-
parser = main_parser.add_mutually_exclusive_group()
13-
parser.add_argument('-d', '--default', help='set the default production', nargs='?', const='not_set')
14-
parser.add_argument('-l', '--list', help='list productions', action='store_true')
15-
parser.add_argument('-s', '--start', help='start a production', nargs='?', const='not_set')
16-
start = main_parser.add_argument_group('start arguments')
17-
start.add_argument('-D', '--detach', help='start a production in detach mode', action='store_true')
18-
parser.add_argument('-S', '--stop', help='stop a production', action='store_true')
19-
parser.add_argument('-k', '--kill', help='kill a production', action='store_true')
20-
parser.add_argument('-r', '--restart', help='restart a production', action='store_true')
21-
parser.add_argument('-x', '--status', help='status a production', action='store_true')
22-
parser.add_argument('-m', '-M', '--migrate', help='migrate production and classes with settings file')
23-
parser.add_argument('-e', '--export', help='export a production', nargs='?', const='not_set')
24-
parser.add_argument('-v', '--version', help='display version', action='store_true')
25-
parser.add_argument('-L', '--log', help='display log', nargs='?', const='not_set')
26-
parser.add_argument('-i', '--init', help='init the pex module in iris', nargs='?', const='not_set')
27-
parser.add_argument('-t', '--test', help='test the pex module in iris', nargs='?', const='not_set')
28-
test = main_parser.add_argument_group('test arguments')
29-
# add classname argument
30-
test.add_argument('-C', '--classname', help='test classname', nargs='?', const='not_set')
31-
# body argument
32-
test.add_argument('-B', '--body', help='test body', nargs='?', const='not_set')
33-
return main_parser
34-
35-
def main(argv=None):
36-
# build arguments
37-
parser = parse_args()
38-
args = parser.parse_args(argv)
39-
40-
if args.default:
41-
# set default production
42-
if args.default == 'not_set':
43-
# display default production name
14+
class CommandType(Enum):
15+
DEFAULT = auto()
16+
LIST = auto()
17+
START = auto()
18+
STOP = auto()
19+
KILL = auto()
20+
RESTART = auto()
21+
STATUS = auto()
22+
TEST = auto()
23+
VERSION = auto()
24+
EXPORT = auto()
25+
MIGRATE = auto()
26+
LOG = auto()
27+
INIT = auto()
28+
HELP = auto()
29+
30+
@dataclass
31+
class CommandArgs:
32+
"""Container for parsed command arguments"""
33+
default: Optional[str] = None
34+
list: bool = False
35+
start: Optional[str] = None
36+
detach: bool = False
37+
stop: bool = False
38+
kill: bool = False
39+
restart: bool = False
40+
status: bool = False
41+
migrate: Optional[str] = None
42+
export: Optional[str] = None
43+
version: bool = False
44+
log: Optional[str] = None
45+
init: Optional[str] = None
46+
test: Optional[str] = None
47+
classname: Optional[str] = None
48+
body: Optional[str] = None
49+
50+
class Command:
51+
def __init__(self, args: CommandArgs):
52+
self.args = args
53+
54+
def execute(self) -> None:
55+
command_type = self._determine_command_type()
56+
command_handlers = {
57+
CommandType.DEFAULT: self._handle_default,
58+
CommandType.LIST: self._handle_list,
59+
CommandType.START: self._handle_start,
60+
CommandType.STOP: self._handle_stop,
61+
CommandType.KILL: self._handle_kill,
62+
CommandType.RESTART: self._handle_restart,
63+
CommandType.STATUS: self._handle_status,
64+
CommandType.TEST: self._handle_test,
65+
CommandType.VERSION: self._handle_version,
66+
CommandType.EXPORT: self._handle_export,
67+
CommandType.MIGRATE: self._handle_migrate,
68+
CommandType.LOG: self._handle_log,
69+
CommandType.INIT: self._handle_init,
70+
CommandType.HELP: self._handle_help
71+
}
72+
handler = command_handlers.get(command_type)
73+
if handler:
74+
handler()
75+
76+
def _determine_command_type(self) -> CommandType:
77+
if self.args.default: return CommandType.DEFAULT
78+
if self.args.list: return CommandType.LIST
79+
if self.args.start: return CommandType.START
80+
if self.args.stop: return CommandType.STOP
81+
if self.args.kill: return CommandType.KILL
82+
if self.args.restart: return CommandType.RESTART
83+
if self.args.status: return CommandType.STATUS
84+
if self.args.test: return CommandType.TEST
85+
if self.args.version: return CommandType.VERSION
86+
if self.args.export: return CommandType.EXPORT
87+
if self.args.migrate: return CommandType.MIGRATE
88+
if self.args.log: return CommandType.LOG
89+
if self.args.init: return CommandType.INIT
90+
return CommandType.HELP
91+
92+
def _handle_default(self) -> None:
93+
if self.args.default == 'not_set':
4494
print(_Director.get_default_production())
4595
else:
46-
_Director.set_default_production(args.default)
96+
_Director.set_default_production(self.args.default)
4797

48-
elif args.list:
49-
# display list of productions
98+
def _handle_list(self) -> None:
5099
dikt = _Director.list_productions()
51100
print(json.dumps(dikt, indent=4))
52101

53-
elif args.start:
54-
production_name = None
55-
if args.start == 'not_set':
56-
# start default production
57-
production_name = _Director.get_default_production()
58-
else:
59-
# start production with name
60-
production_name = args.start
61-
if args.detach:
62-
# start production in detach mode
102+
def _handle_start(self) -> None:
103+
production_name = self.args.start if self.args.start != 'not_set' else _Director.get_default_production()
104+
if self.args.detach:
63105
_Director.start_production(production_name)
64106
print(f"Production {production_name} started")
65107
else:
66108
_Director.start_production_with_log(production_name)
67109

68-
elif args.init:
69-
if args.init == 'not_set':
70-
# set arg to None
71-
args.init = None
72-
_Utils.setup(args.start)
110+
def _handle_stop(self) -> None:
111+
_Director.stop_production()
112+
print(f"Production {_Director.get_default_production()} stopped")
73113

74-
elif args.kill:
75-
# kill a production
114+
def _handle_kill(self) -> None:
76115
_Director.shutdown_production()
77116

78-
elif args.restart:
79-
# restart a production
117+
def _handle_restart(self) -> None:
80118
_Director.restart_production()
81119

82-
elif args.migrate:
83-
# check if migrate is absolute path
84-
if os.path.isabs(args.migrate):
85-
# migrate a production with absolute path
86-
_Utils.migrate(args.migrate)
87-
else:
88-
# migrate a production with relative path
89-
_Utils.migrate(os.path.join(os.getcwd(), args.migrate))
120+
def _handle_status(self) -> None:
121+
print(json.dumps(_Director.status_production(), indent=4))
122+
123+
def _handle_test(self) -> None:
124+
test_name = None if self.args.test == 'not_set' else self.args.test
125+
response = _Director.test_component(
126+
test_name,
127+
classname=self.args.classname if self.args.classname != 'not_set' else None,
128+
body=self.args.body if self.args.body != 'not_set' else None
129+
)
130+
print(response)
90131

91-
elif args.version:
92-
# display version
132+
def _handle_version(self) -> None:
93133
print(version('iris-pex-embedded-python'))
94134

95-
elif args.log:
96-
# display log
97-
if args.log == 'not_set':
98-
# display default production log
99-
_Director.log_production()
100-
else:
101-
_Director.log_production_top(args.log)
135+
def _handle_export(self) -> None:
136+
export_name = _Director.get_default_production() if self.args.export == 'not_set' else self.args.export
137+
print(json.dumps(_Utils.export_production(export_name), indent=4))
102138

103-
elif args.stop:
104-
# stop a production
105-
_Director.stop_production()
106-
print(f"Production {_Director.get_default_production()} stopped")
139+
def _handle_migrate(self) -> None:
140+
migrate_path = self.args.migrate
141+
if not os.path.isabs(migrate_path):
142+
migrate_path = os.path.join(os.getcwd(), migrate_path)
143+
_Utils.migrate(migrate_path)
107144

108-
elif args.status:
109-
dikt=_Director.status_production()
110-
print(json.dumps(dikt, indent=4))
145+
def _handle_log(self) -> None:
146+
log_name = _Director.get_default_production() if self.args.log == 'not_set' else self.args.log
147+
print(_Director.get_production_log(log_name))
111148

112-
elif args.test:
113-
classname = None
114-
body = None
115-
if args.test == 'not_set':
116-
# set arg to None
117-
args.test = None
118-
if args.classname:
119-
classname = args.classname
120-
if args.body:
121-
body = args.body
122-
response = _Director.test_component(args.test, classname=classname, body=body)
123-
print(response)
149+
def _handle_init(self) -> None:
150+
_Utils.setup(None)
124151

125-
elif args.export:
126-
if args.export == 'not_set':
127-
# export default production
128-
args.export=_Director.get_default_production()
152+
def _handle_help(self) -> None:
153+
create_parser().print_help()
154+
print(f"\nDefault production: {_Director.get_default_production()}")
129155

130-
dikt = _Utils.export_production(args.export)
131-
print(json.dumps(dikt, indent=4))
156+
def create_parser() -> argparse.ArgumentParser:
157+
"""Create and configure argument parser"""
158+
main_parser = argparse.ArgumentParser()
159+
parser = main_parser.add_mutually_exclusive_group()
160+
161+
# Main commands
162+
parser.add_argument('-d', '--default', help='set the default production', nargs='?', const='not_set')
163+
parser.add_argument('-l', '--list', help='list productions', action='store_true')
164+
parser.add_argument('-s', '--start', help='start a production', nargs='?', const='not_set')
165+
parser.add_argument('-S', '--stop', help='stop a production', action='store_true')
166+
parser.add_argument('-k', '--kill', help='kill a production', action='store_true')
167+
parser.add_argument('-r', '--restart', help='restart a production', action='store_true')
168+
parser.add_argument('-x', '--status', help='status a production', action='store_true')
169+
parser.add_argument('-m', '-M', '--migrate', help='migrate production and classes with settings file')
170+
parser.add_argument('-e', '--export', help='export a production', nargs='?', const='not_set')
171+
parser.add_argument('-v', '--version', help='display version', action='store_true')
172+
parser.add_argument('-L', '--log', help='display log', nargs='?', const='not_set')
173+
parser.add_argument('-i', '--init', help='init the pex module in iris', nargs='?', const='not_set')
174+
parser.add_argument('-t', '--test', help='test the pex module in iris', nargs='?', const='not_set')
132175

133-
else:
134-
# display help
135-
parser.print_help()
136-
print()
137-
print("Default production : " + _Director.get_default_production())
176+
# Command groups
177+
start = main_parser.add_argument_group('start arguments')
178+
start.add_argument('-D', '--detach', help='start a production in detach mode', action='store_true')
179+
180+
test = main_parser.add_argument_group('test arguments')
181+
test.add_argument('-C', '--classname', help='test classname', nargs='?', const='not_set')
182+
test.add_argument('-B', '--body', help='test body', nargs='?', const='not_set')
183+
184+
return main_parser
138185

186+
def main(argv=None) -> None:
187+
parser = create_parser()
188+
args = parser.parse_args(argv)
189+
cmd_args = CommandArgs(**vars(args))
190+
191+
command = Command(cmd_args)
192+
command.execute()
193+
sys.exit(0)
139194

140195
if __name__ == '__main__':
141196
main()

0 commit comments

Comments
 (0)