This repository has been archived by the owner on Nov 3, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
script.py
272 lines (224 loc) · 8.2 KB
/
script.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#!/usr/bin/env python3
# Copyright (c) Facebook, Inc. and its affiliates.
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
"""
The general ParlAI Script interface.
An abstract class to help standardize the call to ParlAI scripts, enabling them to be
completed easily.
Also contains helper classes for loading scripts, etc.
"""
import io
import argparse
from typing import List, Optional, Dict, Any
from parlai.core.opt import Opt
from parlai.core.params import ParlaiParser, CustomHelpFormatter
from abc import abstractmethod
import importlib
import pkgutil
import parlai.scripts
import parlai.utils.logging as logging
from parlai.core.loader import register_script, SCRIPT_REGISTRY # noqa: F401
def setup_script_registry():
"""
Loads the scripts so that @register_script is hit for all.
"""
for module in pkgutil.iter_modules(parlai.scripts.__path__, 'parlai.scripts.'):
importlib.import_module(module.name)
class ParlaiScript(object):
"""
A ParlAI script is a standardized form of access.
"""
parser: ParlaiParser
@classmethod
@abstractmethod
def setup_args(cls) -> ParlaiParser:
"""
Create the parser with args.
"""
# we want to later deprecate this for add_cmdline_args
pass
def __init__(self, opt: Opt):
self.opt = opt
@abstractmethod
def run(self):
"""
The main method.
Must be implemented by the script writer.
"""
raise NotImplementedError()
@classmethod
def _run_kwargs(cls, kwargs: Dict[str, Any]):
"""
Construct and run the script using kwargs, pseudo-parsing them.
"""
parser = cls.setup_args()
opt = parser.parse_kwargs(**kwargs)
return cls._run_from_parser_and_opt(opt, parser)
@classmethod
def _run_args(cls, args: Optional[List[str]] = None):
"""
Construct and run the script using args, defaulting to getting from CLI.
"""
parser = cls.setup_args()
opt = parser.parse_args(args=args)
return cls._run_from_parser_and_opt(opt, parser)
@classmethod
def _run_from_parser_and_opt(cls, opt: Opt, parser: ParlaiParser):
script = cls(opt)
script.parser = parser
return script.run()
@classmethod
def main(cls, *args, **kwargs):
"""
Run the program, possibly with some given args.
You may provide command line args in the form of strings, or
options. For example:
>>> MyScript.main(['--task', 'convai2'])
>>> MyScript.main(task='convai2')
You may not combine both args and kwargs.
"""
assert not (bool(args) and bool(kwargs))
if args:
return cls._run_args(args)
elif kwargs:
return cls._run_kwargs(kwargs)
else:
return cls._run_args(None)
@classmethod
def help(cls, **kwargs):
f = io.StringIO()
parser = cls.setup_args()
parser.prog = cls.__name__
parser.add_extra_args(parser._kwargs_to_str_args(**kwargs))
parser.print_help(f)
return f.getvalue()
class _SupercommandParser(ParlaiParser):
"""
Specialty ParlAI parser used for the supercommand.
Contains some special behavior.
"""
def __init__(self, *args, **kwargs):
from parlai.utils.strings import colorize
logo = ""
logo += colorize(' _', 'red') + "\n"
logo += colorize(' /', 'red') + colorize('"', 'brightblack')
logo += colorize(")", "yellow") + "\n"
logo += colorize(' //', 'red') + colorize(')', 'yellow') + '\n'
logo += colorize(' ==', 'green')
logo += colorize("/", 'blue') + colorize('/', 'red') + colorize("'", 'yellow')
logo += colorize("===", 'green') + " ParlAI\n"
logo += colorize(" /", 'blue')
kwargs['description'] = logo
return super().__init__(*args, **kwargs)
def add_extra_args(self, args):
sa = [a for a in self._actions if isinstance(a, argparse._SubParsersAction)]
assert len(sa) == 1
sa = sa[0]
for _, v in sa.choices.items():
v.add_extra_args(args)
def add_subparsers(self, **kwargs):
return super().add_subparsers(**kwargs)
def _unsuppress_hidden(self):
"""
Restore the help messages of hidden commands.
"""
spa = [a for a in self._actions if isinstance(a, argparse._SubParsersAction)]
assert len(spa) == 1
spa = spa[0]
for choices_action in spa._choices_actions:
dest = choices_action.dest
if choices_action.help == argparse.SUPPRESS:
choices_action.help = spa.choices[dest].description
def print_helpall(self):
self._unsuppress_hidden()
self.print_help()
class _SubcommandParser(ParlaiParser):
"""
ParlaiParser which always sets add_parlai_args and add_model_args to False.
Used in the superscript to initialize just the args for that command.
"""
def __init__(self, **kwargs):
kwargs['add_parlai_args'] = False
kwargs['add_model_args'] = False
if 'description' not in kwargs:
kwargs['description'] = None
return super().__init__(**kwargs)
def parse_known_args(self, args=None, namespace=None, nohelp=False):
if not nohelp:
self.add_extra_args(args)
return super().parse_known_args(args, namespace, nohelp)
def _SuperscriptHelpFormatter(**kwargs):
kwargs['width'] = 100
kwargs['max_help_position'] = 9999
return CustomHelpFormatter(**kwargs)
def superscript_main(args=None):
"""
Superscript is a loader for all the other scripts.
"""
setup_script_registry()
parser = _SupercommandParser(
False, False, formatter_class=_SuperscriptHelpFormatter
)
parser.add_argument(
'--helpall',
action='helpall',
help='show all commands, including advanced ones.',
)
parser.set_defaults(super_command=None)
subparsers = parser.add_subparsers(
parser_class=_SubcommandParser, title="Commands", metavar="COMMAND"
)
hparser = subparsers.add_parser(
'help',
aliases=['h'],
help=argparse.SUPPRESS,
description="List the main commands",
)
hparser.set_defaults(super_command='help')
hparser = subparsers.add_parser(
'helpall',
help=argparse.SUPPRESS,
description="List all commands, including advanced ones.",
)
hparser.set_defaults(super_command='helpall')
# build the supercommand
for script_name, registration in SCRIPT_REGISTRY.items():
logging.verbose(f"Discovered command {script_name}")
script_parser = registration.klass.setup_args()
if script_parser is None:
# user didn't bother defining command line args. let's just fill
# in for them
script_parser = ParlaiParser(False, False)
help_ = argparse.SUPPRESS if registration.hidden else script_parser.description
subparser = subparsers.add_parser(
script_name,
aliases=registration.aliases,
help=help_,
description=script_parser.description,
formatter_class=CustomHelpFormatter,
)
subparser.set_defaults(
# carries the name of the full command so we know what to execute
super_command=script_name,
# used in ParlAI parser to find CLI options set by user
_subparser=subparser,
)
subparser.set_defaults(**script_parser._defaults)
for action in script_parser._actions:
subparser._add_action(action)
for action_group in script_parser._action_groups:
subparser._action_groups.append(action_group)
try:
import argcomplete
argcomplete.autocomplete(parser)
except ModuleNotFoundError:
pass
opt = parser.parse_args(args)
cmd = opt.pop('super_command')
if cmd == 'helpall':
parser.print_helpall()
elif cmd == 'help' or cmd is None:
parser.print_help()
elif cmd is not None:
SCRIPT_REGISTRY[cmd].klass._run_from_parser_and_opt(opt, parser)