jpayne@69
|
1 from typing import (
|
jpayne@69
|
2 Callable,
|
jpayne@69
|
3 List,
|
jpayne@69
|
4 Optional,
|
jpayne@69
|
5 Tuple,
|
jpayne@69
|
6 Iterable,
|
jpayne@69
|
7 Union,
|
jpayne@69
|
8 )
|
jpayne@69
|
9
|
jpayne@69
|
10 from pysam.libcutils import _pysam_dispatch
|
jpayne@69
|
11
|
jpayne@69
|
12
|
jpayne@69
|
13 class SamtoolsError(Exception):
|
jpayne@69
|
14 '''exception raised in case of an error incurred in the samtools
|
jpayne@69
|
15 library.'''
|
jpayne@69
|
16
|
jpayne@69
|
17 def __init__(self, value):
|
jpayne@69
|
18 self.value = value
|
jpayne@69
|
19
|
jpayne@69
|
20 def __str__(self):
|
jpayne@69
|
21 return repr(self.value)
|
jpayne@69
|
22
|
jpayne@69
|
23
|
jpayne@69
|
24 class PysamDispatcher(object):
|
jpayne@69
|
25 '''The dispatcher emulates the samtools/bctools command line.
|
jpayne@69
|
26
|
jpayne@69
|
27 Captures stdout and stderr.
|
jpayne@69
|
28
|
jpayne@69
|
29 Raises a :class:`pysam.SamtoolsError` exception in case samtools
|
jpayne@69
|
30 exits with an error code other than 0.
|
jpayne@69
|
31
|
jpayne@69
|
32 Some command line options are associated with parsers. For
|
jpayne@69
|
33 example, the samtools command "pileup -c" creates a tab-separated
|
jpayne@69
|
34 table on standard output. In order to associate parsers with
|
jpayne@69
|
35 options, an optional list of parsers can be supplied. The list
|
jpayne@69
|
36 will be processed in order checking for the presence of each
|
jpayne@69
|
37 option.
|
jpayne@69
|
38
|
jpayne@69
|
39 If no parser is given or no appropriate parser is found, the
|
jpayne@69
|
40 stdout output of samtools/bcftools commands will be returned.
|
jpayne@69
|
41
|
jpayne@69
|
42 '''
|
jpayne@69
|
43
|
jpayne@69
|
44 dispatch = None
|
jpayne@69
|
45 parsers = None
|
jpayne@69
|
46 collection = None
|
jpayne@69
|
47
|
jpayne@69
|
48 def __init__(
|
jpayne@69
|
49 self,
|
jpayne@69
|
50 collection: str,
|
jpayne@69
|
51 dispatch: str,
|
jpayne@69
|
52 parsers: Optional[Iterable[Tuple[str, Callable[[Union[str, List[str]]], Union[str, List[str]]]]]] = None,
|
jpayne@69
|
53 ):
|
jpayne@69
|
54 self.collection = collection
|
jpayne@69
|
55 self.dispatch = dispatch
|
jpayne@69
|
56 self.parsers = parsers
|
jpayne@69
|
57 self.stderr = []
|
jpayne@69
|
58
|
jpayne@69
|
59 def __call__(self, *args: str, **kwargs) -> Union[str, List[str]]:
|
jpayne@69
|
60 '''
|
jpayne@69
|
61 execute a samtools command.
|
jpayne@69
|
62
|
jpayne@69
|
63 Keyword arguments:
|
jpayne@69
|
64 catch_stdout -- redirect stdout from the samtools command and
|
jpayne@69
|
65 return as variable (default True)
|
jpayne@69
|
66 save_stdout -- redirect stdout to a filename.
|
jpayne@69
|
67 raw -- ignore any parsers associated with this samtools command.
|
jpayne@69
|
68 split_lines -- return stdout (if catch_stdout is True and stderr
|
jpayne@69
|
69 as a list of strings.
|
jpayne@69
|
70 '''
|
jpayne@69
|
71 retval, stderr, stdout = _pysam_dispatch(
|
jpayne@69
|
72 self.collection,
|
jpayne@69
|
73 self.dispatch,
|
jpayne@69
|
74 args,
|
jpayne@69
|
75 catch_stdout=kwargs.get("catch_stdout", True),
|
jpayne@69
|
76 save_stdout=kwargs.get("save_stdout", None))
|
jpayne@69
|
77
|
jpayne@69
|
78 if kwargs.get("split_lines", False):
|
jpayne@69
|
79 stdout = stdout.splitlines()
|
jpayne@69
|
80 if stderr:
|
jpayne@69
|
81 stderr = stderr.splitlines()
|
jpayne@69
|
82
|
jpayne@69
|
83 if retval:
|
jpayne@69
|
84 raise SamtoolsError(
|
jpayne@69
|
85 "%s returned with error %i: "
|
jpayne@69
|
86 "stdout=%s, stderr=%s" %
|
jpayne@69
|
87 (self.collection,
|
jpayne@69
|
88 retval,
|
jpayne@69
|
89 stdout,
|
jpayne@69
|
90 stderr))
|
jpayne@69
|
91
|
jpayne@69
|
92 self.stderr = stderr
|
jpayne@69
|
93
|
jpayne@69
|
94 # call parser for stdout:
|
jpayne@69
|
95 if not kwargs.get("raw") and stdout and self.parsers:
|
jpayne@69
|
96 for options, parser in self.parsers:
|
jpayne@69
|
97 for option in options:
|
jpayne@69
|
98 if option not in args:
|
jpayne@69
|
99 break
|
jpayne@69
|
100 else:
|
jpayne@69
|
101 return parser(stdout)
|
jpayne@69
|
102
|
jpayne@69
|
103 return stdout
|
jpayne@69
|
104
|
jpayne@69
|
105 def get_messages(self):
|
jpayne@69
|
106 return self.stderr
|
jpayne@69
|
107
|
jpayne@69
|
108 def usage(self):
|
jpayne@69
|
109 '''return the samtools usage information for this command'''
|
jpayne@69
|
110 retval, stderr, stdout = _pysam_dispatch(
|
jpayne@69
|
111 self.collection,
|
jpayne@69
|
112 self.dispatch,
|
jpayne@69
|
113 is_usage=True,
|
jpayne@69
|
114 catch_stdout=True)
|
jpayne@69
|
115 # some tools write usage to stderr, such as mpileup
|
jpayne@69
|
116 if stderr:
|
jpayne@69
|
117 return stderr
|
jpayne@69
|
118 else:
|
jpayne@69
|
119 return stdout
|
jpayne@69
|
120
|
jpayne@69
|
121
|
jpayne@69
|
122 class unquoted_str(str):
|
jpayne@69
|
123 '''Tag a value as an unquoted string. Meta-information in the VCF
|
jpayne@69
|
124 header takes the form of key=value pairs. By default, pysam will
|
jpayne@69
|
125 enclose the value in quotation marks. Tagging that value with
|
jpayne@69
|
126 unquoted_str will prevent this quoting.'''
|