ONE - On-device Neural Engine
Loading...
Searching...
No Matches
argumentparse.py
Go to the documentation of this file.
1#!/usr/bin/env python
2
3# Copyright (c) 2024 Samsung Electronics Co., Ltd. All Rights Reserved
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16"""
17This is for the command schema feature.
18
19_one-cmds_ has lots of tools such as one-import, one-optimize, etc.
20They have their own section in the configuration file and users can
21 give arguments with key-value pairs.
22
23But, backend tools such as one-codegen and one-profile hasn't the same
24 mechanism. Rather, they should pass all the arguments with `command` key
25 because _onecc_ can't know the backends' interface in advance.
26
27The command schema has been introduced for resolving these difficulties.
28If users provide _onecc_ with the command schema that describes the interface
29 of the backend, users can give arguments with key-value paris like other tools.
30
31NOTE. Command schema feature works only when target option is given.
32
33[AS-IS]
34
35# example.cfg
36[backend]
37target=my_target
38
39[one-codegen]
40backend=my_backend
41commnad=--output sample.tvn sample.circle
42
43[TO-BE]
44
45# /usr/share/one/backends/command/my_backend/codegen.py
46from onelib import argumentparse
47from onelib.argumentparse import DriverName, NormalOption, TargetOption
48
49
50def command_schema():
51 parser = argumentparse.ArgumentParser()
52 parser.add_argument("my_backend-compile", action=DriverName)
53 parser.add_argument("--output", action=NormalOption)
54 parser.add_argument("input", action=NormalOption)
55
56 return parser
57
58# /usr/share/one/target/my_target.ini
59TARGET=my_target
60BACKEND=my_backend
61
62# example.cfg
63[one-codegen]
64output=sample.tvn
65input=sample.circle
66
67
68---
69
70Command schema file should define `command_schema` function. And, you can add
71 arguments by calling `add_argument`. You should specify an action according to
72the option category.
73
74[Action List]
75- DriverName: the name of backend driver
76- TargetOption: the target option of the drvier.
77- NormalOption: the option of the driver. Starting with dash('-') implies the option
78 is optional rather than positional one.
79"""
80
81import ntpath
82from types import SimpleNamespace
83from typing import List, Tuple, Union, Type
84import shutil
85
86import onelib.backends as backends
87import onelib.utils as oneutils
88
89
90class Action():
91 pass
92
93
95 pass
96
97
99 pass
100
101
103 pass
104
105
106class Option():
107 pass
108
109
111 pass
112
113
115 pass
116
117
119 _SUPPORTED_ACTION_TYPE = [DriverName, NormalOption, TargetOption]
120
121 def __init__(self):
122 # List[args, action type, data type, option type]
123 self._actions: List[Tuple[Tuple[str], Action, Union[Type[str],
124 Type[bool]]]] = list()
125 self.driver: str = None
126 self.target: str = None
127
128 def print_help(self):
129 backends_list = backends.get_list(self.driver)
130 driver_path = None
131 for cand in backends_list:
132 if ntpath.basename(cand) == self.driver:
133 driver_path = cand
134 if not driver_path:
135 driver_path = shutil.which(self.driver)
136
137 if not driver_path:
138 raise FileNotFoundError(self.driver + ' not found')
139
140 oneutils.run([driver_path, '-h'], err_prefix=self.driver)
141
142 def get_option_names(self, *, flatten=False, without_dash=False):
143 """
144 Get registered option names.
145
146 :param flatten: single option can have multiple names.
147 If it is True, such options are returned after flattened.
148 :param without_dash: optional argument has leading dash on its names.
149 If it is True, option names are returned without such dashes.
150
151 For example, say there are options like these.
152
153 parser.add_argument("--verbose", action=NormalOption, dtype=bool)
154 parser.add_argument("--output", "--output_path", action=NormalOption)
155
156 [EXAMPLES]
157 get_option_names()
158 [[--verbose], [--output, --output_path]]
159 get_option_names(without_dash=True)
160 [[verbose], [output, output_path]]
161 get_option_names(flatten=True)
162 [--verbose, --output, --output_path]
163 get_option_names(flatten=True, without_dash=True)
164 [verbose, output, output_path]
165 """
166 names = []
167 for action in self._actions:
168 names.append(action[0])
169
170 if flatten:
171 names = [name for name_l in names for name in name_l]
172 if without_dash:
173 names = [name.lstrip('-') for name in names]
174
175 return names
176
177 def check_if_valid_option_name(self, *args, **kwargs):
178 existing_options = self.get_option_names(flatten=True, without_dash=True)
179 args_without_dash = [arg.lstrip('-') for arg in args]
180 if any(arg in existing_options for arg in args_without_dash):
181 raise RuntimeError('Duplicate option names')
182 if not 'action' in kwargs:
183 raise RuntimeError('"action" keyword argument is required')
184
185 action = kwargs['action']
186 if not action in self._SUPPORTED_ACTION_TYPE:
187 raise RuntimeError('Invalid action')
188 if not args:
189 raise RuntimeError('Invalid option name')
190 if action == DriverName and len(args) >= 2:
191 raise RuntimeError('onecc doesn\'t support multiple driver name')
192
193 dtype = kwargs.get('dtype', str)
194 if dtype == bool and action != NormalOption:
195 raise RuntimeError('Only normal option can be boolean type')
196 if dtype == bool and not all(a.startswith('-') for a in args):
197 raise RuntimeError('Boolean type option should start with dash("-")')
198
199 def add_argument(self, *args, **kwargs):
200 self.check_if_valid_option_name(*args, **kwargs)
201
202 action = kwargs['action']
203 dtype = kwargs.get('dtype', str)
204 if action == DriverName:
205 assert len(args) == 1
206 self.driver = args[0]
207 else:
208 if all(a.startswith('-') for a in args):
209 otype = Optional
210 elif all(not a.startswith('-') for a in args):
211 otype = Positional
212 else:
213 raise RuntimeError(
214 'Invalid option names. Only either of option type is allowed: positional or optional'
215 )
216 self._actions.append((args, kwargs['action'], dtype, otype))
217
218 def make_cmd(self, cfg_args: SimpleNamespace) -> List:
219 assert self.target, "Target should be set before making commands"
220 assert self.driver, "Driver should be set before making commands"
221 # find driver path
222 driver_name = self.driver
223 driver_list = backends.get_list(driver_name)
224 if not driver_list:
225 driver_list = [shutil.which(driver_name)]
226 if not driver_list:
227 raise FileNotFoundError(f'{driver_name} not found')
228 # use first driver
229 driver_path = driver_list[0]
230 cmd: List = [driver_path]
231 invalid_options = list(cfg_args.__dict__.keys())
232 # traverse the action in order and make commands
233 for action in self._actions:
234 args, act, dtype, otype = action
235 assert act in [NormalOption, TargetOption]
236 if otype == Optional:
237 option_names = []
238 for arg in args:
239 if arg.startswith('--'):
240 option_names.append(arg[len('--'):])
241 elif arg.startswith('-'):
242 option_names.append(arg[len('-'):])
243 elif otype == Positional:
244 option_names = args
245 else:
246 assert False
247
248 given_option = None
249 if act == NormalOption:
250 for option_name in option_names:
251 if oneutils.is_valid_attr(cfg_args, option_name):
252 given_option = option_name
253 break
254 if not given_option:
255 continue
256 if dtype == bool and given_option.lower() == "false":
257 continue
258 if otype == Optional:
259 # use first option
260 cmd += [args[0]]
261 if act == TargetOption:
262 cmd += [self.target]
263 else:
264 assert act == NormalOption
265 if dtype == str:
266 cmd += [getattr(cfg_args, given_option)]
267 invalid_options.remove(given_option)
268
269 if len(invalid_options):
270 print(f'WARNING: there are invalid options {invalid_options}')
271 self.print_help()
272 return cmd
check_if_valid_option_name(self, *args, **kwargs)
add_argument(self, *args, **kwargs)
get_option_names(self, *flatten=False, without_dash=False)
get_list(cmdname)
Definition backends.py:58