Revision 16d7b9ff docs/developers/adding-commands.rst
b/docs/developers/adding-commands.rst | ||
---|---|---|
2 | 2 |
=============== |
3 | 3 |
|
4 | 4 |
Kamaki commands are implemented as python classes, decorated with a special |
5 |
decorator called *command*. This decorator is a method of kamaki.cli that adds |
|
6 |
a new command in a CommandTree structure (kamaki.cli.commant_tree). The later |
|
7 |
is used by interfaces to manage kamaki commands. |
|
5 |
decorator called *command*. This decorator is a method of *kamaki.cli* that |
|
6 |
adds a new command in a *CommandTree* structure. A *CommandTree* (package |
|
7 |
*kamaki.cli.commant_tree*) is a data structure used by kamaki to manage command |
|
8 |
namespaces. |
|
8 | 9 |
|
9 |
In the following, a set of kamaki commands will be implemented:: |
|
10 |
For demonstration purposes, the following set of kamaki commands will be |
|
11 |
implemented in this document:: |
|
10 | 12 |
|
11 | 13 |
mygrp1 list all //show a list |
12 | 14 |
mygrp1 list details [--match=<>] //show list of details |
13 | 15 |
mygrp2 list all [regular expression] [-l] //list all subjects |
14 | 16 |
mygrp2 info <id> [name] //get information for subject with id |
15 | 17 |
|
16 |
There are two command sets to implement, namely mygrp1 and mygrp2. The first
|
|
17 |
will contain two commands, namely list-all and list-details. The second one
|
|
18 |
will also contain two commands, list-all and info. To avoid ambiguities,
|
|
19 |
command names should rather be prefixed with the group they belong to, e.g.
|
|
20 |
mygrp1-list-all and mygrp2-list-all.
|
|
18 |
There are two command groups to implement i.e., *mygrp1* and *mygrp2*,
|
|
19 |
containing two commands each (*list_all*, *list_details* and *list_all*, *info*
|
|
20 |
respectively). To avoid ambiguities, command names are prefixed with the
|
|
21 |
command group they belong to, e.g., *mygrp1_list_all* and *mygrp2_list_all*.
|
|
22 |
The underscore is used to separate command namespaces.
|
|
21 | 23 |
|
22 |
The first command has the simplest possible syntax: no parameters, no runtime
|
|
23 |
arguments. The second accepts an optional runtime argument with a value. The
|
|
24 |
third features an optional argument and an optional runtime flag argument. The
|
|
25 |
last is an example of a command with an obligatory and an optional argument.
|
|
24 |
The first command (*mygrp1_list_all*) has the simplest possible syntax: no
|
|
25 |
parameters, no runtime arguments. The second accepts an optional runtime argument with a value. The third features an optional parameter and an optional
|
|
26 |
runtime flag argument. The last is an example of a command with an obligatory
|
|
27 |
and an optional parameter.
|
|
26 | 28 |
|
27 |
Samples of the expected behavior in one-command mode are following:
|
|
29 |
Examples of the expected behavior in one-command mode:
|
|
28 | 30 |
|
29 | 31 |
.. code-block:: console |
30 | 32 |
|
... | ... | |
35 | 37 |
- - - - |
36 | 38 |
list |
37 | 39 |
$ kamaki mygrp1 list |
38 |
Syntax Error |
|
39 | 40 |
|
40 | 41 |
Options |
41 | 42 |
- - - - |
42 | 43 |
all show a list |
43 | 44 |
details show a list of details |
44 | 45 |
$ kamaki mygrp1 list all |
45 |
... (mygrp1 client method is called) ...
|
|
46 |
... (a mygrp1_list_all instance runs) ...
|
|
46 | 47 |
$ kamaki mygrp2 list all 'Z[.]' -l |
47 |
... (mygrp2 client method is called) ...
|
|
48 |
... (a mygrp2_list_all instance runs) ...
|
|
48 | 49 |
$ |
49 | 50 |
|
50 |
The above example will be used throughout the present guide. |
|
51 |
|
|
52 | 51 |
The CommandTree structure |
53 | 52 |
------------------------- |
54 | 53 |
|
55 |
CommandTree manages a command by its path. Each command is stored in multiple |
|
56 |
nodes on the tree, so that the last term is a leaf and the route from root to |
|
57 |
that leaf represents the command path. For example the commands *file upload*, |
|
58 |
*file list* and *file info* are stored together as shown bellow:: |
|
54 |
CommandTree manages a command by its namespace. Each command is stored in |
|
55 |
a tree path, where each node is a name. A leaf is the end term of a namespace and contains a pointer to the command class to be executed. |
|
56 |
|
|
57 |
Here is an example from the actual kamaki command structure, where the commands |
|
58 |
*file upload*, *file list* and *file info* are represented as shown bellow:: |
|
59 | 59 |
|
60 | 60 |
- file |
61 | 61 |
''''''''|- info |
62 | 62 |
|- list |
63 | 63 |
|- upload |
64 | 64 |
|
65 |
The example used in the present, should result to the creation of two trees::
|
|
65 |
Now, let's load the showcase example on CommandTrees::
|
|
66 | 66 |
|
67 | 67 |
- mygrp1 |
68 | 68 |
''''''''|- list |
... | ... | |
74 | 74 |
'''''''|- all |
75 | 75 |
|- info |
76 | 76 |
|
77 |
Each command group should be stored on a different CommandTree. For that |
|
78 |
reason, command specification modules should contain a list of CommandTree |
|
79 |
objects, named *_commands* |
|
77 |
Each command group should be stored on a different CommandTree. |
|
78 |
|
|
79 |
For that reason, command specification modules should contain a list of CommandTree objects, named *_commands*. This mechanism allows any interface |
|
80 |
application to load the list of commands from the *_commands* array. |
|
80 | 81 |
|
81 |
A command group information (name, description) is provided at CommandTree |
|
82 |
structure initialization: |
|
82 |
The first name of the command path and a description (name, description) are needed to initializeg a CommandTree: |
|
83 | 83 |
|
84 | 84 |
.. code-block:: python |
85 | 85 |
|
... | ... | |
88 | 88 |
|
89 | 89 |
_commands = [_mygrp1_commands, _mygrp2_commands] |
90 | 90 |
|
91 |
|
|
91 | 92 |
The command decorator |
92 | 93 |
--------------------- |
93 | 94 |
|
94 |
The *command* decorator mines all the information necessary to build a command |
|
95 |
specification which is then inserted in a CommanTree instance:: |
|
95 |
All commands are specified by subclasses of *kamaki.cli.commands._command_init* |
|
96 |
These classes are called "command specifications". |
|
97 |
|
|
98 |
The *command* decorator mines all the information needed to build a namespace |
|
99 |
from a command specification:: |
|
96 | 100 |
|
97 | 101 |
class code ---> command() --> updated CommandTree structure |
98 | 102 |
|
99 |
Kamaki interfaces make use of this CommandTree structure. Optimizations are
|
|
103 |
Kamaki interfaces make use of the CommandTree structure. Optimizations are
|
|
100 | 104 |
possible by using special parameters on the command decorator method. |
101 | 105 |
|
102 | 106 |
.. code-block:: python |
... | ... | |
108 | 112 |
|
109 | 113 |
:param prefix: of the commands allowed to be inserted ('' for all) |
110 | 114 |
|
111 |
:param descedants_depth: is the depth of the tree descedants of the |
|
115 |
:param descedants_depth: is the depth of the tree descendants of the
|
|
112 | 116 |
prefix command. |
113 | 117 |
""" |
114 | 118 |
|
115 | 119 |
Creating a new command specification set |
116 | 120 |
---------------------------------------- |
117 | 121 |
|
118 |
A command specification developer should create a new module (python file) with as many classes as the command specifications to be offered. Each class should be decorated with *command*. |
|
122 |
A command specification developer should create a new module (python file) with |
|
123 |
one command specification class per command. Each class should be decorated |
|
124 |
with *command*. |
|
119 | 125 |
|
120 | 126 |
.. code-block:: python |
121 | 127 |
|
... | ... | |
129 | 135 |
... |
130 | 136 |
|
131 | 137 |
A list of CommandTree structures must exist in the module scope, with the name |
132 |
_commands, as shown above. Different CommandTree objects correspond to
|
|
133 |
different command groups.
|
|
138 |
*_commands*. Different CommandTree objects correspond to different command
|
|
139 |
groups. |
|
134 | 140 |
|
135 |
Get command description
|
|
141 |
Set command description
|
|
136 | 142 |
----------------------- |
137 | 143 |
|
138 | 144 |
The description of each command is the first line of the class commend. The |
... | ... | |
143 | 149 |
|
144 | 150 |
... |
145 | 151 |
@command(_mygrp2_commands) |
146 |
class mygrp2_info() |
|
147 |
"""get information for subject with id""" |
|
152 |
class mygrp2_info(): |
|
153 |
"""get information for subject with id |
|
154 |
Anything from this point and bellow constitutes the long description |
|
155 |
Please, mind the indentation, pep8 is not forgiving. |
|
156 |
""" |
|
148 | 157 |
... |
149 | 158 |
|
159 |
Description placeholders |
|
160 |
------------------------ |
|
161 |
|
|
162 |
There is possible to create an empty command, that can act as a description |
|
163 |
placeholder. For example, the *mygrp1_list* namespace does not correspond to an |
|
164 |
executable command, but it can have a helpful description. In that case, create |
|
165 |
a command specification class with a command and no code: |
|
166 |
|
|
167 |
.. code-block:: python |
|
168 |
|
|
169 |
@command(_mygrp1_commands) |
|
170 |
class mygrp1_list(): |
|
171 |
"""List mygrp1 objects. |
|
172 |
There are two versions: short and detailed |
|
173 |
""" |
|
174 |
|
|
175 |
.. warning:: A command specification class with no description is invalid and |
|
176 |
will cause an error. |
|
177 |
|
|
150 | 178 |
Declare run-time argument |
151 | 179 |
------------------------- |
152 | 180 |
|
153 |
The argument mechanism allows the definition of run-time arguments. Some basic |
|
154 |
argument types are defined at the |
|
181 |
A special argument mechanism allows the definition of run-time arguments. This |
|
182 |
mechanism is based on argparse and is designed to simplify argument definitions |
|
183 |
when specifying commands. |
|
184 |
|
|
185 |
Some basic argument types are defined at the |
|
155 | 186 |
`argument module <code.html#module-kamaki.cli.argument>`_, but it is not |
156 |
uncommon to extent these classes in order to achieve specialized type checking
|
|
157 |
and syntax control (e.g. at
|
|
158 |
`pithos cli module <code.html#module-kamaki.cli.commands.pithos>`_).
|
|
187 |
a bad idea to extent these classes in order to achieve specialized type
|
|
188 |
checking and syntax control. Still, in most cases, the argument types of the
|
|
189 |
argument package are enough for most cases.
|
|
159 | 190 |
|
160 |
To declare a run-time argument on a specific command, the object class should
|
|
161 |
initialize a dict called *arguments* , where Argument objects are stored. Each
|
|
162 |
argument object is a possible run-time argument. Syntax checking happens at
|
|
163 |
client level, while the type checking is implemented in the Argument code
|
|
164 |
(thus, many different Argument types might be needed).`
|
|
191 |
To declare a run-time argument on a specific command, the specification class
|
|
192 |
should contain a dict called *arguments* , where Argument objects are stored.
|
|
193 |
Each argument object is a run-time argument. Syntax checking happens at client
|
|
194 |
level, while the type checking is implemented in the Argument code (e.g.,
|
|
195 |
IntArgument checks if the value is an int).
|
|
165 | 196 |
|
166 | 197 |
.. code-block:: python |
167 | 198 |
|
... | ... | |
190 | 221 |
|
191 | 222 |
arguments = dict( |
192 | 223 |
match=ValueArgument( |
193 |
'Filter output to match string', ('-m', --match')) |
|
224 |
'Filter output to match string', ('-m', --match'))
|
|
194 | 225 |
) |
195 | 226 |
|
196 | 227 |
Accessing run-time arguments |
197 | 228 |
---------------------------- |
198 | 229 |
|
199 |
To access run-time arguments, users can use the _command_init interface, which
|
|
200 |
implements __item__ accessors to handle run-time argument values. In specific,
|
|
201 |
an instance of _command_init can use brackets to set or read <argument>.value .
|
|
230 |
To access run-time arguments, users can use the *_command_init* interface,
|
|
231 |
which implements *__item__* accessors to handle run-time argument values. In
|
|
232 |
other words, one may get the value of an argument with *self[<argument>]*.
|
|
202 | 233 |
|
203 | 234 |
.. code-block:: python |
204 | 235 |
|
... | ... | |
222 | 253 |
The main method and command parameters |
223 | 254 |
-------------------------------------- |
224 | 255 |
|
225 |
The command behavior for each command / class is coded in *main*. The |
|
226 |
parameters of *main* method defines the command parameters part of the syntax. |
|
227 |
In specific:: |
|
256 |
The command behavior for each command class is coded in *main*. The |
|
257 |
parameters of *main* method affect the syntax of the command. In specific:: |
|
228 | 258 |
|
229 | 259 |
main(self, param) - obligatory parameter <param> |
230 | 260 |
main(self, param=None) - optional parameter [param] |
... | ... | |
236 | 266 |
main(self, *args) - arbitary number of params [...] |
237 | 267 |
main(self, param1____param2, *args) - <param1:param2> [...] |
238 | 268 |
|
239 |
The information that can be mined by *command* for each individual command is
|
|
240 |
presented in the following:
|
|
269 |
Let's have a look at the command specification class again, and highlight the
|
|
270 |
parts that affect the command syntax:
|
|
241 | 271 |
|
242 | 272 |
.. code-block:: python |
243 | 273 |
:linenos: |
... | ... | |
245 | 275 |
from kamaki.cli.argument import FlagArgument |
246 | 276 |
... |
247 | 277 |
|
248 |
_commands = [_mygrp1_commands, _mygrp2=commands]
|
|
278 |
_commands = [_mygrp1_commands, _mygrp2_commands]
|
|
249 | 279 |
... |
250 | 280 |
|
251 | 281 |
@command(_mygrp2_commands) |
252 | 282 |
class mygrp2_list_all(): |
253 |
"""List all subjects""" |
|
283 |
"""List all subjects |
|
284 |
Refers to the subject accessible by current user |
|
285 |
""" |
|
254 | 286 |
|
255 | 287 |
arguments = dict(FlagArgument('detailed list', '-l')) |
256 | 288 |
|
257 | 289 |
def main(self, reg_exp=None): |
258 | 290 |
... |
259 | 291 |
|
260 |
This will load the following information on the CommandTree: |
|
292 |
The above lines contain the following information: |
|
293 |
|
|
294 |
* Namespace and name (line 8): mygrp2 list all |
|
295 |
* Short (line 9) and long (line 10) description |
|
296 |
* Parameters (line 15): [reg exp] |
|
297 |
* Runtime arguments (line 13): [-l] |
|
298 |
* Runtime arguments help (line 13): detailed list |
|
261 | 299 |
|
262 |
* Syntax (from lines 8,12,19): mygrp list all [reg exp] [-l] |
|
263 |
* Description (form line 9): List all subjects |
|
264 |
* Arguments help (from line 13,14): -l: detailed list |
|
300 |
.. tip:: It is suggested to code the main functionality in a member method |
|
301 |
called *_run*. This allows the separation between syntax and logic. For |
|
302 |
example, an external library may need to call a command without caring |
|
303 |
about its command line behavior. |
|
265 | 304 |
|
266 | 305 |
Letting kamaki know |
267 | 306 |
------------------- |
... | ... | |
270 | 309 |
option. To demonstrate this, let the command specifications coded above be |
271 | 310 |
stored in a file named *grps.py*. |
272 | 311 |
|
273 |
The developer should move file *grps.py* to kamaki/cli/commands, the default |
|
274 |
place for command specifications, although running a command specification from |
|
275 |
a different path is also a kamaki feature. |
|
312 |
The developer should move the file *grps.py* to *kamaki/cli/commands*, the |
|
313 |
default place for command specifications |
|
276 | 314 |
|
277 |
The user has to use a configuration file where the following is added: |
|
315 |
These lines should be contained in the kamaki configuration file for a new |
|
316 |
command specification module to work: |
|
278 | 317 |
:: |
279 | 318 |
|
280 | 319 |
[global] |
... | ... | |
288 | 327 |
$ kamaki config set mygrp1_cli grps |
289 | 328 |
$ kamaki config set mygrp2_cli grps |
290 | 329 |
|
291 |
Command specification modules don't need to live in kamaki/cli/commands, |
|
292 |
although this is suggested for uniformity. If a command module exist in another |
|
293 |
path:: |
|
330 |
.. note:: running a command specification from a different path is supported. |
|
331 |
To achieve this, add a *<group>_cli = </path/to/module>* line in the |
|
332 |
configure file under the *global* section. |
|
333 |
:: |
|
294 | 334 |
|
295 | 335 |
[global] |
296 | 336 |
mygrp_cli = /another/path/grps.py |
... | ... | |
320 | 360 |
|
321 | 361 |
|
322 | 362 |
@command(_mygrp1_commands) |
363 |
class mygrp1_list(_command_init): |
|
364 |
"""List mygrp1 objects. |
|
365 |
There are two versions: short and detailed |
|
366 |
""" |
|
367 |
|
|
368 |
|
|
369 |
@command(_mygrp1_commands) |
|
323 | 370 |
class mygrp1_list_all(_command_init): |
324 | 371 |
"""show a list""" |
325 | 372 |
|
326 |
def main(self):
|
|
373 |
def _run():
|
|
327 | 374 |
... |
328 | 375 |
|
376 |
def main(self): |
|
377 |
self._run() |
|
378 |
|
|
329 | 379 |
|
330 | 380 |
@command(_mygrp1_commands) |
331 | 381 |
class mygrp1_list_details(_command_init): |
... | ... | |
336 | 386 |
'Filter output to match string', ('-m', --match')) |
337 | 387 |
) |
338 | 388 |
|
339 |
def main(self): |
|
340 |
... |
|
389 |
def _run(self): |
|
341 | 390 |
match_value = self['match'] |
342 | 391 |
... |
343 | 392 |
|
393 |
def main(self): |
|
394 |
self._run() |
|
395 |
|
|
396 |
|
|
397 |
#The following will also create a mygrp2_list command with no description |
|
398 |
|
|
344 | 399 |
|
345 | 400 |
@command(_mygrp2_commands) |
346 | 401 |
class mygrp2_list_all(_command_init): |
... | ... | |
350 | 405 |
list=FlagArgument('detailed listing', '-l') |
351 | 406 |
) |
352 | 407 |
|
353 |
def main(self, regular_expression=None): |
|
354 |
... |
|
355 |
detail_flag = self['list'] |
|
408 |
def _run(self, regexp): |
|
356 | 409 |
... |
357 |
if detail_flag:
|
|
410 |
if self['list']:
|
|
358 | 411 |
... |
359 |
... |
|
360 |
if regular_expression: |
|
412 |
else: |
|
361 | 413 |
... |
362 |
... |
|
414 |
|
|
415 |
def main(self, regular_expression=None): |
|
416 |
self._run(regular_expression) |
|
363 | 417 |
|
364 | 418 |
|
365 | 419 |
@command(_mygrp2_commands) |
366 | 420 |
class mygrp2_info(_command_init): |
367 | 421 |
"""get information for subject with id""" |
368 | 422 |
|
369 |
def main(self, id, name=''):
|
|
423 |
def _run(self, grp_id, grp_name):
|
|
370 | 424 |
... |
425 |
|
|
426 |
def main(self, id, name=''): |
|
427 |
self._run(id, name) |
Also available in: Unified diff