Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/planemo/io.py @ 0:26e78fe6e8c4 draft
"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author | shellac |
---|---|
date | Sat, 02 May 2020 07:14:21 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:26e78fe6e8c4 |
---|---|
1 from __future__ import absolute_import | |
2 from __future__ import print_function | |
3 | |
4 import contextlib | |
5 import errno | |
6 import fnmatch | |
7 import os | |
8 import shutil | |
9 import subprocess | |
10 import sys | |
11 import tempfile | |
12 import time | |
13 from sys import platform as _platform | |
14 from xml.sax.saxutils import escape | |
15 | |
16 import click | |
17 from galaxy.tool_util.deps import commands | |
18 from galaxy.tool_util.deps.commands import download_command | |
19 from six import ( | |
20 string_types, | |
21 StringIO | |
22 ) | |
23 | |
24 from .exit_codes import ( | |
25 EXIT_CODE_NO_SUCH_TARGET, | |
26 EXIT_CODE_OK, | |
27 ) | |
28 | |
29 | |
30 IS_OS_X = _platform == "darwin" | |
31 | |
32 | |
33 def args_to_str(args): | |
34 if args is None or isinstance(args, string_types): | |
35 return args | |
36 else: | |
37 return commands.argv_to_str(args) | |
38 | |
39 | |
40 def communicate(cmds, **kwds): | |
41 cmd_string = args_to_str(cmds) | |
42 info(cmd_string) | |
43 p = commands.shell_process(cmds, **kwds) | |
44 if kwds.get("stdout", None) is None and commands.redirecting_io(sys=sys): | |
45 output = commands.redirect_aware_commmunicate(p) | |
46 else: | |
47 output = p.communicate() | |
48 | |
49 if p.returncode != 0: | |
50 template = "Problem executing commands {0} - ({1}, {2})" | |
51 msg = template.format(cmd_string, output[0], output[1]) | |
52 raise RuntimeError(msg) | |
53 return output | |
54 | |
55 | |
56 def shell(cmds, **kwds): | |
57 cmd_string = args_to_str(cmds) | |
58 info(cmd_string) | |
59 return commands.shell(cmds, **kwds) | |
60 | |
61 | |
62 def info(message, *args): | |
63 if args: | |
64 message = message % args | |
65 click.echo(click.style(message, bold=True, fg='green')) | |
66 | |
67 | |
68 def can_write_to_path(path, **kwds): | |
69 if not kwds["force"] and os.path.exists(path): | |
70 error("%s already exists, exiting." % path) | |
71 return False | |
72 return True | |
73 | |
74 | |
75 def error(message, *args): | |
76 if args: | |
77 message = message % args | |
78 click.echo(click.style(message, bold=True, fg='red'), err=True) | |
79 | |
80 | |
81 def warn(message, *args): | |
82 if args: | |
83 message = message % args | |
84 click.echo(click.style(message, fg='red'), err=True) | |
85 | |
86 | |
87 def shell_join(*args): | |
88 """Join potentially empty commands together with '&&'.""" | |
89 return " && ".join(args_to_str(_) for _ in args if _) | |
90 | |
91 | |
92 def write_file(path, content, force=True): | |
93 if os.path.exists(path) and not force: | |
94 return | |
95 | |
96 with open(path, "w") as f: | |
97 f.write(content) | |
98 | |
99 | |
100 def untar_to(url, tar_args=None, path=None, dest_dir=None): | |
101 if tar_args: | |
102 assert not (path and dest_dir) | |
103 if dest_dir: | |
104 if not os.path.exists(dest_dir): | |
105 os.makedirs(dest_dir) | |
106 tar_args[0:0] = ['-C', dest_dir] | |
107 if path: | |
108 tar_args.insert(0, '-O') | |
109 | |
110 download_cmd = download_command(url) | |
111 download_p = commands.shell_process(download_cmd, stdout=subprocess.PIPE) | |
112 untar_cmd = ['tar'] + tar_args | |
113 if path: | |
114 with open(path, 'wb') as fh: | |
115 shell(untar_cmd, stdin=download_p.stdout, stdout=fh) | |
116 else: | |
117 shell(untar_cmd, stdin=download_p.stdout) | |
118 download_p.wait() | |
119 else: | |
120 cmd = download_command(url, to=path) | |
121 shell(cmd) | |
122 | |
123 | |
124 def find_matching_directories(path, pattern, recursive): | |
125 """Find directories below supplied path with file matching pattern. | |
126 | |
127 Returns an empty list if no matches are found, and if recursive is False | |
128 only the top directory specified by path will be considered. | |
129 """ | |
130 dirs = [] | |
131 if recursive: | |
132 if not os.path.isdir(path): | |
133 template = "--recursive specified with non-directory path [%s]" | |
134 message = template % (path) | |
135 raise Exception(message) | |
136 | |
137 for base_path, dirnames, filenames in os.walk(path): | |
138 dirnames.sort() | |
139 for filename in fnmatch.filter(filenames, pattern): | |
140 dirs.append(base_path) | |
141 else: | |
142 if os.path.exists(os.path.join(path, pattern)): | |
143 dirs.append(path) | |
144 elif os.path.basename(path) == pattern: | |
145 dirs.append(os.path.dirname(path)) | |
146 return dirs | |
147 | |
148 | |
149 @contextlib.contextmanager | |
150 def real_io(): | |
151 """Ensure stdout and stderr have supported ``fileno()`` method. | |
152 | |
153 nosetests replaces these streams with :class:`StringIO` objects | |
154 that may not work the same in every situtation - :func:`subprocess.Popen` | |
155 calls in particular. | |
156 """ | |
157 original_stdout = sys.stdout | |
158 original_stderr = sys.stderr | |
159 try: | |
160 if commands.redirecting_io(sys=sys): | |
161 sys.stdout = sys.__stdout__ | |
162 sys.stderr = sys.__stderr__ | |
163 yield | |
164 finally: | |
165 sys.stdout = original_stdout | |
166 sys.stderr = original_stderr | |
167 | |
168 | |
169 @contextlib.contextmanager | |
170 def temp_directory(prefix="planemo_tmp_", dir=None, **kwds): | |
171 if dir is not None: | |
172 try: | |
173 os.makedirs(dir) | |
174 except OSError as e: | |
175 if e.errno != errno.EEXIST: | |
176 raise | |
177 temp_dir = tempfile.mkdtemp(prefix=prefix, dir=dir, **kwds) | |
178 try: | |
179 yield temp_dir | |
180 finally: | |
181 shutil.rmtree(temp_dir) | |
182 | |
183 | |
184 def ps1_for_path(path, base="PS1"): | |
185 """ Used by environment commands to build a PS1 shell | |
186 variables for tool or directory of tools. | |
187 """ | |
188 file_name = os.path.basename(path) | |
189 base_name = os.path.splitext(file_name)[0] | |
190 ps1 = "(%s)${%s}" % (base_name, base) | |
191 return ps1 | |
192 | |
193 | |
194 def kill_pid_file(pid_file): | |
195 try: | |
196 os.stat(pid_file) | |
197 except OSError as e: | |
198 if e.errno == errno.ENOENT: | |
199 return False | |
200 | |
201 with open(pid_file, "r") as fh: | |
202 pid = int(fh.read()) | |
203 kill_posix(pid) | |
204 try: | |
205 os.unlink(pid_file) | |
206 except Exception: | |
207 pass | |
208 | |
209 | |
210 def kill_posix(pid): | |
211 def _check_pid(): | |
212 try: | |
213 os.kill(pid, 0) | |
214 return True | |
215 except OSError: | |
216 return False | |
217 | |
218 if _check_pid(): | |
219 for sig in [15, 9]: | |
220 try: | |
221 os.kill(pid, sig) | |
222 except OSError: | |
223 return | |
224 time.sleep(1) | |
225 if not _check_pid(): | |
226 return | |
227 | |
228 | |
229 @contextlib.contextmanager | |
230 def conditionally_captured_io(capture, tee=False): | |
231 captured_std = [] | |
232 if capture: | |
233 with Capturing() as captured_std: | |
234 yield captured_std | |
235 if tee: | |
236 tee_captured_output(captured_std) | |
237 else: | |
238 yield | |
239 | |
240 | |
241 @contextlib.contextmanager | |
242 def captured_io_for_xunit(kwds, captured_io): | |
243 captured_std = [] | |
244 with_xunit = kwds.get('report_xunit', False) | |
245 with conditionally_captured_io(with_xunit, tee=True): | |
246 time1 = time.time() | |
247 yield | |
248 time2 = time.time() | |
249 | |
250 if with_xunit: | |
251 stdout = [escape(m['data']) for m in captured_std | |
252 if m['logger'] == 'stdout'] | |
253 stderr = [escape(m['data']) for m in captured_std | |
254 if m['logger'] == 'stderr'] | |
255 captured_io["stdout"] = stdout | |
256 captured_io["stderr"] = stderr | |
257 captured_io["time"] = (time2 - time1) | |
258 else: | |
259 captured_io["stdout"] = None | |
260 captured_io["stderr"] = None | |
261 captured_io["time"] = None | |
262 | |
263 | |
264 class Capturing(list): | |
265 """Function context which captures stdout/stderr | |
266 | |
267 This keeps planemo's codebase clean without requiring planemo to hold onto | |
268 messages, or pass user-facing messages back at all. This could probably be | |
269 solved by swapping planemo entirely to a logger and reading from/writing | |
270 to that, but this is easier. | |
271 | |
272 This swaps sys.std{out,err} with StringIOs and then makes that output | |
273 available. | |
274 """ | |
275 # http://stackoverflow.com/a/16571630 | |
276 | |
277 def __enter__(self): | |
278 self._stdout = sys.stdout | |
279 self._stderr = sys.stderr | |
280 sys.stdout = self._stringio_stdout = StringIO() | |
281 sys.stderr = self._stringio_stderr = StringIO() | |
282 return self | |
283 | |
284 def __exit__(self, *args): | |
285 self.extend([{'logger': 'stdout', 'data': x} for x in | |
286 self._stringio_stdout.getvalue().splitlines()]) | |
287 self.extend([{'logger': 'stderr', 'data': x} for x in | |
288 self._stringio_stderr.getvalue().splitlines()]) | |
289 | |
290 sys.stdout = self._stdout | |
291 sys.stderr = self._stderr | |
292 | |
293 | |
294 def tee_captured_output(output): | |
295 """For messages captured with Capturing, send them to their correct | |
296 locations so as to not interfere with normal user experience. | |
297 """ | |
298 for message in output: | |
299 # Append '\n' due to `splitlines()` above | |
300 if message['logger'] == 'stdout': | |
301 sys.stdout.write(message['data'] + '\n') | |
302 if message['logger'] == 'stderr': | |
303 sys.stderr.write(message['data'] + '\n') | |
304 | |
305 | |
306 def wait_on(function, desc, timeout=5, polling_backoff=0): | |
307 """Wait on given function's readiness. Grow the polling | |
308 interval incrementally by the polling_backoff.""" | |
309 delta = .25 | |
310 timing = 0 | |
311 while True: | |
312 if timing > timeout: | |
313 message = "Timed out waiting on %s." % desc | |
314 raise Exception(message) | |
315 timing += delta | |
316 delta += polling_backoff | |
317 value = function() | |
318 if value is not None: | |
319 return value | |
320 time.sleep(delta) | |
321 | |
322 | |
323 @contextlib.contextmanager | |
324 def open_file_or_standard_output(path, *args, **kwds): | |
325 if path == "-": | |
326 yield sys.stdout | |
327 else: | |
328 yield open(path, *args, **kwds) | |
329 | |
330 | |
331 def filter_paths(paths, cwd=None, **kwds): | |
332 if cwd is None: | |
333 cwd = os.getcwd() | |
334 | |
335 def norm(path): | |
336 if not os.path.isabs(path): | |
337 path = os.path.join(cwd, path) | |
338 return os.path.normpath(path) | |
339 | |
340 def exclude_func(exclude_path): | |
341 def path_startswith(p): | |
342 """Check that p starts with exclude_path and that the first | |
343 character of p not included in exclude_path (if any) is the | |
344 directory separator. | |
345 """ | |
346 norm_p = norm(p) | |
347 norm_exclude_path = norm(exclude_path) | |
348 if norm_p.startswith(norm_exclude_path): | |
349 return norm_p[len(norm_exclude_path):len(norm_exclude_path) + 1] in ['', os.sep] | |
350 return False | |
351 return path_startswith | |
352 | |
353 filters_as_funcs = [] | |
354 filters_as_funcs.extend(map(exclude_func, kwds.get("exclude", []))) | |
355 | |
356 for exclude_paths_ins in kwds.get("exclude_from", []): | |
357 with open(exclude_paths_ins, "r") as f: | |
358 for line in f.readlines(): | |
359 line = line.strip() | |
360 if not line or line.startswith("#"): | |
361 continue | |
362 filters_as_funcs.append(exclude_func(line)) | |
363 | |
364 return [p for p in paths if not any(f(p) for f in filters_as_funcs)] | |
365 | |
366 | |
367 def coalesce_return_codes(ret_codes, assert_at_least_one=False): | |
368 # Return 0 if everything is fine, otherwise pick the least | |
369 # specific non-0 return code - preferring to report errors | |
370 # to other non-0 exit codes. | |
371 if assert_at_least_one and len(ret_codes) == 0: | |
372 return EXIT_CODE_NO_SUCH_TARGET | |
373 | |
374 coalesced_return_code = EXIT_CODE_OK | |
375 for ret_code in ret_codes: | |
376 # None is equivalent to 0 in these methods. | |
377 ret_code = 0 if ret_code is None else ret_code | |
378 if ret_code == 0: | |
379 # Everything is fine, keep moving... | |
380 pass | |
381 elif coalesced_return_code == 0: | |
382 coalesced_return_code = ret_code | |
383 # At this point in logic both ret_code and coalesced_return_code are | |
384 # are non-zero | |
385 elif ret_code < 0: | |
386 # Error state, this should override eveything else. | |
387 coalesced_return_code = ret_code | |
388 elif ret_code > 0 and coalesced_return_code < 0: | |
389 # Keep error state recorded. | |
390 pass | |
391 elif ret_code > 0: | |
392 # Lets somewhat arbitrarily call the smaller exit code | |
393 # the less specific. | |
394 coalesced_return_code = min(ret_code, coalesced_return_code) | |
395 | |
396 if coalesced_return_code < 0: | |
397 # Map -1 => 254, -2 => 253, etc... | |
398 # Not sure it is helpful to have negative error codes | |
399 # this was a design and API mistake in planemo. | |
400 coalesced_return_code = 255 + coalesced_return_code | |
401 | |
402 return coalesced_return_code |