comparison planemo/lib/python3.7/site-packages/galaxy/util/commands.py @ 1:56ad4e20f292 draft

"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author guerler
date Fri, 31 Jul 2020 00:32:28 -0400
parents
children
comparison
equal deleted inserted replaced
0:d30785e31577 1:56ad4e20f292
1 """Generic I/O and shell processing code used by Galaxy tool dependencies."""
2 import logging
3 import os
4 import subprocess
5 import sys as _sys
6
7 import six
8 from six.moves import shlex_quote
9
10 from galaxy.util import (
11 unicodify,
12 which
13 )
14
15 log = logging.getLogger(__name__)
16
17 STDOUT_INDICATOR = "-"
18
19
20 def redirecting_io(sys=_sys):
21 """Predicate to determine if we are redicting stdout in process."""
22 assert sys is not None
23 try:
24 # Need to explicitly call fileno() because sys.stdout could be a
25 # io.StringIO object, which has a fileno() method but only raises an
26 # io.UnsupportedOperation exception
27 sys.stdout.fileno()
28 except Exception:
29 return True
30 else:
31 return False
32
33
34 def redirect_aware_commmunicate(p, sys=_sys):
35 """Variant of process.communicate that works with in process I/O redirection."""
36 assert sys is not None
37 out, err = p.communicate()
38 if redirecting_io(sys=sys):
39 if out:
40 # We don't unicodify in Python2 because sys.stdout may be a
41 # cStringIO.StringIO object, which does not accept Unicode strings
42 if not six.PY2:
43 out = unicodify(out)
44 sys.stdout.write(out)
45 out = None
46 if err:
47 if not six.PY2:
48 err = unicodify(err)
49 sys.stderr.write(err)
50 err = None
51 return out, err
52
53
54 def shell(cmds, env=None, **kwds):
55 """Run shell commands with `shell_process` and wait."""
56 sys = kwds.get("sys", _sys)
57 assert sys is not None
58 p = shell_process(cmds, env, **kwds)
59 if redirecting_io(sys=sys):
60 redirect_aware_commmunicate(p, sys=sys)
61 exit = p.returncode
62 return exit
63 else:
64 return p.wait()
65
66
67 def shell_process(cmds, env=None, **kwds):
68 """A high-level method wrapping subprocess.Popen.
69
70 Handles details such as environment extension and in process I/O
71 redirection.
72 """
73 sys = kwds.get("sys", _sys)
74 popen_kwds = dict()
75 if isinstance(cmds, six.string_types):
76 log.warning("Passing program arguments as a string may be a security hazard if combined with untrusted input")
77 popen_kwds['shell'] = True
78 if kwds.get("stdout", None) is None and redirecting_io(sys=sys):
79 popen_kwds["stdout"] = subprocess.PIPE
80 if kwds.get("stderr", None) is None and redirecting_io(sys=sys):
81 popen_kwds["stderr"] = subprocess.PIPE
82
83 popen_kwds.update(**kwds)
84 if env:
85 new_env = os.environ.copy()
86 new_env.update(env)
87 popen_kwds["env"] = new_env
88 p = subprocess.Popen(cmds, **popen_kwds)
89 return p
90
91
92 def execute(cmds, input=None):
93 """Execute commands and throw an exception on a non-zero exit.
94 if input is not None then the string is sent to the process' stdin.
95
96 Return the standard output if the commands are successful
97 """
98 return _wait(cmds, input=input, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
99
100
101 def argv_to_str(command_argv, quote=True):
102 """Convert an argv command list to a string for shell subprocess.
103
104 If None appears in the command list it is simply excluded.
105
106 Arguments are quoted with shlex_quote. That said, this method is not meant to be
107 used in security critical paths of code and should not be used to sanitize
108 code.
109 """
110 map_func = shlex_quote if quote else lambda x: x
111 return " ".join(map_func(c) for c in command_argv if c is not None)
112
113
114 def _wait(cmds, input=None, **popen_kwds):
115 p = subprocess.Popen(cmds, **popen_kwds)
116 stdout, stderr = p.communicate(input)
117 stdout, stderr = unicodify(stdout), unicodify(stderr)
118 if p.returncode != 0:
119 raise CommandLineException(argv_to_str(cmds), stdout, stderr, p.returncode)
120 return stdout
121
122
123 def download_command(url, to=STDOUT_INDICATOR):
124 """Build a command line to download a URL.
125
126 By default the URL will be downloaded to standard output but a specific
127 file can be specified with the `to` argument.
128 """
129 if which("wget"):
130 download_cmd = ["wget", "-q"]
131 if to == STDOUT_INDICATOR:
132 download_cmd.extend(["-O", STDOUT_INDICATOR, url])
133 else:
134 download_cmd.extend(["--recursive", "-O", to, url])
135 else:
136 download_cmd = ["curl", "-L", url]
137 if to != STDOUT_INDICATOR:
138 download_cmd.extend(["-o", to])
139 return download_cmd
140
141
142 class CommandLineException(Exception):
143 """An exception indicating a non-zero command-line exit."""
144
145 def __init__(self, command, stdout, stderr, returncode):
146 """Construct a CommandLineException from command and standard I/O."""
147 self.command = command
148 self.stdout = stdout
149 self.stderr = stderr
150 self.returncode = returncode
151 self.message = ("Failed to execute command-line %s, stderr was:\n"
152 "-------->>begin stderr<<--------\n"
153 "%s\n"
154 "-------->>end stderr<<--------\n"
155 "-------->>begin stdout<<--------\n"
156 "%s\n"
157 "-------->>end stdout<<--------\n"
158 ) % (command, stderr, stdout)
159
160 def __str__(self):
161 """Return a verbose error message indicating the command problem."""
162 return self.message
163
164
165 __all__ = (
166 'argv_to_str',
167 'CommandLineException',
168 'download_command',
169 'execute',
170 'redirect_aware_commmunicate',
171 'redirecting_io',
172 'shell',
173 'shell_process',
174 'which',
175 )