1OSRF_PYCOMMON(1) osrf_pycommon OSRF_PYCOMMON(1)
2
3
4
6 osrf_pycommon - osrf_pycommon Documentation
7
8 osrf_pycommon is a python package which contains commonly used Python
9 boilerplate code and patterns. Things like ansi terminal coloring,
10 capturing colored output from programs using subprocess, or even a sim‐
11 ple logging system which provides some nice functionality over the
12 built-in Python logging system.
13
14 The functionality provided here should be generic enough to be reused
15 in arbitrary scenarios and should avoid bringing in dependencies which
16 are not part of the standard Python library. Where possible Windows
17 and Linux/OS X should be supported, and where it cannot it should be
18 gracefully degrading. Code should be pure Python as well as Python 2
19 and Python 3 bilingual.
20
21 Contents:
22
24 This module provides functions and patterns for creating Command Line
25 Interface (CLI) tools.
26
27 Common CLI Functions
28 Commonly used, CLI related functions.
29
30 osrf_pycommon.cli_utils.common.extract_argument_group(args, delimit‐
31 ing_option)
32 Extract a group of arguments from a list of arguments using a
33 delimiter.
34
35 Here is an example:
36
37 >>> extract_argument_group(['foo', '--args', 'bar', '--baz'], '--args')
38 (['foo'], ['bar', '--baz'])
39
40 The group can always be ended using the double hyphen --. In
41 order to pass a double hyphen as arguments, use three hyphens
42 ---. Any set of hyphens encountered after the delimiter, and up
43 to --, which have three or more hyphens and are isolated, will
44 be captured and reduced by one hyphen.
45
46 For example:
47
48 >> extract_argument_group(['foo',
49 '--args', 'bar', '--baz', '---', '--',
50 '--foo-option'], '--args')
51 (['foo', '--foo-option'], ['bar', '--baz', '--'])
52
53 In the result the -- comes from the --- in the input. The
54 --args and the corresponding -- are removed entirely.
55
56 The delimiter and -- terminator combination can also happen mul‐
57 tiple times, in which case the bodies of arguments are combined
58 and returned in the order they appeared.
59
60 For example:
61
62 >> extract_argument_group(['foo',
63 '--args', 'ping', '--',
64 'bar',
65 '--args', 'pong', '--',
66 'baz',
67 '--args', '--'], '--args')
68 (['foo', 'bar', 'baz'], ['ping', 'pong'])
69
70 Note: -- cannot be used as the delimiting_option.
71
72 Parameters
73
74 • args (list) -- list of strings which are ordered argu‐
75 ments.
76
77 • delimiting_option (str) -- option which denotes where
78 to split the args.
79
80 Returns
81 tuple of arguments before and after the delimiter.
82
83 Return type
84 tuple
85
86 Raises ValueError if the delimiting_option is --.
87
88 osrf_pycommon.cli_utils.common.extract_jobs_flags(arguments)
89 Extracts make job flags from a list of other make flags, i.e.
90 -j8 -l8
91
92 The input arguments are given as a string separated by white‐
93 space. Make job flags are matched and removed from the argu‐
94 ments, and the Make job flags and what is left over from the in‐
95 put arguments are returned.
96
97 If no job flags are encountered, then an empty string is re‐
98 turned as the first element of the returned tuple.
99
100 Examples:
101
102 >> extract_jobs_flags('-j8 -l8')
103 ('-j8 -l8', '')
104 >> extract_jobs_flags('-j8 ')
105 ('-j8', ' ')
106 >> extract_jobs_flags('target -j8 -l8 --some-option')
107 ('-j8 -l8', 'target --some-option')
108 >> extract_jobs_flags('target --some-option')
109 ('', 'target --some-option')
110
111 Parameters
112 arguments (str) -- string of space separated arguments
113 which may or may not contain make job flags
114
115 Returns
116 tuple of make jobs flags as a space separated string and
117 leftover arguments as a space separated string
118
119 Return type
120 tuple
121
122 The Verb Pattern
123 The verb pattern is a pattern where a single command aggregates multi‐
124 ple related commands by taking a required positional argument which is
125 the "verb" for the action you want to perform. For example, catkin
126 build is an example of a command and verb pair, where catkin is the
127 command and build is the verb. In this example, the catkin command
128 groups "actions" which are related to catkin together using verbs like
129 build which will build a workspace of catkin packages.
130
131 Command Boilerplate
132 This is an example boilerplate of a command which will use verbs:
133
134 from __future__ import print_function
135
136 import argparse
137 import sys
138
139 from osrf_pycommon.cli_utils.verb_pattern import create_subparsers
140 from osrf_pycommon.cli_utils.verb_pattern import list_verbs
141 from osrf_pycommon.cli_utils.verb_pattern import split_arguments_by_verb
142
143 COMMAND_NAME = '<INSERT COMMAND NAME HERE>'
144
145 VERBS_ENTRY_POINT = '{0}.verbs'.format(COMMAND_NAME)
146
147
148 def main(sysargs=None):
149 # Assign sysargs if not set
150 sysargs = sys.argv[1:] if sysargs is None else sysargs
151
152 # Create a top level parser
153 parser = argparse.ArgumentParser(
154 description="{0} command".format(COMMAND_NAME)
155 )
156
157 # Generate a list of verbs available
158 verbs = list_verbs(VERBS_ENTRY_POINT)
159
160 # Create the subparsers for each verb and collect the arg preprocessors
161 argument_preprocessors, verb_subparsers = create_subparsers(
162 parser,
163 COMMAND_NAME,
164 verbs,
165 VERBS_ENTRY_POINT,
166 sysargs,
167 )
168
169 # Determine the verb, splitting arguments into pre and post verb
170 verb, pre_verb_args, post_verb_args = split_arguments_by_verb(sysargs)
171
172 # Short circuit -h and --help
173 if '-h' in pre_verb_args or '--help' in pre_verb_args:
174 parser.print_help()
175 sys.exit(0)
176
177 # Error on no verb provided
178 if verb is None:
179 print(parser.format_usage())
180 sys.exit("Error: No verb provided.")
181 # Error on unknown verb provided
182 if verb not in verbs:
183 print(parser.format_usage())
184 sys.exit("Error: Unknown verb '{0}' provided.".format(verb))
185
186 # Short circuit -h and --help for verbs
187 if '-h' in post_verb_args or '--help' in post_verb_args:
188 verb_subparsers[verb].print_help()
189 sys.exit(0)
190
191 # First allow the verb's argument preprocessor to strip any args
192 # and return any "extra" information it wants as a dict
193 processed_post_verb_args, extras = \
194 argument_preprocessors[verb](post_verb_args)
195 # Then allow argparse to process the left over post-verb arguments along
196 # with the pre-verb arguments and the verb itself
197 args = parser.parse_args(pre_verb_args + [verb] + processed_post_verb_args)
198 # Extend the argparse result with the extras from the preprocessor
199 for key, value in extras.items():
200 setattr(args, key, value)
201
202 # Finally call the subparser's main function with the processed args
203 # and the extras which the preprocessor may have returned
204 sys.exit(args.main(args) or 0)
205
206 This function is mostly boilerplate in that it will likely not change
207 much between commands of different types, but it would also be less
208 transparent to have this function created for you. If you are using
209 this boilerplate to implement your command, then you should be careful
210 to update COMMAND_NAME to reflect your command's name.
211
212 This line defines the entry_point group for your command's verbs:
213
214 VERBS_ENTRY_POINT = '{0}.verbs'.format(COMMAND_NAME)
215
216 In the case that your command is called foo then this would become
217 foo.verbs. This name is important because it is how verbs for this
218 command can be provided by your Python package or others. For example,
219 each verb for your command foo will need entry in the setup.py of its
220 containing package, like this:
221
222 setup(
223 ...
224 entry_points={
225 ...
226 'foo.verbs': [
227 'bar = foo.verbs.bar:entry_point_data',
228 ],
229 }
230 )
231
232 You can see here that you are defining bar to be a an entry_point of
233 type foo.verbs which in turn points to a module and reference
234 foo.verbs.bar and entry_point_data. At run time this verb pattern will
235 let your command lookup all things defined as foo.verbs and load up the
236 reference to which they point.
237
238 Adding Verbs
239 In order to add a verb to your command, a few things must happen.
240
241 First you must have an entry in the setup.py as described above. This
242 allows the command to find the entry_point for your verb at run time.
243 The entry_point for these verbs should point to a dictionary which de‐
244 scribes the verb being added.
245
246 This is an example of an entry_point_data dictionary for a verb:
247
248 entry_point_data = dict(
249 verb='build',
250 description='Builds a workspace of packages',
251 # Called for execution, given parsed arguments object
252 main=main,
253 # Called first to setup argparse, given argparse parser
254 prepare_arguments=prepare_arguments,
255 # Called after prepare_arguments, but before argparse.parse_args
256 argument_preprocessor=argument_preprocessor,
257 )
258
259 As you can see this dictionary describes the verb and gives references
260 to functions which allow the command to describe the verb, hook into
261 argparse parameter creation for the verb, and to execute the verb. The
262 verb, description, main, and prepare_arguments keys of the dictionary
263 are required, but the argument_preprocessor key is optional.
264
265 • verb: This is the name of the verb, and is how the command knows
266 which verb implementation to match to a verb on the command line.
267
268 • description: This is used by the argument parsing to describe the
269 verb in --help.
270
271 • prepare_arguments: This function gets called to allow the verb to
272 setup it's own argparse options. This function should always take one
273 parameter which is the argparse.ArgumentParser for this verb, to
274 which arguments can be added. It can optionally take a second parame‐
275 ter which are the current command line arguments. This is not always
276 needed, but can be useful in some cases. This function should always
277 return the parser.
278
279 • argument_preprocessor: This function is optional, but allows your
280 verb an opportunity to process the raw arguments before they are
281 passed to argparse's parse_args function. This can be useful when
282 argparse is not capable of processing the options correctly.
283
284 • main: This is the implementation of the verb, it gets called last and
285 is passed the parsed arguments. The return type of this function is
286 used for sys.exit, a return type of None is interpreted as 0.
287
288 Here is an invented example of main, prepare_arguments, and argu‐
289 ment_preprocessor:
290
291 def prepare_arguments(parser):
292 parser.add_argument('--some-argument', action='store_true', default=False)
293 return parser
294
295 def argument_preprocessor(args):
296 extras = {}
297
298 if '-strange-argument' in args:
299 args.remove('-strange-argument')
300 extras['strange_argument'] = True
301
302 return args, extras
303
304 def main(options):
305 print('--some-argument:', options.some_argument)
306 print('-strange-argument:', options.strange_argument)
307 if options.strange_argument:
308 return 1
309 return 0
310
311 The above example is simply to illustrate the signature of these func‐
312 tions and how they might be used.
313
314 Verb Pattern API
315 API for implementing commands and verbs which used the verb pattern.
316
317 osrf_pycommon.cli_utils.verb_pattern.call_prepare_arguments(func,
318 parser, sysargs=None)
319 Call a prepare_arguments function with the correct number of pa‐
320 rameters.
321
322 The prepare_arguments function of a verb can either take one pa‐
323 rameter, parser, or two parameters parser and args, where args
324 are the current arguments being processed.
325
326 Parameters
327
328 • func (Callable) -- Callable prepare_arguments function.
329
330 • parser (argparse.ArgumentParser) -- parser which is al‐
331 ways passed to the function
332
333 • sysargs (list) -- arguments to optionally pass to the
334 function, if needed
335
336 Returns
337 return value of function or the parser if the function
338 returns None.
339
340 Return type
341 argparse.ArgumentParser
342
343 Raises ValueError if a function with the wrong number of parame‐
344 ters is given
345
346 osrf_pycommon.cli_utils.verb_pattern.create_subparsers(parser,
347 cmd_name, verbs, group, sysargs, title=None)
348 Creates argparse subparsers for each verb which can be discov‐
349 ered.
350
351 Using the verbs parameter, the available verbs are iterated
352 through. For each verb a subparser is created for it using the
353 parser parameter. The cmd_name is used to fill the title and
354 description of the add_subparsers function call. The group pa‐
355 rameter is used with each verb to load the verb's description,
356 prepare_arguments function, and the verb's argument_preproces‐
357 sors if available. Each verb's prepare_arguments function is
358 called, allowing them to add arguments. Finally a list of argu‐
359 ment_preprocessors functions and verb subparsers are returned,
360 one for each verb.
361
362 Parameters
363
364 • parser (argparse.ArgumentParser) -- parser for this
365 command
366
367 • cmd_name (str) -- name of the command to which the
368 verbs are being added
369
370 • verbs (list) -- list of verbs (by name as a string)
371
372 • group (str) -- name of the entry_point group for the
373 verbs
374
375 • sysargs (list) -- list of system arguments
376
377 • title (str) -- optional custom title for the command
378
379 Returns
380 tuple of argument_preprocessors and verb subparsers
381
382 Return type
383 tuple
384
385 osrf_pycommon.cli_utils.verb_pattern.default_argument_preproces‐
386 sor(args)
387 Return unmodified args and an empty dict for extras
388
389 osrf_pycommon.cli_utils.verb_pattern.list_verbs(group)
390 List verbs available for a given entry_point group.
391
392 Parameters
393 group (str) -- entry_point group name for the verbs to
394 list
395
396 Returns
397 list of verb names for the given entry_point group
398
399 Return type
400 list of str
401
402 osrf_pycommon.cli_utils.verb_pattern.load_verb_description(verb_name,
403 group)
404 Load description of a verb in a given group by name.
405
406 Parameters
407
408 • verb_name (str) -- name of the verb to load, as a
409 string
410
411 • group (str) -- entry_point group name which the verb is
412 in
413
414 Returns
415 verb description
416
417 Return type
418 dict
419
420 osrf_pycommon.cli_utils.verb_pattern.split_arguments_by_verb(arguments)
421 Split arguments by verb.
422
423 Given a list of arguments (list of strings), the verb, the pre
424 verb arguments, and the post verb arguments are returned.
425
426 For example:
427
428 >>> args = ['--command-arg1', 'verb', '--verb-arg1', '--verb-arg2']
429 >>> split_arguments_by_verb(args)
430 ('verb', ['--command-arg1'], ['--verb-arg1', '--verb-arg2'])
431
432 Parameters
433 arguments (list) -- list of system arguments
434
435 Returns
436 the verb (str), pre verb args (list), and post verb args
437 (list)
438
439 Return type
440 tuple
441
443 This module provides functions for doing process management.
444
445 These are the main sections of this module:
446
447 • Asynchronous Process Utilities
448
449 • Synchronous Process Utilities
450
451 • Utility Functions
452
453 Asynchronous Process Utilities
454 There is a function and class which can be used together with your cus‐
455 tom Tollius or asyncio run loop.
456
457 The osrf_pycommon.process_utils.async_execute_process() function is a
458 coroutine which allows you to run a process and get the output back bit
459 by bit in real-time, either with stdout and stderr separated or com‐
460 bined. This function also allows you to emulate the terminal using a
461 pty simply by toggling a flag in the parameters.
462
463 Along side this coroutine is a Protocol class,
464 osrf_pycommon.process_utils.AsyncSubprocessProtocol, from which you can
465 inherit in order to customize how the yielded output is handled.
466
467 Because this coroutine is built on the trollius/asyncio framework’s
468 subprocess functions, it is portable and should behave the same on all
469 major OS’s. (including on Windows where an IOCP implementation is used)
470
471 async osrf_pycommon.process_utils.async_execute_process(protocol_class,
472 cmd=None, cwd=None, env=None, shell=False, emulate_tty=False,
473 stderr_to_stdout=True)
474 Coroutine to execute a subprocess and yield the output back
475 asynchronously.
476
477 This function is meant to be used with the Python asyncio mod‐
478 ule, which is available via pip with Python 3.3 and built-in to
479 Python 3.4. On Python >= 2.6 you can use the trollius module to
480 get the same functionality, but without using the new yield from
481 syntax.
482
483 Here is an example of how to use this function:
484
485 import asyncio
486 from osrf_pycommon.process_utils import async_execute_process
487 from osrf_pycommon.process_utils import AsyncSubprocessProtocol
488 from osrf_pycommon.process_utils import get_loop
489
490
491 @asyncio.coroutine
492 def setup():
493 transport, protocol = yield from async_execute_process(
494 AsyncSubprocessProtocol, ['ls', '/usr'])
495 returncode = yield from protocol.complete
496 return returncode
497
498 retcode = get_loop().run_until_complete(setup())
499 get_loop().close()
500
501 That same example using trollius would look like this:
502
503 import trollius as asyncio
504 from osrf_pycommon.process_utils import async_execute_process
505 from osrf_pycommon.process_utils import AsyncSubprocessProtocol
506 from osrf_pycommon.process_utils import get_loop
507
508
509 @asyncio.coroutine
510 def setup():
511 transport, protocol = yield asyncio.From(async_execute_process(
512 AsyncSubprocessProtocol, ['ls', '/usr']))
513 returncode = yield asyncio.From(protocol.complete)
514 raise asyncio.Return(returncode)
515
516 retcode = get_loop().run_until_complete(setup())
517 get_loop().close()
518
519 This difference is required because in Python < 3.3 the yield
520 from syntax is not valid.
521
522 In both examples, the first argument is the default
523 AsyncSubprocessProtocol protocol class, which simply prints out‐
524 put from stdout to stdout and output from stderr to stderr.
525
526 If you want to capture and do something with the output or write
527 to the stdin, then you need to subclass from the
528 AsyncSubprocessProtocol class, and override the on_stdout_re‐
529 ceived, on_stderr_received, and on_process_exited functions.
530
531 See the documentation for the AsyncSubprocessProtocol class for
532 more details, but here is an example which uses asyncio from
533 Python 3.4:
534
535 import asyncio
536 from osrf_pycommon.process_utils import async_execute_process
537 from osrf_pycommon.process_utils import AsyncSubprocessProtocol
538 from osrf_pycommon.process_utils import get_loop
539
540
541 class MyProtocol(AsyncSubprocessProtocol):
542 def __init__(self, file_name, **kwargs):
543 self.fh = open(file_name, 'w')
544 AsyncSubprocessProtocol.__init__(self, **kwargs)
545
546 def on_stdout_received(self, data):
547 # Data has line endings intact, but is bytes in Python 3
548 self.fh.write(data.decode('utf-8'))
549
550 def on_stderr_received(self, data):
551 self.fh.write(data.decode('utf-8'))
552
553 def on_process_exited(self, returncode):
554 self.fh.write("Exited with return code: {0}".format(returncode))
555 self.fh.close()
556
557
558 @asyncio.coroutine
559 def log_command_to_file(cmd, file_name):
560
561 def create_protocol(**kwargs):
562 return MyProtocol(file_name, **kwargs)
563
564 transport, protocol = yield from async_execute_process(
565 create_protocol, cmd)
566 returncode = yield from protocol.complete
567 return returncode
568
569 get_loop().run_until_complete(
570 log_command_to_file(['ls', '/'], '/tmp/out.txt'))
571 get_loop().close()
572
573 See the subprocess.Popen class for more details on some of the
574 parameters to this function like cwd, env, and shell.
575
576 See the osrf_pycommon.process_utils.execute_process() function
577 for more details on the emulate_tty parameter.
578
579 Parameters
580
581 • protocol_class (AsyncSubprocessProtocol or a subclass)
582 – Protocol class which handles subprocess callbacks
583
584 • cmd (list) – list of arguments where the executable is
585 the first item
586
587 • cwd (str) – directory in which to run the command
588
589 • env (dict) – a dictionary of environment variable names
590 to values
591
592 • shell (bool) – if True, the cmd variable is interpreted
593 by a the shell
594
595 • emulate_tty (bool) – if True, pty’s are passed to the
596 subprocess for stdout and stderr, see
597 osrf_pycommon.process_utils.execute_process().
598
599 • stderr_to_stdout (bool) – if True, stderr is directed
600 to stdout, so they are not captured separately.
601
602 class osrf_pycommon.process_utils.AsyncSubprocessProtocol(stdin=None,
603 stdout=None, stderr=None)
604 Protocol to subclass to get events from async_execute_process().
605
606 When subclassing this Protocol class, you should override these
607 functions:
608
609 def on_stdout_received(self, data):
610 # ...
611
612 def on_stderr_received(self, data):
613 # ...
614
615 def on_process_exited(self, returncode):
616 # ...
617
618 By default these functions just print the data received from
619 stdout and stderr and does nothing when the process exits.
620
621 Data received by the on_stdout_received and on_stderr_received
622 functions is always in bytes (str in Python2 and bytes in
623 Python3). Therefore, it may be necessary to call .decode() on
624 the data before printing to the screen.
625
626 Additionally, the data received will not be stripped of new
627 lines, so take that into consideration when printing the result.
628
629 You can also override these less commonly used functions:
630
631 def on_stdout_open(self):
632 # ...
633
634 def on_stdout_close(self, exc):
635 # ...
636
637 def on_stderr_open(self):
638 # ...
639
640 def on_stderr_close(self, exc):
641 # ...
642
643 These functions are called when stdout/stderr are opened and
644 closed, and can be useful when using pty’s for example. The exc
645 parameter of the *_close functions is None unless there was an
646 exception.
647
648 In addition to the overridable functions this class has a few
649 useful public attributes. The stdin attribute is a reference to
650 the PipeProto which follows the asyncio.WriteTransport inter‐
651 face. The stdout and stderr attributes also reference their
652 PipeProto. The complete attribute is a asyncio.Future which is
653 set to complete when the process exits and its result is the re‐
654 turn code.
655
656 The complete attribute can be used like this:
657
658 import asyncio
659 from osrf_pycommon.process_utils import async_execute_process
660 from osrf_pycommon.process_utils import AsyncSubprocessProtocol
661 from osrf_pycommon.process_utils import get_loop
662
663
664 @asyncio.coroutine
665 def setup():
666 transport, protocol = yield from async_execute_process(
667 AsyncSubprocessProtocol, ['ls', '-G', '/usr'])
668 retcode = yield from protocol.complete
669 print("Exited with", retcode)
670
671 # This will block until the protocol.complete Future is done.
672 get_loop().run_until_complete(setup())
673 get_loop().close()
674
675 connection_made(transport)
676 Called when a connection is made.
677
678 The argument is the transport representing the pipe con‐
679 nection. To receive data, wait for data_received()
680 calls. When the connection is closed, connection_lost()
681 is called.
682
683 pipe_data_received(fd, data)
684 Called when the subprocess writes data into stdout/stderr
685 pipe.
686
687 fd is int file descriptor. data is bytes object.
688
689 process_exited()
690 Called when subprocess has exited.
691
692 In addtion to these functions, there is a utility function for getting
693 the correct asyncio event loop:
694
695 osrf_pycommon.process_utils.get_loop()
696 This function will return the proper event loop for the subpro‐
697 cess async calls.
698
699 On Unix this just returns asyncio.get_event_loop(), but on Win‐
700 dows it will set and return a asyncio.ProactorEventLoop instead.
701
702 Treatment of File Descriptors
703 Unlike subprocess.Popen, all of the process_utils functions behave the
704 same way on Python versions 2.7 through 3.4, and they do not close in‐
705 heritable <https://docs.python.org/3.4/library/os.html#fd-inheritance>.
706 file descriptors before starting subprocesses. This is equivalent to
707 passing close_fds=False to subprocess.Popen on all Python versions.
708
709 In Python 3.2, the subprocess.Popen default for the close_fds option
710 changed from False to True so that file descriptors opened by the par‐
711 ent process were closed before spawning the child process. In Python
712 3.4, PEP 0446 additionally made it so even when close_fds=False file
713 descriptors which are non-inheritable are still closed before spawning
714 the subprocess.
715
716 If you want to be able to pass file descriptors to subprocesses in
717 Python 3.4 or higher, you will need to make sure they are inheritable
718 <https://docs.python.org/3.4/library/os.html#fd-inheritance>.
719
720 Synchronous Process Utilities
721 For synchronous execution and output capture of subprocess, there are
722 two functions:
723
724 • osrf_pycommon.process_utils.execute_process()
725
726 • osrf_pycommon.process_utils.execute_process_split()
727
728 These functions are not yet using the trollius/asyncio framework as a
729 back-end and therefore on Windows will not stream the data from the
730 subprocess as it does on Unix machines. Instead data will not be
731 yielded until the subprocess is finished and all output is buffered
732 (the normal warnings about long running programs with lots of output
733 apply).
734
735 The streaming of output does not work on Windows because on Windows the
736 select.select() method only works on sockets and not file-like objects
737 which are used with subprocess pipes. asyncio implements Windows sub‐
738 process support by implementing a Proactor event loop based on Window’s
739 IOCP API. One future option will be to implement this synchronous
740 style method using IOCP in this module, but another option is to just
741 make synchronous the asynchronous calls, but there are issues with that
742 as well. In the mean time, if you need streaming of output in both
743 Windows and Unix, use the asynchronous calls.
744
745 osrf_pycommon.process_utils.execute_process(cmd, cwd=None, env=None,
746 shell=False, emulate_tty=False)
747 Executes a command with arguments and returns output line by
748 line.
749
750 All arguments, except emulate_tty, are passed directly to sub‐
751 process.Popen.
752
753 execute_process returns a generator which yields the output,
754 line by line, until the subprocess finishes at which point the
755 return code is yielded.
756
757 This is an example of how this function should be used:
758
759 from __future__ import print_function
760 from osrf_pycommon.process_utils import execute_process
761
762 cmd = ['ls', '-G']
763 for line in execute_process(cmd, cwd='/usr'):
764 if isinstance(line, int):
765 # This is a return code, the command has exited
766 print("'{0}' exited with: {1}".format(' '.join(cmd), line))
767 continue # break would also be appropriate here
768 # In Python 3, it will be a bytes array which needs to be decoded
769 if not isinstance(line, str):
770 line = line.decode('utf-8')
771 # Then print it to the screen
772 print(line, end='')
773
774 stdout and stderr are always captured together and returned line
775 by line through the returned generator. New line characters are
776 preserved in the output, so if re-printing the data take care to
777 use end='' or first rstrip the output lines.
778
779 When emulate_tty is used on Unix systems, commands will identify
780 that they are on a tty and should output color to the screen as
781 if you were running it on the terminal, and therefore there
782 should not be any need to pass arguments like -c color.ui=always
783 to commands like git. Additionally, programs might also behave
784 differently in when emulate_tty is being used, for example,
785 Python will default to unbuffered output when it detects a tty.
786
787 emulate_tty works by using psuedo-terminals on Unix machines,
788 and so if you are running this command many times in parallel
789 (like hundreds of times) then you may get one of a few different
790 OSError’s. For example, “OSError: [Errno 24] Too many open
791 files: ‘/dev/ttyp0’” or “OSError: out of pty devices”. You
792 should also be aware that you share pty devices with the rest of
793 the system, so even if you are not using a lot, it is possible
794 to get this error. You can catch this error before getting data
795 from the generator, so when using emulate_tty you might want to
796 do something like this:
797
798 from __future__ import print_function
799 from osrf_pycommon.process_utils import execute_process
800
801 cmd = ['ls', '-G', '/usr']
802 try:
803 output = execute_process(cmd, emulate_tty=True)
804 except OSError:
805 output = execute_process(cmd, emulate_tty=False)
806 for line in output:
807 if isinstance(line, int):
808 print("'{0}' exited with: {1}".format(' '.join(cmd), line))
809 continue
810 # In Python 3, it will be a bytes array which needs to be decoded
811 if not isinstance(line, str):
812 line = line.decode('utf-8')
813 print(line, end='')
814
815 This way if a pty cannot be opened in order to emulate the tty
816 then you can try again without emulation, and any other OSError
817 should raise again with emulate_tty set to False. Obviously,
818 you only want to do this if emulating the tty is non-critical to
819 your processing, like when you are using it to capture color.
820
821 Any color information that the command outputs as ANSI escape
822 sequences is captured by this command. That way you can print
823 the output to the screen and preserve the color formatting.
824
825 If you do not want color to be in the output, then try setting
826 emulate_tty to False, but that does not guarantee that there is
827 no color in the output, instead it only will cause called pro‐
828 cesses to identify that they are not being run in a terminal.
829 Most well behaved programs will not output color if they detect
830 that they are not being executed in a terminal, but you
831 shouldn’t rely on that.
832
833 If you want to ensure there is no color in the output from an
834 executed process, then use this function:
835
836 osrf_pycommon.terminal_color.remove_ansi_escape_sequences()
837
838 Exceptions can be raised by functions called by the implementa‐
839 tion, for example, subprocess.Popen can raise an OSError when
840 the given command is not found. If you want to check for the
841 existence of an executable on the path, see: which(). However,
842 this function itself does not raise any special exceptions.
843
844 Parameters
845
846 • cmd (list) – list of strings with the first item being
847 a command and subsequent items being any arguments to
848 that command; passed directly to subprocess.Popen.
849
850 • cwd (str) – path in which to run the command, defaults
851 to None which means os.getcwd() is used; passed di‐
852 rectly to subprocess.Popen.
853
854 • env (dict) – environment dictionary to use for execut‐
855 ing the command, default is None which uses the os.env‐
856 iron environment; passed directly to subprocess.Popen.
857
858 • shell (bool) – If True the system shell is used to
859 evaluate the command, default is False; passed directly
860 to subprocess.Popen.
861
862 • emulate_tty (bool) – If True attempts to use a pty to
863 convince subprocess’s that they are being run in a ter‐
864 minal. Typically this is useful for capturing colorized
865 output from commands. This does not work on Windows (no
866 pty’s), so it is considered False even when True. De‐
867 faults to False.
868
869 Returns
870 a generator which yields output from the command line by
871 line
872
873 Return type
874 generator which yields strings
875
876 Availability: Unix (streaming), Windows (blocking)
877
878 osrf_pycommon.process_utils.execute_process_split(cmd, cwd=None,
879 env=None, shell=False, emulate_tty=False)
880 execute_process(), except stderr is returned separately.
881
882 Instead of yielding output line by line until yielding a return
883 code, this function always a triplet of stdout, stderr, and re‐
884 turn code. Each time only one of the three will not be None.
885 Once you receive a non-None return code (type will be int) there
886 will be no more stdout or stderr. Therefore you can use the
887 command like this:
888
889 from __future__ import print_function
890 import sys
891 from osrf_pycommon.process_utils import execute_process_split
892
893 cmd = ['time', 'ls', '-G']
894 for out, err, ret in execute_process_split(cmd, cwd='/usr'):
895 # In Python 3, it will be a bytes array which needs to be decoded
896 out = out.decode('utf-8') if out is not None else None
897 err = err.decode('utf-8') if err is not None else None
898 if ret is not None:
899 # This is a return code, the command has exited
900 print("'{0}' exited with: {1}".format(' '.join(cmd), ret))
901 break
902 if out is not None:
903 print(out, end='')
904 if err is not None:
905 print(err, end='', file=sys.stderr)
906
907 When using this, it is possible that the stdout and stderr data
908 can be returned in a different order than what would happen on
909 the terminal. This is due to the fact that the subprocess is
910 given different buffers for stdout and stderr and so there is a
911 race condition on the subprocess writing to the different buf‐
912 fers and this command reading the buffers. This can be avoided
913 in most scenarios by using emulate_tty, because of the use of
914 pty’s, though the ordering can still not be guaranteed and the
915 number of pty’s is finite as explained in the documentation for
916 execute_process(). For situations where output ordering between
917 stdout and stderr are critical, they should not be returned sep‐
918 arately and instead should share one buffer, and so
919 execute_process() should be used.
920
921 For all other parameters and documentation see:
922 execute_process()
923
924 Availability: Unix (streaming), Windows (blocking)
925
926 Utility Functions
927 Currently there is only one utility function, a Python implementation
928 of the which shell command.
929
930 osrf_pycommon.process_utils.which(cmd, mode=1, path=None, **kwargs)
931 Given a command, mode, and a PATH string, return the path which
932 conforms to the given mode on the PATH, or None if there is no
933 such file.
934
935 mode defaults to os.F_OK | os.X_OK. path defaults to the result
936 of os.environ.get("PATH"), or can be overridden with a custom
937 search path.
938
939 Backported from shutil.which() (‐
940 https://docs.python.org/3.3/library/shutil.html#shutil.which),
941 available in Python 3.3.
942
944 This module provides tools for colorizing terminal output.
945
946 This module defines the ansi escape sequences used for colorizing the
947 output from terminal programs in Linux. You can access the ansi escape
948 sequences using the ansi() function:
949
950 >>> from osrf_pycommon.terminal_color import ansi
951 >>> print(["This is ", ansi('red'), "red", ansi('reset'), "."])
952 ['This is ', '\x1b[31m', 'red', '\x1b[0m', '.']
953
954 You can also use format_color() to do in-line substitution of keys
955 wrapped in @{} markers for their ansi escape sequences:
956
957 >>> from osrf_pycommon.terminal_color import format_color
958 >>> print(format_color("This is @{bf}blue@{reset}.").split())
959 ['This', 'is', '\x1b[34mblue\x1b[0m.']
960
961 This is a list of all of the available substitutions:
962
963 ┌──────────────┬─────────┬──────────┐
964 │Long Form │ Shorter │ Value │
965 ├──────────────┼─────────┼──────────┤
966 │@{blackf} │ @{kf} │ \033[30m │
967 ├──────────────┼─────────┼──────────┤
968 │@{redf} │ @{rf} │ \033[31m │
969 ├──────────────┼─────────┼──────────┤
970 │@{greenf} │ @{gf} │ \033[32m │
971 ├──────────────┼─────────┼──────────┤
972 │@{yellowf} │ @{yf} │ \033[33m │
973 ├──────────────┼─────────┼──────────┤
974 │@{bluef} │ @{bf} │ \033[34m │
975 ├──────────────┼─────────┼──────────┤
976 │@{purplef} │ @{pf} │ \033[35m │
977 ├──────────────┼─────────┼──────────┤
978 │@{cyanf} │ @{cf} │ \033[36m │
979 ├──────────────┼─────────┼──────────┤
980 │@{whitef} │ @{wf} │ \033[37m │
981 ├──────────────┼─────────┼──────────┤
982 │@{blackb} │ @{kb} │ \033[40m │
983 ├──────────────┼─────────┼──────────┤
984 │@{redb} │ @{rb} │ \033[41m │
985 ├──────────────┼─────────┼──────────┤
986 │@{greenb} │ @{gb} │ \033[42m │
987 ├──────────────┼─────────┼──────────┤
988 │@{yellowb} │ @{yb} │ \033[43m │
989 ├──────────────┼─────────┼──────────┤
990 │@{blueb} │ @{bb} │ \033[44m │
991 ├──────────────┼─────────┼──────────┤
992 │@{purpleb} │ @{pb} │ \033[45m │
993 ├──────────────┼─────────┼──────────┤
994 │@{cyanb} │ @{cb} │ \033[46m │
995 ├──────────────┼─────────┼──────────┤
996 │@{whiteb} │ @{wb} │ \033[47m │
997 ├──────────────┼─────────┼──────────┤
998 │@{escape} │ │ \033 │
999 ├──────────────┼─────────┼──────────┤
1000 │@{reset} │ @| │ \033[0m │
1001 ├──────────────┼─────────┼──────────┤
1002 │@{boldon} │ @! │ \033[1m │
1003 ├──────────────┼─────────┼──────────┤
1004 │@{italicson} │ @/ │ \033[3m │
1005 ├──────────────┼─────────┼──────────┤
1006 │@{ulon} │ @_ │ \033[4m │
1007 ├──────────────┼─────────┼──────────┤
1008 │@{invon} │ │ \033[7m │
1009 ├──────────────┼─────────┼──────────┤
1010 │@{boldoff} │ │ \033[22m │
1011 ├──────────────┼─────────┼──────────┤
1012 │@{italicsoff} │ │ \033[23m │
1013 ├──────────────┼─────────┼──────────┤
1014 │@{uloff} │ │ \033[24m │
1015 ├──────────────┼─────────┼──────────┤
1016 │@{invoff} │ │ \033[27m │
1017 └──────────────┴─────────┴──────────┘
1018
1019 These substitution’s values come from the ANSI color escape sequences,
1020 see: http://en.wikipedia.org/wiki/ANSI_escape_code
1021
1022 Also for any of the keys which have a trailing f, you can safely drop
1023 the trailing f and get the same thing.
1024
1025 For example, format_color("@{redf}") and format_color("@{red}") are
1026 functionally equivalent.
1027
1028 Also, many of the substitutions have shorten forms for convenience,
1029 such that @{redf}, @{rf}, @{red}, and @{r} are all the same.
1030
1031 Note that a trailing b is always required when specifying a background.
1032
1033 Some of the most common non-color sequences have {}’less versions.
1034
1035 For example, @{boldon}’s shorter form is @!.
1036
1037 By default, the substitutions (and calls to ansi()) resolve to escape
1038 sequences, but if you call disable_ansi_color_substitution_globally()
1039 then they will resolve to empty strings.
1040
1041 This allows you to always use the substitution strings and disable them
1042 globally when desired.
1043
1044 On Windows the substitutions are always resolved to empty strings as
1045 the ansi escape sequences do not work on Windows. Instead strings an‐
1046 notated with @{} style substitutions or raw \x1b[xxm style ansi escape
1047 sequences must be passed to print_color() in order for colors to be
1048 displayed on windows. Also the print_ansi_color_win32() function can
1049 be used on strings which only contain ansi escape sequences.
1050
1051 NOTE:
1052 There are existing Python modules like colorama which provide ansi
1053 colorization on multiple platforms, so a valid question is: “why
1054 write this module?”. The reason for writing this module is to pro‐
1055 vide the color annotation of strings and functions for removing or
1056 replacing ansi escape sequences which are not provided by modules
1057 like colorama. This module could have depended on colorama for col‐
1058 orization on Windows, but colorama works by replacing the built-in
1059 sys.stdout and sys.stderr, which we did not want and it has extra
1060 functionality that we do not need. So, instead of depending on col‐
1061 orama, the Windows color printing code was used as the inspiration
1062 for the Windows color printing in the windows.py module in this ter‐
1063 minal_color package. The colorama license was placed in the header
1064 of that file and the colorama license is compatible with this pack‐
1065 age’s license.
1066
1067 osrf_pycommon.terminal_color.ansi(key)
1068 Returns the escape sequence for a given ansi color key.
1069
1070 osrf_pycommon.terminal_color.disable_ansi_color_substitution_globally()
1071 Causes format_color() to replace color annotations with empty
1072 strings.
1073
1074 It also affects ansi().
1075
1076 This is not the case by default, so if you want to make all sub‐
1077 stitutions given to either function mentioned above return empty
1078 strings then call this function.
1079
1080 The default behavior can be restored by calling
1081 enable_ansi_color_substitution_globally().
1082
1083 osrf_pycommon.terminal_color.enable_ansi_color_substitution_globally()
1084 Causes format_color() to replace color annotations with ansi
1085 esacpe sequences.
1086
1087 It also affects ansi().
1088
1089 This is the case by default, so there is no need to call this
1090 everytime.
1091
1092 If you have previously caused all substitutions to evaluate to
1093 an empty string by calling
1094 disable_ansi_color_substitution_globally(), then you can restore
1095 the escape sequences for substitutions by calling this function.
1096
1097 osrf_pycommon.terminal_color.format_color(msg)
1098 Replaces color annotations with ansi escape sequences.
1099
1100 See this module’s documentation for the list of available sub‐
1101 stitutions.
1102
1103 If disable_ansi_color_substitution_globally() has been called
1104 then all color annotations will be replaced by empty strings.
1105
1106 Also, on Windows all color annotations will be replaced with
1107 empty strings. If you want colorization on Windows, you must
1108 pass annotated strings to print_color().
1109
1110 Parameters
1111 msg (str) – string message to be colorized
1112
1113 Returns
1114 colorized string
1115
1116 Return type
1117 str
1118
1119 osrf_pycommon.terminal_color.get_ansi_dict()
1120 Returns a copy of the dictionary of keys and ansi escape se‐
1121 quences.
1122
1123 osrf_pycommon.terminal_color.print_ansi_color_win32(*args, **kwargs)
1124 Prints color string containing ansi escape sequences to console
1125 in Windows.
1126
1127 If called on a non-Windows system, a NotImplementedError occurs.
1128
1129 Does not respect disable_ansi_color_substitution_globally().
1130
1131 Does not substitute color annotations like @{r} or @!, the
1132 string must already contain the \033[1m style ansi escape se‐
1133 quences.
1134
1135 Works by splitting each argument up by ansi escape sequence,
1136 printing the text between the sequences, and doing the corre‐
1137 sponding win32 action for each ansi sequence encountered.
1138
1139 osrf_pycommon.terminal_color.print_color(*args, **kwargs)
1140 Colorizes and prints with an implicit ansi reset at the end
1141
1142 Calls format_color() on each positional argument and then sends
1143 all positional and keyword arguments to print.
1144
1145 If the end keyword argument is not present then the default end
1146 value ansi('reset') + '\n' is used and passed to print.
1147
1148 os.linesep is used to determine the actual value for \n.
1149
1150 Therefore, if you use the end keyword argument be sure to in‐
1151 clude an ansi reset escape sequence if necessary.
1152
1153 On Windows the substituted arguments and keyword arguments are
1154 passed to print_ansi_color_win32() instead of just print.
1155
1156 osrf_pycommon.terminal_color.remove_ansi_escape_senquences(string)
1157 Removes any ansi escape sequences found in the given string and
1158 returns it.
1159
1160 osrf_pycommon.terminal_color.remove_ansi_escape_sequences(string)
1161 Removes any ansi escape sequences found in the given string and
1162 returns it.
1163
1164 osrf_pycommon.terminal_color.sanitize(msg)
1165 Sanitizes the given string to prevent format_color() from sub‐
1166 stituting content.
1167
1168 For example, when the string 'Email: {user}@{org}' is passed to
1169 format_color() the @{org} will be incorrectly recognized as a
1170 colorization annotation and it will fail to substitute with a
1171 KeyError: org.
1172
1173 In order to prevent this, you can first “sanitize” the string,
1174 add color annotations, and then pass the whole string to
1175 format_color().
1176
1177 If you give this function the string 'Email: {user}@{org}', then
1178 it will return 'Email: {{user}}@@{{org}}'. Then if you pass that
1179 to format_color() it will return 'Email: {user}@{org}'. In this
1180 way format_color() is the reverse of this function and so it is
1181 safe to call this function on any incoming data if it will even‐
1182 tually be passed to format_color().
1183
1184 In addition to expanding { => {{, } => }}, and @ => @@, this
1185 function will also replace any instances of @!, @/, @_, and @|
1186 with @{atexclamation}, @{atfwdslash}, @{atunderscore}, and @{at‐
1187 bar} respectively. And then there are corresponding keys in the
1188 ansi dict to convert them back.
1189
1190 For example, if you pass the string '|@ Notice @|' to this func‐
1191 tion it will return '|@@ Notice @{atbar}'. And since ansi('at‐
1192 bar') always returns @|, even when
1193 disable_ansi_color_substitution_globally() has been called, the
1194 result of passing that string to format_color() will be '|@ No‐
1195 tice @|' again.
1196
1197 There are two main strategies for constructing strings which use
1198 both the Python str.format() function and the colorization anno‐
1199 tations.
1200
1201 One way is to just build each piece and concatenate the result:
1202
1203 print_color("@{r}", "{error}".format(error=error_str))
1204 # Or using print (remember to include an ansi reset)
1205 print(format_color("@{r}" + "{error}".format(error=error_str) + "@|"))
1206
1207 Another way is to use this function on the format string, con‐
1208 catenate to the annotations, pass the whole string to
1209 format_color(), and then format the whole thing:
1210
1211 print(format_color("@{r}" + sanitize("{error}") + "@|")
1212 .format(error=error_str))
1213
1214 However, the most common use for this function is to sanitize
1215 incoming strings which may have unknown content:
1216
1217 def my_func(user_content):
1218 print_color("@{y}" + sanitize(user_content))
1219
1220 This function is not intended to be used on strings with color
1221 annotations.
1222
1223 Parameters
1224 msg (str) – string message to be sanitized
1225
1226 Returns
1227 sanitized string
1228
1229 Return type
1230 str
1231
1232 osrf_pycommon.terminal_color.split_by_ansi_escape_sequence(string, in‐
1233 clude_delimiters=False)
1234 Splits a string into a list using any ansi escape sequence as a
1235 delimiter.
1236
1237 Parameters
1238
1239 • string (str) – string to be split
1240
1241 • include_delimiters (bool) – If True include matched es‐
1242 cape sequences in the list (default: False)
1243
1244 Returns
1245 list of strings, split from original string by escape se‐
1246 quences
1247
1248 Return type
1249 list
1250
1251 osrf_pycommon.terminal_color.test_colors(file=None)
1252 Prints a color testing block using print_color()
1253
1255 This module has a miscellaneous set of functions for working with ter‐
1256 minals.
1257
1258 You can use the get_terminal_dimensions() to get the width and height
1259 of the terminal as a tuple.
1260
1261 You can also use the is_tty() function to determine if a given object
1262 is a tty.
1263
1264 exception osrf_pycommon.terminal_utils.GetTerminalDimensionsError
1265 Raised when the terminal dimensions cannot be determined.
1266
1267 osrf_pycommon.terminal_utils.get_terminal_dimensions()
1268 Returns the width and height of the terminal.
1269
1270 Returns
1271 width and height in that order as a tuple
1272
1273 Return type
1274 tuple
1275
1276 Raises GetTerminalDimensionsError when the terminal dimensions
1277 cannot be determined
1278
1279 osrf_pycommon.terminal_utils.is_tty(stream)
1280 Returns True if the given stream is a tty, else False
1281
1282 Parameters
1283 stream -- object to be checked for being a tty
1284
1285 Returns
1286 True if the given object is a tty, otherwise False
1287
1288 Return type
1289 bool
1290
1292 Given that you have a copy of the source code, you can install osrf_py‐
1293 common like this:
1294
1295 $ python setup.py install
1296
1297 NOTE:
1298 If you are installing to a system Python you may need to use sudo.
1299
1300 If you do not want to install osrf_pycommon into your system Python, or
1301 you don’t have access to sudo, then you can use a virtualenv.
1302
1304 Because osrf_pycommon uses setuptools you can (and should) use the
1305 develop feature:
1306
1307 $ python setup.py develop
1308
1309 NOTE:
1310 If you are developing against the system Python, you may need sudo.
1311
1312 This will “install” osrf_pycommon to your Python path, but rather than
1313 copying the source files, it will instead place a marker file in the
1314 PYTHONPATH redirecting Python to your source directory. This allows
1315 you to use it as if it were installed but where changes to the source
1316 code take immediate affect.
1317
1318 When you are done with develop mode you can (and should) undo it like
1319 this:
1320
1321 $ python setup.py develop -u
1322
1323 NOTE:
1324 If you are developing against the system Python, you may need sudo.
1325
1326 That will “uninstall” the hooks into the PYTHONPATH which point to your
1327 source directory, but you should be wary that sometimes console scripts
1328 do not get removed from the bin folder.
1329
1331 In order to run the tests you will need to install nosetests, flake8,
1332 and Mock.
1333
1334 Once you have installed those, then run nosetest in the root of the
1335 osrf_pycommon source directory:
1336
1337 $ nosetests
1338
1340 In order to build the docs you will need to first install Sphinx.
1341
1342 You can build the documentation by invoking the Sphinx provided make
1343 target in the docs folder:
1344
1345 $ # In the docs folder
1346 $ make html
1347 $ open _build/html/index.html
1348
1349 Sometimes Sphinx does not pickup on changes to modules in packages
1350 which utilize the __all__ mechanism, so on repeat builds you may need
1351 to clean the docs first:
1352
1353 $ # In the docs folder
1354 $ make clean
1355 $ make html
1356 $ open _build/html/index.html
1357
1359 William Woodall
1360
1362 2021, Open Source Robotics Foundation
1363
1364
1365
1366
13670.0 Feb 04, 2021 OSRF_PYCOMMON(1)