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 |
